├── libpcapfiles ├── linux │ ├── readme.md │ ├── libpcap.so │ └── libpcap64.so ├── macos │ └── libpcap.1.8.1.dylib └── windows_npcap │ ├── x64 │ ├── Packet.dll │ └── wpcap.dll │ ├── x86 │ ├── Packet.dll │ └── wpcap.dll │ └── readme.md ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── How2BuildLibPcap.md ├── release.md ├── README.md ├── src ├── test │ └── java │ │ └── io │ │ └── gitee │ │ └── gaoxingliang │ │ └── macdetector │ │ └── Test.java └── main │ └── java │ └── io │ └── gitee │ └── gaoxingliang │ └── macdetector │ ├── PacketDump.java │ └── MacAddressHelper.java ├── gradlew.bat ├── gradlew └── LICENSE /libpcapfiles/linux/readme.md: -------------------------------------------------------------------------------- 1 | The version is 1.8.1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | /.idea/ 3 | /build/ 4 | -------------------------------------------------------------------------------- /libpcapfiles/linux/libpcap.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoxingliang/mac-address-detector-java/HEAD/libpcapfiles/linux/libpcap.so -------------------------------------------------------------------------------- /libpcapfiles/linux/libpcap64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoxingliang/mac-address-detector-java/HEAD/libpcapfiles/linux/libpcap64.so -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoxingliang/mac-address-detector-java/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /libpcapfiles/macos/libpcap.1.8.1.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoxingliang/mac-address-detector-java/HEAD/libpcapfiles/macos/libpcap.1.8.1.dylib -------------------------------------------------------------------------------- /libpcapfiles/windows_npcap/x64/Packet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoxingliang/mac-address-detector-java/HEAD/libpcapfiles/windows_npcap/x64/Packet.dll -------------------------------------------------------------------------------- /libpcapfiles/windows_npcap/x64/wpcap.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoxingliang/mac-address-detector-java/HEAD/libpcapfiles/windows_npcap/x64/wpcap.dll -------------------------------------------------------------------------------- /libpcapfiles/windows_npcap/x86/Packet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoxingliang/mac-address-detector-java/HEAD/libpcapfiles/windows_npcap/x86/Packet.dll -------------------------------------------------------------------------------- /libpcapfiles/windows_npcap/x86/wpcap.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaoxingliang/mac-address-detector-java/HEAD/libpcapfiles/windows_npcap/x86/wpcap.dll -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://mirrors.cloud.tencent.com/gradle//gradle-8.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /libpcapfiles/windows_npcap/readme.md: -------------------------------------------------------------------------------- 1 | https://nmap.org/npcap/
2 | those dll is uploaded by installing the npcap.exe and those files will under systemroot/System32/Npcap
3 | and current version is 0.94
4 | Packet64.dll and wpcap64.dll is for x64.
5 | Packet.dll and wpcap.dll is for x86.
6 | 7 | -------------------------------------------------------------------------------- /How2BuildLibPcap.md: -------------------------------------------------------------------------------- 1 | # Abstract 2 | This page will describe how to build a libpcap.
3 | I also uploaded the latest 1.8.1 release files under directory libpcapfiles for you to test quickly
4 | 5 | # Linux 6 | `wget http://www.tcpdump.org/release/libpcap-1.8.1.tar.gz`
7 | `tar -xvzf libpcap-1.8.1.tar.gz`
8 | `yum install flex bison`
9 | `./configure`
10 | `make`
11 | Then the file is libpcap.so.1.8.1 under same directory, rename this to libpcap.so or libpcap64.so
12 | 13 | # Windows 14 | For windows, I used the npcap to do the lower layer packet sending and process. 15 | Download those libs from https://nmap.org/npcap/ sdk files. 16 | 17 | # MacOS 18 | `wget http://www.tcpdump.org/release/libpcap-1.8.1.tar.gz`
19 | `tar -xvzf libpcap-1.8.1.tar.gz`
20 | `./configure`
21 | `make`
22 | Then the file is libpcap.1.8.1.dylib under same directory 23 | -------------------------------------------------------------------------------- /release.md: -------------------------------------------------------------------------------- 1 | # how to release to maven central 2 | some envs are stored in local ~/.zshrc 3 | ```shell 4 | export JRELEASER_GPG_PASSPHRASE= 5 | export JRELEASER_GITHUB_TOKEN= 6 | export JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_USERNAME= 7 | export JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_PASSWORD= 8 | ``` 9 | 10 | gpg files are stored in local vm. 11 | ```shell 12 | publicKey = '/Users/edward/.jreleaser/public.pgp' 13 | secretKey = '/Users/edward/.jreleaser/private.pgp' 14 | ``` 15 | 16 | Now it's manually uploaded by guide [here](https://central.sonatype.org/publish/publish-portal-upload/#switching-to-ossrh-during-portal-early-access). 17 | So the commands are:
18 | ```shell 19 | ./gradlew clean 20 | ./gradlew publish 21 | ./gradlew jreleaserFullRelease 22 | # upload this bundle zip: 23 | ls ./build/jreleaser/deploy/mavenCentral/sonatype 24 | ``` 25 |
26 | 27 | 28 | It should use gradle command by guide [here](https://jreleaser.org/guide/latest/examples/maven/maven-central.html#_gradle) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mac-address-detector-java 2 | Use pcap4j to detect a mac address of remote host 3 | # Reason 4 | I searched the whole website, and I found no solution provided to detect a mac address of remote host under same subnetwork.
5 |
6 | So I created this project. 7 | # Implementation 8 | I used pcap4j to send and filtering packets. 9 | and for a IPv4 address, I used arp protocol related function to extract the mac address. 10 | and for a IPv6 address, I used ndp (neighbor discovery) protocol related function to extract the mac address. 11 | 12 | 13 | # Dependency 14 | 1. pcap4j 1.7.3
15 | 2. libpcap. (ready for use version files has been uploaded into libpcapfiles) 16 | 17 | # how to use 18 | ## 1. import this dependency 19 | 20 | gradle: 21 | ```shell 22 | implementation group: 'io.gitee.gaoxingliang', name: 'mac-address-detector-java', version: '0.0.1' 23 | ``` 24 | 25 | mvn: 26 | ```xml 27 | 28 | io.gitee.gaoxingliang 29 | mac-address-detector-java 30 | 0.0.1 31 | 32 | ``` 33 | and use it, see full [Test](./src/test/java/io/gitee/gaoxingliang/macdetector/Test.java): 34 | 35 | ```java 36 | MacAddress address = MacAddressHelper.getInstance() 37 | .getMacAddress(Inet4Address.getByName("192.168.110.100")); 38 | ``` 39 | 40 | ## 2.1 Use it on linux 41 | the pre-built dep files are under `libpcapfiles` directory.
42 | on linux, you can load the libpcap directly without install anything 43 | 44 | `` 45 | java -Dorg.pcap4j.core.pcapLibName=libpcap.so YourProgram 46 | `` 47 | 48 | ## 2.2 Use it on windows 49 | On windows, the npcap is required to be installed. (no reboot required) 50 | 51 | `` 52 | java -Dorg.pcap4j.core.pcapLibName=wpcap.dll -Dorg.pcap4j.core.packetLibName=Packet.dll YourProgram 53 | `` 54 | 55 | # Changes 56 | ## 09-06-2024 57 | release the lib to mvn repo. and renaming the package. 58 | ## 01-24-2018 59 | Add a func to do packet dump by using pcap4j. 60 | After build the jar, call the main class PacketDump. 61 | `` 62 | java -Dorg.pcap4j.core.pcapLibName=libpcap.so -cp .:networkutils-all-0.2.jar PacketDump "udp" 63 | `` -------------------------------------------------------------------------------- /src/test/java/io/gitee/gaoxingliang/macdetector/Test.java: -------------------------------------------------------------------------------- 1 | package io.gitee.gaoxingliang.macdetector; 2 | 3 | import org.pcap4j.util.*; 4 | 5 | import java.net.*; 6 | 7 | /** 8 | * simple demo 9 | * Created by edward.gao on 28/09/2017. 10 | */ 11 | public class Test { 12 | 13 | /** 14 | * args[0] is the ip you want to detected 15 | * @param args 16 | * @throws Exception 17 | */ 18 | public static void main(String[] args) throws Exception { 19 | InetAddress addr1 = InetAddress.getByName("172.16.14.13"); 20 | InetAddress addr2 = InetAddress.getByName("172.16.14.12"); 21 | InetAddress addr3 = InetAddress.getByName("172.16.15.12"); 22 | InetAddress mask = InetAddress.getByName("255.255.255.0"); 23 | 24 | System.out.println(MacAddressHelper._isUnderSameSubNet(addr1, addr2, mask)); 25 | System.out.println(MacAddressHelper._isUnderSameSubNet(addr1, addr3, mask)); 26 | 27 | //address: [/fe80:0:0:0:1016:168a:afda:7c7b] netmask: [/ffff:ffff:ffff:ffff:0:0:0:0] broadcastAddr: [null] dstAddr [null] 28 | InetAddress addr1v6 = InetAddress.getByName("fe80:0:0:0:1016:168a:afda:7c7b"); 29 | InetAddress maskv6 = InetAddress.getByName("ffff:ffff:ffff:ffff:0:0:0:0"); 30 | InetAddress addr2v6 = InetAddress.getByName("fe80:0:0:0:1016:168a:afda:7c7e"); 31 | InetAddress addr3v6 = InetAddress.getByName("fe80:0:0:1:1016:168a:afda:7c7e"); 32 | System.out.println(MacAddressHelper._isUnderSameSubNet(addr1v6, addr2v6, maskv6)); 33 | System.out.println(MacAddressHelper._isUnderSameSubNet(addr3v6, addr2v6, maskv6)); 34 | 35 | 36 | // address: [/fe80:0:0:0:1016:168a:afda:7c7b] netmask: [/ffff:ffff:ffff:ffff:0:0:0:0] broadcastAddr: [null] dstAddr [null] 37 | 38 | 39 | // list all interfaces.... 40 | MacAddressHelper.getInstance().getLocalInterfaces().forEach(l -> System.out.println("Found interface " + l)); 41 | if (args.length > 0) { 42 | MacAddress address = MacAddressHelper.getInstance().getMacAddress(Inet4Address.getByName(args[0])); 43 | System.out.println(String.format("ip=%s, mac=%s", args[0], address)); 44 | } 45 | MacAddressHelper.getInstance().shutdown(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/main/java/io/gitee/gaoxingliang/macdetector/PacketDump.java: -------------------------------------------------------------------------------- 1 | package io.gitee.gaoxingliang.macdetector; 2 | 3 | import org.pcap4j.core.*; 4 | import org.pcap4j.packet.Packet; 5 | import org.pcap4j.util.LinkLayerAddress; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Date; 13 | import java.util.List; 14 | import java.util.concurrent.CountDownLatch; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.concurrent.atomic.AtomicReference; 17 | 18 | /** 19 | * dump packets 20 | * Copy from pcap4j examples 21 | * The filter syntax is - https://biot.com/capstats/bpf.html 22 | *

23 | * When it's working for dns, you may got an IllegalArgumentException, 24 | * Which fixed in here: https://github.com/kaitoy/pcap4j/issues/123 25 | */ 26 | public class PacketDump { 27 | 28 | private static final String LINE_SEPARATOR = "\n"; 29 | 30 | // packets count 31 | private static final String COUNT_KEY = "count"; 32 | private static final int COUNT 33 | = Integer.getInteger(COUNT_KEY, Integer.MAX_VALUE); 34 | 35 | // run in quiet mode 36 | private static final String QUIET_KEY = "quiet"; 37 | private static final boolean QUIET = Boolean.getBoolean(QUIET_KEY); // default is false 38 | 39 | // interface 40 | private static final String INTF_KEY = "intf"; 41 | private static final String INTF = System.getProperty(INTF_KEY, ""); 42 | 43 | // how long to run in seconds 44 | private static final String RUN_KEY = "run"; 45 | private static final int RUN = Integer.getInteger(RUN_KEY, 120); 46 | 47 | private static final String READ_TIMEOUT_KEY = "readTimeout"; 48 | private static final int READ_TIMEOUT 49 | = Integer.getInteger(READ_TIMEOUT_KEY, 10); // [ms] 50 | 51 | private static final String SNAPLEN_KEY = "snaplen"; 52 | private static final int SNAPLEN 53 | = Integer.getInteger(SNAPLEN_KEY, 65536); // [bytes] 54 | 55 | private static final String TIMESTAMP_PRECISION_NANO_KEY = "timestampPrecision.nano"; 56 | private static final boolean TIMESTAMP_PRECISION_NANO 57 | = Boolean.getBoolean(TIMESTAMP_PRECISION_NANO_KEY); 58 | 59 | private static final String PCAP_FILE_KEY = "pcapFile"; 60 | private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss"); 61 | 62 | private static final String PCAP_FILE 63 | = System.getProperty(PCAP_FILE_KEY, "Dump" + sdf.format(new Date()) + ".pcap"); 64 | 65 | 66 | private PacketDump() { 67 | } 68 | 69 | static void usage() { 70 | System.out.println("============Usage============="); 71 | System.out.println("You can set other attributes as -D:"); 72 | System.out.println(String.format("\t%-20s -> %s", COUNT_KEY, "Packets numbers. Not supported Anymore. use -Drun. ")); 73 | System.out.println(String.format("\t%-20s -> %s", READ_TIMEOUT_KEY, "Read timeout in ms")); 74 | System.out.println(String.format("\t%-20s -> %s", SNAPLEN_KEY, "SnapLen")); 75 | System.out.println(String.format("\t%-20s -> %s", RUN_KEY, "How long in seconds to run")); 76 | System.out.println(String.format("\t%-20s -> %s", QUIET_KEY, "Run in quiet mode? if it's true, you should set the - " + INTF_KEY)); 77 | System.out.println(String.format("\t%-20s -> %s", INTF_KEY, "Set the network interface")); 78 | System.out.println(String.format("\t%-20s -> %s", PCAP_FILE_KEY, "Set the output file path")); 79 | System.out.println("============Usage=============\n\n"); 80 | } 81 | public static void main(String[] args) throws PcapNativeException, NotOpenException, IOException { 82 | String filter = args.length != 0 ? args[0] : ""; 83 | if (filter.isEmpty()) { 84 | System.out.println("You Should set the filter expression as the first argument"); 85 | System.out.println("The filter expression is same with the capture filter in the Wireshark or tcpdump"); 86 | usage(); 87 | return; 88 | } 89 | 90 | if (System.getProperty("quiet") == null) { 91 | System.out.println("Must set whether this is in a quiet mode. use -Dquiet=true|false"); 92 | usage(); 93 | return; 94 | } 95 | 96 | 97 | 98 | System.out.println(READ_TIMEOUT_KEY + ": " + READ_TIMEOUT); 99 | System.out.println(SNAPLEN_KEY + ": " + SNAPLEN); 100 | System.out.println(TIMESTAMP_PRECISION_NANO_KEY + ": " + TIMESTAMP_PRECISION_NANO); 101 | System.out.println(RUN_KEY + ": " + RUN); 102 | System.out.println(QUIET_KEY + ": " + QUIET); 103 | System.out.println(INTF_KEY + ": " + INTF); 104 | System.out.println("Filter is " + filter); 105 | System.out.println("\n"); 106 | 107 | // select the nifs 108 | PcapNetworkInterface nif; 109 | 110 | if (QUIET) { 111 | nif = _getNifByName(INTF); 112 | } 113 | else { 114 | if (INTF.isEmpty()) { 115 | try { 116 | nif = _selectNif(); 117 | } 118 | catch (Exception e) { 119 | e.printStackTrace(); 120 | return; 121 | } 122 | } 123 | else { 124 | nif = _getNifByName(INTF); 125 | } 126 | } 127 | 128 | if (nif == null) { 129 | System.out.println("No interfaces found, make sure your set the correct libpcap files"); 130 | return; 131 | } 132 | 133 | System.out.println(nif.getName() + "(" + nif.getDescription() + ")"); 134 | 135 | PcapHandle.Builder phb 136 | = new PcapHandle.Builder(nif.getName()) 137 | .snaplen(SNAPLEN) 138 | .promiscuousMode(PcapNetworkInterface.PromiscuousMode.PROMISCUOUS) 139 | .timeoutMillis(READ_TIMEOUT); 140 | if (TIMESTAMP_PRECISION_NANO) { 141 | phb.timestampPrecision(PcapHandle.TimestampPrecision.NANO); 142 | } 143 | PcapHandle handle = phb.build(); 144 | 145 | handle.setFilter( 146 | filter, 147 | BpfProgram.BpfCompileMode.OPTIMIZE 148 | ); 149 | 150 | // add one shutdownhook to print the output file 151 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 152 | File dumpfile = new File(PCAP_FILE); 153 | try { 154 | if (dumpfile.exists()) { 155 | System.out.println("Output file is - " + dumpfile.getCanonicalPath()); 156 | } 157 | else { 158 | System.out.println("Dump file not existed - " + dumpfile.getCanonicalPath()); 159 | } 160 | } catch (IOException e) {} 161 | })); 162 | 163 | CountDownLatch latch = new CountDownLatch(1); 164 | AtomicReference dumperAtomicReference = new AtomicReference<>(); 165 | Thread dumpThread = new Thread(new Runnable() { 166 | @Override 167 | public void run() { 168 | PcapDumper dumper = null; 169 | int num = 0; 170 | try { 171 | dumper = handle.dumpOpen(PCAP_FILE); 172 | dumperAtomicReference.set(dumper); 173 | while (!Thread.interrupted()) { 174 | Packet packet = handle.getNextPacket(); 175 | if (packet == null) { 176 | continue; 177 | } 178 | else { 179 | dumper.dump(packet, handle.getTimestamp()); 180 | num++; 181 | if (num >= COUNT) { 182 | break; 183 | } 184 | } 185 | } 186 | } 187 | catch(Exception e) { 188 | e.printStackTrace(); 189 | } 190 | finally { 191 | if (dumper != null) { 192 | try { 193 | dumper.close(); 194 | } catch (Exception e){} 195 | } 196 | try { 197 | handle.close(); 198 | } catch (Exception e){} 199 | latch.countDown(); 200 | } 201 | } 202 | }); 203 | dumpThread.start(); 204 | try { 205 | latch.await(RUN, TimeUnit.SECONDS); 206 | } 207 | catch (InterruptedException e) { 208 | } 209 | finally { 210 | dumpThread.interrupt(); 211 | PcapDumper dumper = dumperAtomicReference.get(); 212 | if (dumper != null) { 213 | try { 214 | dumper.close(); 215 | } catch (Exception e){} 216 | } 217 | try { 218 | handle.close(); 219 | } catch (Exception e){} 220 | } 221 | 222 | } 223 | 224 | 225 | private static PcapNetworkInterface _selectNif() throws Exception { 226 | List allDevs = _getAllNifs(); 227 | _listAllNifs(); 228 | int nifIdx = 0; 229 | while (true) { 230 | write(String.format("Select a device number ( 0 - %d) to capture packets, or enter 'q' to quit > ", allDevs.size() - 1)); 231 | String input; 232 | if ((input = read()) == null) { 233 | continue; 234 | } 235 | 236 | if (input.equals("q")) { 237 | return null; 238 | } 239 | 240 | try { 241 | nifIdx = Integer.parseInt(input); 242 | if (nifIdx < 0 || nifIdx >= allDevs.size()) { 243 | write("Invalid input." + LINE_SEPARATOR); 244 | continue; 245 | } 246 | else { 247 | break; 248 | } 249 | } 250 | catch (NumberFormatException e) { 251 | write("Invalid input." + LINE_SEPARATOR); 252 | continue; 253 | } 254 | } 255 | 256 | return allDevs.get(nifIdx); 257 | 258 | } 259 | 260 | 261 | /** 262 | * @param msg msg 263 | * @throws IOException if fails to write. 264 | */ 265 | protected static void write(String msg) throws IOException { 266 | System.out.print(msg); 267 | } 268 | 269 | /** 270 | * @return string 271 | * @throws IOException if fails to read. 272 | */ 273 | protected static String read() throws IOException { 274 | BufferedReader reader 275 | = new BufferedReader(new InputStreamReader(System.in)); 276 | return reader.readLine(); 277 | } 278 | 279 | private static List _getAllNifs() throws IOException { 280 | List allDevs = null; 281 | try { 282 | allDevs = Pcaps.findAllDevs(); 283 | } 284 | catch (PcapNativeException e) { 285 | throw new IOException(e.getMessage()); 286 | } 287 | 288 | if (allDevs == null || allDevs.isEmpty()) { 289 | throw new IOException("No NIF found, is the libpcap successfully set/installed?"); 290 | } 291 | return allDevs; 292 | } 293 | 294 | private static PcapNetworkInterface _getNifByName(String name) throws IOException { 295 | StringBuilder allNames = new StringBuilder(); 296 | 297 | for (PcapNetworkInterface i : _getAllNifs()) { 298 | allNames.append(i.getName()).append(";"); 299 | if (i.getName().equals(name)) { 300 | return i; 301 | } 302 | } 303 | _listAllNifs(); 304 | throw new IllegalArgumentException("Please choose one interface from above names. Unknown interface - " + name); 305 | } 306 | 307 | private static void _listAllNifs() throws IOException { 308 | List allDevs = _getAllNifs(); 309 | StringBuilder sb = new StringBuilder(); 310 | int nifIdx = 0; 311 | for (PcapNetworkInterface nif : allDevs) { 312 | sb.append("NIF[").append(nifIdx).append("]: ") 313 | .append(nif.getName()).append(LINE_SEPARATOR); 314 | 315 | if (nif.getDescription() != null) { 316 | sb.append(" : description: ") 317 | .append(nif.getDescription()).append(LINE_SEPARATOR); 318 | } 319 | 320 | for (LinkLayerAddress addr : nif.getLinkLayerAddresses()) { 321 | sb.append(" : link layer address: ") 322 | .append(addr).append(LINE_SEPARATOR); 323 | } 324 | 325 | for (PcapAddress addr : nif.getAddresses()) { 326 | sb.append(" : address: ") 327 | .append(addr.getAddress()).append(LINE_SEPARATOR); 328 | } 329 | sb.append(LINE_SEPARATOR).append(LINE_SEPARATOR); 330 | nifIdx++; 331 | } 332 | sb.append(LINE_SEPARATOR); 333 | System.out.println(sb.toString()); 334 | } 335 | 336 | } 337 | -------------------------------------------------------------------------------- /src/main/java/io/gitee/gaoxingliang/macdetector/MacAddressHelper.java: -------------------------------------------------------------------------------- 1 | package io.gitee.gaoxingliang.macdetector; 2 | 3 | import org.pcap4j.core.BpfProgram; 4 | import org.pcap4j.core.NotOpenException; 5 | import org.pcap4j.core.PacketListener; 6 | import org.pcap4j.core.PcapAddress; 7 | import org.pcap4j.core.PcapHandle; 8 | import org.pcap4j.core.PcapNativeException; 9 | import org.pcap4j.core.PcapNetworkInterface; 10 | import org.pcap4j.core.Pcaps; 11 | import org.pcap4j.packet.ArpPacket; 12 | import org.pcap4j.packet.EthernetPacket; 13 | import org.pcap4j.packet.IcmpV6CommonPacket; 14 | import org.pcap4j.packet.IcmpV6NeighborSolicitationPacket; 15 | import org.pcap4j.packet.IpV6NeighborDiscoverySourceLinkLayerAddressOption; 16 | import org.pcap4j.packet.IpV6Packet; 17 | import org.pcap4j.packet.IpV6SimpleFlowLabel; 18 | import org.pcap4j.packet.IpV6SimpleTrafficClass; 19 | import org.pcap4j.packet.Packet; 20 | import org.pcap4j.packet.namednumber.ArpHardwareType; 21 | import org.pcap4j.packet.namednumber.ArpOperation; 22 | import org.pcap4j.packet.namednumber.EtherType; 23 | import org.pcap4j.packet.namednumber.IcmpV6Code; 24 | import org.pcap4j.packet.namednumber.IcmpV6Type; 25 | import org.pcap4j.packet.namednumber.IpNumber; 26 | import org.pcap4j.packet.namednumber.IpVersion; 27 | import org.pcap4j.util.ByteArrays; 28 | import org.pcap4j.util.MacAddress; 29 | 30 | import java.io.File; 31 | import java.net.Inet4Address; 32 | import java.net.Inet6Address; 33 | import java.net.InetAddress; 34 | import java.net.NetworkInterface; 35 | import java.net.UnknownHostException; 36 | import java.util.ArrayList; 37 | import java.util.Enumeration; 38 | import java.util.HashMap; 39 | import java.util.List; 40 | import java.util.Map; 41 | import java.util.concurrent.BrokenBarrierException; 42 | import java.util.concurrent.CyclicBarrier; 43 | import java.util.concurrent.Executors; 44 | import java.util.concurrent.Future; 45 | import java.util.concurrent.ScheduledExecutorService; 46 | import java.util.concurrent.TimeUnit; 47 | import java.util.concurrent.TimeoutException; 48 | import java.util.concurrent.atomic.AtomicReference; 49 | import java.util.logging.*; 50 | 51 | /** 52 | * Detect a mac address 53 | * The main logic is: 54 | * 0) set the filter 55 | * 1) compose the packet 56 | * 2) start the receive thread task 57 | * 3) send tha packet 58 | * 4) wait response task result or timeout happen 59 | *

60 | * Created by edward.gao on 10/09/2017. 61 | */ 62 | public class MacAddressHelper { 63 | 64 | private static Logger logger = Logger.getLogger(MacAddressHelper.class.getCanonicalName()); 65 | 66 | private static final String _KEY_READTIMEOUT_IN_MILLS = "macaddress.readTimeout.mills"; 67 | private static final String _KEY_RECEIVETIMEOUT_IN_MILLS = "macaddress.receiveTimeout.mills"; 68 | private static final String _KEY_SEND_RECEIVE_THREADS = "macaddress.sendreceive.threads"; 69 | private static final String _KEY_WAIT_RECEIVE_START_IN_MILLS = "macaddress.waitStartedRunning.mills"; 70 | 71 | private List _localPcapNetworkInterfaces = null; 72 | private Map _localAddresse2MacAddress = null; 73 | private byte[] _IPv6_BROADCAST_IPADDRESS_PREFIX = null; 74 | private byte[] _IPv6_BROADCAST_MACADDRESS_PREFIX = null; 75 | 76 | private boolean _initted = false; 77 | 78 | private Throwable _initError = null; 79 | 80 | /** 81 | * NOTICE, the pcap will block for those time even when the data returned.... 82 | */ 83 | private int _readTimeoutInMillSeconds = 100; // 100 ms for reading a response from the packet 84 | private int _waitResponseTimeoutInMillSeconds = 2000; // 2 second, this should be quick enough under same switch 85 | 86 | // wait till the receive task started running, this should not be too large 87 | private int _waitReceiveTaskStartRunningInSeconds = 5000; 88 | 89 | private ScheduledExecutorService _executor; // send and receive thread pool 90 | private int _threadCount = 10; // send and receive thread pool count 91 | private int _snapLen = 65535; 92 | private int _sendPacketCount = 1; 93 | 94 | 95 | static { 96 | // for linux, only required libpcap, so you see the filename is with version 97 | // for windows, we need Packet.dll and wpcap.dll, and wpcap required Packet.dll so the filename is WITHOUT version 98 | String pcapLibKey = "org.pcap4j.core.pcapLibName"; 99 | String packetDllKey = "org.pcap4j.core.packetLibName"; 100 | } 101 | 102 | private MacAddressHelper() { 103 | try { 104 | 105 | _localPcapNetworkInterfaces = Pcaps.findAllDevs(); 106 | _executor = Executors.newScheduledThreadPool(_threadCount); 107 | _IPv6_BROADCAST_IPADDRESS_PREFIX = Inet6Address.getByName("FF02::1:FF00:0000").getAddress(); 108 | _IPv6_BROADCAST_MACADDRESS_PREFIX = MacAddress.getByName("33:33:ff:00:00:00").getAddress(); 109 | _localAddresse2MacAddress = new HashMap<>(); 110 | 111 | Enumeration localNetworkInterfaces = NetworkInterface.getNetworkInterfaces(); 112 | while (localNetworkInterfaces.hasMoreElements()) { 113 | NetworkInterface nwInterface = localNetworkInterfaces.nextElement(); 114 | byte[] mac = nwInterface.getHardwareAddress(); 115 | Enumeration addresses = nwInterface.getInetAddresses(); 116 | while (addresses.hasMoreElements()) { 117 | InetAddress currentIp = addresses.nextElement(); 118 | if (mac == null) { 119 | logger.warning("Can't find mac address for local ip=" + currentIp); 120 | _localAddresse2MacAddress.put(currentIp, null); 121 | } 122 | else { 123 | // although jdk said, the returned value is a mac address, but actually this may fail on some local mac address 124 | // the length is NOT 6 bytes 125 | if (mac.length != MacAddress.SIZE_IN_BYTES) { 126 | _localAddresse2MacAddress.put(currentIp, null); 127 | logger.warning(String.format("Found invalid mac address ip=%s,mac=%s", 128 | currentIp, ByteArrays.toHexString(mac, ":")) 129 | ); 130 | } 131 | else { 132 | _localAddresse2MacAddress.put(currentIp, MacAddress.getByAddress(mac)); 133 | } 134 | } 135 | 136 | } 137 | } 138 | logger.info(String.format("Mac Address helper init done localips=%d, threadPool=%d, readTimeout(ms)=%d, waitResponse(ms)" + 139 | "=%d, waitReceiveTaskStart(ms)=%d", 140 | _localAddresse2MacAddress.size(), _threadCount, 141 | _readTimeoutInMillSeconds, _waitResponseTimeoutInMillSeconds, _waitReceiveTaskStartRunningInSeconds)); 142 | _initted = true; 143 | } 144 | catch (Throwable e) { 145 | _initError = e; 146 | } 147 | } 148 | 149 | public static MacAddressHelper getInstance() { 150 | return MacAddressHelperHolder._INSTANCE; 151 | } 152 | 153 | 154 | public boolean successInit() { 155 | return _initted; 156 | } 157 | 158 | public List getLocalInterfaces() { 159 | if (!_initted) { 160 | throw new IllegalStateException("Fail to init component", _initError); 161 | } 162 | return _localPcapNetworkInterfaces; 163 | } 164 | 165 | public MacAddress getMacAddress(InetAddress address) { 166 | if (address == null) { 167 | throw new IllegalArgumentException("Address is null"); 168 | } 169 | if (!_initted) { 170 | throw new IllegalStateException("Fail to init component", _initError); 171 | } 172 | if (_localAddresse2MacAddress.containsKey(address)) { 173 | return _localAddresse2MacAddress.get(address); 174 | } 175 | try { 176 | return _getMacAddress(address); 177 | } 178 | catch (Exception e) { 179 | e.printStackTrace(); 180 | } 181 | return null; 182 | } 183 | 184 | public void shutdown() { 185 | if (_executor != null) { 186 | _executor.shutdown(); 187 | } 188 | } 189 | 190 | /** 191 | * get the remote mac address for a ip v4 address 192 | * 193 | * @param inetAddress 194 | * @return 195 | * @throws PcapNativeException 196 | * @throws NotOpenException 197 | * @throws InterruptedException 198 | */ 199 | private MacAddress _getMacAddress(InetAddress inetAddress) throws PcapNativeException, NotOpenException, InterruptedException, 200 | UnknownHostException { 201 | 202 | SelectedInterface intf = _selectSuitableNetworkInterface(inetAddress); 203 | if (intf._selectedNetworkInterface == null) { 204 | throw new IllegalStateException("Can't find interface for address " + inetAddress); 205 | } 206 | 207 | 208 | MacAddress localMacAddress = MacAddress.getByAddress(intf._selectedNetworkInterface.getLinkLayerAddresses().get(0).getAddress()); 209 | InetAddress localIpAddress = intf._selectedIpAddress; 210 | 211 | PcapHandle receiveHandle = null; 212 | PcapHandle sendHandle = null; 213 | final AtomicReference remoteMacAddress = new AtomicReference<>(); 214 | final boolean isIPv4Address = inetAddress instanceof Inet4Address; 215 | try { 216 | 217 | receiveHandle 218 | = intf._selectedNetworkInterface.openLive(_snapLen, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, 219 | _readTimeoutInMillSeconds); 220 | 221 | 222 | String filter = isIPv4Address ? _getFilter4IPv4(inetAddress, localMacAddress, localIpAddress) : 223 | _getFilter4IPv6((Inet6Address) inetAddress, localMacAddress, (Inet6Address) localIpAddress); 224 | System.out.println("receive filter set filter=" + filter); 225 | 226 | 227 | receiveHandle.setFilter(filter, BpfProgram.BpfCompileMode.OPTIMIZE); 228 | 229 | 230 | sendHandle 231 | = intf._selectedNetworkInterface.openLive(_snapLen, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, 232 | _readTimeoutInMillSeconds); 233 | PacketListener listener = (packet) -> { 234 | System.out.println("Packet received" + packet.toString()); 235 | if (isIPv4Address && packet.contains(ArpPacket.class)) { 236 | ArpPacket arp = packet.get(ArpPacket.class); 237 | if (arp.getHeader().getOperation().equals(ArpOperation.REPLY)) { 238 | remoteMacAddress.set(arp.getHeader().getSrcHardwareAddr()); 239 | } 240 | } 241 | else if (!isIPv4Address && packet.contains(EthernetPacket.class)) { 242 | EthernetPacket ns = packet.get(EthernetPacket.class); 243 | remoteMacAddress.set(ns.getHeader().getSrcAddr()); 244 | } 245 | }; 246 | 247 | CyclicBarrier cyclicBarrier = new CyclicBarrier(2); 248 | 249 | ReceiveTask receiveTask = new ReceiveTask(receiveHandle, listener, cyclicBarrier); 250 | Future receiveFuture = _executor.submit(receiveTask); 251 | 252 | EthernetPacket.Builder etherBuilder = isIPv4Address ? _getPacketBuilder4IPv4(inetAddress, localMacAddress, localIpAddress) : 253 | _getPacketBuilder4IPv6((Inet6Address) inetAddress, localMacAddress, (Inet6Address) localIpAddress); 254 | Packet p = etherBuilder.build(); 255 | try { 256 | cyclicBarrier.await(_waitReceiveTaskStartRunningInSeconds, TimeUnit.MILLISECONDS); 257 | } 258 | catch (BrokenBarrierException | TimeoutException e) { 259 | e.printStackTrace(); 260 | return remoteMacAddress.get(); 261 | } 262 | sendHandle.sendPacket(p); 263 | try { 264 | receiveFuture.get(_waitResponseTimeoutInMillSeconds, TimeUnit.MILLISECONDS); 265 | } 266 | catch (Exception e) { 267 | logger.severe("Fail to get the mac addr " + e); 268 | return null; 269 | } 270 | return remoteMacAddress.get(); 271 | } 272 | finally { 273 | _closeHandle(receiveHandle); 274 | _closeHandle(sendHandle); 275 | } 276 | } 277 | 278 | private String _getFilter4IPv4(InetAddress remoteAddress, MacAddress localMacAddress, InetAddress localIpAdress) { 279 | return String.format("arp and src host %s and dst host %s and ether dst %s", 280 | remoteAddress.getHostAddress(), 281 | localIpAdress.getHostAddress(), 282 | Pcaps.toBpfString(localMacAddress)); 283 | } 284 | 285 | private String _removePercentInIPv6Address(String ipv6Address) { 286 | int index = ipv6Address.indexOf("%"); 287 | if (index > 0) { 288 | return ipv6Address.substring(0, index); 289 | } 290 | return ipv6Address; 291 | } 292 | 293 | 294 | private String _getFilter4IPv6(Inet6Address remoteAddress, MacAddress localMacAddress, Inet6Address localIpAdress) { 295 | return String.format("icmp6 and src host %s and dst host %s and ether dst %s", 296 | _removePercentInIPv6Address(remoteAddress.getHostAddress()), 297 | _removePercentInIPv6Address(localIpAdress.getHostAddress()), 298 | Pcaps.toBpfString(localMacAddress)); 299 | } 300 | 301 | 302 | private EthernetPacket.Builder _getPacketBuilder4IPv4(InetAddress remoteAddress, MacAddress localMacAddress, InetAddress 303 | localIpAdress) { 304 | ArpPacket.Builder arpBuilder = new ArpPacket.Builder(); 305 | arpBuilder 306 | .hardwareType(ArpHardwareType.ETHERNET) 307 | .protocolType(EtherType.IPV4) 308 | .hardwareAddrLength((byte) MacAddress.SIZE_IN_BYTES) 309 | .protocolAddrLength((byte) ByteArrays.INET4_ADDRESS_SIZE_IN_BYTES) 310 | .srcHardwareAddr(localMacAddress) 311 | .srcProtocolAddr(localIpAdress) 312 | .dstHardwareAddr(MacAddress.ETHER_BROADCAST_ADDRESS) 313 | .operation(ArpOperation.REQUEST) 314 | .dstProtocolAddr(remoteAddress); 315 | 316 | 317 | EthernetPacket.Builder etherBuilder = new EthernetPacket.Builder(); 318 | etherBuilder.dstAddr(MacAddress.ETHER_BROADCAST_ADDRESS) 319 | .srcAddr(localMacAddress) 320 | .type(EtherType.ARP) 321 | .payloadBuilder(arpBuilder) 322 | .paddingAtBuild(true); 323 | 324 | return etherBuilder; 325 | } 326 | 327 | 328 | private EthernetPacket.Builder _getPacketBuilder4IPv6(Inet6Address remoteAddress, MacAddress localMacAddress, Inet6Address 329 | localIpAdress) throws UnknownHostException { 330 | MacAddress broadcastMacAddress = _getBroadcastMacAddress4IPv6(remoteAddress); 331 | Inet6Address broadcasetIPAddress = _getBroadcastIPAddress4IPv6(remoteAddress); 332 | 333 | IcmpV6NeighborSolicitationPacket.Builder v6Builder = new IcmpV6NeighborSolicitationPacket.Builder(); 334 | v6Builder.targetAddress(remoteAddress); 335 | v6Builder.reserved(0); 336 | IpV6NeighborDiscoverySourceLinkLayerAddressOption.Builder optionBuilder = new IpV6NeighborDiscoverySourceLinkLayerAddressOption 337 | .Builder(); 338 | IpV6NeighborDiscoverySourceLinkLayerAddressOption option = optionBuilder 339 | .linkLayerAddress(localMacAddress.getAddress()) 340 | .correctLengthAtBuild(true) 341 | .build(); 342 | List options = new ArrayList<>(); 343 | options.add(option); 344 | v6Builder.options(options); 345 | 346 | 347 | IcmpV6CommonPacket.Builder icmpV6b = new IcmpV6CommonPacket.Builder(); 348 | icmpV6b.type(IcmpV6Type.NEIGHBOR_SOLICITATION) 349 | .code(IcmpV6Code.NO_CODE) 350 | .srcAddr(localIpAdress) 351 | .dstAddr(broadcasetIPAddress) 352 | .payloadBuilder(v6Builder) 353 | .correctChecksumAtBuild(true); 354 | 355 | 356 | IpV6Packet.Builder ipv6b = new IpV6Packet.Builder(); 357 | ipv6b.version(IpVersion.IPV6) 358 | .trafficClass(IpV6SimpleTrafficClass.newInstance((byte) 0x12)) 359 | .flowLabel(IpV6SimpleFlowLabel.newInstance(0)) 360 | .nextHeader(IpNumber.ICMPV6) 361 | .hopLimit((byte) 255) 362 | .srcAddr(localIpAdress) 363 | .dstAddr(broadcasetIPAddress) // "fe80:0:0:0:250:56ff:febc:2688" -> "FF02::1:FFbc:2688" 364 | .correctLengthAtBuild(true) 365 | .payloadBuilder(icmpV6b); 366 | 367 | EthernetPacket.Builder etherBuilder = new EthernetPacket.Builder(); 368 | etherBuilder.dstAddr(broadcastMacAddress) 369 | .srcAddr(localMacAddress) 370 | .type(EtherType.IPV6) 371 | .payloadBuilder(ipv6b) 372 | .paddingAtBuild(true); 373 | 374 | return etherBuilder; 375 | } 376 | 377 | 378 | private void _closeHandle(PcapHandle handle) { 379 | if (handle != null) { 380 | try { 381 | handle.breakLoop(); 382 | } 383 | catch (Exception e) { 384 | } 385 | try { 386 | handle.close(); 387 | } 388 | catch (Exception e) { 389 | 390 | } 391 | } 392 | } 393 | 394 | /** 395 | * select a most suitable network interface according to the address 396 | * 397 | * @param address 398 | * @return 399 | */ 400 | private SelectedInterface _selectSuitableNetworkInterface(InetAddress address) { 401 | int similiarBytes = Integer.MIN_VALUE; 402 | SelectedInterface selectedInterface = new SelectedInterface(); 403 | 404 | byte[] inputIpInBytes = address.getAddress(); 405 | for (PcapNetworkInterface currentInterface : _localPcapNetworkInterfaces) { 406 | List addresses = currentInterface.getAddresses(); 407 | 408 | if (addresses != null) { 409 | for (PcapAddress ipAddress : addresses) { 410 | // make sure the address should be same type, all ipv4 or all ipv6 411 | if (!_isSameTypeAddress(address, ipAddress.getAddress())) { 412 | continue; 413 | } 414 | 415 | // we also need to make sure they are under same netmask 416 | // because in some cases, a router may return an external arp protocol 417 | // this worked for ipv4 and ipv6 418 | InetAddress maskAddr = ipAddress.getNetmask(); 419 | if (maskAddr != null 420 | && !_isUnderSameSubNet(address, ipAddress.getAddress(), maskAddr)) { 421 | continue; 422 | } 423 | 424 | byte[] ipInBytes = ipAddress.getAddress().getAddress(); 425 | int currentSimiliarBytes = _similarBytes(inputIpInBytes, ipInBytes); 426 | if (currentSimiliarBytes > similiarBytes) { 427 | selectedInterface._selectedNetworkInterface = currentInterface; 428 | selectedInterface._selectedIpAddress = ipAddress.getAddress(); 429 | similiarBytes = currentSimiliarBytes; 430 | } 431 | } 432 | } 433 | } 434 | return selectedInterface; 435 | } 436 | 437 | private boolean _isSameTypeAddress(InetAddress address1, InetAddress address2) { 438 | return (address1 instanceof Inet6Address && address2 instanceof Inet6Address) 439 | || (address1 instanceof Inet4Address && address2 instanceof Inet4Address); 440 | } 441 | 442 | private class ReceiveTask implements Runnable { 443 | 444 | private final PcapHandle receiveHandle; 445 | private final PacketListener listener; 446 | private final CyclicBarrier cyclicBarrier; 447 | private String loggerComponent, loggerId; 448 | 449 | public ReceiveTask(PcapHandle receiveHandle, PacketListener listener, CyclicBarrier cyclicBarrier) { 450 | this.receiveHandle = receiveHandle; 451 | this.listener = listener; 452 | this.cyclicBarrier = cyclicBarrier; 453 | } 454 | 455 | public void setLogger(String loggerComponent, String loggerId) { 456 | this.loggerComponent = loggerComponent; 457 | this.loggerId = loggerId; 458 | } 459 | 460 | @Override 461 | public void run() { 462 | try { 463 | cyclicBarrier.await(); 464 | receiveHandle.loop(_sendPacketCount, listener); 465 | } 466 | catch (Exception e) { 467 | e.printStackTrace(); 468 | } 469 | } 470 | } 471 | 472 | private class SelectedInterface { 473 | private PcapNetworkInterface _selectedNetworkInterface; 474 | private InetAddress _selectedIpAddress; 475 | } 476 | 477 | private int _similarBytes(byte[] b1, byte[] b2) { 478 | int n = b1.length; 479 | int i = 0; 480 | for (i = 0; i < n && b1[i] == b2[i]; i++) { 481 | } 482 | return i; 483 | } 484 | 485 | static boolean _isUnderSameSubNet(InetAddress testAddr, InetAddress currentAddr, InetAddress maskAddr) { 486 | byte [] test = testAddr.getAddress(); 487 | byte [] current = currentAddr.getAddress(); 488 | byte [] mask = maskAddr.getAddress(); 489 | boolean equal = true; 490 | for (int i =0; i < test.length; i++) { 491 | if ((test[i] & mask[i]) != (current[i] & mask[i])) { 492 | equal = false; 493 | break; 494 | } 495 | } 496 | return equal; 497 | } 498 | 499 | /** 500 | * The broadcast ip for a icmp v6 request is replaced all bytes except the last three bytes with _IPv6_BROADCAST_IPADDRESS_PREFIX 501 | * 502 | * @param inet6Address 503 | * @return 504 | */ 505 | private Inet6Address _getBroadcastIPAddress4IPv6(Inet6Address inet6Address) throws UnknownHostException { 506 | //"fe80:0:0:0:250:56ff:febc:2688" -> "FF02::1:FFbc:2688" 507 | byte[] ipInBytes = inet6Address.getAddress(); 508 | byte[] broadcastIpAddress = new byte[ipInBytes.length]; 509 | System.arraycopy(_IPv6_BROADCAST_IPADDRESS_PREFIX, 0, broadcastIpAddress, 0, _IPv6_BROADCAST_IPADDRESS_PREFIX.length); 510 | int reservedBytes = 3; 511 | System.arraycopy(ipInBytes, ipInBytes.length - reservedBytes, broadcastIpAddress, _IPv6_BROADCAST_IPADDRESS_PREFIX.length - 512 | reservedBytes, reservedBytes); 513 | return (Inet6Address) Inet6Address.getByAddress(broadcastIpAddress); 514 | } 515 | 516 | /** 517 | * The broadcast mac address for a icmpv6 request is replaced all bytes with 33:33:ff except the last thress bytes 518 | * fe80::250:56ff:fe95:f8d -> 33:33:ff:95:0f:8d 519 | * 520 | * @param inet6Address 521 | * @return 522 | */ 523 | private MacAddress _getBroadcastMacAddress4IPv6(Inet6Address inet6Address) { 524 | byte[] ipInBytes = inet6Address.getAddress(); 525 | byte[] broadcastMacAddress = new byte[_IPv6_BROADCAST_MACADDRESS_PREFIX.length]; 526 | System.arraycopy(_IPv6_BROADCAST_MACADDRESS_PREFIX, 0, broadcastMacAddress, 0, _IPv6_BROADCAST_MACADDRESS_PREFIX.length); 527 | int reservedBytes = 3; 528 | System.arraycopy(ipInBytes, ipInBytes.length - reservedBytes, broadcastMacAddress, _IPv6_BROADCAST_MACADDRESS_PREFIX.length - 529 | reservedBytes, reservedBytes); 530 | return MacAddress.getByAddress(broadcastMacAddress); 531 | } 532 | 533 | private static class MacAddressHelperHolder { 534 | // to make sure this is really lazy init 535 | // and the static block in the MacAddressHelper will run before this construction 536 | private static MacAddressHelper _INSTANCE = new MacAddressHelper(); 537 | 538 | private MacAddressHelperHolder() { 539 | } 540 | 541 | public static final MacAddressHelper get() { 542 | return _INSTANCE; 543 | } 544 | } 545 | 546 | 547 | /** 548 | * @param args the remote device ip lists split by comma 549 | */ 550 | public static void main(String[] args) { 551 | 552 | // check libpcap file really exists 553 | String libpcapKeyName = "org.pcap4j.core.pcapLibName"; 554 | String libpcapSet = System.getProperty(libpcapKeyName); 555 | if (libpcapSet == null || libpcapSet.isEmpty()) { 556 | System.out.println(String.format("no libpcap property set, try with -D%s=YourLibPcapFile", libpcapKeyName)); 557 | return; 558 | } 559 | File libpcapFile = new File(libpcapSet); 560 | if (!(libpcapFile.exists() && libpcapFile.isFile())) { 561 | System.out.println("libpcap file not exists " + libpcapFile.getAbsolutePath()); 562 | return; 563 | } 564 | System.out.println("Use libpcap file - " + libpcapFile.getAbsolutePath()); 565 | System.out.println(); 566 | System.out.println("Version - " + Pcaps.libVersion()); 567 | // list all interfaces 568 | List localInterfaces = MacAddressHelper.getInstance().getLocalInterfaces(); 569 | System.out.println("List local interfaces"); 570 | for (PcapNetworkInterface localIntf : localInterfaces) { 571 | System.out.println("\t" + localIntf); 572 | } 573 | 574 | if (args == null || args.length == 0) { 575 | System.out.println("No remote device ips provided. try with arguments IP1,IP2 ...."); 576 | return; 577 | } 578 | for (String ip : args) { 579 | System.out.println("Start find mac for ip - " + ip); 580 | try { 581 | MacAddress mac = MacAddressHelper.getInstance().getMacAddress(InetAddress.getByName(ip)); 582 | System.out.println("The mac is - " + mac); 583 | } 584 | catch (UnknownHostException e) { 585 | System.out.println("Unknown host " + ip); 586 | e.printStackTrace(); 587 | } 588 | } 589 | 590 | MacAddressHelper.getInstance().shutdown(); 591 | 592 | } 593 | 594 | } 595 | --------------------------------------------------------------------------------