├── .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 |
6 |
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 |
8 |
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