├── .gitignore ├── .idea ├── .name ├── codeStyles │ └── Project.xml ├── misc.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mocyx │ │ └── basic_client │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mocyx │ │ │ └── basic_client │ │ │ ├── LocalVPNService.java │ │ │ ├── MainActivity.java │ │ │ ├── bio │ │ │ ├── BioTcpHandler.java │ │ │ ├── BioUdpHandler.java │ │ │ ├── BioUtil.java │ │ │ └── NioSingleThreadTcpHandler.java │ │ │ ├── config │ │ │ └── Config.java │ │ │ ├── protocol │ │ │ └── tcpip │ │ │ │ ├── IpUtil.java │ │ │ │ ├── Packet.java │ │ │ │ └── TCBStatus.java │ │ │ └── util │ │ │ ├── ByteBufferPool.java │ │ │ ├── ObjAttrUtil.java │ │ │ └── ProxyException.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── content_main.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── mocyx │ └── basic_client │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | # Built application files 16 | *.apk 17 | *.aar 18 | *.ap_ 19 | *.aab 20 | 21 | # Files for the ART/Dalvik VM 22 | *.dex 23 | 24 | # Java class files 25 | *.class 26 | 27 | # Generated files 28 | bin/ 29 | gen/ 30 | out/ 31 | # Uncomment the following line in case you need and you don't have the release build type files in your app 32 | # release/ 33 | 34 | # Gradle files 35 | .gradle/ 36 | build/ 37 | 38 | # Local configuration file (sdk path, etc) 39 | local.properties 40 | 41 | # Proguard folder generated by Eclipse 42 | proguard/ 43 | 44 | # Log Files 45 | *.log 46 | 47 | # Android Studio Navigation editor temp files 48 | .navigation/ 49 | 50 | # Android Studio captures folder 51 | captures/ 52 | 53 | # IntelliJ 54 | *.iml 55 | .idea/workspace.xml 56 | .idea/tasks.xml 57 | .idea/gradle.xml 58 | .idea/assetWizardSettings.xml 59 | .idea/dictionaries 60 | .idea/libraries 61 | # Android Studio 3 in .gitignore file. 62 | .idea/caches 63 | .idea/modules.xml 64 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 65 | .idea/navEditor.xml 66 | 67 | # Keystore files 68 | # Uncomment the following lines if you do not want to check your keystore files in. 69 | #*.jks 70 | #*.keystore 71 | 72 | # External native build folder generated in Android Studio 2.2 and later 73 | .externalNativeBuild 74 | .cxx/ 75 | 76 | # Google Services (e.g. APIs or Firebase) 77 | # google-services.json 78 | 79 | # Freeline 80 | freeline.py 81 | freeline/ 82 | freeline_project_description.json 83 | 84 | # fastlane 85 | fastlane/report.xml 86 | fastlane/Preview.html 87 | fastlane/screenshots 88 | fastlane/test_output 89 | fastlane/readme.md 90 | 91 | # Version control 92 | vcs.xml 93 | 94 | # lint 95 | lint/intermediates/ 96 | lint/generated/ 97 | lint/outputs/ 98 | lint/tmp/ 99 | # lint/reports/ 100 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | basic-client -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /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 | android vpnservice 2 | === 3 | 4 | ## Background/About this project 5 | 6 | an android vpnservice example 7 | proxy udp and tcp 8 | 9 | ## Requirements 10 | 11 | Android 12 | 13 | ## Usage 14 | 15 | 点击start打开vpn 16 | 点击dns进行一次dns请求 17 | 点击http进行一次http请求 18 | 最下方的文本框展示io字节数 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.3" 6 | defaultConfig { 7 | applicationId "com.mocyx.basic_client" 8 | minSdkVersion 24 9 | targetSdkVersion 29 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | dexOptions{ 21 | 22 | javaMaxHeapSize "4g" 23 | 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | implementation 'androidx.appcompat:appcompat:1.0.2' 30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 31 | implementation 'com.google.android.material:material:1.0.0' 32 | testImplementation 'junit:junit:4.12' 33 | androidTestImplementation 'androidx.test.ext:junit:1.1.0' 34 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 35 | } 36 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/mocyx/basic_client/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.mocyx.basic_client", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/LocalVPNService.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client; 2 | 3 | 4 | import android.app.PendingIntent; 5 | import android.content.Intent; 6 | import android.net.VpnService; 7 | import android.os.ParcelFileDescriptor; 8 | import android.util.Log; 9 | 10 | import com.mocyx.basic_client.bio.BioTcpHandler; 11 | import com.mocyx.basic_client.bio.BioUdpHandler; 12 | import com.mocyx.basic_client.bio.NioSingleThreadTcpHandler; 13 | import com.mocyx.basic_client.config.Config; 14 | import com.mocyx.basic_client.protocol.tcpip.Packet; 15 | import com.mocyx.basic_client.util.ByteBufferPool; 16 | 17 | import java.io.Closeable; 18 | import java.io.FileDescriptor; 19 | import java.io.FileInputStream; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.nio.ByteBuffer; 23 | import java.nio.channels.FileChannel; 24 | import java.util.concurrent.ArrayBlockingQueue; 25 | import java.util.concurrent.BlockingQueue; 26 | import java.util.concurrent.ExecutorService; 27 | import java.util.concurrent.Executors; 28 | 29 | public class LocalVPNService extends VpnService { 30 | private static final String TAG = LocalVPNService.class.getSimpleName(); 31 | private static final String VPN_ADDRESS = "10.0.0.2"; // Only IPv4 support for now 32 | private static final String VPN_ROUTE = "0.0.0.0"; // Intercept everything 33 | 34 | private ParcelFileDescriptor vpnInterface = null; 35 | 36 | private PendingIntent pendingIntent; 37 | 38 | private BlockingQueue deviceToNetworkUDPQueue; 39 | private BlockingQueue deviceToNetworkTCPQueue; 40 | private BlockingQueue networkToDeviceQueue; 41 | private ExecutorService executorService; 42 | 43 | @Override 44 | public void onCreate() { 45 | super.onCreate(); 46 | setupVPN(); 47 | deviceToNetworkUDPQueue = new ArrayBlockingQueue(1000); 48 | deviceToNetworkTCPQueue = new ArrayBlockingQueue(1000); 49 | networkToDeviceQueue = new ArrayBlockingQueue<>(1000); 50 | 51 | executorService = Executors.newFixedThreadPool(10); 52 | executorService.submit(new BioUdpHandler(deviceToNetworkUDPQueue, networkToDeviceQueue, this)); 53 | //executorService.submit(new BioTcpHandler(deviceToNetworkTCPQueue, networkToDeviceQueue, this)); 54 | executorService.submit(new NioSingleThreadTcpHandler(deviceToNetworkTCPQueue, networkToDeviceQueue, this)); 55 | 56 | executorService.submit(new VPNRunnable(vpnInterface.getFileDescriptor(), 57 | deviceToNetworkUDPQueue, deviceToNetworkTCPQueue, networkToDeviceQueue)); 58 | 59 | Log.i(TAG, "Started"); 60 | } 61 | 62 | private void setupVPN() { 63 | try { 64 | if (vpnInterface == null) { 65 | Builder builder = new Builder(); 66 | builder.addAddress(VPN_ADDRESS, 32); 67 | builder.addRoute(VPN_ROUTE, 0); 68 | builder.addDnsServer(Config.dns); 69 | if (Config.testLocal) { 70 | builder.addAllowedApplication("com.mocyx.basic_client"); 71 | } 72 | vpnInterface = builder.setSession(getString(R.string.app_name)).setConfigureIntent(pendingIntent).establish(); 73 | } 74 | } catch (Exception e) { 75 | Log.e(TAG, "error", e); 76 | System.exit(0); 77 | } 78 | } 79 | 80 | @Override 81 | public int onStartCommand(Intent intent, int flags, int startId) { 82 | return START_STICKY; 83 | } 84 | 85 | 86 | @Override 87 | public void onDestroy() { 88 | super.onDestroy(); 89 | executorService.shutdownNow(); 90 | cleanup(); 91 | Log.i(TAG, "Stopped"); 92 | } 93 | 94 | private void cleanup() { 95 | deviceToNetworkTCPQueue = null; 96 | deviceToNetworkUDPQueue = null; 97 | networkToDeviceQueue = null; 98 | closeResources(vpnInterface); 99 | } 100 | 101 | // TODO: Move this to a "utils" class for reuse 102 | private static void closeResources(Closeable... resources) { 103 | for (Closeable resource : resources) { 104 | try { 105 | resource.close(); 106 | } catch (IOException e) { 107 | // Ignore 108 | } 109 | } 110 | } 111 | 112 | private static class VPNRunnable implements Runnable { 113 | private static final String TAG = VPNRunnable.class.getSimpleName(); 114 | 115 | private FileDescriptor vpnFileDescriptor; 116 | 117 | private BlockingQueue deviceToNetworkUDPQueue; 118 | private BlockingQueue deviceToNetworkTCPQueue; 119 | private BlockingQueue networkToDeviceQueue; 120 | 121 | public VPNRunnable(FileDescriptor vpnFileDescriptor, 122 | BlockingQueue deviceToNetworkUDPQueue, 123 | BlockingQueue deviceToNetworkTCPQueue, 124 | BlockingQueue networkToDeviceQueue) { 125 | this.vpnFileDescriptor = vpnFileDescriptor; 126 | this.deviceToNetworkUDPQueue = deviceToNetworkUDPQueue; 127 | this.deviceToNetworkTCPQueue = deviceToNetworkTCPQueue; 128 | this.networkToDeviceQueue = networkToDeviceQueue; 129 | } 130 | 131 | 132 | static class WriteVpnThread implements Runnable { 133 | FileChannel vpnOutput; 134 | private BlockingQueue networkToDeviceQueue; 135 | 136 | WriteVpnThread(FileChannel vpnOutput, BlockingQueue networkToDeviceQueue) { 137 | this.vpnOutput = vpnOutput; 138 | this.networkToDeviceQueue = networkToDeviceQueue; 139 | } 140 | 141 | @Override 142 | public void run() { 143 | while (true) { 144 | try { 145 | ByteBuffer bufferFromNetwork = networkToDeviceQueue.take(); 146 | bufferFromNetwork.flip(); 147 | while (bufferFromNetwork.hasRemaining()) { 148 | int w = vpnOutput.write(bufferFromNetwork); 149 | if (w > 0) { 150 | MainActivity.downByte.addAndGet(w); 151 | } 152 | if (Config.logRW) { 153 | Log.d(TAG, "vpn write " + w); 154 | } 155 | } 156 | } catch (Exception e) { 157 | Log.i(TAG, "WriteVpnThread fail", e); 158 | } 159 | } 160 | 161 | } 162 | } 163 | 164 | @Override 165 | public void run() { 166 | Log.i(TAG, "Started"); 167 | FileChannel vpnInput = new FileInputStream(vpnFileDescriptor).getChannel(); 168 | FileChannel vpnOutput = new FileOutputStream(vpnFileDescriptor).getChannel(); 169 | Thread t = new Thread(new WriteVpnThread(vpnOutput, networkToDeviceQueue)); 170 | t.start(); 171 | try { 172 | ByteBuffer bufferToNetwork = null; 173 | while (!Thread.interrupted()) { 174 | bufferToNetwork = ByteBufferPool.acquire(); 175 | int readBytes = vpnInput.read(bufferToNetwork); 176 | 177 | MainActivity.upByte.addAndGet(readBytes); 178 | 179 | if (readBytes > 0) { 180 | bufferToNetwork.flip(); 181 | 182 | Packet packet = new Packet(bufferToNetwork); 183 | if (packet.isUDP()) { 184 | if (Config.logRW) { 185 | Log.i(TAG, "read udp" + readBytes); 186 | } 187 | deviceToNetworkUDPQueue.offer(packet); 188 | } else if (packet.isTCP()) { 189 | if (Config.logRW) { 190 | Log.i(TAG, "read tcp " + readBytes); 191 | } 192 | deviceToNetworkTCPQueue.offer(packet); 193 | } else { 194 | Log.w(TAG, String.format("Unknown packet protocol type %d", packet.ip4Header.protocolNum)); 195 | } 196 | } else { 197 | try { 198 | Thread.sleep(10); 199 | } catch (InterruptedException e) { 200 | e.printStackTrace(); 201 | } 202 | } 203 | } 204 | } catch (IOException e) { 205 | Log.w(TAG, e.toString(), e); 206 | } finally { 207 | closeResources(vpnInput, vpnOutput); 208 | } 209 | } 210 | } 211 | } 212 | 213 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client; 2 | 3 | import android.content.Intent; 4 | import android.net.VpnService; 5 | import android.os.Bundle; 6 | 7 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 8 | import com.google.android.material.snackbar.Snackbar; 9 | import com.mocyx.basic_client.bio.BioTcpHandler; 10 | 11 | import androidx.appcompat.app.AppCompatActivity; 12 | import androidx.appcompat.widget.Toolbar; 13 | 14 | import android.util.Log; 15 | import android.view.View; 16 | import android.view.Menu; 17 | import android.view.MenuItem; 18 | import android.widget.TextView; 19 | 20 | import java.io.BufferedReader; 21 | import java.io.IOException; 22 | import java.io.InputStreamReader; 23 | import java.net.DatagramPacket; 24 | import java.net.HttpURLConnection; 25 | import java.net.InetAddress; 26 | import java.net.InetSocketAddress; 27 | import java.net.URL; 28 | import java.net.URLConnection; 29 | import java.nio.ByteBuffer; 30 | import java.nio.channels.DatagramChannel; 31 | import java.util.concurrent.atomic.AtomicInteger; 32 | import java.util.concurrent.atomic.AtomicLong; 33 | 34 | public class MainActivity extends AppCompatActivity { 35 | 36 | 37 | public static AtomicLong downByte = new AtomicLong(0); 38 | public static AtomicLong upByte = new AtomicLong(0); 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_main); 44 | Toolbar toolbar = findViewById(R.id.toolbar); 45 | 46 | 47 | setSupportActionBar(toolbar); 48 | 49 | FloatingActionButton fab = findViewById(R.id.fab); 50 | fab.setOnClickListener(new View.OnClickListener() { 51 | @Override 52 | public void onClick(View view) { 53 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 54 | .setAction("Action", null).show(); 55 | } 56 | }); 57 | 58 | TextView textView = findViewById(R.id.textView1); 59 | 60 | Thread t = new Thread(new UpdateText(textView)); 61 | t.start(); 62 | } 63 | 64 | static class UpdateText implements Runnable { 65 | 66 | TextView textView; 67 | 68 | UpdateText(TextView textView) { 69 | this.textView = textView; 70 | } 71 | 72 | @Override 73 | public void run() { 74 | while (true) { 75 | try { 76 | Thread.sleep(100); 77 | textView.setText(String.format("up %dKB down %dKB", MainActivity.upByte.get() / 1024, MainActivity.downByte.get() / 1024)); 78 | } catch (InterruptedException e) { 79 | e.printStackTrace(); 80 | } 81 | 82 | } 83 | 84 | } 85 | } 86 | 87 | private static final String TAG = BioTcpHandler.class.getSimpleName(); 88 | 89 | @Override 90 | public boolean onCreateOptionsMenu(Menu menu) { 91 | // Inflate the menu; this adds items to the action bar if it is present. 92 | getMenuInflater().inflate(R.menu.menu_main, menu); 93 | return true; 94 | } 95 | 96 | @Override 97 | public boolean onOptionsItemSelected(MenuItem item) { 98 | // Handle action bar item clicks here. The action bar will 99 | // automatically handle clicks on the Home/Up button, so long 100 | // as you specify a parent activity in AndroidManifest.xml. 101 | int id = item.getItemId(); 102 | 103 | //noinspection SimplifiableIfStatement 104 | if (id == R.id.action_settings) { 105 | return true; 106 | } 107 | 108 | return super.onOptionsItemSelected(item); 109 | } 110 | 111 | private static final int VPN_REQUEST_CODE = 0x0F; 112 | 113 | @Override 114 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 115 | super.onActivityResult(requestCode, resultCode, data); 116 | if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) { 117 | //waitingForVPNStart = true; 118 | startService(new Intent(this, LocalVPNService.class)); 119 | //enableButton(false); 120 | } 121 | } 122 | 123 | private void startVpn() { 124 | 125 | Intent vpnIntent = VpnService.prepare(this); 126 | 127 | if (vpnIntent != null) 128 | startActivityForResult(vpnIntent, VPN_REQUEST_CODE); 129 | else 130 | onActivityResult(VPN_REQUEST_CODE, RESULT_OK, null); 131 | } 132 | 133 | 134 | public void clickSwitch(View view) { 135 | System.out.println("hello"); 136 | this.startVpn(); 137 | 138 | 139 | } 140 | 141 | @Override 142 | protected void onDestroy() { 143 | super.onDestroy(); 144 | 145 | } 146 | 147 | 148 | public void clickHttp(View view) { 149 | Thread t = new Thread(new Runnable() { 150 | @Override 151 | public void run() { 152 | try { 153 | long ts = System.currentTimeMillis(); 154 | //URL yahoo = new URL("https://www.google.com/"); 155 | URL yahoo = new URL("https://www.baidu.com/"); 156 | HttpURLConnection yc = (HttpURLConnection) yahoo.openConnection(); 157 | 158 | yc.setRequestProperty("Connection", "close"); 159 | yc.setConnectTimeout(30000); 160 | 161 | yc.setReadTimeout(30000); 162 | BufferedReader in = new BufferedReader( 163 | new InputStreamReader(yc.getInputStream())); 164 | String inputLine; 165 | while ((inputLine = in.readLine()) != null) { 166 | System.out.println(inputLine); 167 | } 168 | 169 | yc.disconnect(); 170 | in.close(); 171 | long te = System.currentTimeMillis(); 172 | Log.i(TAG, String.format("http cost %d", te - ts)); 173 | 174 | System.out.printf("http readline end\n"); 175 | } catch (Exception e) { 176 | e.printStackTrace(); 177 | } 178 | } 179 | }); 180 | t.start(); 181 | } 182 | 183 | public void clickStop(View view) { 184 | // 185 | } 186 | 187 | public static void displayStuff(String whichHost, InetAddress inetAddress) { 188 | System.out.println("--------------------------"); 189 | System.out.println("Which Host:" + whichHost); 190 | System.out.println("Canonical Host Name:" + inetAddress.getCanonicalHostName()); 191 | System.out.println("Host Name:" + inetAddress.getHostName()); 192 | System.out.println("Host Address:" + inetAddress.getHostAddress()); 193 | } 194 | 195 | public void clickDns(View view) { 196 | 197 | Thread t = new Thread(new Runnable() { 198 | @Override 199 | public void run() { 200 | try { 201 | long ts = System.currentTimeMillis(); 202 | String host = "www.baidu.com"; 203 | 204 | for (InetAddress inetAddress : InetAddress.getAllByName(host)) { 205 | displayStuff(host, inetAddress); 206 | } 207 | long te = System.currentTimeMillis(); 208 | Log.i(TAG, String.format("dns cost %d", te - ts)); 209 | 210 | } catch (Exception e) { 211 | e.printStackTrace(); 212 | } 213 | } 214 | }); 215 | t.start(); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/bio/BioTcpHandler.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.bio; 2 | 3 | import android.net.VpnService; 4 | import android.os.Build; 5 | import android.util.Log; 6 | 7 | import com.mocyx.basic_client.protocol.tcpip.IpUtil; 8 | import com.mocyx.basic_client.util.ByteBufferPool; 9 | import com.mocyx.basic_client.protocol.tcpip.Packet; 10 | import com.mocyx.basic_client.protocol.tcpip.Packet.IP4Header; 11 | import com.mocyx.basic_client.protocol.tcpip.Packet.TCPHeader; 12 | import com.mocyx.basic_client.config.Config; 13 | import com.mocyx.basic_client.protocol.tcpip.TCBStatus; 14 | import com.mocyx.basic_client.util.ProxyException; 15 | 16 | import java.io.IOException; 17 | import java.net.InetAddress; 18 | import java.net.InetSocketAddress; 19 | import java.nio.ByteBuffer; 20 | import java.nio.channels.ClosedChannelException; 21 | import java.nio.channels.SocketChannel; 22 | import java.util.Random; 23 | import java.util.concurrent.ArrayBlockingQueue; 24 | import java.util.concurrent.BlockingQueue; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | 28 | public class BioTcpHandler implements Runnable { 29 | 30 | BlockingQueue queue; 31 | ConcurrentHashMap tunnels = new ConcurrentHashMap(); 32 | 33 | private static int HEADER_SIZE = Packet.IP4_HEADER_SIZE + Packet.TCP_HEADER_SIZE; 34 | 35 | static class TcpTunnel { 36 | 37 | static AtomicInteger tunnelIds = new AtomicInteger(0); 38 | public final int tunnelId = tunnelIds.addAndGet(1); 39 | 40 | public long mySequenceNum = 0; 41 | public long theirSequenceNum = 0; 42 | public long myAcknowledgementNum = 0; 43 | public long theirAcknowledgementNum = 0; 44 | 45 | public TCBStatus tcbStatus = TCBStatus.SYN_SENT; 46 | public BlockingQueue tunnelInputQueue = new ArrayBlockingQueue(1024); 47 | public InetSocketAddress sourceAddress; 48 | public InetSocketAddress destinationAddress; 49 | public SocketChannel destSocket; 50 | private VpnService vpnService; 51 | BlockingQueue networkToDeviceQueue; 52 | 53 | public int packId = 1; 54 | 55 | public boolean upActive = true; 56 | public boolean downActive = true; 57 | public String tunnelKey; 58 | public BlockingQueue tunnelCloseMsgQueue; 59 | 60 | } 61 | 62 | private static final String TAG = BioTcpHandler.class.getSimpleName(); 63 | 64 | private VpnService vpnService; 65 | BlockingQueue networkToDeviceQueue; 66 | 67 | public BioTcpHandler(BlockingQueue queue, BlockingQueue networkToDeviceQueue, VpnService vpnService) { 68 | this.queue = queue; 69 | this.vpnService = vpnService; 70 | this.networkToDeviceQueue = networkToDeviceQueue; 71 | } 72 | 73 | private static void sendTcpPack(TcpTunnel tunnel, byte flag, byte[] data) { 74 | // 75 | // if(true){ 76 | // return; 77 | // } 78 | int dataLen = 0; 79 | if (data != null) { 80 | dataLen = data.length; 81 | } 82 | Packet packet = IpUtil.buildTcpPacket(tunnel.destinationAddress, tunnel.sourceAddress, flag, 83 | tunnel.myAcknowledgementNum, tunnel.mySequenceNum, tunnel.packId); 84 | tunnel.packId += 1; 85 | ByteBuffer byteBuffer = ByteBufferPool.acquire(); 86 | // 87 | byteBuffer.position(HEADER_SIZE); 88 | if (data != null) { 89 | if (byteBuffer.remaining() < data.length) { 90 | System.currentTimeMillis(); 91 | } 92 | byteBuffer.put(data); 93 | } 94 | 95 | packet.updateTCPBuffer(byteBuffer, flag, tunnel.mySequenceNum, tunnel.myAcknowledgementNum, dataLen); 96 | byteBuffer.position(HEADER_SIZE + dataLen); 97 | 98 | tunnel.networkToDeviceQueue.offer(byteBuffer); 99 | 100 | if ((flag & (byte) TCPHeader.SYN) != 0) { 101 | tunnel.mySequenceNum += 1; 102 | } 103 | if ((flag & (byte) TCPHeader.FIN) != 0) { 104 | tunnel.mySequenceNum += 1; 105 | } 106 | if ((flag & (byte) TCPHeader.ACK) != 0) { 107 | tunnel.mySequenceNum += dataLen; 108 | } 109 | } 110 | 111 | private static class UpStreamWorker implements Runnable { 112 | 113 | TcpTunnel tunnel; 114 | 115 | public UpStreamWorker(TcpTunnel tunnel) { 116 | this.tunnel = tunnel; 117 | } 118 | 119 | 120 | private void startDownStream() { 121 | Thread t = new Thread(new DownStreamWorker(tunnel)); 122 | t.start(); 123 | } 124 | 125 | private void connectRemote() { 126 | try { 127 | //connect 128 | SocketChannel remote = SocketChannel.open(); 129 | tunnel.vpnService.protect(remote.socket()); 130 | InetSocketAddress address = tunnel.destinationAddress; 131 | 132 | Long ts = System.currentTimeMillis(); 133 | remote.socket().connect(address, 5000); 134 | Long te = System.currentTimeMillis(); 135 | 136 | Log.i(TAG, String.format("connectRemote %d cost %d remote %s", tunnel.tunnelId, te - ts, tunnel.destinationAddress.toString())); 137 | tunnel.destSocket = remote; 138 | 139 | startDownStream(); 140 | } catch (Exception e) { 141 | Log.e(TAG, e.getMessage(), e); 142 | throw new ProxyException("connectRemote fail" + tunnel.destinationAddress.toString()); 143 | } 144 | } 145 | 146 | int synCount = 0; 147 | 148 | private void handleSyn(Packet packet) { 149 | 150 | if (tunnel.tcbStatus == TCBStatus.SYN_SENT) { 151 | tunnel.tcbStatus = TCBStatus.SYN_RECEIVED; 152 | } 153 | Log.i(TAG, String.format("handleSyn %d %d", tunnel.tunnelId, packet.packId)); 154 | TCPHeader tcpHeader = packet.tcpHeader; 155 | if (synCount == 0) { 156 | tunnel.mySequenceNum = 1; 157 | tunnel.theirSequenceNum = tcpHeader.sequenceNumber; 158 | tunnel.myAcknowledgementNum = tcpHeader.sequenceNumber + 1; 159 | tunnel.theirAcknowledgementNum = tcpHeader.acknowledgementNumber; 160 | sendTcpPack(tunnel, (byte) (TCPHeader.SYN | TCPHeader.ACK), null); 161 | } else { 162 | tunnel.myAcknowledgementNum = tcpHeader.sequenceNumber + 1; 163 | } 164 | synCount += 1; 165 | } 166 | 167 | 168 | private void writeToRemote(ByteBuffer buffer) throws IOException { 169 | if (tunnel.upActive) { 170 | int payloadSize = buffer.remaining(); 171 | int write = tunnel.destSocket.write(buffer); 172 | } 173 | } 174 | 175 | private void handleAck(Packet packet) throws IOException { 176 | 177 | if (tunnel.tcbStatus == TCBStatus.SYN_RECEIVED) { 178 | tunnel.tcbStatus = TCBStatus.ESTABLISHED; 179 | 180 | } 181 | 182 | if (Config.logAck) { 183 | Log.d(TAG, String.format("handleAck %d ", packet.packId)); 184 | } 185 | 186 | TCPHeader tcpHeader = packet.tcpHeader; 187 | int payloadSize = packet.backingBuffer.remaining(); 188 | 189 | if (payloadSize == 0) { 190 | return; 191 | } 192 | 193 | long newAck = tcpHeader.sequenceNumber + payloadSize; 194 | if (newAck <= tunnel.myAcknowledgementNum) { 195 | if (Config.logAck) { 196 | Log.d(TAG, String.format("handleAck duplicate ack", tunnel.myAcknowledgementNum, newAck)); 197 | } 198 | return; 199 | } 200 | 201 | tunnel.myAcknowledgementNum = tcpHeader.sequenceNumber; 202 | tunnel.theirAcknowledgementNum = tcpHeader.acknowledgementNumber; 203 | 204 | tunnel.myAcknowledgementNum += payloadSize; 205 | writeToRemote(packet.backingBuffer); 206 | 207 | sendTcpPack(tunnel, (byte) TCPHeader.ACK, null); 208 | 209 | System.currentTimeMillis(); 210 | } 211 | 212 | private void handleFin(Packet packet) { 213 | Log.i(TAG, String.format("handleFin %d", tunnel.tunnelId)); 214 | tunnel.myAcknowledgementNum = packet.tcpHeader.sequenceNumber + 1; 215 | tunnel.theirAcknowledgementNum = packet.tcpHeader.acknowledgementNumber; 216 | sendTcpPack(tunnel, (byte) (TCPHeader.ACK), null); 217 | //closeTunnel(tunnel); 218 | //closeDownStream(); 219 | closeUpStream(tunnel); 220 | tunnel.tcbStatus = TCBStatus.CLOSE_WAIT; 221 | } 222 | 223 | private void handleRst(Packet packet) { 224 | Log.i(TAG, String.format("handleRst %d", tunnel.tunnelId)); 225 | try { 226 | synchronized (tunnel) { 227 | if (tunnel.destSocket != null) { 228 | tunnel.destSocket.close(); 229 | } 230 | 231 | } 232 | } catch (IOException e) { 233 | Log.e(TAG, "close error", e); 234 | } 235 | 236 | synchronized (tunnel) { 237 | tunnel.upActive = false; 238 | tunnel.downActive = false; 239 | tunnel.tcbStatus = TCBStatus.CLOSE_WAIT; 240 | } 241 | } 242 | 243 | 244 | private void loop() { 245 | while (true) { 246 | Packet packet = null; 247 | try { 248 | packet = tunnel.tunnelInputQueue.take(); 249 | 250 | //Log.i(TAG, "lastIdentification " + tunnel.lastIdentification); 251 | synchronized (tunnel) { 252 | boolean end = false; 253 | TCPHeader tcpHeader = packet.tcpHeader; 254 | 255 | if (tcpHeader.isSYN()) { 256 | handleSyn(packet); 257 | end = true; 258 | } 259 | if (!end && tcpHeader.isRST()) { 260 | // 261 | //Log.i(TAG, String.format("handleRst %d", tunnel.tunnelId)); 262 | //tunnel.destSocket.close(); 263 | handleRst(packet); 264 | end = true; 265 | break; 266 | } 267 | if (!end && tcpHeader.isFIN()) { 268 | handleFin(packet); 269 | end = true; 270 | } 271 | if (!end && tcpHeader.isACK()) { 272 | handleAck(packet); 273 | } 274 | // if (!tunnel.downActive && !tunnel.upActive) { 275 | // closeTotalTunnel(); 276 | // break; 277 | // } 278 | } 279 | } catch (InterruptedException e) { 280 | e.printStackTrace(); 281 | } catch (IOException e) { 282 | e.printStackTrace(); 283 | return; 284 | } 285 | } 286 | Log.i(TAG, String.format("UpStreamWorker quit")); 287 | } 288 | 289 | @Override 290 | public void run() { 291 | try { 292 | connectRemote(); 293 | loop(); 294 | } catch (ProxyException e) { 295 | //closeTotalTunnel(); 296 | e.printStackTrace(); 297 | } catch (Exception e) { 298 | e.printStackTrace(); 299 | } 300 | } 301 | } 302 | 303 | public static boolean isClosedTunnel(TcpTunnel tunnel) { 304 | return !tunnel.upActive && !tunnel.downActive; 305 | } 306 | 307 | private static void closeDownStream(TcpTunnel tunnel) { 308 | synchronized (tunnel) { 309 | Log.i(TAG, String.format("closeDownStream %d", tunnel.tunnelId)); 310 | try { 311 | if (tunnel.destSocket != null && tunnel.destSocket.isOpen()) { 312 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 313 | tunnel.destSocket.shutdownInput(); 314 | } else { 315 | tunnel.destSocket.close(); 316 | tunnel.destSocket = null; 317 | } 318 | } 319 | } catch (Exception e) { 320 | e.printStackTrace(); 321 | } 322 | sendTcpPack(tunnel, (byte) (TCPHeader.FIN | Packet.TCPHeader.ACK), null); 323 | tunnel.downActive = false; 324 | if (isClosedTunnel(tunnel)) { 325 | tunnel.tunnelCloseMsgQueue.add(tunnel.tunnelKey); 326 | } 327 | } 328 | } 329 | 330 | private static void closeUpStream(TcpTunnel tunnel) { 331 | synchronized (tunnel) { 332 | Log.i(TAG, String.format("closeUpStream %d", tunnel.tunnelId)); 333 | try { 334 | if (tunnel.destSocket != null && tunnel.destSocket.isOpen()) { 335 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 336 | tunnel.destSocket.shutdownOutput(); 337 | } else { 338 | tunnel.destSocket.close(); 339 | tunnel.destSocket = null; 340 | } 341 | } 342 | 343 | } catch (Exception e) { 344 | e.printStackTrace(); 345 | } 346 | Log.i(TAG, String.format("closeUpStream %d", tunnel.tunnelId)); 347 | tunnel.upActive = false; 348 | if (isClosedTunnel(tunnel)) { 349 | tunnel.tunnelCloseMsgQueue.add(tunnel.tunnelKey); 350 | } 351 | } 352 | } 353 | 354 | private static void closeRst(TcpTunnel tunnel) { 355 | synchronized (tunnel) { 356 | Log.i(TAG, String.format("closeRst %d", tunnel.tunnelId)); 357 | try { 358 | if (tunnel.destSocket != null && tunnel.destSocket.isOpen()) { 359 | tunnel.destSocket.close(); 360 | tunnel.destSocket = null; 361 | } 362 | } catch (Exception e) { 363 | e.printStackTrace(); 364 | } 365 | sendTcpPack(tunnel, (byte) TCPHeader.RST, null); 366 | tunnel.upActive = false; 367 | tunnel.downActive = false; 368 | } 369 | } 370 | 371 | private static class DownStreamWorker implements Runnable { 372 | TcpTunnel tunnel; 373 | 374 | public DownStreamWorker(TcpTunnel tunnel) { 375 | this.tunnel = tunnel; 376 | } 377 | 378 | @Override 379 | public void run() { 380 | ByteBuffer buffer = ByteBuffer.allocate(4 * 1024); 381 | 382 | String quitType = "rst"; 383 | 384 | try { 385 | while (true) { 386 | buffer.clear(); 387 | if (tunnel.destSocket == null) { 388 | throw new ProxyException("tunnel maybe closed"); 389 | } 390 | int n = BioUtil.read(tunnel.destSocket, buffer); 391 | 392 | synchronized (tunnel) { 393 | if (n == -1) { 394 | quitType = "fin"; 395 | break; 396 | } else { 397 | if (tunnel.tcbStatus != TCBStatus.CLOSE_WAIT) { 398 | buffer.flip(); 399 | byte[] data = new byte[buffer.remaining()]; 400 | buffer.get(data); 401 | sendTcpPack(tunnel, (byte) (TCPHeader.ACK), data); 402 | } 403 | } 404 | } 405 | } 406 | } catch (ClosedChannelException e) { 407 | Log.w(TAG, String.format("channel closed %s", e.getMessage())); 408 | quitType = "rst"; 409 | } catch (IOException e) { 410 | Log.e(TAG, e.getMessage(), e); 411 | quitType = "rst"; 412 | } catch (Exception e) { 413 | quitType = "rst"; 414 | Log.e(TAG, "DownStreamWorker fail", e); 415 | } 416 | //Log.i(TAG, String.format("DownStreamWorker quit %d", tunnel.tunnelId)); 417 | synchronized (tunnel) { 418 | if (quitType.equals("fin")) { 419 | closeDownStream(tunnel); 420 | //closeUpStream(tunnel); 421 | //closeRst(tunnel); 422 | } else if (quitType.equals("rst")) { 423 | closeRst(tunnel); 424 | } 425 | 426 | } 427 | 428 | } 429 | } 430 | 431 | private TcpTunnel initTunnel(Packet packet) { 432 | TcpTunnel tunnel = new TcpTunnel(); 433 | tunnel.sourceAddress = new InetSocketAddress(packet.ip4Header.sourceAddress, packet.tcpHeader.sourcePort); 434 | tunnel.destinationAddress = new InetSocketAddress(packet.ip4Header.destinationAddress, packet.tcpHeader.destinationPort); 435 | tunnel.vpnService = vpnService; 436 | tunnel.networkToDeviceQueue = networkToDeviceQueue; 437 | tunnel.tunnelCloseMsgQueue = tunnelCloseMsgQueue; 438 | Thread t = new Thread(new UpStreamWorker(tunnel)); 439 | t.start(); 440 | 441 | return tunnel; 442 | } 443 | 444 | public BlockingQueue tunnelCloseMsgQueue = new ArrayBlockingQueue<>(1024); 445 | 446 | @Override 447 | public void run() { 448 | 449 | while (true) { 450 | try { 451 | Packet currentPacket = queue.take(); 452 | InetAddress destinationAddress = currentPacket.ip4Header.destinationAddress; 453 | TCPHeader tcpHeader = currentPacket.tcpHeader; 454 | //Log.d(TAG, String.format("get pack %d tcp " + tcpHeader.printSimple() + " ", currentPacket.packId)); 455 | int destinationPort = tcpHeader.destinationPort; 456 | int sourcePort = tcpHeader.sourcePort; 457 | String ipAndPort = destinationAddress.getHostAddress() + ":" + 458 | destinationPort + ":" + sourcePort; 459 | // 460 | while (true) { 461 | String s = this.tunnelCloseMsgQueue.poll(); 462 | if (s == null) { 463 | break; 464 | } else { 465 | tunnels.remove(ipAndPort); 466 | Log.i(TAG, String.format("remove tunnel %s", ipAndPort)); 467 | } 468 | } 469 | // 470 | if (!tunnels.containsKey(ipAndPort)) { 471 | TcpTunnel tcpTunnel = initTunnel(currentPacket); 472 | tcpTunnel.tunnelKey = ipAndPort; 473 | tunnels.put(ipAndPort, tcpTunnel); 474 | } 475 | TcpTunnel tcpTunnel = tunnels.get(ipAndPort); 476 | // 477 | tcpTunnel.tunnelInputQueue.offer(currentPacket); 478 | } catch (Exception e) { 479 | e.printStackTrace(); 480 | } 481 | } 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/bio/BioUdpHandler.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.bio; 2 | 3 | import android.net.VpnService; 4 | import android.util.Log; 5 | 6 | import com.mocyx.basic_client.protocol.tcpip.IpUtil; 7 | import com.mocyx.basic_client.util.ByteBufferPool; 8 | import com.mocyx.basic_client.protocol.tcpip.Packet; 9 | import com.mocyx.basic_client.config.Config; 10 | 11 | import java.io.IOException; 12 | import java.net.InetAddress; 13 | import java.net.InetSocketAddress; 14 | import java.nio.ByteBuffer; 15 | import java.nio.channels.DatagramChannel; 16 | import java.nio.channels.SelectionKey; 17 | import java.nio.channels.Selector; 18 | import java.util.HashMap; 19 | import java.util.Iterator; 20 | import java.util.Map; 21 | import java.util.Set; 22 | import java.util.concurrent.ArrayBlockingQueue; 23 | import java.util.concurrent.BlockingQueue; 24 | import java.util.concurrent.atomic.AtomicInteger; 25 | 26 | /** 27 | * 28 | * BIO UDP处理器 29 | * 从queue获取udp包,转发给指定的dns 30 | * 31 | */ 32 | public class BioUdpHandler implements Runnable { 33 | 34 | BlockingQueue queue; 35 | 36 | BlockingQueue networkToDeviceQueue; 37 | VpnService vpnService; 38 | 39 | private Selector selector; 40 | private static final int HEADER_SIZE = Packet.IP4_HEADER_SIZE + Packet.UDP_HEADER_SIZE; 41 | 42 | private static class UdpDownWorker implements Runnable { 43 | 44 | BlockingQueue networkToDeviceQueue; 45 | BlockingQueue tunnelQueue; 46 | Selector selector; 47 | 48 | private static AtomicInteger ipId = new AtomicInteger(); 49 | 50 | private void sendUdpPack(UdpTunnel tunnel, InetSocketAddress source, byte[] data) throws IOException { 51 | int dataLen = 0; 52 | if (data != null) { 53 | dataLen = data.length; 54 | } 55 | Packet packet = IpUtil.buildUdpPacket(tunnel.remote, tunnel.local, ipId.addAndGet(1)); 56 | 57 | ByteBuffer byteBuffer = ByteBufferPool.acquire(); 58 | // 59 | byteBuffer.position(HEADER_SIZE); 60 | if (data != null) { 61 | if (byteBuffer.remaining() < data.length) { 62 | System.currentTimeMillis(); 63 | } 64 | byteBuffer.put(data); 65 | } 66 | packet.updateUDPBuffer(byteBuffer, dataLen); 67 | byteBuffer.position(HEADER_SIZE + dataLen); 68 | this.networkToDeviceQueue.offer(byteBuffer); 69 | } 70 | 71 | 72 | public UdpDownWorker(Selector selector, BlockingQueue networkToDeviceQueue, BlockingQueue tunnelQueue) { 73 | this.networkToDeviceQueue = networkToDeviceQueue; 74 | this.tunnelQueue = tunnelQueue; 75 | this.selector = selector; 76 | } 77 | 78 | @Override 79 | public void run() { 80 | try { 81 | while (true) { 82 | int readyChannels = selector.select(); 83 | while (true) { 84 | UdpTunnel tunnel = tunnelQueue.poll(); 85 | if (tunnel == null) { 86 | break; 87 | } else { 88 | try { 89 | SelectionKey key = tunnel.channel.register(selector, SelectionKey.OP_READ, tunnel); 90 | key.interestOps(SelectionKey.OP_READ); 91 | boolean isvalid = key.isValid(); 92 | } catch (IOException e) { 93 | Log.d(TAG, "register fail", e); 94 | } 95 | } 96 | } 97 | if (readyChannels == 0) { 98 | selector.selectedKeys().clear(); 99 | continue; 100 | } 101 | Set keys = selector.selectedKeys(); 102 | Iterator keyIterator = keys.iterator(); 103 | while (keyIterator.hasNext()) { 104 | SelectionKey key = keyIterator.next(); 105 | keyIterator.remove(); 106 | if (key.isValid() && key.isReadable()) { 107 | try { 108 | DatagramChannel inputChannel = (DatagramChannel) key.channel(); 109 | 110 | ByteBuffer receiveBuffer = ByteBufferPool.acquire(); 111 | int readBytes = inputChannel.read(receiveBuffer); 112 | receiveBuffer.flip(); 113 | byte[] data = new byte[receiveBuffer.remaining()]; 114 | receiveBuffer.get(data); 115 | sendUdpPack((UdpTunnel) key.attachment(), (InetSocketAddress) inputChannel.getLocalAddress(), data); 116 | } catch (IOException e) { 117 | Log.e(TAG, "error", e); 118 | } 119 | 120 | } 121 | } 122 | } 123 | } catch (Exception e) { 124 | Log.e(TAG, "error", e); 125 | System.exit(0); 126 | } finally { 127 | Log.d(TAG, "BioUdpHandler quit"); 128 | } 129 | 130 | 131 | } 132 | } 133 | 134 | public BioUdpHandler(BlockingQueue queue, BlockingQueue networkToDeviceQueue, VpnService vpnService) { 135 | this.queue = queue; 136 | this.networkToDeviceQueue = networkToDeviceQueue; 137 | this.vpnService = vpnService; 138 | } 139 | 140 | private static final String TAG = BioUdpHandler.class.getSimpleName(); 141 | 142 | 143 | Map udpSockets = new HashMap(); 144 | 145 | 146 | private static class UdpTunnel { 147 | InetSocketAddress local; 148 | InetSocketAddress remote; 149 | DatagramChannel channel; 150 | 151 | } 152 | 153 | @Override 154 | public void run() { 155 | try { 156 | BlockingQueue tunnelQueue = new ArrayBlockingQueue<>(100); 157 | selector = Selector.open(); 158 | Thread t = new Thread(new UdpDownWorker(selector, networkToDeviceQueue, tunnelQueue)); 159 | t.start(); 160 | 161 | 162 | while (true) { 163 | Packet packet = queue.take(); 164 | 165 | InetAddress destinationAddress = packet.ip4Header.destinationAddress; 166 | Packet.UDPHeader header = packet.udpHeader; 167 | 168 | //Log.d(TAG, String.format("get pack %d udp %d ", packet.packId, header.length)); 169 | 170 | int destinationPort = header.destinationPort; 171 | int sourcePort = header.sourcePort; 172 | String ipAndPort = destinationAddress.getHostAddress() + ":" + destinationPort + ":" + sourcePort; 173 | if (!udpSockets.containsKey(ipAndPort)) { 174 | DatagramChannel outputChannel = DatagramChannel.open(); 175 | vpnService.protect(outputChannel.socket()); 176 | outputChannel.socket().bind(null); 177 | outputChannel.connect(new InetSocketAddress(destinationAddress, destinationPort)); 178 | 179 | outputChannel.configureBlocking(false); 180 | 181 | UdpTunnel tunnel = new UdpTunnel(); 182 | tunnel.local = new InetSocketAddress(packet.ip4Header.sourceAddress, header.sourcePort); 183 | tunnel.remote = new InetSocketAddress(packet.ip4Header.destinationAddress, header.destinationPort); 184 | tunnel.channel = outputChannel; 185 | tunnelQueue.offer(tunnel); 186 | 187 | selector.wakeup(); 188 | 189 | udpSockets.put(ipAndPort, outputChannel); 190 | } 191 | 192 | DatagramChannel outputChannel = udpSockets.get(ipAndPort); 193 | ByteBuffer buffer = packet.backingBuffer; 194 | try { 195 | while (packet.backingBuffer.hasRemaining()) { 196 | int w = outputChannel.write(buffer); 197 | if (Config.logRW) { 198 | Log.d(TAG, String.format("write udp pack %d len %d %s ", packet.packId, w, ipAndPort)); 199 | } 200 | 201 | } 202 | } catch (IOException e) { 203 | Log.e(TAG, "udp write error", e); 204 | outputChannel.close(); 205 | udpSockets.remove(ipAndPort); 206 | } 207 | } 208 | } catch (Exception e) { 209 | Log.e(TAG, "error", e); 210 | System.exit(0); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/bio/BioUtil.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.bio; 2 | 3 | import android.util.Log; 4 | 5 | import com.mocyx.basic_client.config.Config; 6 | 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | import java.nio.channels.SocketChannel; 10 | 11 | public class BioUtil { 12 | 13 | private static final String TAG = BioUtil.class.getSimpleName(); 14 | 15 | public static int write(SocketChannel channel, ByteBuffer byteBuffer) throws IOException { 16 | int len = channel.write(byteBuffer); 17 | Log.i(TAG, String.format("write %d %s ", len, channel.toString())); 18 | return len; 19 | } 20 | 21 | public static int read(SocketChannel channel, ByteBuffer byteBuffer) throws IOException { 22 | int len = channel.read(byteBuffer); 23 | if(Config.logRW){ 24 | Log.d(TAG, String.format("read %d %s ", len, channel.toString())); 25 | } 26 | 27 | return len; 28 | } 29 | 30 | public static String byteToString(byte[] data, int off, int len) { 31 | len = Math.min(128, len); 32 | StringBuilder sb = new StringBuilder(); 33 | for (int i = off; i < off + len; i++) { 34 | sb.append(String.format("%02x ", data[i])); 35 | } 36 | return sb.toString(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/bio/NioSingleThreadTcpHandler.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.bio; 2 | 3 | import android.net.VpnService; 4 | import android.util.Log; 5 | 6 | import com.mocyx.basic_client.config.Config; 7 | import com.mocyx.basic_client.protocol.tcpip.IpUtil; 8 | import com.mocyx.basic_client.protocol.tcpip.Packet; 9 | import com.mocyx.basic_client.protocol.tcpip.Packet.TCPHeader; 10 | import com.mocyx.basic_client.protocol.tcpip.TCBStatus; 11 | import com.mocyx.basic_client.util.ByteBufferPool; 12 | import com.mocyx.basic_client.util.ObjAttrUtil; 13 | 14 | import java.io.IOException; 15 | import java.net.InetAddress; 16 | import java.net.InetSocketAddress; 17 | import java.nio.ByteBuffer; 18 | import java.nio.channels.ClosedChannelException; 19 | import java.nio.channels.SelectionKey; 20 | import java.nio.channels.Selector; 21 | import java.nio.channels.ServerSocketChannel; 22 | import java.nio.channels.SocketChannel; 23 | import java.util.HashMap; 24 | import java.util.Iterator; 25 | import java.util.Map; 26 | import java.util.concurrent.BlockingQueue; 27 | 28 | public class NioSingleThreadTcpHandler implements Runnable { 29 | 30 | private static final String TAG = NioSingleThreadTcpHandler.class.getSimpleName(); 31 | 32 | BlockingQueue queue;//用于读包 33 | BlockingQueue networkToDeviceQueue;//用于写数据 34 | VpnService vpnService;//用于保护地址 35 | 36 | private ObjAttrUtil objAttrUtil = new ObjAttrUtil(); 37 | private Selector selector; 38 | 39 | private Map pipes = new HashMap<>(); 40 | 41 | 42 | public NioSingleThreadTcpHandler(BlockingQueue queue,//用于读包 43 | BlockingQueue networkToDeviceQueue,//用于写数据 44 | VpnService vpnService//用于保护地址 45 | ) { 46 | this.queue = queue; 47 | this.vpnService = vpnService; 48 | this.networkToDeviceQueue = networkToDeviceQueue; 49 | } 50 | 51 | static class TcpPipe { 52 | public long mySequenceNum = 0; 53 | public long theirSequenceNum = 0; 54 | public long myAcknowledgementNum = 0; 55 | public long theirAcknowledgementNum = 0; 56 | static Integer tunnelIds = 0; 57 | public final int tunnelId = tunnelIds++; 58 | public String tunnelKey; 59 | public InetSocketAddress sourceAddress; 60 | public InetSocketAddress destinationAddress; 61 | public SocketChannel remote; 62 | public TCBStatus tcbStatus = TCBStatus.SYN_SENT; 63 | private ByteBuffer remoteOutBuffer = ByteBuffer.allocate(8 * 1024); 64 | // 65 | public boolean upActive = true; 66 | public boolean downActive = true; 67 | 68 | public int packId = 1; 69 | public long timestamp=0L; 70 | 71 | int synCount = 0; 72 | 73 | } 74 | 75 | 76 | private TcpPipe initPipe(Packet packet) throws Exception { 77 | TcpPipe pipe = new TcpPipe(); 78 | pipe.sourceAddress = new InetSocketAddress(packet.ip4Header.sourceAddress, packet.tcpHeader.sourcePort); 79 | pipe.destinationAddress = new InetSocketAddress(packet.ip4Header.destinationAddress, packet.tcpHeader.destinationPort); 80 | pipe.remote = SocketChannel.open(); 81 | objAttrUtil.setAttr(pipe.remote, "type", "remote"); 82 | objAttrUtil.setAttr(pipe.remote, "pipe", pipe); 83 | pipe.remote.configureBlocking(false); 84 | SelectionKey key = pipe.remote.register(selector, SelectionKey.OP_CONNECT); 85 | objAttrUtil.setAttr(pipe.remote, "key", key); 86 | //very important, protect 87 | vpnService.protect(pipe.remote.socket()); 88 | boolean b1 = pipe.remote.connect(pipe.destinationAddress); 89 | pipe.timestamp=System.currentTimeMillis(); 90 | Log.i(TAG, String.format("initPipe %s %s", pipe.destinationAddress, b1)); 91 | return pipe; 92 | } 93 | 94 | 95 | private static int HEADER_SIZE = Packet.IP4_HEADER_SIZE + Packet.TCP_HEADER_SIZE; 96 | 97 | private void sendTcpPack(TcpPipe pipe, byte flag, byte[] data) { 98 | int dataLen = 0; 99 | if (data != null) { 100 | dataLen = data.length; 101 | } 102 | Packet packet = IpUtil.buildTcpPacket(pipe.destinationAddress, pipe.sourceAddress, flag, 103 | pipe.myAcknowledgementNum, pipe.mySequenceNum, pipe.packId); 104 | pipe.packId += 1; 105 | ByteBuffer byteBuffer = ByteBuffer.allocate(HEADER_SIZE + dataLen); 106 | // 107 | byteBuffer.position(HEADER_SIZE); 108 | if (data != null) { 109 | if (byteBuffer.remaining() < data.length) { 110 | System.currentTimeMillis(); 111 | } 112 | byteBuffer.put(data); 113 | } 114 | // 115 | packet.updateTCPBuffer(byteBuffer, flag, pipe.mySequenceNum, pipe.myAcknowledgementNum, dataLen); 116 | byteBuffer.position(HEADER_SIZE + dataLen); 117 | // 118 | networkToDeviceQueue.offer(byteBuffer); 119 | // 120 | if ((flag & (byte) TCPHeader.SYN) != 0) { 121 | pipe.mySequenceNum += 1; 122 | } 123 | if ((flag & (byte) TCPHeader.FIN) != 0) { 124 | pipe.mySequenceNum += 1; 125 | } 126 | if ((flag & (byte) TCPHeader.ACK) != 0) { 127 | pipe.mySequenceNum += dataLen; 128 | } 129 | } 130 | 131 | private void handleSyn(Packet packet, TcpPipe pipe) { 132 | if (pipe.tcbStatus == TCBStatus.SYN_SENT) { 133 | pipe.tcbStatus = TCBStatus.SYN_RECEIVED; 134 | Log.i(TAG, String.format("handleSyn %s %s", pipe.destinationAddress, pipe.tcbStatus)); 135 | } 136 | Log.i(TAG, String.format("handleSyn %d %d", pipe.tunnelId, packet.packId)); 137 | TCPHeader tcpHeader = packet.tcpHeader; 138 | if (pipe.synCount == 0) { 139 | pipe.mySequenceNum = 1; 140 | pipe.theirSequenceNum = tcpHeader.sequenceNumber; 141 | pipe.myAcknowledgementNum = tcpHeader.sequenceNumber + 1; 142 | pipe.theirAcknowledgementNum = tcpHeader.acknowledgementNumber; 143 | sendTcpPack(pipe, (byte) (TCPHeader.SYN | TCPHeader.ACK), null); 144 | } else { 145 | pipe.myAcknowledgementNum = tcpHeader.sequenceNumber + 1; 146 | } 147 | pipe.synCount += 1; 148 | } 149 | 150 | private void handleRst(Packet packet, TcpPipe pipe) { 151 | Log.i(TAG, String.format("handleRst %d", pipe.tunnelId)); 152 | pipe.upActive = false; 153 | pipe.downActive = false; 154 | cleanPipe(pipe); 155 | pipe.tcbStatus = TCBStatus.CLOSE_WAIT; 156 | } 157 | 158 | private void handleAck(Packet packet, TcpPipe pipe) throws Exception { 159 | if (pipe.tcbStatus == TCBStatus.SYN_RECEIVED) { 160 | pipe.tcbStatus = TCBStatus.ESTABLISHED; 161 | 162 | Log.i(TAG, String.format("handleAck %s %s", pipe.destinationAddress, pipe.tcbStatus)); 163 | } 164 | 165 | if (Config.logAck) { 166 | Log.d(TAG, String.format("handleAck %d ", packet.packId)); 167 | } 168 | 169 | TCPHeader tcpHeader = packet.tcpHeader; 170 | int payloadSize = packet.backingBuffer.remaining(); 171 | 172 | if (payloadSize == 0) { 173 | return; 174 | } 175 | 176 | long newAck = tcpHeader.sequenceNumber + payloadSize; 177 | if (newAck <= pipe.myAcknowledgementNum) { 178 | if (Config.logAck) { 179 | Log.d(TAG, String.format("handleAck duplicate ack", pipe.myAcknowledgementNum, newAck)); 180 | } 181 | return; 182 | } 183 | 184 | pipe.myAcknowledgementNum = tcpHeader.sequenceNumber; 185 | pipe.theirAcknowledgementNum = tcpHeader.acknowledgementNumber; 186 | 187 | pipe.myAcknowledgementNum += payloadSize; 188 | //TODO 189 | pipe.remoteOutBuffer.put(packet.backingBuffer); 190 | pipe.remoteOutBuffer.flip(); 191 | tryFlushWrite(pipe, pipe.remote); 192 | sendTcpPack(pipe, (byte) TCPHeader.ACK, null); 193 | System.currentTimeMillis(); 194 | } 195 | 196 | private SelectionKey getKey(SocketChannel channel) { 197 | return (SelectionKey) objAttrUtil.getAttr(channel, "key"); 198 | } 199 | 200 | private boolean tryFlushWrite(TcpPipe pipe, SocketChannel channel) throws Exception { 201 | 202 | ByteBuffer buffer = pipe.remoteOutBuffer; 203 | if (pipe.remote.socket().isOutputShutdown() && buffer.remaining() != 0) { 204 | sendTcpPack(pipe, (byte) (TCPHeader.FIN | Packet.TCPHeader.ACK), null); 205 | buffer.compact(); 206 | return false; 207 | } 208 | if (!channel.isConnected()) { 209 | Log.i(TAG, "not yet connected"); 210 | SelectionKey key = (SelectionKey) objAttrUtil.getAttr(channel, "key"); 211 | int ops = key.interestOps() | SelectionKey.OP_WRITE; 212 | key.interestOps(ops); 213 | System.currentTimeMillis(); 214 | buffer.compact(); 215 | return false; 216 | } 217 | while (buffer.hasRemaining()) { 218 | int n = 0; 219 | n = channel.write(buffer); 220 | if (n > 4000) { 221 | System.currentTimeMillis(); 222 | } 223 | Log.i(TAG, String.format("tryFlushWrite write %s", n)); 224 | if (n <= 0) { 225 | Log.i(TAG, "write fail"); 226 | // 227 | SelectionKey key = (SelectionKey) objAttrUtil.getAttr(channel, "key"); 228 | int ops = key.interestOps() | SelectionKey.OP_WRITE; 229 | key.interestOps(ops); 230 | System.currentTimeMillis(); 231 | buffer.compact(); 232 | return false; 233 | } 234 | } 235 | buffer.clear(); 236 | if (!pipe.upActive) { 237 | pipe.remote.shutdownOutput(); 238 | } 239 | return true; 240 | } 241 | 242 | 243 | private void closeUpStream(TcpPipe pipe) throws Exception { 244 | Log.i(TAG, String.format("closeUpStream %d", pipe.tunnelId)); 245 | try { 246 | if (pipe.remote != null && pipe.remote.isOpen()) { 247 | if (pipe.remote.isConnected()) { 248 | pipe.remote.shutdownOutput(); 249 | } 250 | 251 | } 252 | } catch (Exception e) { 253 | e.printStackTrace(); 254 | } 255 | Log.i(TAG, String.format("closeUpStream %d", pipe.tunnelId)); 256 | pipe.upActive = false; 257 | 258 | if (isClosedTunnel(pipe)) { 259 | cleanPipe(pipe); 260 | } 261 | } 262 | 263 | private void handleFin(Packet packet, TcpPipe pipe) throws Exception { 264 | Log.i(TAG, String.format("handleFin %d", pipe.tunnelId)); 265 | pipe.myAcknowledgementNum = packet.tcpHeader.sequenceNumber + 1; 266 | pipe.theirAcknowledgementNum = packet.tcpHeader.acknowledgementNumber; 267 | //TODO 268 | sendTcpPack(pipe, (byte) (TCPHeader.ACK), null); 269 | closeUpStream(pipe); 270 | pipe.tcbStatus = TCBStatus.CLOSE_WAIT; 271 | 272 | Log.i(TAG, String.format("handleFin %s %s", pipe.destinationAddress, pipe.tcbStatus)); 273 | } 274 | 275 | private void handlePacket(TcpPipe pipe, Packet packet) throws Exception { 276 | boolean end = false; 277 | TCPHeader tcpHeader = packet.tcpHeader; 278 | if (tcpHeader.isSYN()) { 279 | handleSyn(packet, pipe); 280 | end = true; 281 | } 282 | if (!end && tcpHeader.isRST()) { 283 | handleRst(packet, pipe); 284 | return; 285 | } 286 | if (!end && tcpHeader.isFIN()) { 287 | handleFin(packet, pipe); 288 | end = true; 289 | } 290 | if (!end && tcpHeader.isACK()) { 291 | handleAck(packet, pipe); 292 | } 293 | 294 | } 295 | 296 | private void handleReadFromVpn() throws Exception { 297 | while (true) { 298 | Packet currentPacket = queue.poll(); 299 | if (currentPacket == null) { 300 | return; 301 | } 302 | InetAddress destinationAddress = currentPacket.ip4Header.destinationAddress; 303 | TCPHeader tcpHeader = currentPacket.tcpHeader; 304 | //Log.d(TAG, String.format("get pack %d tcp " + tcpHeader.printSimple() + " ", currentPacket.packId)); 305 | int destinationPort = tcpHeader.destinationPort; 306 | int sourcePort = tcpHeader.sourcePort; 307 | String ipAndPort = destinationAddress.getHostAddress() + ":" + 308 | destinationPort + ":" + sourcePort; 309 | 310 | 311 | if (!pipes.containsKey(ipAndPort)) { 312 | TcpPipe tcpTunnel = initPipe(currentPacket); 313 | tcpTunnel.tunnelKey = ipAndPort; 314 | pipes.put(ipAndPort, tcpTunnel); 315 | } 316 | TcpPipe pipe = pipes.get(ipAndPort); 317 | handlePacket(pipe, currentPacket); 318 | System.currentTimeMillis(); 319 | } 320 | } 321 | 322 | private void doAccept(ServerSocketChannel serverChannel) throws Exception { 323 | throw new RuntimeException(""); 324 | } 325 | 326 | private void doRead(SocketChannel channel) throws Exception { 327 | ByteBuffer buffer = ByteBuffer.allocate(4 * 1024); 328 | String quitType = ""; 329 | 330 | TcpPipe pipe = (TcpPipe) objAttrUtil.getAttr(channel, "pipe"); 331 | 332 | while (true) { 333 | buffer.clear(); 334 | int n = BioUtil.read(channel, buffer); 335 | Log.i(TAG, String.format("read %s", n)); 336 | if (n == -1) { 337 | quitType = "fin"; 338 | break; 339 | } else if (n == 0) { 340 | break; 341 | } else { 342 | if (pipe.tcbStatus != TCBStatus.CLOSE_WAIT) { 343 | buffer.flip(); 344 | byte[] data = new byte[buffer.remaining()]; 345 | buffer.get(data); 346 | sendTcpPack(pipe, (byte) (TCPHeader.ACK), data); 347 | } 348 | } 349 | } 350 | if (quitType.equals("fin")) { 351 | closeDownStream(pipe); 352 | } 353 | } 354 | 355 | private void cleanPipe(TcpPipe pipe) { 356 | try { 357 | if (pipe.remote != null && pipe.remote.isOpen()) { 358 | pipe.remote.close(); 359 | } 360 | pipes.remove(pipe.tunnelKey); 361 | } catch (Exception e) { 362 | e.printStackTrace(); 363 | } 364 | } 365 | 366 | private void closeRst(TcpPipe pipe) throws Exception { 367 | Log.i(TAG, String.format("closeRst %d", pipe.tunnelId)); 368 | cleanPipe(pipe); 369 | sendTcpPack(pipe, (byte) TCPHeader.RST, null); 370 | pipe.upActive = false; 371 | pipe.downActive = false; 372 | } 373 | 374 | private void closeDownStream(TcpPipe pipe) throws Exception { 375 | Log.i(TAG, String.format("closeDownStream %d", pipe.tunnelId)); 376 | if (pipe.remote != null && pipe.remote.isConnected()) { 377 | pipe.remote.shutdownInput(); 378 | int ops = getKey(pipe.remote).interestOps() & (~SelectionKey.OP_READ); 379 | getKey(pipe.remote).interestOps(ops); 380 | } 381 | 382 | sendTcpPack(pipe, (byte) (TCPHeader.FIN | Packet.TCPHeader.ACK), null); 383 | pipe.downActive = false; 384 | if (isClosedTunnel(pipe)) { 385 | cleanPipe(pipe); 386 | } 387 | } 388 | 389 | public boolean isClosedTunnel(TcpPipe tunnel) { 390 | return !tunnel.upActive && !tunnel.downActive; 391 | } 392 | 393 | private void doConnect(SocketChannel socketChannel) throws Exception { 394 | Log.i(TAG, String.format("tick %s", tick)); 395 | // 396 | String type = (String) objAttrUtil.getAttr(socketChannel, "type"); 397 | TcpPipe pipe = (TcpPipe) objAttrUtil.getAttr(socketChannel, "pipe"); 398 | SelectionKey key = (SelectionKey) objAttrUtil.getAttr(socketChannel, "key"); 399 | if (type.equals("remote")) { 400 | boolean b1 = socketChannel.finishConnect(); 401 | Log.i(TAG, String.format("connect %s %s %s", pipe.destinationAddress, b1,System.currentTimeMillis()-pipe.timestamp)); 402 | pipe.timestamp=System.currentTimeMillis(); 403 | pipe.remoteOutBuffer.flip(); 404 | key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); 405 | } 406 | 407 | } 408 | 409 | private void doWrite(SocketChannel socketChannel) throws Exception { 410 | Log.i(TAG, String.format("tick %s", tick)); 411 | TcpPipe pipe = (TcpPipe) objAttrUtil.getAttr(socketChannel, "pipe"); 412 | boolean flushed = tryFlushWrite(pipe, socketChannel); 413 | if (flushed) { 414 | SelectionKey key1 = (SelectionKey) objAttrUtil.getAttr(socketChannel, "key"); 415 | key1.interestOps(SelectionKey.OP_READ); 416 | } 417 | } 418 | 419 | 420 | private void handleSockets() throws Exception { 421 | 422 | while (selector.selectNow() > 0) { 423 | for (Iterator it = selector.selectedKeys().iterator(); it.hasNext(); ) { 424 | SelectionKey key = (SelectionKey) it.next(); 425 | it.remove(); 426 | TcpPipe pipe = (TcpPipe) objAttrUtil.getAttr(key.channel(), "pipe"); 427 | if (key.isValid()) { 428 | try { 429 | if (key.isAcceptable()) { 430 | doAccept((ServerSocketChannel) key.channel()); 431 | } else if (key.isReadable()) { 432 | doRead((SocketChannel) key.channel()); 433 | } else if (key.isConnectable()) { 434 | doConnect((SocketChannel) key.channel()); 435 | System.currentTimeMillis(); 436 | } else if (key.isWritable()) { 437 | doWrite((SocketChannel) key.channel()); 438 | System.currentTimeMillis(); 439 | } 440 | } catch (Exception e) { 441 | Log.e(TAG, e.getMessage(), e); 442 | if (pipe != null) { 443 | closeRst(pipe); 444 | } 445 | } 446 | } 447 | } 448 | } 449 | } 450 | 451 | private long tick = 0; 452 | 453 | @Override 454 | public void run() { 455 | try { 456 | selector = Selector.open(); 457 | while (true) { 458 | handleReadFromVpn(); 459 | handleSockets(); 460 | tick += 1; 461 | Thread.sleep(1); 462 | } 463 | } catch (Exception e) { 464 | Log.e(e.getMessage(), "", e); 465 | } 466 | 467 | 468 | } 469 | } 470 | 471 | 472 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.config; 2 | 3 | public class Config { 4 | //只代理本app的流量 5 | public static boolean testLocal = false; 6 | //配置dns 7 | public static String dns = "114.114.114.114"; 8 | //io日志 9 | @Deprecated 10 | public static boolean logRW = false; 11 | //ack日志 12 | @Deprecated 13 | public static boolean logAck = false; 14 | } 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/protocol/tcpip/IpUtil.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.protocol.tcpip; 2 | 3 | import com.mocyx.basic_client.util.ByteBufferPool; 4 | 5 | import java.net.InetSocketAddress; 6 | import java.nio.ByteBuffer; 7 | 8 | public class IpUtil { 9 | public static Packet buildUdpPacket(InetSocketAddress source, InetSocketAddress dest, int ipId) { 10 | Packet packet = new Packet(); 11 | packet.isTCP = false; 12 | packet.isUDP = true; 13 | Packet.IP4Header ip4Header = new Packet.IP4Header(); 14 | ip4Header.version = 4; 15 | ip4Header.IHL = 5; 16 | ip4Header.destinationAddress = dest.getAddress(); 17 | ip4Header.headerChecksum = 0; 18 | ip4Header.headerLength = 20; 19 | 20 | //int ipId=0; 21 | int ipFlag = 0x40; 22 | int ipOff = 0; 23 | 24 | ip4Header.identificationAndFlagsAndFragmentOffset = ipId << 16 | ipFlag << 8 | ipOff; 25 | 26 | ip4Header.optionsAndPadding = 0; 27 | ip4Header.protocol = Packet.IP4Header.TransportProtocol.UDP; 28 | ip4Header.protocolNum = 17; 29 | ip4Header.sourceAddress = source.getAddress(); 30 | ip4Header.totalLength = 60; 31 | ip4Header.typeOfService = 0; 32 | ip4Header.TTL = 64; 33 | 34 | Packet.UDPHeader udpHeader = new Packet.UDPHeader(); 35 | udpHeader.sourcePort = source.getPort(); 36 | udpHeader.destinationPort = dest.getPort(); 37 | udpHeader.length = 0; 38 | 39 | ByteBuffer byteBuffer = ByteBufferPool.acquire(); 40 | byteBuffer.flip(); 41 | 42 | packet.ip4Header = ip4Header; 43 | packet.udpHeader = udpHeader; 44 | packet.backingBuffer = byteBuffer; 45 | return packet; 46 | } 47 | public static Packet buildTcpPacket(InetSocketAddress source, InetSocketAddress dest, byte flag, long ack, long seq, int ipId) { 48 | Packet packet = new Packet(); 49 | packet.isTCP = true; 50 | packet.isUDP = false; 51 | Packet.IP4Header ip4Header = new Packet.IP4Header(); 52 | ip4Header.version = 4; 53 | ip4Header.IHL = 5; 54 | ip4Header.destinationAddress = dest.getAddress(); 55 | ip4Header.headerChecksum = 0; 56 | ip4Header.headerLength = 20; 57 | 58 | //int ipId=0; 59 | int ipFlag = 0x40; 60 | int ipOff = 0; 61 | 62 | ip4Header.identificationAndFlagsAndFragmentOffset = ipId << 16 | ipFlag << 8 | ipOff; 63 | 64 | ip4Header.optionsAndPadding = 0; 65 | ip4Header.protocol = Packet.IP4Header.TransportProtocol.TCP; 66 | ip4Header.protocolNum = 6; 67 | ip4Header.sourceAddress = source.getAddress(); 68 | ip4Header.totalLength = 60; 69 | ip4Header.typeOfService = 0; 70 | ip4Header.TTL = 64; 71 | 72 | Packet.TCPHeader tcpHeader = new Packet.TCPHeader(); 73 | tcpHeader.acknowledgementNumber = ack; 74 | tcpHeader.checksum = 0; 75 | tcpHeader.dataOffsetAndReserved = -96; 76 | tcpHeader.destinationPort = dest.getPort(); 77 | tcpHeader.flags = flag; 78 | tcpHeader.headerLength = 40; 79 | tcpHeader.optionsAndPadding = null; 80 | tcpHeader.sequenceNumber = seq; 81 | tcpHeader.sourcePort = source.getPort(); 82 | tcpHeader.urgentPointer = 0; 83 | tcpHeader.window = 65535; 84 | 85 | ByteBuffer byteBuffer = ByteBufferPool.acquire(); 86 | byteBuffer.flip(); 87 | 88 | packet.ip4Header = ip4Header; 89 | packet.tcpHeader = tcpHeader; 90 | packet.backingBuffer = byteBuffer; 91 | return packet; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/protocol/tcpip/Packet.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.protocol.tcpip; 2 | 3 | 4 | import java.net.InetAddress; 5 | import java.net.UnknownHostException; 6 | import java.nio.ByteBuffer; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | /** 10 | * Representation of an IP Packet 11 | */ 12 | // TODO: Reduce public mutability 13 | public class Packet { 14 | public static final int IP4_HEADER_SIZE = 20; 15 | public static final int TCP_HEADER_SIZE = 20; 16 | public static final int UDP_HEADER_SIZE = 8; 17 | 18 | 19 | private static AtomicInteger globalPackId=new AtomicInteger(); 20 | public int packId=globalPackId.addAndGet(1); 21 | public IP4Header ip4Header; 22 | public TCPHeader tcpHeader; 23 | public UDPHeader udpHeader; 24 | public ByteBuffer backingBuffer; 25 | 26 | public boolean isTCP; 27 | public boolean isUDP; 28 | 29 | public Packet() { 30 | 31 | } 32 | 33 | public Packet(ByteBuffer buffer) throws UnknownHostException { 34 | this.ip4Header = new IP4Header(buffer); 35 | if (this.ip4Header.protocol == IP4Header.TransportProtocol.TCP) { 36 | this.tcpHeader = new TCPHeader(buffer); 37 | this.isTCP = true; 38 | } else if (ip4Header.protocol == IP4Header.TransportProtocol.UDP) { 39 | this.udpHeader = new UDPHeader(buffer); 40 | this.isUDP = true; 41 | } 42 | this.backingBuffer = buffer; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | final StringBuilder sb = new StringBuilder("Packet{"); 48 | sb.append("ip4Header=").append(ip4Header); 49 | if (isTCP) sb.append(", tcpHeader=").append(tcpHeader); 50 | else if (isUDP) sb.append(", udpHeader=").append(udpHeader); 51 | sb.append(", payloadSize=").append(backingBuffer.limit() - backingBuffer.position()); 52 | sb.append('}'); 53 | return sb.toString(); 54 | } 55 | 56 | public boolean isTCP() { 57 | return isTCP; 58 | } 59 | 60 | public boolean isUDP() { 61 | return isUDP; 62 | } 63 | 64 | 65 | public void updateTCPBuffer(ByteBuffer buffer, byte flags, long sequenceNum, long ackNum, int payloadSize) { 66 | buffer.position(0); 67 | fillHeader(buffer); 68 | backingBuffer = buffer; 69 | 70 | tcpHeader.flags = flags; 71 | backingBuffer.put(IP4_HEADER_SIZE + 13, flags); 72 | 73 | tcpHeader.sequenceNumber = sequenceNum; 74 | backingBuffer.putInt(IP4_HEADER_SIZE + 4, (int) sequenceNum); 75 | 76 | tcpHeader.acknowledgementNumber = ackNum; 77 | backingBuffer.putInt(IP4_HEADER_SIZE + 8, (int) ackNum); 78 | 79 | // Reset header size, since we don't need options 80 | byte dataOffset = (byte) (TCP_HEADER_SIZE << 2); 81 | tcpHeader.dataOffsetAndReserved = dataOffset; 82 | backingBuffer.put(IP4_HEADER_SIZE + 12, dataOffset); 83 | 84 | updateTCPChecksum(payloadSize); 85 | 86 | int ip4TotalLength = IP4_HEADER_SIZE + TCP_HEADER_SIZE + payloadSize; 87 | backingBuffer.putShort(2, (short) ip4TotalLength); 88 | ip4Header.totalLength = ip4TotalLength; 89 | 90 | updateIP4Checksum(); 91 | } 92 | 93 | public void updateUDPBuffer(ByteBuffer buffer, int payloadSize) { 94 | buffer.position(0); 95 | fillHeader(buffer); 96 | backingBuffer = buffer; 97 | 98 | int udpTotalLength = UDP_HEADER_SIZE + payloadSize; 99 | backingBuffer.putShort(IP4_HEADER_SIZE + 4, (short) udpTotalLength); 100 | udpHeader.length = udpTotalLength; 101 | 102 | // Disable UDP checksum validation 103 | backingBuffer.putShort(IP4_HEADER_SIZE + 6, (short) 0); 104 | udpHeader.checksum = 0; 105 | 106 | int ip4TotalLength = IP4_HEADER_SIZE + udpTotalLength; 107 | backingBuffer.putShort(2, (short) ip4TotalLength); 108 | ip4Header.totalLength = ip4TotalLength; 109 | 110 | updateIP4Checksum(); 111 | } 112 | 113 | private void updateIP4Checksum() { 114 | ByteBuffer buffer = backingBuffer.duplicate(); 115 | buffer.position(0); 116 | 117 | // Clear previous checksum 118 | buffer.putShort(10, (short) 0); 119 | 120 | int ipLength = ip4Header.headerLength; 121 | int sum = 0; 122 | while (ipLength > 0) { 123 | sum += BitUtils.getUnsignedShort(buffer.getShort()); 124 | ipLength -= 2; 125 | } 126 | while (sum >> 16 > 0) 127 | sum = (sum & 0xFFFF) + (sum >> 16); 128 | 129 | sum = ~sum; 130 | ip4Header.headerChecksum = sum; 131 | backingBuffer.putShort(10, (short) sum); 132 | } 133 | 134 | private void updateTCPChecksum(int payloadSize) { 135 | int sum = 0; 136 | int tcpLength = TCP_HEADER_SIZE + payloadSize; 137 | 138 | // Calculate pseudo-header checksum 139 | ByteBuffer buffer = ByteBuffer.wrap(ip4Header.sourceAddress.getAddress()); 140 | sum = BitUtils.getUnsignedShort(buffer.getShort()) + BitUtils.getUnsignedShort(buffer.getShort()); 141 | 142 | buffer = ByteBuffer.wrap(ip4Header.destinationAddress.getAddress()); 143 | sum += BitUtils.getUnsignedShort(buffer.getShort()) + BitUtils.getUnsignedShort(buffer.getShort()); 144 | 145 | sum += IP4Header.TransportProtocol.TCP.getNumber() + tcpLength; 146 | 147 | buffer = backingBuffer.duplicate(); 148 | // Clear previous checksum 149 | buffer.putShort(IP4_HEADER_SIZE + 16, (short) 0); 150 | 151 | // Calculate TCP segment checksum 152 | buffer.position(IP4_HEADER_SIZE); 153 | while (tcpLength > 1) { 154 | sum += BitUtils.getUnsignedShort(buffer.getShort()); 155 | tcpLength -= 2; 156 | } 157 | if (tcpLength > 0) 158 | sum += BitUtils.getUnsignedByte(buffer.get()) << 8; 159 | 160 | while (sum >> 16 > 0) 161 | sum = (sum & 0xFFFF) + (sum >> 16); 162 | 163 | sum = ~sum; 164 | tcpHeader.checksum = sum; 165 | backingBuffer.putShort(IP4_HEADER_SIZE + 16, (short) sum); 166 | } 167 | 168 | private void fillHeader(ByteBuffer buffer) { 169 | ip4Header.fillHeader(buffer); 170 | if (isUDP) 171 | udpHeader.fillHeader(buffer); 172 | else if (isTCP) 173 | tcpHeader.fillHeader(buffer); 174 | } 175 | 176 | public static class IP4Header { 177 | public byte version; 178 | public byte IHL; 179 | public int headerLength; 180 | public short typeOfService; 181 | public int totalLength; 182 | 183 | public int identificationAndFlagsAndFragmentOffset; 184 | 185 | public short TTL; 186 | public short protocolNum; 187 | public TransportProtocol protocol; 188 | public int headerChecksum; 189 | 190 | public InetAddress sourceAddress; 191 | public InetAddress destinationAddress; 192 | 193 | public int optionsAndPadding; 194 | 195 | public enum TransportProtocol { 196 | TCP(6), 197 | UDP(17), 198 | Other(0xFF); 199 | 200 | private int protocolNumber; 201 | 202 | TransportProtocol(int protocolNumber) { 203 | this.protocolNumber = protocolNumber; 204 | } 205 | 206 | private static TransportProtocol numberToEnum(int protocolNumber) { 207 | if (protocolNumber == 6) 208 | return TCP; 209 | else if (protocolNumber == 17) 210 | return UDP; 211 | else 212 | return Other; 213 | } 214 | 215 | public int getNumber() { 216 | return this.protocolNumber; 217 | } 218 | } 219 | 220 | public IP4Header() { 221 | 222 | } 223 | 224 | private IP4Header(ByteBuffer buffer) throws UnknownHostException { 225 | byte versionAndIHL = buffer.get(); 226 | this.version = (byte) (versionAndIHL >> 4); 227 | this.IHL = (byte) (versionAndIHL & 0x0F); 228 | this.headerLength = this.IHL << 2; 229 | 230 | this.typeOfService = BitUtils.getUnsignedByte(buffer.get()); 231 | this.totalLength = BitUtils.getUnsignedShort(buffer.getShort()); 232 | 233 | this.identificationAndFlagsAndFragmentOffset = buffer.getInt(); 234 | 235 | this.TTL = BitUtils.getUnsignedByte(buffer.get()); 236 | this.protocolNum = BitUtils.getUnsignedByte(buffer.get()); 237 | this.protocol = TransportProtocol.numberToEnum(protocolNum); 238 | this.headerChecksum = BitUtils.getUnsignedShort(buffer.getShort()); 239 | 240 | byte[] addressBytes = new byte[4]; 241 | buffer.get(addressBytes, 0, 4); 242 | this.sourceAddress = InetAddress.getByAddress(addressBytes); 243 | 244 | buffer.get(addressBytes, 0, 4); 245 | this.destinationAddress = InetAddress.getByAddress(addressBytes); 246 | 247 | //this.optionsAndPadding = buffer.getInt(); 248 | } 249 | 250 | public void fillHeader(ByteBuffer buffer) { 251 | buffer.put((byte) (this.version << 4 | this.IHL)); 252 | buffer.put((byte) this.typeOfService); 253 | buffer.putShort((short) this.totalLength); 254 | 255 | buffer.putInt(this.identificationAndFlagsAndFragmentOffset); 256 | 257 | buffer.put((byte) this.TTL); 258 | buffer.put((byte) this.protocol.getNumber()); 259 | buffer.putShort((short) this.headerChecksum); 260 | 261 | buffer.put(this.sourceAddress.getAddress()); 262 | buffer.put(this.destinationAddress.getAddress()); 263 | } 264 | 265 | @Override 266 | public String toString() { 267 | final StringBuilder sb = new StringBuilder("IP4Header{"); 268 | sb.append("version=").append(version); 269 | sb.append(", IHL=").append(IHL); 270 | sb.append(", typeOfService=").append(typeOfService); 271 | sb.append(", totalLength=").append(totalLength); 272 | sb.append(", identificationAndFlagsAndFragmentOffset=").append(identificationAndFlagsAndFragmentOffset); 273 | sb.append(", TTL=").append(TTL); 274 | sb.append(", protocol=").append(protocolNum).append(":").append(protocol); 275 | sb.append(", headerChecksum=").append(headerChecksum); 276 | sb.append(", sourceAddress=").append(sourceAddress.getHostAddress()); 277 | sb.append(", destinationAddress=").append(destinationAddress.getHostAddress()); 278 | sb.append('}'); 279 | return sb.toString(); 280 | } 281 | } 282 | 283 | public static class TCPHeader { 284 | public static final int FIN = 0x01; 285 | public static final int SYN = 0x02; 286 | public static final int RST = 0x04; 287 | public static final int PSH = 0x08; 288 | public static final int ACK = 0x10; 289 | public static final int URG = 0x20; 290 | 291 | public int sourcePort; 292 | public int destinationPort; 293 | 294 | public long sequenceNumber; 295 | public long acknowledgementNumber; 296 | 297 | public byte dataOffsetAndReserved; 298 | public int headerLength; 299 | public byte flags; 300 | public int window; 301 | 302 | public int checksum; 303 | public int urgentPointer; 304 | 305 | public byte[] optionsAndPadding; 306 | 307 | public TCPHeader(ByteBuffer buffer) { 308 | this.sourcePort = BitUtils.getUnsignedShort(buffer.getShort()); 309 | this.destinationPort = BitUtils.getUnsignedShort(buffer.getShort()); 310 | 311 | this.sequenceNumber = BitUtils.getUnsignedInt(buffer.getInt()); 312 | this.acknowledgementNumber = BitUtils.getUnsignedInt(buffer.getInt()); 313 | 314 | this.dataOffsetAndReserved = buffer.get(); 315 | this.headerLength = (this.dataOffsetAndReserved & 0xF0) >> 2; 316 | this.flags = buffer.get(); 317 | this.window = BitUtils.getUnsignedShort(buffer.getShort()); 318 | 319 | this.checksum = BitUtils.getUnsignedShort(buffer.getShort()); 320 | this.urgentPointer = BitUtils.getUnsignedShort(buffer.getShort()); 321 | 322 | int optionsLength = this.headerLength - TCP_HEADER_SIZE; 323 | if (optionsLength > 0) { 324 | optionsAndPadding = new byte[optionsLength]; 325 | buffer.get(optionsAndPadding, 0, optionsLength); 326 | } 327 | } 328 | 329 | public TCPHeader() { 330 | 331 | } 332 | 333 | public boolean isFIN() { 334 | return (flags & FIN) == FIN; 335 | } 336 | 337 | public boolean isSYN() { 338 | return (flags & SYN) == SYN; 339 | } 340 | 341 | 342 | 343 | public boolean isRST() { 344 | return (flags & RST) == RST; 345 | } 346 | 347 | public boolean isPSH() { 348 | return (flags & PSH) == PSH; 349 | } 350 | 351 | public boolean isACK() { 352 | return (flags & ACK) == ACK; 353 | } 354 | 355 | public boolean isURG() { 356 | return (flags & URG) == URG; 357 | } 358 | 359 | private void fillHeader(ByteBuffer buffer) { 360 | buffer.putShort((short) sourcePort); 361 | buffer.putShort((short) destinationPort); 362 | 363 | buffer.putInt((int) sequenceNumber); 364 | buffer.putInt((int) acknowledgementNumber); 365 | 366 | buffer.put(dataOffsetAndReserved); 367 | buffer.put(flags); 368 | buffer.putShort((short) window); 369 | 370 | buffer.putShort((short) checksum); 371 | buffer.putShort((short) urgentPointer); 372 | } 373 | 374 | public static String flagToString(byte flags){ 375 | final StringBuilder sb = new StringBuilder(""); 376 | if ((flags & FIN) == FIN) sb.append("FIN "); 377 | if ((flags & SYN) == SYN) sb.append("SYN " ); 378 | if ((flags & RST) == RST) sb.append("RST "); 379 | if ((flags & PSH) == PSH) sb.append("PSH "); 380 | if ((flags & ACK) == ACK) sb.append("ACK " ); 381 | if ((flags & URG) == URG) sb.append("URG "); 382 | return sb.toString(); 383 | } 384 | public String printSimple() { 385 | final StringBuilder sb = new StringBuilder(""); 386 | if (isFIN()) sb.append("FIN "); 387 | if (isSYN()) sb.append("SYN "); 388 | if (isRST()) sb.append("RST "); 389 | if (isPSH()) sb.append("PSH "); 390 | if (isACK()) sb.append("ACK "); 391 | if (isURG()) sb.append("URG "); 392 | sb.append("seq "+sequenceNumber +" "); 393 | sb.append("ack "+acknowledgementNumber+" "); 394 | return sb.toString(); 395 | } 396 | 397 | @Override 398 | public String toString() { 399 | final StringBuilder sb = new StringBuilder("TCPHeader{"); 400 | sb.append("sourcePort=").append(sourcePort); 401 | sb.append(", destinationPort=").append(destinationPort); 402 | sb.append(", sequenceNumber=").append(sequenceNumber); 403 | sb.append(", acknowledgementNumber=").append(acknowledgementNumber); 404 | sb.append(", headerLength=").append(headerLength); 405 | sb.append(", window=").append(window); 406 | sb.append(", checksum=").append(checksum); 407 | sb.append(", flags="); 408 | if (isFIN()) sb.append(" FIN"); 409 | if (isSYN()) sb.append(" SYN"); 410 | if (isRST()) sb.append(" RST"); 411 | if (isPSH()) sb.append(" PSH"); 412 | if (isACK()) sb.append(" ACK"); 413 | if (isURG()) sb.append(" URG"); 414 | sb.append('}'); 415 | return sb.toString(); 416 | } 417 | } 418 | 419 | public static class UDPHeader { 420 | public int sourcePort; 421 | public int destinationPort; 422 | 423 | public int length; 424 | public int checksum; 425 | 426 | 427 | public UDPHeader(){ 428 | 429 | } 430 | 431 | private UDPHeader(ByteBuffer buffer) { 432 | this.sourcePort = BitUtils.getUnsignedShort(buffer.getShort()); 433 | this.destinationPort = BitUtils.getUnsignedShort(buffer.getShort()); 434 | 435 | this.length = BitUtils.getUnsignedShort(buffer.getShort()); 436 | this.checksum = BitUtils.getUnsignedShort(buffer.getShort()); 437 | } 438 | 439 | private void fillHeader(ByteBuffer buffer) { 440 | buffer.putShort((short) this.sourcePort); 441 | buffer.putShort((short) this.destinationPort); 442 | 443 | buffer.putShort((short) this.length); 444 | buffer.putShort((short) this.checksum); 445 | } 446 | 447 | @Override 448 | public String toString() { 449 | final StringBuilder sb = new StringBuilder("UDPHeader{"); 450 | sb.append("sourcePort=").append(sourcePort); 451 | sb.append(", destinationPort=").append(destinationPort); 452 | sb.append(", length=").append(length); 453 | sb.append(", checksum=").append(checksum); 454 | sb.append('}'); 455 | return sb.toString(); 456 | } 457 | } 458 | 459 | private static class BitUtils { 460 | private static short getUnsignedByte(byte value) { 461 | return (short) (value & 0xFF); 462 | } 463 | 464 | private static int getUnsignedShort(short value) { 465 | return value & 0xFFFF; 466 | } 467 | 468 | private static long getUnsignedInt(int value) { 469 | return value & 0xFFFFFFFFL; 470 | } 471 | } 472 | } 473 | 474 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/protocol/tcpip/TCBStatus.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.protocol.tcpip; 2 | 3 | public enum TCBStatus { 4 | SYN_SENT, 5 | SYN_RECEIVED, 6 | ESTABLISHED, 7 | CLOSE_WAIT, 8 | LAST_ACK, 9 | //new 10 | CLOSED, 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/util/ByteBufferPool.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.util; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | public class ByteBufferPool { 6 | private static final int BUFFER_SIZE = 16384; // XXX: Is this ideal? 7 | 8 | public static ByteBuffer acquire() { 9 | return ByteBuffer.allocate(BUFFER_SIZE); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/util/ObjAttrUtil.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class ObjAttrUtil { 7 | private Map> objAttrs = new HashMap<>(); 8 | 9 | public synchronized Object getAttr(Object obj, String k) { 10 | Map map = objAttrs.get(obj); 11 | if (map == null) { 12 | return null; 13 | } 14 | return map.get(k); 15 | } 16 | 17 | public synchronized void setAttr(Object obj, String k, Object value) { 18 | Map map = objAttrs.get(obj); 19 | if (map == null) { 20 | objAttrs.put(obj, new HashMap()); 21 | map = objAttrs.get(obj); 22 | } 23 | map.put(k, value); 24 | } 25 | public synchronized void delAttr(Object obj, String k, Object value) { 26 | Map map = objAttrs.get(obj); 27 | if (map == null) { 28 | objAttrs.put(obj, new HashMap()); 29 | map = objAttrs.get(obj); 30 | } 31 | map.remove(k); 32 | } 33 | 34 | public synchronized void delObj(Object obj) { 35 | objAttrs.remove(obj); 36 | } 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/mocyx/basic_client/util/ProxyException.java: -------------------------------------------------------------------------------- 1 | package com.mocyx.basic_client.util; 2 | 3 | /** 4 | * @author Administrator 5 | */ 6 | public class ProxyException extends RuntimeException { 7 | public ProxyException(String msg) { 8 | super(msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 |