diff --git a/.gitignore b/.gitignore index 4159c1fd..beb84642 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,10 @@ a.out /jxnet-spring-boot-starter/build/ /jxnet-spring-boot-starter/target /jxnet-spring-boot-starter/out +/jxnet-spring-boot-starter-example/.gradle +/jxnet-spring-boot-starter-example/build/ +/jxnet-spring-boot-starter-example/target +/jxnet-spring-boot-starter-example/out /jxnet-android/.gradle /jxnet-android/build/ /jxnet-android/target diff --git a/gradle/configure.gradle b/gradle/configure.gradle index 0205ac43..caa248ba 100644 --- a/gradle/configure.gradle +++ b/gradle/configure.gradle @@ -7,7 +7,7 @@ ext { NAME = 'Jxnet' GROUP = 'com.ardikars.jxnet' - VERSION = '1.4.0.RC2' + VERSION = '1.4.0.RC3' NDK_HOME = "${System.env.NDK_HOME}" TOOLS_DIR = "${rootDir}/tools" diff --git a/jxnet-context/src/main/java/com/ardikars/jxnet/Application.java b/jxnet-context/src/main/java/com/ardikars/jxnet/Application.java index d5e73786..093aeb62 100644 --- a/jxnet-context/src/main/java/com/ardikars/jxnet/Application.java +++ b/jxnet-context/src/main/java/com/ardikars/jxnet/Application.java @@ -36,12 +36,15 @@ private Application() { /** * Bootstraping application. + * @param aplicationName application name. + * @param applicationDisplayName application display name. + * @param applicationVersion application version. * @param builder pcap builder. */ - public static void run(Builder builder) { + public static void run(String aplicationName, String applicationDisplayName, String applicationVersion, Builder builder) { Validate.notIllegalArgument(builder != null, new IllegalArgumentException("Pcap builder should be not null.")); - instance.context = new ApplicationContext(builder); + instance.context = new ApplicationContext(aplicationName, applicationDisplayName, applicationVersion, builder); } /** diff --git a/jxnet-context/src/main/java/com/ardikars/jxnet/ApplicationContext.java b/jxnet-context/src/main/java/com/ardikars/jxnet/ApplicationContext.java index b22efbeb..42214403 100644 --- a/jxnet-context/src/main/java/com/ardikars/jxnet/ApplicationContext.java +++ b/jxnet-context/src/main/java/com/ardikars/jxnet/ApplicationContext.java @@ -25,13 +25,10 @@ import com.ardikars.jxnet.exception.PcapDumperCloseException; import com.ardikars.jxnet.exception.PlatformNotSupportedException; -import java.io.IOException; -import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.Properties; -import java.util.logging.Logger; +import java.util.concurrent.Executor; /** * @author Ardika Rommy Sanjaya @@ -39,11 +36,11 @@ */ public final class ApplicationContext implements Context { - private static final Logger LOGGER = Logger.getLogger(ApplicationContext.class.getSimpleName()); + private final String applicationName; - private String applicationName; + private final String applicationDisplayName; - private String applicationVersion; + private final String applicationVersion; private final Pcap pcap; @@ -51,7 +48,7 @@ public final class ApplicationContext implements Context { private PcapDumper pcapDumper; - protected ApplicationContext(Builder builder) { + protected ApplicationContext(String applicationName, String applicationDisplayName, String applicationVersion, Builder builder) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { @@ -65,21 +62,11 @@ public void run() { if (pcapDumper != null && !pcapDumper.isClosed()) { Jxnet.PcapDumpClose(pcapDumper); } - LOGGER.info("Application closed gracefully."); } }); - final Properties properties = new Properties(); - try (InputStream stream = ClassLoader.class.getResourceAsStream("application.properties")) { - if (stream != null) { - properties.load(stream); - } - this.applicationName = properties.getProperty("jxnet.application.name", ""); - this.applicationVersion = properties.getProperty("jxnet.application.version", ""); - } catch (IOException e) { - this.applicationName = ""; - this.applicationVersion = ""; - LOGGER.warning(e.getMessage()); - } + this.applicationName = applicationName; + this.applicationDisplayName = applicationDisplayName; + this.applicationVersion = applicationVersion; Validate.notIllegalArgument(builder != null, new IllegalArgumentException("Pcap builder should be not null.")); this.pcap = builder.build(); } @@ -89,14 +76,19 @@ public String getApplicationName() { return applicationName; } - @Override + @Override + public String getApplicationDisplayName() { + return applicationDisplayName; + } + + @Override public String getApplicationVersion() { return applicationVersion; } @Override public Context newInstance(Builder builder) { - return new ApplicationContext(builder); + return new ApplicationContext(this.applicationName, this.applicationDisplayName, this.applicationVersion, builder); } @Override @@ -108,6 +100,27 @@ public PcapCode pcapLoop(int cnt, PcapHandler callback, T user) throws Pc return PcapCode.PCAP_ERROR; } + @Override + public PcapCode pcapLoop(final int cnt, final PcapHandler callback, final T user, final Executor executor) throws PcapCloseException { + Validate.notIllegalArgument(executor != null, + new IllegalArgumentException("Executor should be not null.")); + int result = Jxnet.PcapLoop(pcap, cnt, new PcapHandler() { + @Override + public void nextPacket(final T user, final PcapPktHdr h, final ByteBuffer bytes) { + executor.execute(new Runnable() { + @Override + public void run() { + callback.nextPacket(user, h, bytes); + } + }); + } + }, user); + if (result == 0) { + return PcapCode.PCAP_OK; + } + return PcapCode.PCAP_ERROR; + } + @Override public PcapCode pcapDispatch(int cnt, PcapHandler callback, T user) throws PcapCloseException { int result = Jxnet.PcapDispatch(pcap, cnt, callback, user); @@ -117,6 +130,28 @@ public PcapCode pcapDispatch(int cnt, PcapHandler callback, T user) throw return PcapCode.PCAP_ERROR; } + @Override + public PcapCode pcapDispatch(final int cnt, final PcapHandler callback, final T user, final Executor executor) + throws PcapCloseException { + Validate.notIllegalArgument(executor != null, + new IllegalArgumentException("Executor should be not null.")); + int result = Jxnet.PcapDispatch(pcap, cnt, new PcapHandler() { + @Override + public void nextPacket(final T user, final PcapPktHdr h, final ByteBuffer bytes) { + executor.execute(new Runnable() { + @Override + public void run() { + callback.nextPacket(user, h, bytes); + } + }); + } + }, user); + if (result == 0) { + return PcapCode.PCAP_OK; + } + return PcapCode.PCAP_ERROR; + } + @Override public PcapCode pcapDumpOpen(String fname) throws PcapCloseException { pcapDumper = Jxnet.PcapDumpOpen(pcap, fname); diff --git a/jxnet-context/src/main/java/com/ardikars/jxnet/Context.java b/jxnet-context/src/main/java/com/ardikars/jxnet/Context.java index 06292fa0..b0e800ab 100644 --- a/jxnet-context/src/main/java/com/ardikars/jxnet/Context.java +++ b/jxnet-context/src/main/java/com/ardikars/jxnet/Context.java @@ -27,11 +27,14 @@ import java.nio.ByteBuffer; import java.util.List; +import java.util.concurrent.Executor; public interface Context extends Factory> { String getApplicationName(); + String getApplicationDisplayName(); + String getApplicationVersion(); /** @@ -46,15 +49,32 @@ public interface Context extends Factory> { * @param user args * @param args type. * @return PcapLoop() returns 0 if cnt is exhausted or if, when reading from a - * @throws PcapCloseException pcap close exception. * savefile, no more packets are available. It returns -1 if an error * occurs or -2 if the loop terminated due to a call to PcapBreakLoop() * before any packets were processed. It does not return when live packet * buffer timeouts occur; instead, it attempts to read more packets. + * @throws PcapCloseException pcap close exception. * @since 1.1.4 */ PcapCode pcapLoop(int cnt, PcapHandler callback, T user) throws PcapCloseException; + /** + * Collect a group of packets. + * @param cnt maximum iteration, -1 is infinite iteration. + * @param callback callback funtion. + * @param user args + * @param executor executor service. + * @param args type. + * @return PcapLoop() returns 0 if cnt is exhausted or if, when reading from a + * savefile, no more packets are available. It returns -1 if an error + * occurs or -2 if the loop terminated due to a call to PcapBreakLoop() + * before any packets were processed. It does not return when live packet + * buffer timeouts occur; instead, it attempts to read more packets. + * @throws PcapCloseException pcap close exception. + * @since 1.1.4 + */ + PcapCode pcapLoop(int cnt, PcapHandler callback, T user, Executor executor) throws PcapCloseException; + /** * Collect a group of packets. * @param cnt maximum iteration, -1 to infinite. @@ -62,7 +82,6 @@ public interface Context extends Factory> { * @param user arg. * @param args type. * @return PcapDispatch() returns the number of packets processed on success; - * @throws PcapCloseException pcap close exception. * this can be 0 if no packets were read from a live capture (if, for * example, they were discarded because they didn't pass the packet filter, * or if, on platforms that support a packet buffer timeout that @@ -74,10 +93,35 @@ public interface Context extends Factory> { * before any packets were processed. If your application uses * PcapBreakLoop(), make sure that you explicitly check for -1 and -2, * rather than just checking for a return value less then 0. + * @throws PcapCloseException pcap close exception. * @since 1.1.4 */ PcapCode pcapDispatch(int cnt, PcapHandler callback, T user) throws PcapCloseException; + /** + * Collect a group of packets. + * @param cnt maximum iteration, -1 to infinite. + * @param callback callback function. + * @param user arg. + * @param executor executor. + * @param args type. + * @return PcapDispatch() returns the number of packets processed on success; + * this can be 0 if no packets were read from a live capture (if, for + * example, they were discarded because they didn't pass the packet filter, + * or if, on platforms that support a packet buffer timeout that + * starts before any packets arrive, the timeout expires before any packets + * arrive, or if the file descriptor for the capture device is in non-blocking + * mode and no packets were available to be read) or if no more + * packets are available in a savefile. It returns -1 if an error + * occurs or -2 if the loop terminated due to a call to PcapBreakLoop() + * before any packets were processed. If your application uses + * PcapBreakLoop(), make sure that you explicitly check for -1 and -2, + * rather than just checking for a return value less then 0. + * @throws PcapCloseException pcap close exception. + * @since 1.1.4 + */ + PcapCode pcapDispatch(int cnt, PcapHandler callback, T user, Executor executor) throws PcapCloseException; + /** * Open a file to write packets. * @param fname fname specifies the name of the file to open. The file will have the same format diff --git a/jxnet-example/build.gradle b/jxnet-example/build.gradle new file mode 100644 index 00000000..cd2ff59d --- /dev/null +++ b/jxnet-example/build.gradle @@ -0,0 +1,14 @@ + +dependencies { + compile project(':jxnet-context') +} + +jar { + manifest { + attributes "Main-Class": "com.ardikars.jxnet.example.Application" + } + + from { + configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } + } +} diff --git a/jxnet-example/src/main/java/com/ardikars/jxnet/example/Application.java b/jxnet-example/src/main/java/com/ardikars/jxnet/example/Application.java new file mode 100644 index 00000000..6d39419d --- /dev/null +++ b/jxnet-example/src/main/java/com/ardikars/jxnet/example/Application.java @@ -0,0 +1,149 @@ +/** + * Copyright (C) 2017-2018 Ardika Rommy Sanjaya + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.ardikars.jxnet.example; + +import static com.ardikars.jxnet.Jxnet.OK; +import static com.ardikars.jxnet.Jxnet.PcapFindAllDevs; + +import com.ardikars.common.net.Inet4Address; +import com.ardikars.common.util.Hexs; +import com.ardikars.jxnet.Context; +import com.ardikars.jxnet.DataLinkType; +import com.ardikars.jxnet.ImmediateMode; +import com.ardikars.jxnet.Pcap; +import com.ardikars.jxnet.PcapAddr; +import com.ardikars.jxnet.PcapHandler; +import com.ardikars.jxnet.PcapIf; +import com.ardikars.jxnet.PcapPktHdr; +import com.ardikars.jxnet.PcapTimestampPrecision; +import com.ardikars.jxnet.PcapTimestampType; +import com.ardikars.jxnet.PromiscuousMode; +import com.ardikars.jxnet.RadioFrequencyMonitorMode; +import com.ardikars.jxnet.SockAddr; +import com.ardikars.jxnet.exception.DeviceNotFoundException; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Application { + + public static final Logger LOGGER = Logger.getLogger(Application.class.getName()); + + public static final String source = null; + public static final int SNAPLEN = 65535; + public static final PromiscuousMode PROMISCUOUS = PromiscuousMode.PRIMISCUOUS; + public static final int TIMEOUT = 2000; + public static final ImmediateMode IMMEDIATE = ImmediateMode.IMMEDIATE; + public static final PcapTimestampType TIMESTAMP_TYPE = PcapTimestampType.HOST; + public static final PcapTimestampPrecision TIMESTAMP_PRECISION = PcapTimestampPrecision.MICRO; + public static final RadioFrequencyMonitorMode RFMON = RadioFrequencyMonitorMode.NON_RFMON; + public static final boolean nonBlock = false; + public static final DataLinkType LINK_TYPE = DataLinkType.EN10MB; + public static final String FILE_NAME = "../gradle/resources/pcap/icmp.pcap"; + + public static final Pcap.PcapType PCAP_TYPE = Pcap.PcapType.LIVE; + + public static final int MAX_PACKET = 20; + + public static final int WAIT_TIME_FOR_THREAD_TERMINATION = 10000; + + /** + * Main method. + * @param args args. + * @throws InterruptedException interrupted exception. + */ + public static void main(String[] args) throws InterruptedException { + StringBuilder errbuf = new StringBuilder(); + try { + PcapIf pcapIf = pcapIf(errbuf); + Pcap.Builder builder = new Pcap.Builder() + .source(pcapIf.getName()) + .snaplen(SNAPLEN) + .promiscuousMode(PROMISCUOUS) + .timeout(TIMEOUT) + .immediateMode(IMMEDIATE) + .timestampType(TIMESTAMP_TYPE) + .timestampPrecision(TIMESTAMP_PRECISION) + .rfmon(RFMON) + .enableNonBlock(nonBlock) + .dataLinkType(LINK_TYPE) + .fileName(FILE_NAME) + .errbuf(errbuf) + .pcapType(PCAP_TYPE); + com.ardikars.jxnet.Application.run("application", "Application", "", builder); + Context context = com.ardikars.jxnet.Application.getApplicationContext(); + ExecutorService pool = Executors.newCachedThreadPool(); + context.pcapLoop(MAX_PACKET, new PcapHandler() { + @Override + public void nextPacket(String user, PcapPktHdr pktHdr, ByteBuffer buffer) { + byte[] bytes = new byte[buffer.capacity()]; + buffer.get(bytes, 0, bytes.length); + String hexDump = Hexs.toPrettyHexDump(bytes); + LOGGER.info("User argument : " + user); + LOGGER.info("Packet header : " + pktHdr); + LOGGER.info("Packet buffer : \n" + hexDump); + } + }, "Jxnet!", pool); + pool.shutdown(); + pool.awaitTermination(WAIT_TIME_FOR_THREAD_TERMINATION, TimeUnit.MICROSECONDS); + } catch (DeviceNotFoundException e) { + LOGGER.warning(e.getMessage()); + } + + } + + /** + * Get default pcap interface. + * @param errbuf error buffer. + * @return returns PcapIf. + * @throws DeviceNotFoundException device not found exception. + */ + public static PcapIf pcapIf(StringBuilder errbuf) throws DeviceNotFoundException { + String source = Application.source; + List alldevsp = new ArrayList<>(); + if (PcapFindAllDevs(alldevsp, errbuf) != OK && LOGGER.isLoggable(Level.WARNING)) { + LOGGER.warning("Error: {}" + errbuf.toString()); + } + if (source == null || source.isEmpty()) { + for (PcapIf dev : alldevsp) { + for (PcapAddr addr : dev.getAddresses()) { + if (addr.getAddr().getSaFamily() == SockAddr.Family.AF_INET && addr.getAddr().getData() != null) { + Inet4Address d = Inet4Address.valueOf(addr.getAddr().getData()); + if (!d.equals(Inet4Address.LOCALHOST) && !d.equals(Inet4Address.ZERO)) { + return dev; + } + } + } + } + } else { + for (PcapIf dev : alldevsp) { + if (dev.getName().equals(source)) { + return dev; + } + } + } + throw new DeviceNotFoundException("No device connected to the network."); + } + +} diff --git a/jxnet-spring-boot-autoconfigure/src/main/java/com/ardikars/jxnet/spring/boot/autoconfigure/JxnetAutoConfiguration.java b/jxnet-spring-boot-autoconfigure/src/main/java/com/ardikars/jxnet/spring/boot/autoconfigure/JxnetAutoConfiguration.java index 847798cc..efc27ce6 100644 --- a/jxnet-spring-boot-autoconfigure/src/main/java/com/ardikars/jxnet/spring/boot/autoconfigure/JxnetAutoConfiguration.java +++ b/jxnet-spring-boot-autoconfigure/src/main/java/com/ardikars/jxnet/spring/boot/autoconfigure/JxnetAutoConfiguration.java @@ -38,6 +38,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @@ -50,10 +51,12 @@ public class JxnetAutoConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(JxnetAutoConfiguration.class.getName()); + private final ApplicationContext context; private final JxnetConfigurationProperties properties; @Autowired - public JxnetAutoConfiguration(JxnetConfigurationProperties properties) { + public JxnetAutoConfiguration(ApplicationContext context, JxnetConfigurationProperties properties) { + this.context = context; this.properties = properties; } @@ -88,13 +91,19 @@ public Context context(PcapIf pcapIf, builder.pcapType(Pcap.PcapType.DEAD); break; case OFFLINE: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Opening pcap offline hadler : {}", builder); + } builder.pcapType(Pcap.PcapType.OFFLINE); break; default: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Opening pcap live hadler : {}", builder); + } builder.pcapType(Pcap.PcapType.LIVE); break; } - Application.run(builder); + Application.run(context.getApplicationName(), context.getDisplayName(), "", builder); return Application.getApplicationContext(); } @@ -129,7 +138,7 @@ public PcapIf pcapIf(@Qualifier("com.ardikars.jxnet.errbuf") StringBuilder errbu } } } - throw new DeviceNotFoundException(""); + throw new DeviceNotFoundException("No device connected to the network."); } @Bean("com.ardikars.jxnet.errbuf") diff --git a/jxnet-spring-boot-starter-example/build.gradle b/jxnet-spring-boot-starter-example/build.gradle new file mode 100644 index 00000000..7458fde6 --- /dev/null +++ b/jxnet-spring-boot-starter-example/build.gradle @@ -0,0 +1,37 @@ + +/** + * Copyright (C) 2017-2018 Ardika Rommy Sanjaya + */ + + +buildscript { + ext { + springBootVersion = '2.0.4.RELEASE' + } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + +apply plugin: 'org.springframework.boot' +apply plugin: 'io.spring.dependency-management' + +description = "Jxnet spring boot starter example" + +dependencies { + compile project(":jxnet-spring-boot-starter") + compile("org.springframework.boot:spring-boot-starter") +} + +dependencyManagement { + imports { mavenBom("org.springframework.boot:spring-boot-dependencies:${springBootVersion}") } +} + +bootJar { + manifest { + attributes 'Start-Class': 'com.ardikars.jxnet.spring.boot.starter.example.Application' + } +} diff --git a/jxnet-spring-boot-starter-example/src/main/java/com/ardikars/jxnet/spring/boot/starter/example/Application.java b/jxnet-spring-boot-starter-example/src/main/java/com/ardikars/jxnet/spring/boot/starter/example/Application.java new file mode 100644 index 00000000..0d0ced98 --- /dev/null +++ b/jxnet-spring-boot-starter-example/src/main/java/com/ardikars/jxnet/spring/boot/starter/example/Application.java @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2017-2018 Ardika Rommy Sanjaya + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.ardikars.jxnet.spring.boot.starter.example; + +import com.ardikars.common.util.Hexs; +import com.ardikars.jxnet.Context; +import com.ardikars.jxnet.PcapHandler; +import com.ardikars.jxnet.PcapPktHdr; +import java.nio.ByteBuffer; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application implements CommandLineRunner { + + public static final int MAX_PACKET = 20; + + public static final int WAIT_TIME_FOR_THREAD_TERMINATION = 10000; + + private static final Logger LOGGER = LoggerFactory.getLogger(Application.class.getName()); + + @Autowired + private Context context; + + @Override + public void run(String... args) throws Exception { + ExecutorService pool = Executors.newCachedThreadPool(); + context.pcapLoop(MAX_PACKET, new PcapHandler() { + @Override + public void nextPacket(String user, PcapPktHdr pktHdr, ByteBuffer buffer) { + byte[] bytes = new byte[buffer.capacity()]; + buffer.get(bytes, 0, bytes.length); + String hexDump = Hexs.toPrettyHexDump(bytes); + LOGGER.info("User argument : " + user); + LOGGER.info("Packet header : " + pktHdr); + LOGGER.info("Packet buffer : \n" + hexDump); + } + }, "Jxnet!", pool); + pool.shutdown(); + pool.awaitTermination(WAIT_TIME_FOR_THREAD_TERMINATION, TimeUnit.MICROSECONDS); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/settings.gradle b/settings.gradle index a4864c83..7eddba47 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,4 +5,6 @@ include 'jxnet-context' include 'jxnet-core' include 'jxnet-spring-boot-autoconfigure' include 'jxnet-spring-boot-starter' +include 'jxnet-spring-boot-starter-example' +include 'jxnet-example'