├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src └── main └── java └── io └── github └── isotes └── net └── tun └── io ├── Packet.java ├── TunDevice.java ├── jna ├── Darwin.java ├── FdAndName.java ├── LibC.java ├── Linux.java └── package-info.java └── package-info.java /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /gradle.properties 3 | /settings.gradle 4 | /tun-io-example/ 5 | 6 | 7 | # Created by https://www.gitignore.io/api/gradle,linux,macos,windows 8 | # Edit at https://www.gitignore.io/?templates=gradle,linux,macos,windows 9 | 10 | ### Linux ### 11 | *~ 12 | 13 | # temporary files which can be created if a process still has a handle open of a deleted file 14 | .fuse_hidden* 15 | 16 | # KDE directory preferences 17 | .directory 18 | 19 | # Linux trash folder which might appear on any partition or disk 20 | .Trash-* 21 | 22 | # .nfs files are created when an open file is removed but is still being accessed 23 | .nfs* 24 | 25 | ### macOS ### 26 | # General 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Icon must end with two \r 32 | Icon 33 | 34 | # Thumbnails 35 | ._* 36 | 37 | # Files that might appear in the root of a volume 38 | .DocumentRevisions-V100 39 | .fseventsd 40 | .Spotlight-V100 41 | .TemporaryItems 42 | .Trashes 43 | .VolumeIcon.icns 44 | .com.apple.timemachine.donotpresent 45 | 46 | # Directories potentially created on remote AFP share 47 | .AppleDB 48 | .AppleDesktop 49 | Network Trash Folder 50 | Temporary Items 51 | .apdisk 52 | 53 | ### Windows ### 54 | # Windows thumbnail cache files 55 | Thumbs.db 56 | Thumbs.db:encryptable 57 | ehthumbs.db 58 | ehthumbs_vista.db 59 | 60 | # Dump file 61 | *.stackdump 62 | 63 | # Folder config file 64 | [Dd]esktop.ini 65 | 66 | # Recycle Bin used on file shares 67 | $RECYCLE.BIN/ 68 | 69 | # Windows Installer files 70 | *.cab 71 | *.msi 72 | *.msix 73 | *.msm 74 | *.msp 75 | 76 | # Windows shortcuts 77 | *.lnk 78 | 79 | ### Gradle ### 80 | .gradle 81 | build/ 82 | 83 | # Ignore Gradle GUI config 84 | gradle-app.setting 85 | 86 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 87 | !gradle-wrapper.jar 88 | 89 | # Cache of project 90 | .gradletasknamecache 91 | 92 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 93 | # gradle/wrapper/gradle-wrapper.properties 94 | 95 | ### Gradle Patch ### 96 | **/build/ 97 | 98 | # End of https://www.gitignore.io/api/gradle,linux,macos,windows 99 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | matrix: 4 | include: 5 | - os: linux 6 | - os: osx 7 | 8 | before_cache: 9 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 10 | - rm -rf $HOME/.gradle/caches/*/plugin-resolution/ 11 | cache: 12 | directories: 13 | - $HOME/.gradle/caches/ 14 | - $HOME/.gradle/wrapper/ 15 | 16 | 17 | script: 18 | - git clone https://github.com/isotes/tun-io-example 19 | - cd tun-io-example 20 | - ifconfig 21 | - ./gradlew --include-build .. installDist 22 | - sudo ./build/install/tun-io-example/bin/tun-io-example -a 192.168.222.5 --pings 3 --req-for-req --exit-after-ping 2>&1 | tee build/output.txt 23 | - grep 'ICMP Echo' build/output.txt | wc -l | grep 6 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tun-io [![License](https://img.shields.io/github/license/isotes/tun-io)](LICENSE) [![Build Status](https://travis-ci.com/isotes/tun-io.svg?branch=master)](https://travis-ci.com/isotes/tun-io) [![Javadoc](https://img.shields.io/badge/docs-javadoc-blue)](https://isotes.github.io/javadoc/tun-io-1.0.0/) [![Maven Central](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Fio%2Fgithub%2Fisotes%2Ftun-io%2Fmaven-metadata.xml)](https://search.maven.org/search?q=g:io.github.isotes%20a:tun-io) 2 | JNA based access to TUN devices on Linux and macOS. 3 | 4 | ## Use 5 | See [Maven Central](https://search.maven.org/search?q=g:io.github.isotes%20a:tun-io) for the current Maven coordinates. Additionally, you have to declare a dependency on [JNA](https://search.maven.org/search?q=g:net.java.dev.jna%20a:jna). The library requires Java 8 but should also work with newer versions. 6 | 7 | ## API 8 | The main entry point is the [TunDevice](src/main/java/io/github/isotes/net/tun/io/TunDevice.java) class which contains several `open()` methods to open or create a TUN device. Afterwards, it is possible to read/write IPv4 and IPv6 packets. The following shows a minimal code snippet while a full-fledged example can be found in the [tun-io-example project](https://github.com/isotes/tun-io-example) and more information is in the [API documentation](https://isotes.github.io/javadoc/tun-io-1.0.0/). 9 | 10 | 11 | ```Java 12 | public static void main(String[] args) { 13 | try (TunDevice tun = TunDevice.open()) { 14 | System.out.println("Created tun device " + tun.getName()); 15 | // device configuration (address, ...) required 16 | while (true) { 17 | Packet packet = tun.read(); 18 | System.out.printf("IPv%d packet with %d bytes\n", packet.ipVersion(), packet.size()); 19 | } 20 | } catch (IOException e) { 21 | e.printStackTrace(); 22 | } 23 | } 24 | ``` 25 | 26 | 27 | ## TUN Device Configuration 28 | The configuration of the TUN device is not covered by this library. The [tun-io-example project](https://github.com/isotes/tun-io-example) makes use of the `ip`/`ifconfig` command line utilities provided by Linux and macOS respectively and can be used as inspiration. 29 | 30 | 31 | ## Portability 32 | The library supports only TUN (vs. TAP) interfaces to be compatible with macOS. Nevertheless, there are a couple of further limitations on macOS: 33 | - It is only possible to create new devices (in contrast to opening existing ones). 34 | - Names must follow the pattern 'utun[NUMBER]' with 0 being already in use on macOS Sierra and newer. 35 | 36 | On Linux, the library uses the [TUN/TAP](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) interface provided by the kernel. On macOS, the library uses the [utun kernel control](https://github.com/apple/darwin-xnu/blob/master/bsd/net/if_utun.h) interface. 37 | 38 | The only dependency is [JNA](https://github.com/java-native-access/jna) and the library has been successfully tested on macOS (10.14), Linux on a PC (Ubuntu 18.04 64-bit), and Linux on a Raspberry PI 3 with Raspbian 9 (based on Debian Stretch). 39 | 40 | 41 | ## Development 42 | The library is a Gradle project. The development is usually performed in combination with the tun-io-example project and using the [Gradle composite build](https://docs.gradle.org/current/userguide/composite_builds.html) feature (in conjunction with IntelliJ) has worked flawlessly. Just edit `settings.gradle` of the example project and add the line `includeBuild '../tun-io'` (adjust the path if necessary). Then open that project in the IDE to work on this library and the example project simultaneously. 43 | 44 | 45 | ## License 46 | [Apache 2.0](LICENSE) 47 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'maven-publish' 4 | id 'signing' 5 | } 6 | 7 | group = 'io.github.isotes' 8 | version = '1.1.0-SNAPSHOT' 9 | 10 | sourceCompatibility = 1.8 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | dependencies { 17 | implementation 'net.java.dev.jna:jna:5.3.1' 18 | 19 | testImplementation 'org.zeroturnaround:zt-exec:1.10' 20 | testImplementation 'org.slf4j:slf4j-jdk14:1.7.26' 21 | testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' 22 | } 23 | 24 | javadoc { 25 | source = sourceSets.main.allJava 26 | excludes = ['io/github/isotes/net/tun/io/jna/**'] 27 | options.with { 28 | links 'https://docs.oracle.com/javase/8/docs/api/', 'https://java-native-access.github.io/jna/4.2.1/' 29 | } 30 | if(JavaVersion.current().isJava9Compatible()) { 31 | options.addBooleanOption('html5', true) 32 | options.addStringOption('source', '1.8') 33 | } 34 | } 35 | 36 | 37 | task sourcesJar(type: Jar) { 38 | from sourceSets.main.allJava 39 | archiveClassifier = 'sources' 40 | } 41 | 42 | task javadocJar(type: Jar) { 43 | from javadoc 44 | archiveClassifier = 'javadoc' 45 | } 46 | 47 | publishing { 48 | publications { 49 | mavenJava(MavenPublication) { 50 | from components.java 51 | artifact sourcesJar 52 | artifact javadocJar 53 | versionMapping { 54 | usage('java-api') { 55 | fromResolutionOf('runtimeClasspath') 56 | } 57 | usage('java-runtime') { 58 | fromResolutionResult() 59 | } 60 | } 61 | pom { 62 | name = 'tun-io' 63 | description = 'JNA based access to TUN devices on Linux and macOS' 64 | url = 'https://github.com/isotes/tun-io' 65 | licenses { 66 | license { 67 | name = 'The Apache License, Version 2.0' 68 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 69 | } 70 | } 71 | developers { 72 | developer { 73 | name = 'Robert Sauter' 74 | email = 'isotes@gmail.com' 75 | } 76 | } 77 | scm { 78 | connection = 'scm:git:https://github.com/isotes/tun-io.git' 79 | developerConnection = 'scm:svn:https://github.com/isotes/tun-io.git' 80 | url = 'https://github.com/isotes/tun-io' 81 | } 82 | } 83 | } 84 | } 85 | repositories { 86 | maven { 87 | if (project.hasProperty('publishOssrh') && publishOssrh) { 88 | def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2" 89 | def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots" 90 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 91 | authentication { 92 | basic(BasicAuthentication) 93 | } 94 | credentials { 95 | username = ossrhUsername 96 | password = ossrhPassword 97 | } 98 | } else { 99 | def releasesRepoUrl = "$buildDir/repos/releases" 100 | def snapshotsRepoUrl = "$buildDir/repos/snapshots" 101 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 102 | } 103 | } 104 | } 105 | } 106 | 107 | signing { 108 | required { !version.endsWith('SNAPSHOT') } 109 | if (required) { 110 | sign publishing.publications.mavenJava 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isotes/tun-io/6c840ecccbf04ca8d3c35d54141033f34ebeb8dc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /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 Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /src/main/java/io/github/isotes/net/tun/io/Packet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Robert Sauter 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.github.isotes.net.tun.io; 7 | 8 | import java.nio.ByteBuffer; 9 | 10 | /** 11 | * The packet abstraction used for the primary I/O functions of {@link TunDevice}. 12 | * 13 | *

The main attribute for manipulation is the {@link #packet} {@link ByteBuffer}. Additionally, the class contains a 14 | * small set of utility functions. The uint* functions take the {@link ByteBuffer#order()} into account, which defaults 15 | * to the network byte order (big endian) when obtained from one of the {@link TunDevice#read()} methods. The utility 16 | * methods do not change (or save and restore) the {@link ByteBuffer#position()} of {@link #packet}. 17 | *

18 | * 19 | *

There are no limitations on manipulating a Packet obtained from one of the {@link TunDevice#read()} 20 | * functions.

21 | */ 22 | public final class Packet { 23 | /** 24 | * The packet and the main attribute for manipulation before sending. When obtained by reading from {@link 25 | * TunDevice}, network byte order (big endian) is active. 26 | */ 27 | public final ByteBuffer packet; 28 | 29 | public Packet(int capacity) { 30 | this.packet = ByteBuffer.allocate(capacity); 31 | } 32 | 33 | public Packet(ByteBuffer packet) { 34 | this.packet = packet; 35 | } 36 | 37 | public int uint8(int index) { 38 | return Byte.toUnsignedInt(packet.get(index)); 39 | } 40 | 41 | public int uint16(int index) { 42 | return Short.toUnsignedInt(packet.getShort(index)); 43 | } 44 | 45 | public long uint32(int index) { 46 | return Integer.toUnsignedLong(packet.getInt(index)); 47 | } 48 | 49 | public Packet uint8(int index, int value) { 50 | packet.put(index, (byte) value); 51 | return this; 52 | } 53 | 54 | public Packet uint16(int index, int value) { 55 | packet.putShort(index, (short) value); 56 | return this; 57 | } 58 | 59 | public Packet uint32(int index, long value) { 60 | packet.putInt(index, (int) value); 61 | return this; 62 | } 63 | 64 | public byte[] bytes(int position, int length) { 65 | byte[] r = new byte[length]; 66 | int orgPos = packet.position(); 67 | packet.position(position); 68 | packet.get(r); 69 | packet.position(orgPos); 70 | return r; 71 | } 72 | 73 | public byte[] bytes() { 74 | return bytes(0, packet.limit()); 75 | } 76 | 77 | public Packet bytes(int position, byte[] bytes, int offset, int length) { 78 | int orgPos = packet.position(); 79 | packet.position(position); 80 | packet.put(bytes, offset, length); 81 | packet.position(orgPos); 82 | return this; 83 | } 84 | 85 | public Packet bytes(int position, byte[] bytes) { 86 | return bytes(position, bytes, 0, bytes.length); 87 | } 88 | 89 | public int size() { 90 | return packet.limit(); 91 | } 92 | 93 | public int ipVersion() { 94 | return uint8(0) >> 4; 95 | } 96 | 97 | public boolean isIpv4() { 98 | return ipVersion() == 4; 99 | } 100 | 101 | public boolean isIpv6() { 102 | return ipVersion() == 6; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/io/github/isotes/net/tun/io/TunDevice.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Robert Sauter 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.github.isotes.net.tun.io; 7 | 8 | import com.sun.jna.LastErrorException; 9 | import com.sun.jna.NativeLong; 10 | import io.github.isotes.net.tun.io.jna.Darwin; 11 | import io.github.isotes.net.tun.io.jna.FdAndName; 12 | import io.github.isotes.net.tun.io.jna.LibC; 13 | import io.github.isotes.net.tun.io.jna.Linux; 14 | 15 | import java.io.IOException; 16 | import java.nio.ByteBuffer; 17 | import java.nio.ByteOrder; 18 | 19 | /** 20 | * Open/create a TUN device on macOS and Linux. 21 | * 22 | *

While the class supports both Linux and macOS, there are limitations on the latter:

23 | * 27 | * 28 | *

The basic approach is opening/creating a TUN device with one of the open() methods of this class. After 29 | * configuring the interface, e.g., with ip/ifconfig, which is not covered by this library, it is possible to read/write 30 | * IPv4 and IPv6 packets using the appropriate methods. I/O is always performed on packet granularity.

31 | * 32 | *

The {@link Packet} class is used for reading and can be used for writing (in addition to byte arrays). Apart from 33 | * the {@link ByteBuffer} representing the packet, it contains a number of utility methods for easier manipulation.

34 | * 35 | *

On Linux, we recommend creating and configuring the TUN device with the ip command which allows setting 36 | * permissions so that the application using this library does not need to run with elevated privileges.

37 | * 38 | *

See tun-io-example for a full-fledged 39 | * example.

40 | */ 41 | public class TunDevice implements AutoCloseable { 42 | private static final int DEFAULT_MTU = 2048; 43 | private static final byte[] IPV4_HEADER_DARWIN = new byte[]{0, 0, 0, 2}; // AF_INET in socket.h 44 | private static final byte[] IPV6_HEADER_DARWIN = new byte[]{0, 0, 0, 30}; // AF_INET6 in socket.h 45 | /* package */ final int fd; 46 | private final String name; 47 | /* package */ NativeLong readMtu = new NativeLong(DEFAULT_MTU); 48 | 49 | /* package */ TunDevice(String name, int fd) { 50 | this.name = name; 51 | this.fd = fd; 52 | } 53 | 54 | /** 55 | * Create a new TUN device with the name automatically chosen by the OS. See {@link #open(String)} for more 56 | * information. 57 | * 58 | * @return the new tun device 59 | * @throws IOException if one of the system call fails 60 | */ 61 | public static TunDevice open() throws IOException { 62 | return open(null); 63 | } 64 | 65 | /** 66 | * Create/open a TUN device name 'utun[NUMBER]'. For compatibility with macOS Sierra and newer, the number should be 67 | * greater than 0. See {@link #open(String) } for more information. 68 | * 69 | * @param number the number 70 | * @return the open tun device 71 | * @throws IOException if one of the system call fails 72 | */ 73 | public static TunDevice open(int number) throws IOException { 74 | return open("utun" + number); 75 | } 76 | 77 | /** 78 | * Create/open a TUN device with the specified name. 79 | * 80 | *

On Linux, name can be a simple file name (ASCII) of up to 16 characters.

81 | * 82 | *

On macOS, it is only possible to create new devices and the name must be of the form 'utun[N]', with N 83 | * being a number starting at 0 (e.g., utun12). However, starting from macOS Sierra, utun0 is always created by the 84 | * system and may not be used by other programs. Thus, using a number of 1 or higher is strongly recommended.

85 | * 86 | * @param name the name of the device or null if the name should be automatically chosen by the OS 87 | * @return the open tun device 88 | * @throws IOException if one of the system call fails 89 | */ 90 | public static TunDevice open(String name) throws IOException { 91 | try { 92 | if (System.getProperty("os.name").toLowerCase().contains("mac")) { 93 | FdAndName fdAndName = Darwin.open(name); 94 | return new TunDeviceWithHeader(fdAndName.name, fdAndName.fd, IPV4_HEADER_DARWIN, IPV6_HEADER_DARWIN); 95 | } else { 96 | FdAndName fdAndName = Linux.open(name); 97 | return new TunDevice(fdAndName.name, fdAndName.fd); 98 | } 99 | } catch (LastErrorException ex) { 100 | throw new IOException("Error opening TUN device: " + ex.getMessage(), ex); 101 | } 102 | } 103 | 104 | public String getName() { 105 | return name; 106 | } 107 | 108 | @Override 109 | public void close() throws IOException { 110 | try { 111 | LibC.close(fd); 112 | } catch (LastErrorException ex) { 113 | throw new IOException("Error closing TUN device: " + ex.getMessage(), ex); 114 | } 115 | } 116 | 117 | /** 118 | * Set the MTU used for the buffers in the read() methods 119 | * 120 | * @param readMtu the new buffer size when calling the read() methods 121 | * @return the TunDevice 122 | */ 123 | public TunDevice setReadMtu(int readMtu) { 124 | this.readMtu = new NativeLong(readMtu); 125 | return this; 126 | } 127 | 128 | protected Packet read(int limitIpVersion) throws IOException { 129 | try { 130 | while (true) { 131 | ByteBuffer inbuf = ByteBuffer.allocate(readMtu.intValue()); 132 | int n = LibC.read(fd, inbuf, readMtu); 133 | int version = Byte.toUnsignedInt(inbuf.get(0)) >> 4; 134 | if (version != limitIpVersion && limitIpVersion != 0) { 135 | continue; 136 | } 137 | inbuf.order(ByteOrder.BIG_ENDIAN); 138 | inbuf.limit(n); 139 | return new Packet(inbuf); 140 | } 141 | } catch (LastErrorException ex) { 142 | throw new IOException("Reading from TUN device " + getName() + " failed: " + ex.getMessage(), ex); 143 | } 144 | } 145 | 146 | public Packet read() throws IOException { 147 | return read(0); 148 | } 149 | 150 | public Packet readIPv4Packet() throws IOException { 151 | return read(4); 152 | } 153 | 154 | public Packet readIPv6Packet() throws IOException { 155 | return read(6); 156 | } 157 | 158 | public TunDevice write(Packet packet) throws IOException { 159 | return write(packet.packet); 160 | } 161 | 162 | public TunDevice write(ByteBuffer packet) throws IOException { 163 | try { 164 | LibC.write(fd, packet, new NativeLong(packet.remaining())); 165 | } catch (LastErrorException ex) { 166 | throw new IOException("Writing to TUN device " + getName() + " failed: " + ex.getMessage(), ex); 167 | } 168 | return this; 169 | } 170 | 171 | public TunDevice write(byte[] packet) throws IOException { 172 | try { 173 | LibC.write(fd, packet, new NativeLong(packet.length)); 174 | } catch (LastErrorException ex) { 175 | throw new IOException("Writing to TUN device " + getName() + " failed: " + ex.getMessage(), ex); 176 | } 177 | return this; 178 | } 179 | 180 | public Packet newPacket(int capacity) { 181 | return new Packet(ByteBuffer.allocate(capacity)); 182 | } 183 | 184 | private static class TunDeviceWithHeader extends TunDevice { 185 | private final byte[] ipv4Header; 186 | private final byte[] ipv6Header; 187 | private final int headerSize; 188 | 189 | 190 | /* package */ TunDeviceWithHeader(String name, int fd, byte[] ipv4Header, byte[] ipv6Header) { 191 | super(name, fd); 192 | this.ipv4Header = ipv4Header; 193 | this.ipv6Header = ipv6Header; 194 | this.headerSize = ipv4Header.length; 195 | } 196 | 197 | @Override 198 | protected Packet read(int limitIpVersion) throws IOException { 199 | try { 200 | while (true) { 201 | ByteBuffer inbuf = ByteBuffer.allocate(headerSize + readMtu.intValue()); 202 | int n = LibC.read(fd, inbuf, readMtu); 203 | int version = Byte.toUnsignedInt(inbuf.get(headerSize)) >> 4; 204 | if (version != limitIpVersion && limitIpVersion != 0) { 205 | continue; 206 | } 207 | // slice without the header but with the full capacity allowing the later use of the complete buffer 208 | inbuf.position(headerSize); 209 | ByteBuffer packetBuf = inbuf.slice(); 210 | packetBuf.order(ByteOrder.BIG_ENDIAN); // default to network byte order 211 | packetBuf.limit(n - headerSize); 212 | return new Packet(packetBuf); 213 | } 214 | } catch (LastErrorException ex) { 215 | throw new IOException("Reading from TUN device " + getName() + " failed: " + ex.getMessage(), ex); 216 | } 217 | } 218 | 219 | @Override 220 | public TunDevice write(ByteBuffer packet) throws IOException { 221 | byte[] bytes = new byte[headerSize + packet.remaining()]; 222 | byte[] header = Byte.toUnsignedInt(packet.get(0)) >> 4 == 6 ? ipv6Header : ipv4Header; 223 | System.arraycopy(header, 0, bytes, 0, header.length); 224 | packet.slice().get(bytes, headerSize, packet.remaining()); 225 | return super.write(bytes); 226 | } 227 | 228 | @Override 229 | public TunDevice write(byte[] packet) throws IOException { 230 | byte[] bytes = new byte[headerSize + packet.length]; 231 | byte[] header = Byte.toUnsignedInt(packet[0]) >> 4 == 6 ? ipv6Header : ipv4Header; 232 | System.arraycopy(header, 0, bytes, 0, header.length); 233 | System.arraycopy(packet, 0, bytes, headerSize, packet.length); 234 | return super.write(bytes); 235 | } 236 | 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/main/java/io/github/isotes/net/tun/io/jna/Darwin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Robert Sauter 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.github.isotes.net.tun.io.jna; 7 | 8 | import com.sun.jna.NativeLong; 9 | import com.sun.jna.Structure; 10 | import com.sun.jna.ptr.IntByReference; 11 | 12 | import java.nio.charset.StandardCharsets; 13 | 14 | /** 15 | * macOS implementation for creating a TUN device using the 16 | * utun kernel control) interface 17 | */ 18 | public class Darwin { 19 | private static final int SYSPROTO_CONTROL = 2; 20 | private static final int AF_SYSTEM = 32; 21 | private static final int SOCK_DGRAM = 2; 22 | private static final String UTUN_CONTROL_NAME = "com.apple.net.utun_control"; // from if_utun.h 23 | private static final int UTUN_OPT_IFNAME = 2; // from if_utun.h 24 | private static final NativeLong CTLIOCGINFO = new NativeLong(0xC0644E03L); // from kern_control.h 25 | 26 | public static FdAndName open(String name) { 27 | int number = 0; // default: name automatically chosen by OS 28 | if (name != null && name.isEmpty()) { 29 | name = null; 30 | } 31 | if (name != null) { 32 | if (!name.startsWith("utun")) { 33 | throw new IllegalArgumentException("Parameter 'name' must start with 'utun' on macOS (or be null)"); 34 | } 35 | try { 36 | number = Integer.valueOf(name.substring(4)) + 1; // 1 -> utun0, ... 37 | } catch (NumberFormatException ex) { 38 | throw new IllegalArgumentException("Parameter 'name' must be 'utun' on macOS"); 39 | } 40 | } 41 | 42 | int fd = LibC.socket(AF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); 43 | CtlInfo ctlInfo = new CtlInfo(UTUN_CONTROL_NAME); 44 | LibC.ioctl(fd, CTLIOCGINFO, ctlInfo); 45 | SockAddress sockAddress = new SockAddress(AF_SYSTEM, (short) SYSPROTO_CONTROL, ctlInfo.id, number); 46 | LibC.connect(fd, sockAddress, sockAddress.len); 47 | SockName sockName = new SockName(); 48 | IntByReference sockNameLen = new IntByReference(SockName.LENGTH); 49 | LibC.getsockopt(fd, SYSPROTO_CONTROL, UTUN_OPT_IFNAME, sockName, sockNameLen); 50 | return new FdAndName(fd, new String(sockName.name, 0, sockNameLen.getValue() - 1, StandardCharsets.US_ASCII)); 51 | } 52 | 53 | @SuppressWarnings({"WeakerAccess", "CanBeFinal"}) 54 | @Structure.FieldOrder({"name"}) 55 | public static class SockName extends Structure { 56 | public static final int LENGTH = 16; 57 | public byte[] name = new byte[16]; 58 | } 59 | 60 | @SuppressWarnings({"WeakerAccess", "CanBeFinal"}) 61 | @Structure.FieldOrder({"len", "family", "sysaddr", "reserved"}) 62 | public static class SockAddress extends Structure { 63 | public byte len = 1 + 1 + 2 + (1 + 1 + 5) * 4; 64 | public byte family; 65 | public short sysaddr; 66 | public int[] reserved = new int[7]; 67 | 68 | public SockAddress(int addressFamily, short sysaddr, int... reserved) { 69 | this.family = (byte) addressFamily; 70 | this.sysaddr = sysaddr; 71 | System.arraycopy(reserved, 0, this.reserved, 0, reserved.length); 72 | } 73 | } 74 | 75 | @SuppressWarnings({"WeakerAccess", "CanBeFinal"}) 76 | @Structure.FieldOrder({"id", "name"}) 77 | public static class CtlInfo extends Structure { 78 | public int id; 79 | public byte[] name = new byte[96]; 80 | 81 | public CtlInfo(String name) { 82 | byte[] bytes = name.getBytes(StandardCharsets.US_ASCII); 83 | System.arraycopy(bytes, 0, this.name, 0, bytes.length); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/io/github/isotes/net/tun/io/jna/FdAndName.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Robert Sauter 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.github.isotes.net.tun.io.jna; 7 | 8 | public class FdAndName { 9 | public final int fd; 10 | public final String name; 11 | 12 | public FdAndName(int fd, String name) { 13 | this.fd = fd; 14 | this.name = name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/github/isotes/net/tun/io/jna/LibC.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Robert Sauter 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.github.isotes.net.tun.io.jna; 7 | 8 | import com.sun.jna.*; 9 | import com.sun.jna.ptr.IntByReference; 10 | 11 | import java.nio.ByteBuffer; 12 | 13 | public class LibC { 14 | 15 | static { 16 | Native.register(Platform.C_LIBRARY_NAME); 17 | } 18 | 19 | public static native int open(String pathname, int flags) throws LastErrorException; 20 | 21 | public static native int close(int fd) throws LastErrorException; 22 | 23 | public static native int read(int fd, byte[] data, NativeLong len) throws LastErrorException; 24 | 25 | public static native int read(int fd, ByteBuffer data, NativeLong len) throws LastErrorException; 26 | 27 | public static native int write(int fd, byte[] data, NativeLong len) throws LastErrorException; 28 | 29 | public static native int write(int fd, ByteBuffer data, NativeLong len) throws LastErrorException; 30 | 31 | public static native int socket(int domain, int type, int protocol) throws LastErrorException; 32 | 33 | public static native int connect(int sockfd, Structure address, int addrlen) throws LastErrorException; 34 | 35 | public static native int getsockopt(int sockfd, int level, int optname, Structure opt, IntByReference optlen) throws LastErrorException; 36 | 37 | public static native int ioctl(int fd, NativeLong cmd, Structure p) throws LastErrorException; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/github/isotes/net/tun/io/jna/Linux.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Robert Sauter 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.github.isotes.net.tun.io.jna; 7 | 8 | import com.sun.jna.Native; 9 | import com.sun.jna.NativeLong; 10 | import com.sun.jna.Structure; 11 | 12 | import java.nio.charset.StandardCharsets; 13 | 14 | /** 15 | * Linux implementation for opening/creating a TUN device based using the 16 | * TUN/TAP interface provided by the 17 | * kernel. 18 | */ 19 | public class Linux { 20 | private static final NativeLong TUNSETIFF = new NativeLong(0x400454caL); // from if_tun.h 21 | private static final int O_RDWR = 2; // from fcntl-linux.h 22 | 23 | public static FdAndName open(String name) { 24 | if (name != null && name.isEmpty()) { 25 | name = null; 26 | } 27 | if (name != null) { 28 | if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) { 29 | throw new IllegalArgumentException("'Name' must be an ASCII string (or null)"); 30 | } 31 | if (name.length() >= 16) { 32 | throw new IllegalArgumentException("'Name' must be shorter than 16 characters"); 33 | } 34 | } 35 | 36 | int fd = LibC.open("/dev/net/tun", O_RDWR); 37 | IfReq ifReq = new IfReq(name, (short) (IfReq.FLAGS_IFF_TUN | IfReq.FLAGS_IFF_NO_PI)); 38 | LibC.ioctl(fd, TUNSETIFF, ifReq); 39 | return new FdAndName(fd, Native.toString(ifReq.name, StandardCharsets.US_ASCII)); 40 | } 41 | 42 | /** 43 | * Models struct ifreq in if.h 44 | */ 45 | @SuppressWarnings({"WeakerAccess", "CanBeFinal"}) 46 | @Structure.FieldOrder({"name", "flags", "padding"}) 47 | public static class IfReq extends Structure { 48 | public static final int LENGTH = 0x28; 49 | public static final short FLAGS_IFF_TUN = 0x0001; // from if_tun.h 50 | public static final short FLAGS_IFF_NO_PI = 0x1000; // from if_tun.h 51 | 52 | public byte[] name = new byte[0x10]; 53 | public short flags; 54 | @SuppressWarnings("unused") 55 | public byte[] padding = new byte[LENGTH - 0x10 - 2]; 56 | 57 | IfReq(String name, short flags) { 58 | if (name != null) { 59 | byte[] bytes = name.getBytes(StandardCharsets.US_ASCII); 60 | System.arraycopy(bytes, 0, this.name, 0, bytes.length); 61 | } 62 | this.flags = flags; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/github/isotes/net/tun/io/jna/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Robert Sauter 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | /** Platform specific implementation details for {@link io.github.isotes.net.tun.io.TunDevice} */ 7 | package io.github.isotes.net.tun.io.jna; 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/isotes/net/tun/io/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Robert Sauter 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | /** 7 | * JNA based access to TUN devices on Linux and macOS. 8 | * 9 | *

On Linux, the library uses the 10 | * TUN/TAP 11 | * interface provided by the kernel. On macOS, the library uses the 12 | * utun kernel control 13 | * interface.

14 | * 15 | *

Apart from JNA, the library has no dependencies and has been successfully tested on macOS, Linux on a PC (Ubuntu 16 | * 18.04 64-bit), and Linux on a Raspberry PI 3 with Raspbian 9 (based on Debian Stretch).

17 | * 18 | *

See {@link io.github.isotes.net.tun.io.TunDevice} for more information.

19 | */ 20 | package io.github.isotes.net.tun.io; 21 | --------------------------------------------------------------------------------