├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── vm
│ │ └── shadowsocks
│ │ ├── core
│ │ ├── AppInfo.java
│ │ ├── AppProxyManager.java
│ │ ├── ChinaIpMaskManager.java
│ │ ├── DnsProxy.java
│ │ ├── HttpHostHeaderParser.java
│ │ ├── LocalVpnService.java
│ │ ├── NatSession.java
│ │ ├── NatSessionManager.java
│ │ ├── ProxyConfig.java
│ │ ├── TcpProxyServer.java
│ │ └── TunnelFactory.java
│ │ ├── dns
│ │ ├── DnsFlags.java
│ │ ├── DnsHeader.java
│ │ ├── DnsPacket.java
│ │ ├── Question.java
│ │ ├── Resource.java
│ │ └── ResourcePointer.java
│ │ ├── tcpip
│ │ ├── CommonMethods.java
│ │ ├── IPHeader.java
│ │ ├── TCPHeader.java
│ │ └── UDPHeader.java
│ │ ├── tunnel
│ │ ├── Config.java
│ │ ├── RawTunnel.java
│ │ ├── Tunnel.java
│ │ ├── httpconnect
│ │ │ ├── HttpConnectConfig.java
│ │ │ └── HttpConnectTunnel.java
│ │ └── shadowsocks
│ │ │ ├── AesCrypt.java
│ │ │ ├── BlowFishCrypt.java
│ │ │ ├── CamelliaCrypt.java
│ │ │ ├── Chacha20Crypt.java
│ │ │ ├── CryptBase.java
│ │ │ ├── CryptFactory.java
│ │ │ ├── ICrypt.java
│ │ │ ├── Rc4Md5Crypt.java
│ │ │ ├── SeedCrypt.java
│ │ │ ├── ShadowSocksKey.java
│ │ │ ├── ShadowsocksConfig.java
│ │ │ └── ShadowsocksTunnel.java
│ │ └── ui
│ │ ├── AppManager.java
│ │ └── MainActivity.java
│ └── res
│ ├── drawable-xxhdpi
│ └── shadowsocks.png
│ ├── layout
│ ├── activity_main.xml
│ ├── layout_apps.xml
│ └── layout_apps_item.xml
│ ├── menu
│ └── main_activity_actions.xml
│ ├── raw
│ ├── config
│ └── ipmask
│ ├── values-sw600dp
│ └── dimens.xml
│ ├── values-sw720dp-land
│ └── dimens.xml
│ ├── values-v11
│ └── styles.xml
│ ├── values-v14
│ └── styles.xml
│ ├── values-v21
│ └── styles.xml
│ ├── values-zh-rCN
│ └── strings.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 |
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 |
24 | # Proguard folder generated by Eclipse
25 | proguard/
26 |
27 | # Log Files
28 | *.log
29 |
30 | # Android Studio Navigation editor temp files
31 | .navigation/
32 |
33 | # Android Studio captures folder
34 | captures/
35 |
36 | # Intellij
37 | *.iml
38 | .idea/workspace.xml
39 |
40 | # Keystore files
41 | *.jks
42 |
43 |
44 |
45 |
46 | ######### MAC
47 | *.DS_Store
48 | .AppleDouble
49 | .LSOverride
50 |
51 | # Icon must end with two \r
52 | Icon
53 |
54 |
55 | # Thumbnails
56 | ._*
57 |
58 | # Files that might appear in the root of a volume
59 | .DocumentRevisions-V100
60 | .fseventsd
61 | .Spotlight-V100
62 | .TemporaryItems
63 | .Trashes
64 | .VolumeIcon.icns
65 | .com.apple.timemachine.donotpresent
66 |
67 | # Directories potentially created on remote AFP share
68 | .AppleDB
69 | .AppleDesktop
70 | Network Trash Folder
71 | Temporary Items
72 | .apdisk
73 |
74 |
75 | ######### Windows
76 | # Windows image file caches
77 | Thumbs.db
78 | ehthumbs.db
79 |
80 | # Folder config file
81 | Desktop.ini
82 |
83 | # Recycle Bin used on file shares
84 | $RECYCLE.BIN/
85 |
86 | # Windows Installer files
87 | *.cab
88 | *.msi
89 | *.msm
90 | *.msp
91 |
92 | # Windows shortcuts
93 | *.lnk
94 |
95 |
96 | ######### linux
97 | *~
98 |
99 | # temporary files which can be created if a process still has a handle open of a deleted file
100 | .fuse_hidden*
101 |
102 | # KDE directory preferences
103 | .directory
104 |
105 | # Linux trash folder which might appear on any partition or disk
106 | .Trash-*
107 |
108 | .idea/
109 |
--------------------------------------------------------------------------------
/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 | # shadowsocks-android-java
2 |
3 | Last release: [Download](https://github.com/dawei101/shadowsocks-android-java/releases)
4 |
5 |
6 | This version of shadowsocks for android is pure java version.
7 | Beacuse of the way of implementation, some feature would not work (icmp protocol, and correct dns result under command line)
8 |
9 | Most code is merged from [smartproxy](https://github.com/hedaode/SmartProxy) and [shadowsocks-java](https://github.com/blakey22/shadowsocks-java)
10 | This app inherit Smartproxy's feature: tiny/low power cost/simple operation
11 |
12 |
13 | shadowsocks settings format
14 |
15 | ```
16 | ss://method:password@host:port
17 | ss://base64encode(method:password@host:port)
18 | ```
19 |
20 | And also it inherited the support of http proxy from Smartproxy , Set the url as stardand http(s) proxy format when use it.
21 |
22 | http proxy foramt:
23 |
24 | ```
25 | http://(username:passsword)@host:port
26 | ```
27 | Support methods of encryption:
28 |
29 | ```
30 | bf-cfb
31 | seed-cfb
32 | aes-128-cfb
33 | aes-192-cfb
34 | aes-256-cfb
35 | aes-128-ofb
36 | aes-192-ofb
37 | aes-256-ofb
38 | camellia-128-cfb
39 | camellia-192-cfb
40 | camellia-256-cfb
41 | chacha20
42 | chacha20-ietf
43 | rc4-md5
44 | ```
45 |
46 | #### Brother version
47 |
48 | [Shadowsocks android(Scala)](https://github.com/shadowsocks/shadowsocks-android)
49 |
50 | Scala version is high threshold to lots of developer, so it's a better choice to choose this version.
51 |
52 | -----------
53 |
54 | ### 关于 About
55 |
56 | 本版本为shadowsocks android版的纯java版本
57 | 因为实现原理的缘故,会牺牲掉一些功能(主要是用到icmp协议,以及在命令行下的dns解析的正确地址).
58 |
59 | 代码多整理自 [smartproxy](https://github.com/hedaode/SmartProxy) 和 [shadowsocks-java](https://github.com/blakey22/shadowsocks-java)
60 | 本shadowsocks-android的特点继承了SmartProxy的优点: 体积小,耗电低,设置保持最简单的方式
61 | shadowsocks设置格式:
62 | ```
63 | ss://method:password@host:port
64 | ss://base64encode(method:password@host:port)
65 | ```
66 |
67 | 其中代码保留了SmartProxy对http代理的支持, 使用时将配置链接填写标准http代理格式即可.
68 | http代理格式
69 |
70 | ```
71 | http://(username:passsword)@host:port
72 | ```
73 | 支持的加密类型:
74 |
75 | ```
76 | bf-cfb
77 | seed-cfb
78 | aes-128-cfb
79 | aes-192-cfb
80 | aes-256-cfb
81 | aes-128-ofb
82 | aes-192-ofb
83 | aes-256-ofb
84 | camellia-128-cfb
85 | camellia-192-cfb
86 | camellia-256-cfb
87 | chacha20
88 | chacha20-ietf
89 | rc4-md5
90 | ```
91 |
92 | ### 兄弟版本
93 | Brother version
94 |
95 | ##### [Shadowsocks android(Scala)](https://github.com/shadowsocks/shadowsocks-android)
96 |
97 | ### 作者同系列版本
98 | [shadowsocks 桌面版,一份代码完美支持windows,mac osx,linux](https://github.com/dawei101/tongsheClient.shadowsocks-go)
99 |
100 |
101 | #### LICENSE
102 |
103 | [Apache License](./LICENSE)
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 |
4 | buildscript {
5 | repositories {
6 | mavenCentral()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:2.3.3'
10 | }
11 | }
12 |
13 | android {
14 | compileSdkVersion 26
15 | buildToolsVersion '26.0.1'
16 | useLibrary 'org.apache.http.legacy'
17 |
18 | defaultConfig {
19 | applicationId "com.vm.shadowsocks"
20 | minSdkVersion 15
21 | targetSdkVersion 26
22 | versionCode 1
23 | versionName "1.2"
24 | }
25 | buildTypes {
26 | release {
27 | minifyEnabled true
28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 | }
32 |
33 | dependencies {
34 | compile fileTree(include: ['*.jar'], dir: 'libs')
35 |
36 | testCompile 'junit:junit:4.12'
37 |
38 | compile 'com.journeyapps:zxing-android-embedded:3.5.0'
39 | compile 'com.google.zxing:core:3.0.1'
40 |
41 | compile 'org.bouncycastle:bcprov-jdk15on:1.57'
42 | compile 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5'
43 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
44 | }
45 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Development/adt-bundle/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -keep class com.journeyapps.barcodescanner.** { *; }
19 | -dontwarn org.bouncycastle.**
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/AppInfo.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | import android.graphics.drawable.Drawable;
4 |
5 | public class AppInfo {
6 | private Drawable appIcon;
7 | private String appLabel;
8 | private String pkgName;
9 |
10 | public AppInfo() {
11 | }
12 |
13 | public Drawable getAppIcon() {
14 | return this.appIcon;
15 | }
16 |
17 | public String getAppLabel() {
18 | return this.appLabel;
19 | }
20 |
21 | public String getPkgName() {
22 | return this.pkgName;
23 | }
24 |
25 | public void setAppIcon(Drawable var1) {
26 | this.appIcon = var1;
27 | }
28 |
29 | public void setAppLabel(String var1) {
30 | this.appLabel = var1;
31 | }
32 |
33 | public void setPkgName(String var1) {
34 | this.pkgName = var1;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/AppProxyManager.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.graphics.drawable.Drawable;
6 | import android.os.Build;
7 |
8 | import org.json.JSONArray;
9 | import org.json.JSONObject;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | import static android.content.Context.MODE_PRIVATE;
15 |
16 | public class AppProxyManager {
17 | public static boolean isLollipopOrAbove = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
18 |
19 | public static AppProxyManager Instance;
20 | private static final String PROXY_APPS = "PROXY_APPS";
21 | private Context mContext;
22 |
23 | public List mlistAppInfo = new ArrayList();
24 | public List proxyAppInfo = new ArrayList();
25 |
26 | public AppProxyManager(Context context){
27 | this.mContext = context;
28 | Instance = this;
29 | readProxyAppsList();
30 | }
31 |
32 | public void removeProxyApp(String pkg){
33 | for (AppInfo app : this.proxyAppInfo) {
34 | if (app.getPkgName().equals(pkg)){
35 | proxyAppInfo.remove(app);
36 | break;
37 | }
38 | }
39 | writeProxyAppsList();
40 | }
41 |
42 | public void addProxyApp(String pkg){
43 | for (AppInfo app : this.mlistAppInfo) {
44 | if (app.getPkgName().equals(pkg)){
45 | proxyAppInfo.add(app);
46 | break;
47 | }
48 | }
49 | writeProxyAppsList();
50 | }
51 |
52 | public boolean isAppProxy(String pkg){
53 | for (AppInfo app : this.proxyAppInfo) {
54 | if (app.getPkgName().equals(pkg)){
55 | return true;
56 | }
57 | }
58 | return false;
59 | }
60 |
61 | private void readProxyAppsList() {
62 | SharedPreferences preferences = mContext.getSharedPreferences("shadowsocksProxyUrl", MODE_PRIVATE);
63 | String tmpString = preferences.getString(PROXY_APPS, "");
64 | try {
65 | if (proxyAppInfo != null){
66 | proxyAppInfo.clear();
67 | }
68 | if (tmpString.isEmpty()){
69 | return;
70 | }
71 | JSONArray jsonArray = new JSONArray(tmpString);
72 | for (int i = 0; i < jsonArray.length() ; i++){
73 | JSONObject object = jsonArray.getJSONObject(i);
74 | AppInfo appInfo = new AppInfo();
75 | appInfo.setAppLabel(object.getString("label"));
76 | appInfo.setPkgName(object.getString("pkg"));
77 | proxyAppInfo.add(appInfo);
78 | }
79 | } catch (Exception e){
80 | e.printStackTrace();
81 | }
82 | }
83 |
84 | private void writeProxyAppsList() {
85 | SharedPreferences preferences = mContext.getSharedPreferences("shadowsocksProxyUrl", MODE_PRIVATE);
86 | try {
87 | JSONArray jsonArray = new JSONArray();
88 | for (int i = 0; i < proxyAppInfo.size() ; i++){
89 | JSONObject object = new JSONObject();
90 | AppInfo appInfo = proxyAppInfo.get(i);
91 | object.put("label", appInfo.getAppLabel());
92 | object.put("pkg", appInfo.getPkgName());
93 | jsonArray.put(object);
94 | }
95 | SharedPreferences.Editor editor = preferences.edit();
96 | editor.putString(PROXY_APPS, jsonArray.toString());
97 | editor.apply();
98 | } catch (Exception e){
99 | e.printStackTrace();
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/ChinaIpMaskManager.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | import android.util.SparseIntArray;
4 |
5 | import com.vm.shadowsocks.tcpip.CommonMethods;
6 |
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 |
10 |
11 | public class ChinaIpMaskManager {
12 |
13 | static SparseIntArray ChinaIpMaskDict = new SparseIntArray(3000);
14 | static SparseIntArray MaskDict = new SparseIntArray();
15 |
16 | public static boolean isIPInChina(int ip) {
17 | boolean found = false;
18 | for (int i = 0; i < MaskDict.size(); i++) {
19 | int mask = MaskDict.keyAt(i);
20 | int networkIP = ip & mask;
21 | int mask2 = ChinaIpMaskDict.get(networkIP);
22 | if (mask2 == mask) {
23 | found = true;
24 | break;
25 | }
26 | }
27 | return found;
28 | }
29 |
30 | public static void loadFromFile(InputStream inputStream) {
31 | int count = 0;
32 | try {
33 | byte[] buffer = new byte[4096];
34 | while ((count = inputStream.read(buffer)) > 0) {
35 | for (int i = 0; i < count; i += 8) {
36 | int ip = CommonMethods.readInt(buffer, i);
37 | int mask = CommonMethods.readInt(buffer, i + 4);
38 | ChinaIpMaskDict.put(ip, mask);
39 | MaskDict.put(mask, mask);
40 | //System.out.printf("%s/%s\n", CommonMethods.IP2String(ip),CommonMethods.IP2String(mask));
41 | }
42 | }
43 | inputStream.close();
44 | System.out.printf("ChinaIpMask records count: %d\n", ChinaIpMaskDict.size());
45 | } catch (IOException e) {
46 | // TODO Auto-generated catch block
47 | e.printStackTrace();
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/DnsProxy.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | import android.util.SparseArray;
4 |
5 | import com.vm.shadowsocks.dns.DnsPacket;
6 | import com.vm.shadowsocks.dns.Question;
7 | import com.vm.shadowsocks.dns.Resource;
8 | import com.vm.shadowsocks.dns.ResourcePointer;
9 | import com.vm.shadowsocks.tcpip.CommonMethods;
10 | import com.vm.shadowsocks.tcpip.IPHeader;
11 | import com.vm.shadowsocks.tcpip.UDPHeader;
12 |
13 | import java.io.IOException;
14 | import java.net.DatagramPacket;
15 | import java.net.DatagramSocket;
16 | import java.net.InetSocketAddress;
17 | import java.nio.ByteBuffer;
18 | import java.util.concurrent.ConcurrentHashMap;
19 |
20 |
21 | public class DnsProxy implements Runnable {
22 |
23 | private class QueryState {
24 | public short ClientQueryID;
25 | public long QueryNanoTime;
26 | public int ClientIP;
27 | public short ClientPort;
28 | public int RemoteIP;
29 | public short RemotePort;
30 | }
31 |
32 | public boolean Stopped;
33 | private static final ConcurrentHashMap IPDomainMaps = new ConcurrentHashMap();
34 | private static final ConcurrentHashMap DomainIPMaps = new ConcurrentHashMap();
35 | private final long QUERY_TIMEOUT_NS = 10 * 1000000000L;
36 | private DatagramSocket m_Client;
37 | private Thread m_ReceivedThread;
38 | private short m_QueryID;
39 | private SparseArray m_QueryArray;
40 |
41 | public DnsProxy() throws IOException {
42 | m_QueryArray = new SparseArray();
43 | m_Client = new DatagramSocket(0);
44 | }
45 |
46 | public static String reverseLookup(int ip) {
47 | return IPDomainMaps.get(ip);
48 | }
49 |
50 | public void start() {
51 | m_ReceivedThread = new Thread(this);
52 | m_ReceivedThread.setName("DnsProxyThread");
53 | m_ReceivedThread.start();
54 | }
55 |
56 | public void stop() {
57 | Stopped = true;
58 | if (m_Client != null) {
59 | m_Client.close();
60 | m_Client = null;
61 | }
62 | }
63 |
64 | @Override
65 | public void run() {
66 | try {
67 | byte[] RECEIVE_BUFFER = new byte[2000];
68 | IPHeader ipHeader = new IPHeader(RECEIVE_BUFFER, 0);
69 | ipHeader.Default();
70 | UDPHeader udpHeader = new UDPHeader(RECEIVE_BUFFER, 20);
71 |
72 | ByteBuffer dnsBuffer = ByteBuffer.wrap(RECEIVE_BUFFER);
73 | dnsBuffer.position(28);
74 | dnsBuffer = dnsBuffer.slice();
75 |
76 | DatagramPacket packet = new DatagramPacket(RECEIVE_BUFFER, 28, RECEIVE_BUFFER.length - 28);
77 |
78 | while (m_Client != null && !m_Client.isClosed()) {
79 |
80 | packet.setLength(RECEIVE_BUFFER.length - 28);
81 | m_Client.receive(packet);
82 |
83 | dnsBuffer.clear();
84 | dnsBuffer.limit(packet.getLength());
85 | try {
86 | DnsPacket dnsPacket = DnsPacket.FromBytes(dnsBuffer);
87 | if (dnsPacket != null) {
88 | OnDnsResponseReceived(ipHeader, udpHeader, dnsPacket);
89 | }
90 | } catch (Exception e) {
91 | e.printStackTrace();
92 | LocalVpnService.Instance.writeLog("Parse dns error: %s", e);
93 | }
94 | }
95 | } catch (Exception e) {
96 | e.printStackTrace();
97 | } finally {
98 | System.out.println("DnsResolver Thread Exited.");
99 | this.stop();
100 | }
101 | }
102 |
103 | private int getFirstIP(DnsPacket dnsPacket) {
104 | for (int i = 0; i < dnsPacket.Header.ResourceCount; i++) {
105 | Resource resource = dnsPacket.Resources[i];
106 | if (resource.Type == 1) {
107 | int ip = CommonMethods.readInt(resource.Data, 0);
108 | return ip;
109 | }
110 | }
111 | return 0;
112 | }
113 |
114 | private void tamperDnsResponse(byte[] rawPacket, DnsPacket dnsPacket, int newIP) {
115 | Question question = dnsPacket.Questions[0];
116 |
117 | dnsPacket.Header.setResourceCount((short) 1);
118 | dnsPacket.Header.setAResourceCount((short) 0);
119 | dnsPacket.Header.setEResourceCount((short) 0);
120 |
121 | ResourcePointer rPointer = new ResourcePointer(rawPacket, question.Offset() + question.Length());
122 | rPointer.setDomain((short) 0xC00C);
123 | rPointer.setType(question.Type);
124 | rPointer.setClass(question.Class);
125 | rPointer.setTTL(ProxyConfig.Instance.getDnsTTL());
126 | rPointer.setDataLength((short) 4);
127 | rPointer.setIP(newIP);
128 |
129 | dnsPacket.Size = 12 + question.Length() + 16;
130 | }
131 |
132 | private int getOrCreateFakeIP(String domainString) {
133 | Integer fakeIP = DomainIPMaps.get(domainString);
134 | if (fakeIP == null) {
135 | int hashIP = domainString.hashCode();
136 | do {
137 | fakeIP = ProxyConfig.FAKE_NETWORK_IP | (hashIP & 0x0000FFFF);
138 | hashIP++;
139 | } while (IPDomainMaps.containsKey(fakeIP));
140 |
141 | DomainIPMaps.put(domainString, fakeIP);
142 | IPDomainMaps.put(fakeIP, domainString);
143 | }
144 | return fakeIP;
145 | }
146 |
147 | private boolean dnsPollution(byte[] rawPacket, DnsPacket dnsPacket) {
148 | if (dnsPacket.Header.QuestionCount > 0) {
149 | Question question = dnsPacket.Questions[0];
150 | if (question.Type == 1) {
151 | int realIP = getFirstIP(dnsPacket);
152 | if (ProxyConfig.Instance.needProxy(question.Domain, realIP)) {
153 | int fakeIP = getOrCreateFakeIP(question.Domain);
154 | tamperDnsResponse(rawPacket, dnsPacket, fakeIP);
155 | if (ProxyConfig.IS_DEBUG)
156 | System.out.printf("FakeDns: %s=>%s(%s)\n", question.Domain, CommonMethods.ipIntToString(realIP), CommonMethods.ipIntToString(fakeIP));
157 | return true;
158 | }
159 | }
160 | }
161 | return false;
162 | }
163 |
164 | private void OnDnsResponseReceived(IPHeader ipHeader, UDPHeader udpHeader, DnsPacket dnsPacket) {
165 | QueryState state = null;
166 | synchronized (m_QueryArray) {
167 | state = m_QueryArray.get(dnsPacket.Header.ID);
168 | if (state != null) {
169 | m_QueryArray.remove(dnsPacket.Header.ID);
170 | }
171 | }
172 |
173 | if (state != null) {
174 | //DNS污染,默认污染海外网站
175 | dnsPollution(udpHeader.m_Data, dnsPacket);
176 |
177 | dnsPacket.Header.setID(state.ClientQueryID);
178 | ipHeader.setSourceIP(state.RemoteIP);
179 | ipHeader.setDestinationIP(state.ClientIP);
180 | ipHeader.setProtocol(IPHeader.UDP);
181 | ipHeader.setTotalLength(20 + 8 + dnsPacket.Size);
182 | udpHeader.setSourcePort(state.RemotePort);
183 | udpHeader.setDestinationPort(state.ClientPort);
184 | udpHeader.setTotalLength(8 + dnsPacket.Size);
185 |
186 | LocalVpnService.Instance.sendUDPPacket(ipHeader, udpHeader);
187 | }
188 | }
189 |
190 | private int getIPFromCache(String domain) {
191 | Integer ip = DomainIPMaps.get(domain);
192 | if (ip == null) {
193 | return 0;
194 | } else {
195 | return ip;
196 | }
197 | }
198 |
199 | private boolean interceptDns(IPHeader ipHeader, UDPHeader udpHeader, DnsPacket dnsPacket) {
200 | Question question = dnsPacket.Questions[0];
201 | System.out.println("DNS Qeury " + question.Domain);
202 | if (question.Type == 1) {
203 | if (ProxyConfig.Instance.needProxy(question.Domain, getIPFromCache(question.Domain))) {
204 | int fakeIP = getOrCreateFakeIP(question.Domain);
205 | tamperDnsResponse(ipHeader.m_Data, dnsPacket, fakeIP);
206 |
207 | if (ProxyConfig.IS_DEBUG)
208 | System.out.printf("interceptDns FakeDns: %s=>%s\n", question.Domain, CommonMethods.ipIntToString(fakeIP));
209 |
210 | int sourceIP = ipHeader.getSourceIP();
211 | short sourcePort = udpHeader.getSourcePort();
212 | ipHeader.setSourceIP(ipHeader.getDestinationIP());
213 | ipHeader.setDestinationIP(sourceIP);
214 | ipHeader.setTotalLength(20 + 8 + dnsPacket.Size);
215 | udpHeader.setSourcePort(udpHeader.getDestinationPort());
216 | udpHeader.setDestinationPort(sourcePort);
217 | udpHeader.setTotalLength(8 + dnsPacket.Size);
218 | LocalVpnService.Instance.sendUDPPacket(ipHeader, udpHeader);
219 | return true;
220 | }
221 | }
222 | return false;
223 | }
224 |
225 | private void clearExpiredQueries() {
226 | long now = System.nanoTime();
227 | for (int i = m_QueryArray.size() - 1; i >= 0; i--) {
228 | QueryState state = m_QueryArray.valueAt(i);
229 | if ((now - state.QueryNanoTime) > QUERY_TIMEOUT_NS) {
230 | m_QueryArray.removeAt(i);
231 | }
232 | }
233 | }
234 |
235 | public void onDnsRequestReceived(IPHeader ipHeader, UDPHeader udpHeader, DnsPacket dnsPacket) {
236 | if (!interceptDns(ipHeader, udpHeader, dnsPacket)) {
237 | //转发DNS
238 | QueryState state = new QueryState();
239 | state.ClientQueryID = dnsPacket.Header.ID;
240 | state.QueryNanoTime = System.nanoTime();
241 | state.ClientIP = ipHeader.getSourceIP();
242 | state.ClientPort = udpHeader.getSourcePort();
243 | state.RemoteIP = ipHeader.getDestinationIP();
244 | state.RemotePort = udpHeader.getDestinationPort();
245 |
246 | // 转换QueryID
247 | m_QueryID++;// 增加ID
248 | dnsPacket.Header.setID(m_QueryID);
249 |
250 | synchronized (m_QueryArray) {
251 | clearExpiredQueries();//清空过期的查询,减少内存开销。
252 | m_QueryArray.put(m_QueryID, state);// 关联数据
253 | }
254 |
255 | InetSocketAddress remoteAddress = new InetSocketAddress(CommonMethods.ipIntToInet4Address(state.RemoteIP), state.RemotePort);
256 | DatagramPacket packet = new DatagramPacket(udpHeader.m_Data, udpHeader.m_Offset + 8, dnsPacket.Size);
257 | packet.setSocketAddress(remoteAddress);
258 |
259 | try {
260 | if (LocalVpnService.Instance.protect(m_Client)) {
261 | m_Client.send(packet);
262 | } else {
263 | System.err.println("VPN protect udp socket failed.");
264 | }
265 | } catch (IOException e) {
266 | // TODO Auto-generated catch block
267 | e.printStackTrace();
268 | }
269 | }
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/HttpHostHeaderParser.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | import com.vm.shadowsocks.tcpip.CommonMethods;
4 |
5 | import java.util.Locale;
6 |
7 |
8 | public class HttpHostHeaderParser {
9 |
10 | public static String parseHost(byte[] buffer, int offset, int count) {
11 | try {
12 | switch (buffer[offset]) {
13 | case 'G'://GET
14 | case 'H'://HEAD
15 | case 'P'://POST,PUT
16 | case 'D'://DELETE
17 | case 'O'://OPTIONS
18 | case 'T'://TRACE
19 | case 'C'://CONNECT
20 | return getHttpHost(buffer, offset, count);
21 | case 0x16://SSL
22 | return getSNI(buffer, offset, count);
23 | }
24 | } catch (Exception e) {
25 | e.printStackTrace();
26 | LocalVpnService.Instance.writeLog("Error: parseHost:%s", e);
27 | }
28 | return null;
29 | }
30 |
31 | static String getHttpHost(byte[] buffer, int offset, int count) {
32 | String headerString = new String(buffer, offset, count);
33 | String[] headerLines = headerString.split("\\r\\n");
34 | String requestLine = headerLines[0];
35 | if (requestLine.startsWith("GET") || requestLine.startsWith("POST") || requestLine.startsWith("HEAD") || requestLine.startsWith("OPTIONS")) {
36 | for (int i = 1; i < headerLines.length; i++) {
37 | String[] nameValueStrings = headerLines[i].split(":");
38 | if (nameValueStrings.length == 2) {
39 | String name = nameValueStrings[0].toLowerCase(Locale.ENGLISH).trim();
40 | String value = nameValueStrings[1].trim();
41 | if ("host".equals(name)) {
42 | return value;
43 | }
44 | }
45 | }
46 | }
47 | return null;
48 | }
49 |
50 | static String getSNI(byte[] buffer, int offset, int count) {
51 | int limit = offset + count;
52 | if (count > 43 && buffer[offset] == 0x16) {//TLS Client Hello
53 | offset += 43;//skip 43 bytes header
54 |
55 | //read sessionID:
56 | if (offset + 1 > limit) return null;
57 | int sessionIDLength = buffer[offset++] & 0xFF;
58 | offset += sessionIDLength;
59 |
60 | //read cipher suites:
61 | if (offset + 2 > limit) return null;
62 | int cipherSuitesLength = CommonMethods.readShort(buffer, offset) & 0xFFFF;
63 | offset += 2;
64 | offset += cipherSuitesLength;
65 |
66 | //read Compression method:
67 | if (offset + 1 > limit) return null;
68 | int compressionMethodLength = buffer[offset++] & 0xFF;
69 | offset += compressionMethodLength;
70 |
71 | if (offset == limit) {
72 | System.err.println("TLS Client Hello packet doesn't contains SNI info.(offset == limit)");
73 | return null;
74 | }
75 |
76 | //read Extensions:
77 | if (offset + 2 > limit) return null;
78 | int extensionsLength = CommonMethods.readShort(buffer, offset) & 0xFFFF;
79 | offset += 2;
80 |
81 | if (offset + extensionsLength > limit) {
82 | System.err.println("TLS Client Hello packet is incomplete.");
83 | return null;
84 | }
85 |
86 | while (offset + 4 <= limit) {
87 | int type0 = buffer[offset++] & 0xFF;
88 | int type1 = buffer[offset++] & 0xFF;
89 | int length = CommonMethods.readShort(buffer, offset) & 0xFFFF;
90 | offset += 2;
91 |
92 | if (type0 == 0x00 && type1 == 0x00 && length > 5) { //have SNI
93 | offset += 5;//skip SNI header.
94 | length -= 5;//SNI size;
95 | if (offset + length > limit) return null;
96 | String serverName = new String(buffer, offset, length);
97 | if (ProxyConfig.IS_DEBUG)
98 | System.out.printf("SNI: %s\n", serverName);
99 | return serverName;
100 | } else {
101 | offset += length;
102 | }
103 | }
104 |
105 | System.err.println("TLS Client Hello packet doesn't contains Host field info.");
106 | return null;
107 | } else {
108 | System.err.println("Bad TLS Client Hello packet.");
109 | return null;
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/NatSession.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | public class NatSession {
4 | public int RemoteIP;
5 | public short RemotePort;
6 | public String RemoteHost;
7 | public int BytesSent;
8 | public int PacketSent;
9 | public long LastNanoTime;
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/NatSessionManager.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | import android.util.SparseArray;
4 |
5 | import com.vm.shadowsocks.tcpip.CommonMethods;
6 |
7 | public class NatSessionManager {
8 |
9 | static final int MAX_SESSION_COUNT = 60;
10 | static final long SESSION_TIMEOUT_NS = 60 * 1000000000L;
11 | static final SparseArray Sessions = new SparseArray();
12 |
13 | public static NatSession getSession(int portKey) {
14 | NatSession session = Sessions.get(portKey);
15 | if (session!=null) {
16 | session.LastNanoTime = System.nanoTime();
17 | }
18 | return Sessions.get(portKey);
19 | }
20 |
21 | public static int getSessionCount() {
22 | return Sessions.size();
23 | }
24 |
25 | static void clearExpiredSessions() {
26 | long now = System.nanoTime();
27 | for (int i = Sessions.size() - 1; i >= 0; i--) {
28 | NatSession session = Sessions.valueAt(i);
29 | if (now - session.LastNanoTime > SESSION_TIMEOUT_NS) {
30 | Sessions.removeAt(i);
31 | }
32 | }
33 | }
34 |
35 | public static NatSession createSession(int portKey, int remoteIP, short remotePort) {
36 | if (Sessions.size() > MAX_SESSION_COUNT) {
37 | clearExpiredSessions();//清理过期的会话。
38 | }
39 |
40 | NatSession session = new NatSession();
41 | session.LastNanoTime = System.nanoTime();
42 | session.RemoteIP = remoteIP;
43 | session.RemotePort = remotePort;
44 |
45 | if (ProxyConfig.isFakeIP(remoteIP)) {
46 | session.RemoteHost = DnsProxy.reverseLookup(remoteIP);
47 | }
48 |
49 | if (session.RemoteHost == null) {
50 | session.RemoteHost = CommonMethods.ipIntToString(remoteIP);
51 | }
52 | Sessions.put(portKey, session);
53 | return session;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/ProxyConfig.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Build;
5 |
6 | import com.vm.shadowsocks.tcpip.CommonMethods;
7 | import com.vm.shadowsocks.tunnel.Config;
8 | import com.vm.shadowsocks.tunnel.httpconnect.HttpConnectConfig;
9 | import com.vm.shadowsocks.tunnel.shadowsocks.ShadowsocksConfig;
10 |
11 | import org.apache.http.HttpResponse;
12 | import org.apache.http.client.HttpClient;
13 | import org.apache.http.client.methods.HttpGet;
14 | import org.apache.http.impl.client.DefaultHttpClient;
15 | import org.apache.http.util.EntityUtils;
16 |
17 | import java.io.FileInputStream;
18 | import java.io.InputStream;
19 | import java.net.InetAddress;
20 | import java.net.InetSocketAddress;
21 | import java.util.ArrayList;
22 | import java.util.HashMap;
23 | import java.util.Locale;
24 | import java.util.Timer;
25 | import java.util.TimerTask;
26 | import java.util.regex.Matcher;
27 | import java.util.regex.Pattern;
28 |
29 |
30 | public class ProxyConfig {
31 | public static final ProxyConfig Instance = new ProxyConfig();
32 | public final static boolean IS_DEBUG = false;
33 | public static String AppInstallID;
34 | public static String AppVersion;
35 | public final static int FAKE_NETWORK_MASK = CommonMethods.ipStringToInt("255.255.0.0");
36 | public final static int FAKE_NETWORK_IP = CommonMethods.ipStringToInt("172.25.0.0");
37 |
38 | ArrayList m_IpList;
39 | ArrayList m_DnsList;
40 | ArrayList m_RouteList;
41 | public ArrayList m_ProxyList;
42 | HashMap m_DomainMap;
43 |
44 | public boolean globalMode = false;
45 |
46 | int m_dns_ttl;
47 | String m_welcome_info;
48 | String m_session_name;
49 | String m_user_agent;
50 | boolean m_outside_china_use_proxy = true;
51 | boolean m_isolate_http_host_header = true;
52 | int m_mtu;
53 |
54 | Timer m_Timer;
55 |
56 | public class IPAddress {
57 | public final String Address;
58 | public final int PrefixLength;
59 |
60 | public IPAddress(String address, int prefixLength) {
61 | this.Address = address;
62 | this.PrefixLength = prefixLength;
63 | }
64 |
65 | public IPAddress(String ipAddresString) {
66 | String[] arrStrings = ipAddresString.split("/");
67 | String address = arrStrings[0];
68 | int prefixLength = 32;
69 | if (arrStrings.length > 1) {
70 | prefixLength = Integer.parseInt(arrStrings[1]);
71 | }
72 | this.Address = address;
73 | this.PrefixLength = prefixLength;
74 | }
75 |
76 | @SuppressLint("DefaultLocale")
77 | @Override
78 | public String toString() {
79 | return String.format("%s/%d", Address, PrefixLength);
80 | }
81 |
82 | @Override
83 | public boolean equals(Object o) {
84 | if (o == null) {
85 | return false;
86 | } else {
87 | return this.toString().equals(o.toString());
88 | }
89 | }
90 | }
91 |
92 | public ProxyConfig() {
93 | m_IpList = new ArrayList();
94 | m_DnsList = new ArrayList();
95 | m_RouteList = new ArrayList();
96 | m_ProxyList = new ArrayList();
97 | m_DomainMap = new HashMap();
98 |
99 | m_Timer = new Timer();
100 | m_Timer.schedule(m_Task, 120000, 120000);//每两分钟刷新一次。
101 | }
102 |
103 | TimerTask m_Task = new TimerTask() {
104 | @Override
105 | public void run() {
106 | refreshProxyServer();//定时更新dns缓存
107 | }
108 |
109 | //定时更新dns缓存
110 | void refreshProxyServer() {
111 | try {
112 | for (int i = 0; i < m_ProxyList.size(); i++) {
113 | try {
114 | Config config = m_ProxyList.get(0);
115 | InetAddress address = InetAddress.getByName(config.ServerAddress.getHostName());
116 | if (address != null && !address.equals(config.ServerAddress.getAddress())) {
117 | config.ServerAddress = new InetSocketAddress(address, config.ServerAddress.getPort());
118 | }
119 | } catch (Exception e) {
120 | }
121 | }
122 | } catch (Exception e) {
123 |
124 | }
125 | }
126 | };
127 |
128 |
129 | public static boolean isFakeIP(int ip) {
130 | return (ip & ProxyConfig.FAKE_NETWORK_MASK) == ProxyConfig.FAKE_NETWORK_IP;
131 | }
132 |
133 | public Config getDefaultProxy() {
134 | if (m_ProxyList.size() > 0) {
135 | return m_ProxyList.get(0);
136 | } else {
137 | return null;
138 | }
139 | }
140 |
141 | public Config getDefaultTunnelConfig(InetSocketAddress destAddress) {
142 | return getDefaultProxy();
143 | }
144 |
145 | public IPAddress getDefaultLocalIP() {
146 | if (m_IpList.size() > 0) {
147 | return m_IpList.get(0);
148 | } else {
149 | return new IPAddress("10.8.0.2", 32);
150 | }
151 | }
152 |
153 | public ArrayList getDnsList() {
154 | return m_DnsList;
155 | }
156 |
157 | public ArrayList getRouteList() {
158 | return m_RouteList;
159 | }
160 |
161 | public int getDnsTTL() {
162 | if (m_dns_ttl < 30) {
163 | m_dns_ttl = 30;
164 | }
165 | return m_dns_ttl;
166 | }
167 |
168 | public String getWelcomeInfo() {
169 | return m_welcome_info;
170 | }
171 |
172 | public String getSessionName() {
173 | if (m_session_name == null) {
174 | m_session_name = getDefaultProxy().ServerAddress.getHostName();
175 | }
176 | return m_session_name;
177 | }
178 |
179 | public String getUserAgent() {
180 | if (m_user_agent == null || m_user_agent.isEmpty()) {
181 | m_user_agent = System.getProperty("http.agent");
182 | }
183 | return m_user_agent;
184 | }
185 |
186 | public int getMTU() {
187 | if (m_mtu > 1400 && m_mtu <= 20000) {
188 | return m_mtu;
189 | } else {
190 | return 20000;
191 | }
192 | }
193 |
194 | private Boolean getDomainState(String domain) {
195 | domain = domain.toLowerCase();
196 | while (domain.length() > 0) {
197 | Boolean stateBoolean = m_DomainMap.get(domain);
198 | if (stateBoolean != null) {
199 | return stateBoolean;
200 | } else {
201 | int start = domain.indexOf('.') + 1;
202 | if (start > 0 && start < domain.length()) {
203 | domain = domain.substring(start);
204 | } else {
205 | return null;
206 | }
207 | }
208 | }
209 | return null;
210 | }
211 |
212 | public boolean needProxy(String host, int ip) {
213 | if (globalMode) {
214 | return true;
215 | }
216 | if (host != null) {
217 | Boolean stateBoolean = getDomainState(host);
218 | if (stateBoolean != null) {
219 | return stateBoolean.booleanValue();
220 | }
221 | }
222 |
223 | if (isFakeIP(ip))
224 | return true;
225 |
226 | if (m_outside_china_use_proxy && ip != 0) {
227 | return !ChinaIpMaskManager.isIPInChina(ip);
228 | }
229 | return false;
230 | }
231 |
232 | public boolean isIsolateHttpHostHeader() {
233 | return m_isolate_http_host_header;
234 | }
235 |
236 | private String[] downloadConfig(String url) throws Exception {
237 | try {
238 | HttpClient client = new DefaultHttpClient();
239 | HttpGet requestGet = new HttpGet(url);
240 |
241 | requestGet.addHeader("X-Android-MODEL", Build.MODEL);
242 | requestGet.addHeader("X-Android-SDK_INT", Integer.toString(Build.VERSION.SDK_INT));
243 | requestGet.addHeader("X-Android-RELEASE", Build.VERSION.RELEASE);
244 | requestGet.addHeader("X-App-Version", AppVersion);
245 | requestGet.addHeader("X-App-Install-ID", AppInstallID);
246 | requestGet.setHeader("User-Agent", System.getProperty("http.agent"));
247 | HttpResponse response = client.execute(requestGet);
248 |
249 | String configString = EntityUtils.toString(response.getEntity(), "UTF-8");
250 | String[] lines = configString.split("\\n");
251 | return lines;
252 | } catch (Exception e) {
253 | throw new Exception(String.format("Download config file from %s failed.", url));
254 | }
255 | }
256 |
257 | private String[] readConfigFromFile(String path) throws Exception {
258 | StringBuilder sBuilder = new StringBuilder();
259 | FileInputStream inputStream = null;
260 | try {
261 | byte[] buffer = new byte[8192];
262 | int count = 0;
263 | inputStream = new FileInputStream(path);
264 | while ((count = inputStream.read(buffer)) > 0) {
265 | sBuilder.append(new String(buffer, 0, count, "UTF-8"));
266 | }
267 | return sBuilder.toString().split("\\n");
268 | } catch (Exception e) {
269 | throw new Exception(String.format("Can't read config file: %s", path));
270 | } finally {
271 | if (inputStream != null) {
272 | try {
273 | inputStream.close();
274 | } catch (Exception e2) {
275 | }
276 | }
277 | }
278 | }
279 |
280 | public void loadFromFile(InputStream inputStream) throws Exception {
281 | byte[] bytes = new byte[inputStream.available()];
282 | inputStream.read(bytes);
283 | loadFromLines(new String(bytes).split("\\r?\\n"));
284 | }
285 |
286 | public void loadFromUrl(String url) throws Exception {
287 | String[] lines = null;
288 | if (url.charAt(0) == '/') {
289 | lines = readConfigFromFile(url);
290 | } else {
291 | lines = downloadConfig(url);
292 | }
293 | loadFromLines(lines);
294 | }
295 |
296 | protected void loadFromLines(String[] lines) throws Exception {
297 |
298 | m_IpList.clear();
299 | m_DnsList.clear();
300 | m_RouteList.clear();
301 | m_ProxyList.clear();
302 | m_DomainMap.clear();
303 |
304 | int lineNumber = 0;
305 | for (String line : lines) {
306 | lineNumber++;
307 | String[] items = line.split("\\s+");
308 | if (items.length < 2) {
309 | continue;
310 | }
311 |
312 | String tagString = items[0].toLowerCase(Locale.ENGLISH).trim();
313 | try {
314 | if (!tagString.startsWith("#")) {
315 | if (ProxyConfig.IS_DEBUG)
316 | System.out.println(line);
317 |
318 | if (tagString.equals("ip")) {
319 | addIPAddressToList(items, 1, m_IpList);
320 | } else if (tagString.equals("dns")) {
321 | addIPAddressToList(items, 1, m_DnsList);
322 | } else if (tagString.equals("route")) {
323 | addIPAddressToList(items, 1, m_RouteList);
324 | } else if (tagString.equals("proxy")) {
325 | addProxyToList(items, 1);
326 | } else if (tagString.equals("direct_domain")) {
327 | addDomainToHashMap(items, 1, false);
328 | } else if (tagString.equals("proxy_domain")) {
329 | addDomainToHashMap(items, 1, true);
330 | } else if (tagString.equals("dns_ttl")) {
331 | m_dns_ttl = Integer.parseInt(items[1]);
332 | } else if (tagString.equals("welcome_info")) {
333 | m_welcome_info = line.substring(line.indexOf(" ")).trim();
334 | } else if (tagString.equals("session_name")) {
335 | m_session_name = items[1];
336 | } else if (tagString.equals("user_agent")) {
337 | m_user_agent = line.substring(line.indexOf(" ")).trim();
338 | } else if (tagString.equals("outside_china_use_proxy")) {
339 | m_outside_china_use_proxy = convertToBool(items[1]);
340 | } else if (tagString.equals("isolate_http_host_header")) {
341 | m_isolate_http_host_header = convertToBool(items[1]);
342 | } else if (tagString.equals("mtu")) {
343 | m_mtu = Integer.parseInt(items[1]);
344 | }
345 | }
346 | } catch (Exception e) {
347 | throw new Exception(String.format("config file parse error: line:%d, tag:%s, error:%s", lineNumber, tagString, e));
348 | }
349 |
350 | }
351 |
352 | //查找默认代理。
353 | if (m_ProxyList.size() == 0) {
354 | tryAddProxy(lines);
355 | }
356 | }
357 |
358 | private void tryAddProxy(String[] lines) {
359 | for (String line : lines) {
360 | Pattern p = Pattern.compile("proxy\\s+([^:]+):(\\d+)", Pattern.CASE_INSENSITIVE);
361 | Matcher m = p.matcher(line);
362 | while (m.find()) {
363 | HttpConnectConfig config = new HttpConnectConfig();
364 | config.ServerAddress = new InetSocketAddress(m.group(1), Integer.parseInt(m.group(2)));
365 | if (!m_ProxyList.contains(config)) {
366 | m_ProxyList.add(config);
367 | m_DomainMap.put(config.ServerAddress.getHostName(), false);
368 | }
369 | }
370 | }
371 | }
372 |
373 | public void addProxyToList(String proxyString) throws Exception {
374 | Config config = null;
375 | if (proxyString.startsWith("ss://")) {
376 | config = ShadowsocksConfig.parse(proxyString);
377 | } else {
378 | if (!proxyString.toLowerCase().startsWith("http://")) {
379 | proxyString = "http://" + proxyString;
380 | }
381 | config = HttpConnectConfig.parse(proxyString);
382 | }
383 | if (!m_ProxyList.contains(config)) {
384 | m_ProxyList.add(config);
385 | m_DomainMap.put(config.ServerAddress.getHostName(), false);
386 | }
387 | }
388 |
389 | private void addProxyToList(String[] items, int offset) throws Exception {
390 | for (int i = offset; i < items.length; i++) {
391 | addProxyToList(items[i].trim());
392 | }
393 | }
394 |
395 | private void addDomainToHashMap(String[] items, int offset, Boolean state) {
396 | for (int i = offset; i < items.length; i++) {
397 | String domainString = items[i].toLowerCase().trim();
398 | if (domainString.charAt(0) == '.') {
399 | domainString = domainString.substring(1);
400 | }
401 | m_DomainMap.put(domainString, state);
402 | }
403 | }
404 |
405 | private boolean convertToBool(String valueString) {
406 | if (valueString == null || valueString.isEmpty())
407 | return false;
408 | valueString = valueString.toLowerCase(Locale.ENGLISH).trim();
409 | if (valueString.equals("on") || valueString.equals("1") || valueString.equals("true") || valueString.equals("yes")) {
410 | return true;
411 | } else {
412 | return false;
413 | }
414 | }
415 |
416 |
417 | private void addIPAddressToList(String[] items, int offset, ArrayList list) {
418 | for (int i = offset; i < items.length; i++) {
419 | String item = items[i].trim().toLowerCase();
420 | if (item.startsWith("#")) {
421 | break;
422 | } else {
423 | IPAddress ip = new IPAddress(item);
424 | if (!list.contains(ip)) {
425 | list.add(ip);
426 | }
427 | }
428 | }
429 | }
430 |
431 | }
432 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/TcpProxyServer.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | import com.vm.shadowsocks.tcpip.CommonMethods;
4 | import com.vm.shadowsocks.tunnel.Tunnel;
5 |
6 | import java.io.IOException;
7 | import java.net.InetSocketAddress;
8 | import java.nio.channels.SelectionKey;
9 | import java.nio.channels.Selector;
10 | import java.nio.channels.ServerSocketChannel;
11 | import java.nio.channels.SocketChannel;
12 | import java.util.Iterator;
13 |
14 | public class TcpProxyServer implements Runnable {
15 |
16 | public boolean Stopped;
17 | public short Port;
18 |
19 | Selector m_Selector;
20 | ServerSocketChannel m_ServerSocketChannel;
21 | Thread m_ServerThread;
22 |
23 | public TcpProxyServer(int port) throws IOException {
24 | m_Selector = Selector.open();
25 | m_ServerSocketChannel = ServerSocketChannel.open();
26 | m_ServerSocketChannel.configureBlocking(false);
27 | m_ServerSocketChannel.socket().bind(new InetSocketAddress(port));
28 | m_ServerSocketChannel.register(m_Selector, SelectionKey.OP_ACCEPT);
29 | this.Port = (short) m_ServerSocketChannel.socket().getLocalPort();
30 | System.out.printf("AsyncTcpServer listen on %d success.\n", this.Port & 0xFFFF);
31 | }
32 |
33 | public void start() {
34 | m_ServerThread = new Thread(this);
35 | m_ServerThread.setName("TcpProxyServerThread");
36 | m_ServerThread.start();
37 | }
38 |
39 | public void stop() {
40 | this.Stopped = true;
41 | if (m_Selector != null) {
42 | try {
43 | m_Selector.close();
44 | m_Selector = null;
45 | } catch (Exception e) {
46 | e.printStackTrace();
47 | }
48 | }
49 |
50 | if (m_ServerSocketChannel != null) {
51 | try {
52 | m_ServerSocketChannel.close();
53 | m_ServerSocketChannel = null;
54 | } catch (Exception e) {
55 | e.printStackTrace();
56 | }
57 | }
58 | }
59 |
60 | @Override
61 | public void run() {
62 | try {
63 | while (true) {
64 | m_Selector.select();
65 | Iterator keyIterator = m_Selector.selectedKeys().iterator();
66 | while (keyIterator.hasNext()) {
67 | SelectionKey key = keyIterator.next();
68 | if (key.isValid()) {
69 | try {
70 | if (key.isReadable()) {
71 | ((Tunnel) key.attachment()).onReadable(key);
72 | } else if (key.isWritable()) {
73 | ((Tunnel) key.attachment()).onWritable(key);
74 | } else if (key.isConnectable()) {
75 | ((Tunnel) key.attachment()).onConnectable();
76 | } else if (key.isAcceptable()) {
77 | onAccepted(key);
78 | }
79 | } catch (Exception e) {
80 | System.out.println(e.toString());
81 | }
82 | }
83 | keyIterator.remove();
84 | }
85 | }
86 | } catch (Exception e) {
87 | e.printStackTrace();
88 | } finally {
89 | this.stop();
90 | System.out.println("TcpServer thread exited.");
91 | }
92 | }
93 |
94 | InetSocketAddress getDestAddress(SocketChannel localChannel) {
95 | short portKey = (short) localChannel.socket().getPort();
96 | NatSession session = NatSessionManager.getSession(portKey);
97 | if (session != null) {
98 | if (ProxyConfig.Instance.needProxy(session.RemoteHost, session.RemoteIP)) {
99 | if (ProxyConfig.IS_DEBUG)
100 | System.out.printf("%d/%d:[PROXY] %s=>%s:%d\n", NatSessionManager.getSessionCount(), Tunnel.SessionCount, session.RemoteHost, CommonMethods.ipIntToString(session.RemoteIP), session.RemotePort & 0xFFFF);
101 | return InetSocketAddress.createUnresolved(session.RemoteHost, session.RemotePort & 0xFFFF);
102 | } else {
103 | return new InetSocketAddress(localChannel.socket().getInetAddress(), session.RemotePort & 0xFFFF);
104 | }
105 | }
106 | return null;
107 | }
108 |
109 | void onAccepted(SelectionKey key) {
110 | Tunnel localTunnel = null;
111 | try {
112 | SocketChannel localChannel = m_ServerSocketChannel.accept();
113 | localTunnel = TunnelFactory.wrap(localChannel, m_Selector);
114 |
115 | InetSocketAddress destAddress = getDestAddress(localChannel);
116 | if (destAddress != null) {
117 | Tunnel remoteTunnel = TunnelFactory.createTunnelByConfig(destAddress, m_Selector);
118 | remoteTunnel.setBrotherTunnel(localTunnel);//关联兄弟
119 | localTunnel.setBrotherTunnel(remoteTunnel);//关联兄弟
120 | remoteTunnel.connect(destAddress);//开始连接
121 | } else {
122 | LocalVpnService.Instance.writeLog("Error: socket(%s:%d) target host is null.", localChannel.socket().getInetAddress().toString(), localChannel.socket().getPort());
123 | localTunnel.dispose();
124 | }
125 | } catch (Exception e) {
126 | e.printStackTrace();
127 | LocalVpnService.Instance.writeLog("Error: remote socket create failed: %s", e.toString());
128 | if (localTunnel != null) {
129 | localTunnel.dispose();
130 | }
131 | }
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/core/TunnelFactory.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.core;
2 |
3 | import com.vm.shadowsocks.tunnel.Config;
4 | import com.vm.shadowsocks.tunnel.RawTunnel;
5 | import com.vm.shadowsocks.tunnel.Tunnel;
6 | import com.vm.shadowsocks.tunnel.httpconnect.HttpConnectConfig;
7 | import com.vm.shadowsocks.tunnel.httpconnect.HttpConnectTunnel;
8 | import com.vm.shadowsocks.tunnel.shadowsocks.ShadowsocksConfig;
9 | import com.vm.shadowsocks.tunnel.shadowsocks.ShadowsocksTunnel;
10 |
11 | import java.net.InetSocketAddress;
12 | import java.nio.channels.Selector;
13 | import java.nio.channels.SocketChannel;
14 |
15 | public class TunnelFactory {
16 |
17 | public static Tunnel wrap(SocketChannel channel, Selector selector) {
18 | return new RawTunnel(channel, selector);
19 | }
20 |
21 | public static Tunnel createTunnelByConfig(InetSocketAddress destAddress, Selector selector) throws Exception {
22 | if (destAddress.isUnresolved()) {
23 | Config config = ProxyConfig.Instance.getDefaultTunnelConfig(destAddress);
24 | if (config instanceof HttpConnectConfig) {
25 | return new HttpConnectTunnel((HttpConnectConfig) config, selector);
26 | } else if (config instanceof ShadowsocksConfig) {
27 | return new ShadowsocksTunnel((ShadowsocksConfig) config, selector);
28 | }
29 | throw new Exception("The config is unknow.");
30 | } else {
31 | return new RawTunnel(destAddress, selector);
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/dns/DnsFlags.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.dns;
2 |
3 | public class DnsFlags {
4 | public boolean QR;//1 bits
5 | public int OpCode;//4 bits
6 | public boolean AA;//1 bits
7 | public boolean TC;//1 bits
8 | public boolean RD;//1 bits
9 | public boolean RA;//1 bits
10 | public int Zero;//3 bits
11 | public int Rcode;//4 bits
12 |
13 | public static DnsFlags Parse(short value) {
14 | int m_Flags = value & 0xFFFF;
15 | DnsFlags flags = new DnsFlags();
16 | flags.QR = ((m_Flags >> 7) & 0x01) == 1;
17 | flags.OpCode = (m_Flags >> 3) & 0x0F;
18 | flags.AA = ((m_Flags >> 2) & 0x01) == 1;
19 | flags.TC = ((m_Flags >> 1) & 0x01) == 1;
20 | flags.RD = (m_Flags & 0x01) == 1;
21 | flags.RA = (m_Flags >> 15) == 1;
22 | flags.Zero = (m_Flags >> 12) & 0x07;
23 | flags.Rcode = ((m_Flags >> 8) & 0xF);
24 | return flags;
25 | }
26 |
27 | public short ToShort() {
28 | int m_Flags = 0;
29 | m_Flags |= (this.QR ? 1 : 0) << 7;
30 | m_Flags |= (this.OpCode & 0x0F) << 3;
31 | m_Flags |= (this.AA ? 1 : 0) << 2;
32 | m_Flags |= (this.TC ? 1 : 0) << 1;
33 | m_Flags |= this.RD ? 1 : 0;
34 | m_Flags |= (this.RA ? 1 : 0) << 15;
35 | m_Flags |= (this.Zero & 0x07) << 12;
36 | m_Flags |= (this.Rcode & 0x0F) << 8;
37 | return (short) m_Flags;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/dns/DnsHeader.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.dns;
2 |
3 | import com.vm.shadowsocks.tcpip.CommonMethods;
4 |
5 | import java.nio.ByteBuffer;
6 |
7 |
8 | public class DnsHeader {
9 | public short ID;
10 | public DnsFlags Flags;
11 | public short QuestionCount;
12 | public short ResourceCount;
13 | public short AResourceCount;
14 | public short EResourceCount;
15 |
16 | public static DnsHeader FromBytes(ByteBuffer buffer) {
17 | DnsHeader header = new DnsHeader(buffer.array(), buffer.arrayOffset() + buffer.position());
18 | header.ID = buffer.getShort();
19 | header.Flags = DnsFlags.Parse(buffer.getShort());
20 | header.QuestionCount = buffer.getShort();
21 | header.ResourceCount = buffer.getShort();
22 | header.AResourceCount = buffer.getShort();
23 | header.EResourceCount = buffer.getShort();
24 | return header;
25 | }
26 |
27 | public void ToBytes(ByteBuffer buffer) {
28 | buffer.putShort(this.ID);
29 | buffer.putShort(this.Flags.ToShort());
30 | buffer.putShort(this.QuestionCount);
31 | buffer.putShort(this.ResourceCount);
32 | buffer.putShort(this.AResourceCount);
33 | buffer.putShort(this.EResourceCount);
34 | }
35 |
36 | static final short offset_ID = 0;
37 | static final short offset_Flags = 2;
38 | static final short offset_QuestionCount = 4;
39 | static final short offset_ResourceCount = 6;
40 | static final short offset_AResourceCount = 8;
41 | static final short offset_EResourceCount = 10;
42 |
43 | public byte[] Data;
44 | public int Offset;
45 |
46 | public DnsHeader(byte[] data, int offset) {
47 | this.Offset = offset;
48 | this.Data = data;
49 | }
50 |
51 | public short getID() {
52 | return CommonMethods.readShort(Data, Offset + offset_ID);
53 | }
54 |
55 | public short getFlags() {
56 | return CommonMethods.readShort(Data, Offset + offset_Flags);
57 | }
58 |
59 | public short getQuestionCount() {
60 | return CommonMethods.readShort(Data, Offset + offset_QuestionCount);
61 | }
62 |
63 | public short getResourceCount() {
64 | return CommonMethods.readShort(Data, Offset + offset_ResourceCount);
65 | }
66 |
67 | public short getAResourceCount() {
68 | return CommonMethods.readShort(Data, Offset + offset_AResourceCount);
69 | }
70 |
71 | public short getEResourceCount() {
72 | return CommonMethods.readShort(Data, Offset + offset_EResourceCount);
73 | }
74 |
75 | public void setID(short value) {
76 | CommonMethods.writeShort(Data, Offset + offset_ID, value);
77 | }
78 |
79 | public void setFlags(short value) {
80 | CommonMethods.writeShort(Data, Offset + offset_Flags, value);
81 | }
82 |
83 | public void setQuestionCount(short value) {
84 | CommonMethods.writeShort(Data, Offset + offset_QuestionCount, value);
85 | }
86 |
87 | public void setResourceCount(short value) {
88 | CommonMethods.writeShort(Data, Offset + offset_ResourceCount, value);
89 | }
90 |
91 | public void setAResourceCount(short value) {
92 | CommonMethods.writeShort(Data, Offset + offset_AResourceCount, value);
93 | }
94 |
95 | public void setEResourceCount(short value) {
96 | CommonMethods.writeShort(Data, Offset + offset_EResourceCount, value);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/dns/DnsPacket.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.dns;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | public class DnsPacket {
6 | public DnsHeader Header;
7 | public Question[] Questions;
8 | public Resource[] Resources;
9 | public Resource[] AResources;
10 | public Resource[] EResources;
11 |
12 | public int Size;
13 |
14 | public static DnsPacket FromBytes(ByteBuffer buffer) {
15 | if (buffer.limit() < 12)
16 | return null;
17 | if (buffer.limit() > 512)
18 | return null;
19 |
20 | DnsPacket packet = new DnsPacket();
21 | packet.Size = buffer.limit();
22 | packet.Header = DnsHeader.FromBytes(buffer);
23 |
24 | if (packet.Header.QuestionCount > 2 || packet.Header.ResourceCount > 50 || packet.Header.AResourceCount > 50 || packet.Header.EResourceCount > 50) {
25 | return null;
26 | }
27 |
28 | packet.Questions = new Question[packet.Header.QuestionCount];
29 | packet.Resources = new Resource[packet.Header.ResourceCount];
30 | packet.AResources = new Resource[packet.Header.AResourceCount];
31 | packet.EResources = new Resource[packet.Header.EResourceCount];
32 |
33 | for (int i = 0; i < packet.Questions.length; i++) {
34 | packet.Questions[i] = Question.FromBytes(buffer);
35 | }
36 |
37 | for (int i = 0; i < packet.Resources.length; i++) {
38 | packet.Resources[i] = Resource.FromBytes(buffer);
39 | }
40 |
41 | for (int i = 0; i < packet.AResources.length; i++) {
42 | packet.AResources[i] = Resource.FromBytes(buffer);
43 | }
44 |
45 | for (int i = 0; i < packet.EResources.length; i++) {
46 | packet.EResources[i] = Resource.FromBytes(buffer);
47 | }
48 |
49 | return packet;
50 | }
51 |
52 | public void ToBytes(ByteBuffer buffer) {
53 | Header.QuestionCount = 0;
54 | Header.ResourceCount = 0;
55 | Header.AResourceCount = 0;
56 | Header.EResourceCount = 0;
57 |
58 | if (Questions != null)
59 | Header.QuestionCount = (short) Questions.length;
60 | if (Resources != null)
61 | Header.ResourceCount = (short) Resources.length;
62 | if (AResources != null)
63 | Header.AResourceCount = (short) AResources.length;
64 | if (EResources != null)
65 | Header.EResourceCount = (short) EResources.length;
66 |
67 | this.Header.ToBytes(buffer);
68 |
69 | for (int i = 0; i < Header.QuestionCount; i++) {
70 | this.Questions[i].ToBytes(buffer);
71 | }
72 |
73 | for (int i = 0; i < Header.ResourceCount; i++) {
74 | this.Resources[i].ToBytes(buffer);
75 | }
76 |
77 | for (int i = 0; i < Header.AResourceCount; i++) {
78 | this.AResources[i].ToBytes(buffer);
79 | }
80 |
81 | for (int i = 0; i < Header.EResourceCount; i++) {
82 | this.EResources[i].ToBytes(buffer);
83 | }
84 | }
85 |
86 | public static String ReadDomain(ByteBuffer buffer, int dnsHeaderOffset) {
87 | StringBuilder sb = new StringBuilder();
88 | int len = 0;
89 | while (buffer.hasRemaining() && (len = (buffer.get() & 0xFF)) > 0) {
90 | if ((len & 0xc0) == 0xc0)// pointer 高2位为11表示是指针。如:1100 0000
91 | {
92 | // 指针的取值是前一字节的后6位加后一字节的8位共14位的值。
93 | int pointer = buffer.get() & 0xFF;// 低8位
94 | pointer |= (len & 0x3F) << 8;// 高6位
95 |
96 | ByteBuffer newBuffer = ByteBuffer.wrap(buffer.array(), dnsHeaderOffset + pointer, dnsHeaderOffset + buffer.limit());
97 | sb.append(ReadDomain(newBuffer, dnsHeaderOffset));
98 | return sb.toString();
99 | } else {
100 | while (len > 0 && buffer.hasRemaining()) {
101 | sb.append((char) (buffer.get() & 0xFF));
102 | len--;
103 | }
104 | sb.append('.');
105 | }
106 | }
107 |
108 | if (len == 0 && sb.length() > 0) {
109 | sb.deleteCharAt(sb.length() - 1);//去掉末尾的点(.)
110 | }
111 | return sb.toString();
112 | }
113 |
114 | public static void WriteDomain(String domain, ByteBuffer buffer) {
115 | if (domain == null || domain == "") {
116 | buffer.put((byte) 0);
117 | return;
118 | }
119 |
120 | String[] arr = domain.split("\\.");
121 | for (String item : arr) {
122 | if (arr.length > 1) {
123 | buffer.put((byte) item.length());
124 | }
125 |
126 | for (int i = 0; i < item.length(); i++) {
127 | buffer.put((byte) item.codePointAt(i));
128 | }
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/dns/Question.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.dns;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | public class Question {
6 | public String Domain;
7 | public short Type;
8 | public short Class;
9 |
10 | private int offset;
11 |
12 | public int Offset() {
13 | return offset;
14 | }
15 |
16 | private int length;
17 |
18 | public int Length() {
19 | return length;
20 | }
21 |
22 | public static Question FromBytes(ByteBuffer buffer) {
23 | Question q = new Question();
24 | q.offset = buffer.arrayOffset() + buffer.position();
25 | q.Domain = DnsPacket.ReadDomain(buffer, buffer.arrayOffset());
26 | q.Type = buffer.getShort();
27 | q.Class = buffer.getShort();
28 | q.length = buffer.arrayOffset() + buffer.position() - q.offset;
29 | return q;
30 | }
31 |
32 | public void ToBytes(ByteBuffer buffer) {
33 | this.offset = buffer.position();
34 | DnsPacket.WriteDomain(this.Domain, buffer);
35 | buffer.putShort(this.Type);
36 | buffer.putShort(this.Class);
37 | this.length = buffer.position() - this.offset;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/dns/Resource.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.dns;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | public class Resource {
6 | public String Domain;
7 | public short Type;
8 | public short Class;
9 | public int TTL;
10 | public short DataLength;
11 | public byte[] Data;
12 |
13 | private int offset;
14 |
15 | public int Offset() {
16 | return offset;
17 | }
18 |
19 | private int length;
20 |
21 | public int Length() {
22 | return length;
23 | }
24 |
25 | public static Resource FromBytes(ByteBuffer buffer) {
26 |
27 | Resource r = new Resource();
28 | r.offset = buffer.arrayOffset() + buffer.position();
29 | r.Domain = DnsPacket.ReadDomain(buffer, buffer.arrayOffset());
30 | r.Type = buffer.getShort();
31 | r.Class = buffer.getShort();
32 | r.TTL = buffer.getInt();
33 | r.DataLength = buffer.getShort();
34 | r.Data = new byte[r.DataLength & 0xFFFF];
35 | buffer.get(r.Data);
36 | r.length = buffer.arrayOffset() + buffer.position() - r.offset;
37 | return r;
38 | }
39 |
40 | public void ToBytes(ByteBuffer buffer) {
41 | if (this.Data == null) {
42 | this.Data = new byte[0];
43 | }
44 | this.DataLength = (short) this.Data.length;
45 |
46 | this.offset = buffer.position();
47 | DnsPacket.WriteDomain(this.Domain, buffer);
48 | buffer.putShort(this.Type);
49 | buffer.putShort(this.Class);
50 | buffer.putInt(this.TTL);
51 |
52 | buffer.putShort(this.DataLength);
53 | buffer.put(this.Data);
54 | this.length = buffer.position() - this.offset;
55 | }
56 |
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/dns/ResourcePointer.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.dns;
2 |
3 | import com.vm.shadowsocks.tcpip.CommonMethods;
4 |
5 | public class ResourcePointer {
6 | static final short offset_Domain = 0;
7 | static final short offset_Type = 2;
8 | static final short offset_Class = 4;
9 | static final int offset_TTL = 6;
10 | static final short offset_DataLength = 10;
11 | static final int offset_IP = 12;
12 |
13 | byte[] Data;
14 | int Offset;
15 |
16 | public ResourcePointer(byte[] data, int offset) {
17 | this.Data = data;
18 | this.Offset = offset;
19 | }
20 |
21 | public void setDomain(short value) {
22 | CommonMethods.writeShort(Data, Offset + offset_Domain, value);
23 | }
24 |
25 | public short getType() {
26 | return CommonMethods.readShort(Data, Offset + offset_Type);
27 | }
28 |
29 | public void setType(short value) {
30 | CommonMethods.writeShort(Data, Offset + offset_Type, value);
31 | }
32 |
33 | public short getClass(short value) {
34 | return CommonMethods.readShort(Data, Offset + offset_Class);
35 | }
36 |
37 | public void setClass(short value) {
38 | CommonMethods.writeShort(Data, Offset + offset_Class, value);
39 | }
40 |
41 | public int getTTL() {
42 | return CommonMethods.readInt(Data, Offset + offset_TTL);
43 | }
44 |
45 | public void setTTL(int value) {
46 | CommonMethods.writeInt(Data, Offset + offset_TTL, value);
47 | }
48 |
49 | public short getDataLength() {
50 | return CommonMethods.readShort(Data, Offset + offset_DataLength);
51 | }
52 |
53 | public void setDataLength(short value) {
54 | CommonMethods.writeShort(Data, Offset + offset_DataLength, value);
55 | }
56 |
57 | public int getIP() {
58 | return CommonMethods.readInt(Data, Offset + offset_IP);
59 | }
60 |
61 | public void setIP(int value) {
62 | CommonMethods.writeInt(Data, Offset + offset_IP, value);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tcpip/CommonMethods.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tcpip;
2 |
3 | import java.net.Inet4Address;
4 | import java.net.InetAddress;
5 | import java.net.UnknownHostException;
6 |
7 | public class CommonMethods {
8 |
9 | public static InetAddress ipIntToInet4Address(int ip) {
10 | byte[] ipAddress = new byte[4];
11 | writeInt(ipAddress, 0, ip);
12 | try {
13 | return Inet4Address.getByAddress(ipAddress);
14 | } catch (UnknownHostException e) {
15 | // TODO Auto-generated catch block
16 | e.printStackTrace();
17 | return null;
18 | }
19 | }
20 |
21 | public static String ipIntToString(int ip) {
22 | return String.format("%s.%s.%s.%s", (ip >> 24) & 0x00FF,
23 | (ip >> 16) & 0x00FF, (ip >> 8) & 0x00FF, ip & 0x00FF);
24 | }
25 |
26 | public static String ipBytesToString(byte[] ip) {
27 | return String.format("%s.%s.%s.%s", ip[0] & 0x00FF, ip[1] & 0x00FF, ip[2] & 0x00FF, ip[3] & 0x00FF);
28 | }
29 |
30 | public static int ipStringToInt(String ip) {
31 | String[] arrStrings = ip.split("\\.");
32 | int r = (Integer.parseInt(arrStrings[0]) << 24)
33 | | (Integer.parseInt(arrStrings[1]) << 16)
34 | | (Integer.parseInt(arrStrings[2]) << 8)
35 | | Integer.parseInt(arrStrings[3]);
36 | return r;
37 | }
38 |
39 | public static int readInt(byte[] data, int offset) {
40 | int r = ((data[offset] & 0xFF) << 24)
41 | | ((data[offset + 1] & 0xFF) << 16)
42 | | ((data[offset + 2] & 0xFF) << 8) | (data[offset + 3] & 0xFF);
43 | return r;
44 | }
45 |
46 | public static short readShort(byte[] data, int offset) {
47 | int r = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
48 | return (short) r;
49 | }
50 |
51 | public static void writeInt(byte[] data, int offset, int value) {
52 | data[offset] = (byte) (value >> 24);
53 | data[offset + 1] = (byte) (value >> 16);
54 | data[offset + 2] = (byte) (value >> 8);
55 | data[offset + 3] = (byte) (value);
56 | }
57 |
58 | public static void writeShort(byte[] data, int offset, short value) {
59 | data[offset] = (byte) (value >> 8);
60 | data[offset + 1] = (byte) (value);
61 | }
62 |
63 | // 网络字节顺序与主机字节顺序的转换
64 |
65 | public static short htons(short u) {
66 | int r = ((u & 0xFFFF) << 8) | ((u & 0xFFFF) >> 8);
67 | return (short) r;
68 | }
69 |
70 | public static short ntohs(short u) {
71 | int r = ((u & 0xFFFF) << 8) | ((u & 0xFFFF) >> 8);
72 | return (short) r;
73 | }
74 |
75 | public static int hton(int u) {
76 | int r = (u >> 24) & 0x000000FF;
77 | r |= (u >> 8) & 0x0000FF00;
78 | r |= (u << 8) & 0x00FF0000;
79 | r |= (u << 24) & 0xFF000000;
80 | return r;
81 | }
82 |
83 | public static int ntoh(int u) {
84 | int r = (u >> 24) & 0x000000FF;
85 | r |= (u >> 8) & 0x0000FF00;
86 | r |= (u << 8) & 0x00FF0000;
87 | r |= (u << 24) & 0xFF000000;
88 | return r;
89 | }
90 |
91 | // 计算校验和
92 | public static short checksum(long sum, byte[] buf, int offset, int len) {
93 | sum += getsum(buf, offset, len);
94 | while ((sum >> 16) > 0)
95 | sum = (sum & 0xFFFF) + (sum >> 16);
96 | return (short) ~sum;
97 | }
98 |
99 | public static long getsum(byte[] buf, int offset, int len) {
100 | long sum = 0; /* assume 32 bit long, 16 bit short */
101 | while (len > 1) {
102 | sum += readShort(buf, offset) & 0xFFFF;
103 | offset += 2;
104 | len -= 2;
105 | }
106 |
107 | if (len > 0) /* take care of left over byte */ {
108 | sum += (buf[offset] & 0xFF) << 8;
109 | }
110 | return sum;
111 | }
112 |
113 | // 计算IP包的校验和
114 | public static boolean ComputeIPChecksum(IPHeader ipHeader) {
115 | short oldCrc = ipHeader.getCrc();
116 | ipHeader.setCrc((short) 0);// 计算前置零
117 | short newCrc = CommonMethods.checksum(0, ipHeader.m_Data,
118 | ipHeader.m_Offset, ipHeader.getHeaderLength());
119 | ipHeader.setCrc(newCrc);
120 | return oldCrc == newCrc;
121 | }
122 |
123 | // 计算TCP或UDP的校验和
124 | public static boolean ComputeTCPChecksum(IPHeader ipHeader, TCPHeader tcpHeader) {
125 | ComputeIPChecksum(ipHeader);//计算IP校验和
126 | int ipData_len = ipHeader.getTotalLength() - ipHeader.getHeaderLength();// IP数据长度
127 | if (ipData_len < 0)
128 | return false;
129 | // 计算为伪首部和
130 | long sum = getsum(ipHeader.m_Data, ipHeader.m_Offset
131 | + IPHeader.offset_src_ip, 8);
132 | sum += ipHeader.getProtocol() & 0xFF;
133 | sum += ipData_len;
134 |
135 | short oldCrc = tcpHeader.getCrc();
136 | tcpHeader.setCrc((short) 0);// 计算前置0
137 |
138 | short newCrc = checksum(sum, tcpHeader.m_Data, tcpHeader.m_Offset, ipData_len);// 计算校验和
139 |
140 | tcpHeader.setCrc(newCrc);
141 | return oldCrc == newCrc;
142 | }
143 |
144 | // 计算TCP或UDP的校验和
145 | public static boolean ComputeUDPChecksum(IPHeader ipHeader, UDPHeader udpHeader) {
146 | ComputeIPChecksum(ipHeader);//计算IP校验和
147 | int ipData_len = ipHeader.getTotalLength() - ipHeader.getHeaderLength();// IP数据长度
148 | if (ipData_len < 0)
149 | return false;
150 | // 计算为伪首部和
151 | long sum = getsum(ipHeader.m_Data, ipHeader.m_Offset
152 | + IPHeader.offset_src_ip, 8);
153 | sum += ipHeader.getProtocol() & 0xFF;
154 | sum += ipData_len;
155 |
156 | short oldCrc = udpHeader.getCrc();
157 | udpHeader.setCrc((short) 0);// 计算前置0
158 |
159 | short newCrc = checksum(sum, udpHeader.m_Data, udpHeader.m_Offset, ipData_len);// 计算校验和
160 |
161 | udpHeader.setCrc(newCrc);
162 | return oldCrc == newCrc;
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tcpip/IPHeader.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tcpip;
2 |
3 | public class IPHeader {
4 |
5 | public static final short IP = 0x0800;
6 | public static final byte ICMP = 1;
7 | public static final byte TCP = 6;
8 | public static final byte UDP = 17;
9 |
10 | static final byte offset_ver_ihl = 0; // 0: Version (4 bits) + Internet header length (4// bits)
11 | static final byte offset_tos = 1; // 1: Type of service
12 | static final short offset_tlen = 2; // 2: Total length
13 | static final short offset_identification = 4; // :4 Identification
14 | static final short offset_flags_fo = 6; // 6: Flags (3 bits) + Fragment offset (13 bits)
15 | static final byte offset_ttl = 8; // 8: Time to live
16 | public static final byte offset_proto = 9; // 9: Protocol
17 | static final short offset_crc = 10; // 10: Header checksum
18 | public static final int offset_src_ip = 12; // 12: Source address
19 | public static final int offset_dest_ip = 16; // 16: Destination address
20 | static final int offset_op_pad = 20; // 20: Option + Padding
21 |
22 | public byte[] m_Data;
23 | public int m_Offset;
24 |
25 | public IPHeader(byte[] data, int offset) {
26 | this.m_Data = data;
27 | this.m_Offset = offset;
28 | }
29 |
30 | public void Default() {
31 | setHeaderLength(20);
32 | setTos((byte) 0);
33 | setTotalLength(0);
34 | setIdentification(0);
35 | setFlagsAndOffset((short) 0);
36 | setTTL((byte) 64);
37 | }
38 |
39 | public int getDataLength() {
40 | return this.getTotalLength() - this.getHeaderLength();
41 | }
42 |
43 | public int getHeaderLength() {
44 | return (m_Data[m_Offset + offset_ver_ihl] & 0x0F) * 4;
45 | }
46 |
47 | public void setHeaderLength(int value) {
48 | m_Data[m_Offset + offset_ver_ihl] = (byte) ((4 << 4) | (value / 4));
49 | }
50 |
51 | public byte getTos() {
52 | return m_Data[m_Offset + offset_tos];
53 | }
54 |
55 | public void setTos(byte value) {
56 | m_Data[m_Offset + offset_tos] = value;
57 | }
58 |
59 | public int getTotalLength() {
60 | return CommonMethods.readShort(m_Data, m_Offset + offset_tlen) & 0xFFFF;
61 | }
62 |
63 | public void setTotalLength(int value) {
64 | CommonMethods.writeShort(m_Data, m_Offset + offset_tlen, (short) value);
65 | }
66 |
67 | public int getIdentification() {
68 | return CommonMethods.readShort(m_Data, m_Offset + offset_identification) & 0xFFFF;
69 | }
70 |
71 | public void setIdentification(int value) {
72 | CommonMethods.writeShort(m_Data, m_Offset + offset_identification, (short) value);
73 | }
74 |
75 | public short getFlagsAndOffset() {
76 | return CommonMethods.readShort(m_Data, m_Offset + offset_flags_fo);
77 | }
78 |
79 | public void setFlagsAndOffset(short value) {
80 | CommonMethods.writeShort(m_Data, m_Offset + offset_flags_fo, value);
81 | }
82 |
83 | public byte getTTL() {
84 | return m_Data[m_Offset + offset_ttl];
85 | }
86 |
87 | public void setTTL(byte value) {
88 | m_Data[m_Offset + offset_ttl] = value;
89 | }
90 |
91 | public byte getProtocol() {
92 | return m_Data[m_Offset + offset_proto];
93 | }
94 |
95 | public void setProtocol(byte value) {
96 | m_Data[m_Offset + offset_proto] = value;
97 | }
98 |
99 | public short getCrc() {
100 | return CommonMethods.readShort(m_Data, m_Offset + offset_crc);
101 | }
102 |
103 | public void setCrc(short value) {
104 | CommonMethods.writeShort(m_Data, m_Offset + offset_crc, value);
105 | }
106 |
107 | public int getSourceIP() {
108 | return CommonMethods.readInt(m_Data, m_Offset + offset_src_ip);
109 | }
110 |
111 | public void setSourceIP(int value) {
112 | CommonMethods.writeInt(m_Data, m_Offset + offset_src_ip, value);
113 | }
114 |
115 | public int getDestinationIP() {
116 | return CommonMethods.readInt(m_Data, m_Offset + offset_dest_ip);
117 | }
118 |
119 | public void setDestinationIP(int value) {
120 | CommonMethods.writeInt(m_Data, m_Offset + offset_dest_ip, value);
121 | }
122 |
123 | @Override
124 | public String toString() {
125 | return String.format("%s->%s Pro=%s,HLen=%d", CommonMethods.ipIntToString(getSourceIP()), CommonMethods.ipIntToString(getDestinationIP()), getProtocol(), getHeaderLength());
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tcpip/TCPHeader.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tcpip;
2 |
3 |
4 | public class TCPHeader {
5 |
6 | public static final int FIN = 1;
7 | public static final int SYN = 2;
8 | public static final int RST = 4;
9 | public static final int PSH = 8;
10 | public static final int ACK = 16;
11 | public static final int URG = 32;
12 |
13 | static final short offset_src_port = 0; // 16位源端口
14 | static final short offset_dest_port = 2; // 16位目的端口
15 | static final int offset_seq = 4; // 32位序列号
16 | static final int offset_ack = 8; // 32位确认号
17 | static final byte offset_lenres = 12; // 4位首部长度/4位保留字
18 | static final byte offset_flag = 13; // 6位标志位
19 | static final short offset_win = 14; // 16位窗口大小
20 | static final short offset_crc = 16; // 16位校验和
21 | static final short offset_urp = 18; // 16位紧急数据偏移量
22 |
23 | public byte[] m_Data;
24 | public int m_Offset;
25 |
26 | public TCPHeader(byte[] data, int offset) {
27 | this.m_Data = data;
28 | this.m_Offset = offset;
29 | }
30 |
31 | public int getHeaderLength() {
32 | int lenres = m_Data[m_Offset + offset_lenres] & 0xFF;
33 | return (lenres >> 4) * 4;
34 | }
35 |
36 | public short getSourcePort() {
37 | return CommonMethods.readShort(m_Data, m_Offset + offset_src_port);
38 | }
39 |
40 | public void setSourcePort(short value) {
41 | CommonMethods.writeShort(m_Data, m_Offset + offset_src_port, value);
42 | }
43 |
44 | public short getDestinationPort() {
45 | return CommonMethods.readShort(m_Data, m_Offset + offset_dest_port);
46 | }
47 |
48 | public void setDestinationPort(short value) {
49 | CommonMethods.writeShort(m_Data, m_Offset + offset_dest_port, value);
50 | }
51 |
52 | public byte getFlags() {
53 | return m_Data[m_Offset + offset_flag];
54 | }
55 |
56 | public short getCrc() {
57 | return CommonMethods.readShort(m_Data, m_Offset + offset_crc);
58 | }
59 |
60 | public void setCrc(short value) {
61 | CommonMethods.writeShort(m_Data, m_Offset + offset_crc, value);
62 | }
63 |
64 | public int getSeqID() {
65 | return CommonMethods.readInt(m_Data, m_Offset + offset_seq);
66 | }
67 |
68 | public int getAckID() {
69 | return CommonMethods.readInt(m_Data, m_Offset + offset_ack);
70 | }
71 |
72 | @Override
73 | public String toString() {
74 | // TODO Auto-generated method stub
75 | return String.format("%s%s%s%s%s%s%d->%d %s:%s",
76 | (getFlags() & SYN) == SYN ? "SYN " : "",
77 | (getFlags() & ACK) == ACK ? "ACK " : "",
78 | (getFlags() & PSH) == PSH ? "PSH " : "",
79 | (getFlags() & RST) == RST ? "RST " : "",
80 | (getFlags() & FIN) == FIN ? "FIN " : "",
81 | (getFlags() & URG) == URG ? "URG " : "",
82 | getSourcePort() & 0xFFFF,
83 | getDestinationPort() & 0xFFFF,
84 | getSeqID(),
85 | getAckID());
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tcpip/UDPHeader.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tcpip;
2 |
3 | public class UDPHeader {
4 | static final short offset_src_port = 0; // Source port
5 | static final short offset_dest_port = 2; // Destination port
6 | static final short offset_tlen = 4; // Datagram length
7 | static final short offset_crc = 6; // Checksum
8 |
9 | public byte[] m_Data;
10 | public int m_Offset;
11 |
12 | public UDPHeader(byte[] data, int offset) {
13 | this.m_Data = data;
14 | this.m_Offset = offset;
15 | }
16 |
17 | public short getSourcePort() {
18 | return CommonMethods.readShort(m_Data, m_Offset + offset_src_port);
19 | }
20 |
21 | public void setSourcePort(short value) {
22 | CommonMethods.writeShort(m_Data, m_Offset + offset_src_port, value);
23 | }
24 |
25 | public short getDestinationPort() {
26 | return CommonMethods.readShort(m_Data, m_Offset + offset_dest_port);
27 | }
28 |
29 | public void setDestinationPort(short value) {
30 | CommonMethods.writeShort(m_Data, m_Offset + offset_dest_port, value);
31 | }
32 |
33 | public int getTotalLength() {
34 | return CommonMethods.readShort(m_Data, m_Offset + offset_tlen) & 0xFFFF;
35 | }
36 |
37 | public void setTotalLength(int value) {
38 | CommonMethods.writeShort(m_Data, m_Offset + offset_tlen, (short) value);
39 | }
40 |
41 | public short getCrc() {
42 | return CommonMethods.readShort(m_Data, m_Offset + offset_crc);
43 | }
44 |
45 | public void setCrc(short value) {
46 | CommonMethods.writeShort(m_Data, m_Offset + offset_crc, value);
47 | }
48 |
49 | @Override
50 | public String toString() {
51 | // TODO Auto-generated method stub
52 | return String.format("%d->%d", getSourcePort() & 0xFFFF,
53 | getDestinationPort() & 0xFFFF);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/Config.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tunnel;
2 |
3 | import java.net.InetSocketAddress;
4 |
5 | public abstract class Config {
6 | public InetSocketAddress ServerAddress;
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/RawTunnel.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tunnel;
2 |
3 | import java.net.InetSocketAddress;
4 | import java.nio.ByteBuffer;
5 | import java.nio.channels.Selector;
6 | import java.nio.channels.SocketChannel;
7 |
8 | public class RawTunnel extends Tunnel {
9 |
10 | public RawTunnel(InetSocketAddress serverAddress, Selector selector) throws Exception {
11 | super(serverAddress, selector);
12 | }
13 |
14 | public RawTunnel(SocketChannel innerChannel, Selector selector) {
15 | super(innerChannel, selector);
16 | // TODO Auto-generated constructor stub
17 | }
18 |
19 | @Override
20 | protected void onConnected(ByteBuffer buffer) throws Exception {
21 | onTunnelEstablished();
22 | }
23 |
24 | @Override
25 | protected void beforeSend(ByteBuffer buffer) throws Exception {
26 | // TODO Auto-generated method stub
27 |
28 | }
29 |
30 | @Override
31 | protected void afterReceived(ByteBuffer buffer) throws Exception {
32 | // TODO Auto-generated method stub
33 |
34 | }
35 |
36 | @Override
37 | protected boolean isTunnelEstablished() {
38 | return true;
39 | }
40 |
41 | @Override
42 | protected void onDispose() {
43 | // TODO Auto-generated method stub
44 |
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/Tunnel.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tunnel;
2 |
3 | import android.annotation.SuppressLint;
4 |
5 | import com.vm.shadowsocks.core.LocalVpnService;
6 | import com.vm.shadowsocks.core.ProxyConfig;
7 |
8 | import java.io.IOException;
9 | import java.net.InetSocketAddress;
10 | import java.nio.ByteBuffer;
11 | import java.nio.channels.SelectionKey;
12 | import java.nio.channels.Selector;
13 | import java.nio.channels.SocketChannel;
14 |
15 | public abstract class Tunnel {
16 |
17 | final static ByteBuffer GL_BUFFER = ByteBuffer.allocate(20000);
18 | public static long SessionCount;
19 |
20 | protected abstract void onConnected(ByteBuffer buffer) throws Exception;
21 |
22 | protected abstract boolean isTunnelEstablished();
23 |
24 | protected abstract void beforeSend(ByteBuffer buffer) throws Exception;
25 |
26 | protected abstract void afterReceived(ByteBuffer buffer) throws Exception;
27 |
28 | protected abstract void onDispose();
29 |
30 | private SocketChannel m_InnerChannel;
31 | private ByteBuffer m_SendRemainBuffer;
32 | private Selector m_Selector;
33 | private Tunnel m_BrotherTunnel;
34 | private boolean m_Disposed;
35 | private InetSocketAddress m_ServerEP;
36 | protected InetSocketAddress m_DestAddress;
37 |
38 | public Tunnel(SocketChannel innerChannel, Selector selector) {
39 | this.m_InnerChannel = innerChannel;
40 | this.m_Selector = selector;
41 | SessionCount++;
42 | }
43 |
44 | public Tunnel(InetSocketAddress serverAddress, Selector selector) throws IOException {
45 | SocketChannel innerChannel = SocketChannel.open();
46 | innerChannel.configureBlocking(false);
47 | this.m_InnerChannel = innerChannel;
48 | this.m_Selector = selector;
49 | this.m_ServerEP = serverAddress;
50 | SessionCount++;
51 | }
52 |
53 | public void setBrotherTunnel(Tunnel brotherTunnel) {
54 | m_BrotherTunnel = brotherTunnel;
55 | }
56 |
57 | public void connect(InetSocketAddress destAddress) throws Exception {
58 | if (LocalVpnService.Instance.protect(m_InnerChannel.socket())) {//保护socket不走vpn
59 | m_DestAddress = destAddress;
60 | m_InnerChannel.register(m_Selector, SelectionKey.OP_CONNECT, this);//注册连接事件
61 | m_InnerChannel.connect(m_ServerEP);//连接目标
62 | } else {
63 | throw new Exception("VPN protect socket failed.");
64 | }
65 | }
66 |
67 | protected void beginReceive() throws Exception {
68 | if (m_InnerChannel.isBlocking()) {
69 | m_InnerChannel.configureBlocking(false);
70 | }
71 | m_InnerChannel.register(m_Selector, SelectionKey.OP_READ, this);//注册读事件
72 | }
73 |
74 |
75 | protected boolean write(ByteBuffer buffer, boolean copyRemainData) throws Exception {
76 | int bytesSent;
77 | while (buffer.hasRemaining()) {
78 | bytesSent = m_InnerChannel.write(buffer);
79 | if (bytesSent == 0) {
80 | break;//不能再发送了,终止循环
81 | }
82 | }
83 |
84 | if (buffer.hasRemaining()) {//数据没有发送完毕
85 | if (copyRemainData) {//拷贝剩余数据,然后侦听写入事件,待可写入时写入。
86 | //拷贝剩余数据
87 | if (m_SendRemainBuffer == null) {
88 | m_SendRemainBuffer = ByteBuffer.allocate(buffer.capacity());
89 | }
90 | m_SendRemainBuffer.clear();
91 | m_SendRemainBuffer.put(buffer);
92 | m_SendRemainBuffer.flip();
93 | m_InnerChannel.register(m_Selector, SelectionKey.OP_WRITE, this);//注册写事件
94 | }
95 | return false;
96 | } else {//发送完毕了
97 | return true;
98 | }
99 | }
100 |
101 | protected void onTunnelEstablished() throws Exception {
102 | this.beginReceive();//开始接收数据
103 | m_BrotherTunnel.beginReceive();//兄弟也开始收数据吧
104 | }
105 |
106 | @SuppressLint("DefaultLocale")
107 | public void onConnectable() {
108 | try {
109 | if (m_InnerChannel.finishConnect()) {//连接成功
110 | onConnected(GL_BUFFER);//通知子类TCP已连接,子类可以根据协议实现握手等。
111 | } else {//连接失败
112 | LocalVpnService.Instance.writeLog("Error: connect to %s failed.", m_ServerEP);
113 | this.dispose();
114 | }
115 | } catch (Exception e) {
116 | LocalVpnService.Instance.writeLog("Error: connect to %s failed: %s", m_ServerEP, e);
117 | this.dispose();
118 | }
119 | }
120 |
121 | public void onReadable(SelectionKey key) {
122 | try {
123 | ByteBuffer buffer = GL_BUFFER;
124 | buffer.clear();
125 | int bytesRead = m_InnerChannel.read(buffer);
126 | if (bytesRead > 0) {
127 | buffer.flip();
128 | afterReceived(buffer);//先让子类处理,例如解密数据。
129 | if (isTunnelEstablished() && buffer.hasRemaining()) {//将读到的数据,转发给兄弟。
130 | m_BrotherTunnel.beforeSend(buffer);//发送之前,先让子类处理,例如做加密等。
131 | if (!m_BrotherTunnel.write(buffer, true)) {
132 | key.cancel();//兄弟吃不消,就取消读取事件。
133 | if (ProxyConfig.IS_DEBUG)
134 | System.out.printf("%s can not read more.\n", m_ServerEP);
135 | }
136 | }
137 | } else if (bytesRead < 0) {
138 | this.dispose();//连接已关闭,释放资源。
139 | }
140 | } catch (Exception e) {
141 | e.printStackTrace();
142 | this.dispose();
143 | }
144 | }
145 |
146 | public void onWritable(SelectionKey key) {
147 | try {
148 | this.beforeSend(m_SendRemainBuffer);//发送之前,先让子类处理,例如做加密等。
149 | if (this.write(m_SendRemainBuffer, false)) {//如果剩余数据已经发送完毕
150 | key.cancel();//取消写事件。
151 | if (isTunnelEstablished()) {
152 | m_BrotherTunnel.beginReceive();//这边数据发送完毕,通知兄弟可以收数据了。
153 | } else {
154 | this.beginReceive();//开始接收代理服务器响应数据
155 | }
156 | }
157 | } catch (Exception e) {
158 | this.dispose();
159 | }
160 | }
161 |
162 | public void dispose() {
163 | disposeInternal(true);
164 | }
165 |
166 | void disposeInternal(boolean disposeBrother) {
167 | if (m_Disposed) {
168 | return;
169 | } else {
170 | try {
171 | m_InnerChannel.close();
172 | } catch (Exception e) {
173 | }
174 |
175 | if (m_BrotherTunnel != null && disposeBrother) {
176 | m_BrotherTunnel.disposeInternal(false);//把兄弟的资源也释放了。
177 | }
178 |
179 | m_InnerChannel = null;
180 | m_SendRemainBuffer = null;
181 | m_Selector = null;
182 | m_BrotherTunnel = null;
183 | m_Disposed = true;
184 | SessionCount--;
185 |
186 | onDispose();
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/httpconnect/HttpConnectConfig.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tunnel.httpconnect;
2 |
3 | import android.net.Uri;
4 |
5 | import com.vm.shadowsocks.tunnel.Config;
6 |
7 | import java.net.InetSocketAddress;
8 |
9 | public class HttpConnectConfig extends Config {
10 | public String UserName;
11 | public String Password;
12 |
13 | public static HttpConnectConfig parse(String proxyInfo) {
14 | HttpConnectConfig config = new HttpConnectConfig();
15 | Uri uri = Uri.parse(proxyInfo);
16 | String userInfoString = uri.getUserInfo();
17 | if (userInfoString != null) {
18 | String[] userStrings = userInfoString.split(":");
19 | config.UserName = userStrings[0];
20 | if (userStrings.length >= 2) {
21 | config.Password = userStrings[1];
22 | }
23 | }
24 | config.ServerAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
25 | return config;
26 | }
27 |
28 | @Override
29 | public boolean equals(Object o) {
30 | if (o == null)
31 | return false;
32 | return this.toString().equals(o.toString());
33 | }
34 |
35 | @Override
36 | public String toString() {
37 | return String.format("http://%s:%s@%s", UserName, Password, ServerAddress);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/httpconnect/HttpConnectTunnel.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tunnel.httpconnect;
2 |
3 | import com.vm.shadowsocks.core.ProxyConfig;
4 | import com.vm.shadowsocks.tunnel.Tunnel;
5 |
6 | import java.io.IOException;
7 | import java.nio.ByteBuffer;
8 | import java.nio.channels.Selector;
9 |
10 | public class HttpConnectTunnel extends Tunnel {
11 |
12 | private boolean m_TunnelEstablished;
13 | private HttpConnectConfig m_Config;
14 |
15 | public HttpConnectTunnel(HttpConnectConfig config, Selector selector) throws IOException {
16 | super(config.ServerAddress, selector);
17 | m_Config = config;
18 | }
19 |
20 | @Override
21 | protected void onConnected(ByteBuffer buffer) throws Exception {
22 | String request = String.format("CONNECT %s:%d HTTP/1.0\r\nProxy-Connection: keep-alive\r\nUser-Agent: %s\r\nX-App-Install-ID: %s\r\n\r\n",
23 | m_DestAddress.getHostName(),
24 | m_DestAddress.getPort(),
25 | ProxyConfig.Instance.getUserAgent(),
26 | ProxyConfig.AppInstallID);
27 |
28 | buffer.clear();
29 | buffer.put(request.getBytes());
30 | buffer.flip();
31 | if (this.write(buffer, true)) {//发送连接请求到代理服务器
32 | this.beginReceive();//开始接收代理服务器响应数据
33 | }
34 | }
35 |
36 | void trySendPartOfHeader(ByteBuffer buffer) throws Exception {
37 | int bytesSent = 0;
38 | if (buffer.remaining() > 10) {
39 | int pos = buffer.position() + buffer.arrayOffset();
40 | String firString = new String(buffer.array(), pos, 10).toUpperCase();
41 | if (firString.startsWith("GET /") || firString.startsWith("POST /")) {
42 | int limit = buffer.limit();
43 | buffer.limit(buffer.position() + 10);
44 | super.write(buffer, false);
45 | bytesSent = 10 - buffer.remaining();
46 | buffer.limit(limit);
47 | if (ProxyConfig.IS_DEBUG)
48 | System.out.printf("Send %d bytes(%s) to %s\n", bytesSent, firString, m_DestAddress);
49 | }
50 | }
51 | }
52 |
53 |
54 | @Override
55 | protected void beforeSend(ByteBuffer buffer) throws Exception {
56 | if (ProxyConfig.Instance.isIsolateHttpHostHeader()) {
57 | trySendPartOfHeader(buffer);//尝试发送请求头的一部分,让请求头的host在第二个包里面发送,从而绕过机房的白名单机制。
58 | }
59 | }
60 |
61 | @Override
62 | protected void afterReceived(ByteBuffer buffer) throws Exception {
63 | if (!m_TunnelEstablished) {
64 | //收到代理服务器响应数据
65 | //分析响应并判断是否连接成功
66 | String response = new String(buffer.array(), buffer.position(), 12);
67 | if (response.matches("^HTTP/1.[01] 200$")) {
68 | buffer.limit(buffer.position());
69 | } else {
70 | throw new Exception(String.format("Proxy server responsed an error: %s", response));
71 | }
72 |
73 | m_TunnelEstablished = true;
74 | super.onTunnelEstablished();
75 | }
76 | }
77 |
78 | @Override
79 | protected boolean isTunnelEstablished() {
80 | return m_TunnelEstablished;
81 | }
82 |
83 | @Override
84 | protected void onDispose() {
85 | m_Config = null;
86 | }
87 |
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/AesCrypt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Blake
3 | * All Rights Reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | *
11 | * 2. Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | *
15 | * 3. The name of the author may not be used to endorse or promote
16 | * products derived from this software without specific prior
17 | * written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | * POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package com.vm.shadowsocks.tunnel.shadowsocks;
33 |
34 | import org.bouncycastle.crypto.StreamBlockCipher;
35 | import org.bouncycastle.crypto.engines.AESFastEngine;
36 | import org.bouncycastle.crypto.modes.CFBBlockCipher;
37 | import org.bouncycastle.crypto.modes.OFBBlockCipher;
38 |
39 | import java.io.ByteArrayOutputStream;
40 | import java.security.InvalidAlgorithmParameterException;
41 | import java.util.HashMap;
42 | import java.util.Map;
43 |
44 | import javax.crypto.SecretKey;
45 | import javax.crypto.spec.SecretKeySpec;
46 |
47 | /**
48 | * AES Crypt implementation
49 | */
50 | public class AesCrypt extends CryptBase {
51 |
52 | public final static String CIPHER_AES_128_CFB = "aes-128-cfb";
53 | public final static String CIPHER_AES_192_CFB = "aes-192-cfb";
54 | public final static String CIPHER_AES_256_CFB = "aes-256-cfb";
55 | public final static String CIPHER_AES_128_OFB = "aes-128-ofb";
56 | public final static String CIPHER_AES_192_OFB = "aes-192-ofb";
57 | public final static String CIPHER_AES_256_OFB = "aes-256-ofb";
58 |
59 | public static Map getCiphers() {
60 | Map ciphers = new HashMap();
61 | ciphers.put(CIPHER_AES_128_CFB, AesCrypt.class.getName());
62 | ciphers.put(CIPHER_AES_192_CFB, AesCrypt.class.getName());
63 | ciphers.put(CIPHER_AES_256_CFB, AesCrypt.class.getName());
64 | ciphers.put(CIPHER_AES_128_OFB, AesCrypt.class.getName());
65 | ciphers.put(CIPHER_AES_192_OFB, AesCrypt.class.getName());
66 | ciphers.put(CIPHER_AES_256_OFB, AesCrypt.class.getName());
67 |
68 | return ciphers;
69 | }
70 |
71 | public AesCrypt(String name, String password) {
72 | super(name, password);
73 | }
74 |
75 | @Override
76 | public int getKeyLength() {
77 | if (_name.equals(CIPHER_AES_128_CFB) || _name.equals(CIPHER_AES_128_OFB)) {
78 | return 16;
79 | } else if (_name.equals(CIPHER_AES_192_CFB) || _name.equals(CIPHER_AES_192_OFB)) {
80 | return 24;
81 | } else if (_name.equals(CIPHER_AES_256_CFB) || _name.equals(CIPHER_AES_256_OFB)) {
82 | return 32;
83 | }
84 |
85 | return 0;
86 | }
87 |
88 | @Override
89 | protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
90 | AESFastEngine engine = new AESFastEngine();
91 | StreamBlockCipher cipher;
92 |
93 | if (_name.equals(CIPHER_AES_128_CFB)) {
94 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
95 | } else if (_name.equals(CIPHER_AES_192_CFB)) {
96 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
97 | } else if (_name.equals(CIPHER_AES_256_CFB)) {
98 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
99 | } else if (_name.equals(CIPHER_AES_128_OFB)) {
100 | cipher = new OFBBlockCipher(engine, getIVLength() * 8);
101 | } else if (_name.equals(CIPHER_AES_192_OFB)) {
102 | cipher = new OFBBlockCipher(engine, getIVLength() * 8);
103 | } else if (_name.equals(CIPHER_AES_256_OFB)) {
104 | cipher = new OFBBlockCipher(engine, getIVLength() * 8);
105 | } else {
106 | throw new InvalidAlgorithmParameterException(_name);
107 | }
108 |
109 | return cipher;
110 | }
111 |
112 | @Override
113 | public int getIVLength() {
114 | return 16;
115 | }
116 |
117 | @Override
118 | protected SecretKey getKey() {
119 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
120 | }
121 |
122 | @Override
123 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
124 | int noBytesProcessed;
125 | byte[] buffer = new byte[data.length];
126 |
127 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
128 | stream.write(buffer, 0, noBytesProcessed);
129 | }
130 |
131 | @Override
132 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
133 | int noBytesProcessed;
134 | byte[] buffer = new byte[data.length];
135 |
136 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
137 | stream.write(buffer, 0, noBytesProcessed);
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/BlowFishCrypt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Blake
3 | * All Rights Reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | *
11 | * 2. Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | *
15 | * 3. The name of the author may not be used to endorse or promote
16 | * products derived from this software without specific prior
17 | * written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | * POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package com.vm.shadowsocks.tunnel.shadowsocks;
33 |
34 | import org.bouncycastle.crypto.StreamBlockCipher;
35 | import org.bouncycastle.crypto.engines.BlowfishEngine;
36 | import org.bouncycastle.crypto.modes.CFBBlockCipher;
37 |
38 | import java.io.ByteArrayOutputStream;
39 | import java.security.InvalidAlgorithmParameterException;
40 | import java.util.HashMap;
41 | import java.util.Map;
42 |
43 | import javax.crypto.SecretKey;
44 | import javax.crypto.spec.SecretKeySpec;
45 |
46 | /**
47 | * Blow fish cipher implementation
48 | */
49 | public class BlowFishCrypt extends CryptBase {
50 |
51 | public final static String CIPHER_BLOWFISH_CFB = "bf-cfb";
52 |
53 | public static Map getCiphers() {
54 | Map ciphers = new HashMap();
55 | ciphers.put(CIPHER_BLOWFISH_CFB, BlowFishCrypt.class.getName());
56 |
57 | return ciphers;
58 | }
59 |
60 | public BlowFishCrypt(String name, String password) {
61 | super(name, password);
62 | }
63 |
64 | @Override
65 | public int getKeyLength() {
66 | return 16;
67 | }
68 |
69 | @Override
70 | protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
71 | BlowfishEngine engine = new BlowfishEngine();
72 | StreamBlockCipher cipher;
73 |
74 | if (_name.equals(CIPHER_BLOWFISH_CFB)) {
75 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
76 | } else {
77 | throw new InvalidAlgorithmParameterException(_name);
78 | }
79 |
80 | return cipher;
81 | }
82 |
83 | @Override
84 | public int getIVLength() {
85 | return 8;
86 | }
87 |
88 | @Override
89 | protected SecretKey getKey() {
90 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
91 | }
92 |
93 | @Override
94 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
95 | int noBytesProcessed;
96 | byte[] buffer = new byte[data.length];
97 |
98 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
99 | stream.write(buffer, 0, noBytesProcessed);
100 | }
101 |
102 | @Override
103 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
104 | int noBytesProcessed;
105 | byte[] buffer = new byte[data.length];
106 |
107 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
108 | stream.write(buffer, 0, noBytesProcessed);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/CamelliaCrypt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Blake
3 | * All Rights Reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | *
11 | * 2. Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | *
15 | * 3. The name of the author may not be used to endorse or promote
16 | * products derived from this software without specific prior
17 | * written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | * POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package com.vm.shadowsocks.tunnel.shadowsocks;
33 |
34 | import org.bouncycastle.crypto.StreamBlockCipher;
35 | import org.bouncycastle.crypto.engines.CamelliaEngine;
36 | import org.bouncycastle.crypto.modes.CFBBlockCipher;
37 |
38 | import java.io.ByteArrayOutputStream;
39 | import java.security.InvalidAlgorithmParameterException;
40 | import java.util.HashMap;
41 | import java.util.Map;
42 |
43 | import javax.crypto.SecretKey;
44 | import javax.crypto.spec.SecretKeySpec;
45 |
46 | /**
47 | * Camellia cipher implementation
48 | */
49 | public class CamelliaCrypt extends CryptBase {
50 |
51 | public final static String CIPHER_CAMELLIA_128_CFB = "camellia-128-cfb";
52 | public final static String CIPHER_CAMELLIA_192_CFB = "camellia-192-cfb";
53 | public final static String CIPHER_CAMELLIA_256_CFB = "camellia-256-cfb";
54 |
55 | public static Map getCiphers() {
56 | Map ciphers = new HashMap();
57 | ciphers.put(CIPHER_CAMELLIA_128_CFB, CamelliaCrypt.class.getName());
58 | ciphers.put(CIPHER_CAMELLIA_192_CFB, CamelliaCrypt.class.getName());
59 | ciphers.put(CIPHER_CAMELLIA_256_CFB, CamelliaCrypt.class.getName());
60 |
61 | return ciphers;
62 | }
63 |
64 | public CamelliaCrypt(String name, String password) {
65 | super(name, password);
66 | }
67 |
68 | @Override
69 | public int getKeyLength() {
70 | if (_name.equals(CIPHER_CAMELLIA_128_CFB)) {
71 | return 16;
72 | } else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) {
73 | return 24;
74 | } else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) {
75 | return 32;
76 | }
77 |
78 | return 0;
79 | }
80 |
81 | @Override
82 | protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
83 | CamelliaEngine engine = new CamelliaEngine();
84 | StreamBlockCipher cipher;
85 |
86 | if (_name.equals(CIPHER_CAMELLIA_128_CFB)) {
87 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
88 | } else if (_name.equals(CIPHER_CAMELLIA_192_CFB)) {
89 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
90 | } else if (_name.equals(CIPHER_CAMELLIA_256_CFB)) {
91 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
92 | } else {
93 | throw new InvalidAlgorithmParameterException(_name);
94 | }
95 |
96 | return cipher;
97 | }
98 |
99 | @Override
100 | public int getIVLength() {
101 | return 16;
102 | }
103 |
104 | @Override
105 | protected SecretKey getKey() {
106 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
107 | }
108 |
109 | @Override
110 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
111 | int noBytesProcessed;
112 | byte[] buffer = new byte[data.length];
113 |
114 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
115 | stream.write(buffer, 0, noBytesProcessed);
116 | }
117 |
118 | @Override
119 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
120 | int noBytesProcessed;
121 | byte[] buffer = new byte[data.length];
122 |
123 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
124 | stream.write(buffer, 0, noBytesProcessed);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/Chacha20Crypt.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tunnel.shadowsocks;
2 |
3 | import org.bouncycastle.crypto.StreamCipher;
4 | import org.bouncycastle.crypto.engines.ChaChaEngine;
5 | import org.bouncycastle.crypto.engines.ChaCha7539Engine;
6 |
7 | import java.io.ByteArrayOutputStream;
8 | import java.security.InvalidAlgorithmParameterException;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | import javax.crypto.SecretKey;
13 | import javax.crypto.spec.SecretKeySpec;
14 |
15 | public class Chacha20Crypt extends CryptBase {
16 | public final static String CIPHER_CHACHA20 = "chacha20";
17 | public final static String CIPHER_CHACHA20_IETF = "chacha20-ietf";
18 |
19 | public static Map getCiphers() {
20 | Map ciphers = new HashMap<>();
21 | ciphers.put(CIPHER_CHACHA20, Chacha20Crypt.class.getName());
22 | ciphers.put(CIPHER_CHACHA20_IETF, Chacha20Crypt.class.getName());
23 | return ciphers;
24 | }
25 |
26 | public Chacha20Crypt(String name, String password) {
27 | super(name, password);
28 | }
29 |
30 | @Override
31 | protected StreamCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
32 | if (_name.equals(CIPHER_CHACHA20)) {
33 | return new ChaChaEngine();
34 | }
35 | else if (_name.equals(CIPHER_CHACHA20_IETF)) {
36 | return new ChaCha7539Engine();
37 | }
38 | return null;
39 | }
40 |
41 | @Override
42 | protected SecretKey getKey() {
43 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
44 |
45 | }
46 |
47 | @Override
48 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
49 | int noBytesProcessed;
50 | byte[] buffer = new byte[data.length];
51 |
52 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
53 | stream.write(buffer, 0, noBytesProcessed);
54 | }
55 |
56 | @Override
57 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
58 | int BytesProcessedNum;
59 | byte[] buffer = new byte[data.length];
60 | BytesProcessedNum = decCipher.processBytes(data, 0, data.length, buffer, 0);
61 | stream.write(buffer, 0, BytesProcessedNum);
62 |
63 | }
64 |
65 | @Override
66 | public int getKeyLength() {
67 | if (_name.equals(CIPHER_CHACHA20) || _name.equals(CIPHER_CHACHA20_IETF)) {
68 | return 32;
69 | }
70 | return 0;
71 | }
72 |
73 | @Override
74 | public int getIVLength() {
75 | if (_name.equals(CIPHER_CHACHA20)) {
76 | return 8;
77 | }
78 | else if (_name.equals(CIPHER_CHACHA20_IETF)) {
79 | return 12;
80 | }
81 | return 0;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/CryptBase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Blake
3 | * All Rights Reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | *
11 | * 2. Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | *
15 | * 3. The name of the author may not be used to endorse or promote
16 | * products derived from this software without specific prior
17 | * written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | * POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package com.vm.shadowsocks.tunnel.shadowsocks;
33 |
34 | import org.bouncycastle.crypto.CipherParameters;
35 | import org.bouncycastle.crypto.StreamCipher;
36 | import org.bouncycastle.crypto.params.KeyParameter;
37 | import org.bouncycastle.crypto.params.ParametersWithIV;
38 |
39 | import java.io.ByteArrayOutputStream;
40 | import java.io.IOException;
41 | import java.security.InvalidAlgorithmParameterException;
42 | import java.security.MessageDigest;
43 | import java.security.SecureRandom;
44 | import java.util.concurrent.locks.Lock;
45 | import java.util.concurrent.locks.ReentrantLock;
46 | import java.util.logging.Logger;
47 |
48 | import javax.crypto.SecretKey;
49 |
50 | /**
51 | * Crypt base class implementation
52 | */
53 | public abstract class CryptBase implements ICrypt {
54 |
55 | protected abstract StreamCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException;
56 |
57 | protected abstract SecretKey getKey();
58 |
59 | protected abstract void _encrypt(byte[] data, ByteArrayOutputStream stream);
60 |
61 | protected abstract void _decrypt(byte[] data, ByteArrayOutputStream stream);
62 |
63 | protected CipherParameters getCipherParameters(byte[] iv){
64 | _decryptIV = new byte[_ivLength];
65 | System.arraycopy(iv, 0, _decryptIV, 0, _ivLength);
66 | return new ParametersWithIV(new KeyParameter(_key.getEncoded()), _decryptIV);
67 | }
68 |
69 | protected final String _name;
70 | protected final SecretKey _key;
71 | protected final ShadowSocksKey _ssKey;
72 | protected final int _ivLength;
73 | protected final int _keyLength;
74 | protected boolean _encryptIVSet;
75 | protected boolean _decryptIVSet;
76 | protected byte[] _encryptIV;
77 | protected byte[] _decryptIV;
78 | protected final Lock encLock = new ReentrantLock();
79 | protected final Lock decLock = new ReentrantLock();
80 | protected StreamCipher encCipher;
81 | protected StreamCipher decCipher;
82 | private Logger logger = Logger.getLogger(CryptBase.class.getName());
83 |
84 | public CryptBase(String name, String password) {
85 | _name = name.toLowerCase();
86 | _ivLength = getIVLength();
87 | _keyLength = getKeyLength();
88 | _ssKey = new ShadowSocksKey(password, _keyLength);
89 | _key = getKey();
90 | }
91 |
92 | protected void setIV(byte[] iv, boolean isEncrypt) {
93 | if (_ivLength == 0) {
94 | return;
95 | }
96 |
97 | CipherParameters cipherParameters = null;
98 |
99 | if (isEncrypt) {
100 | cipherParameters = getCipherParameters(iv);
101 | try {
102 | encCipher = getCipher(isEncrypt);
103 | } catch (InvalidAlgorithmParameterException e) {
104 | logger.info(e.toString());
105 | }
106 | encCipher.init(isEncrypt, cipherParameters);
107 | } else {
108 | _decryptIV = new byte[_ivLength];
109 | System.arraycopy(iv, 0, _decryptIV, 0, _ivLength);
110 | cipherParameters = getCipherParameters(iv);
111 | try {
112 | decCipher = getCipher(isEncrypt);
113 | } catch (InvalidAlgorithmParameterException e) {
114 | logger.info(e.toString());
115 | }
116 | decCipher.init(isEncrypt, cipherParameters);
117 | }
118 | }
119 |
120 | public byte[] encrypt(byte[] data) {
121 | ByteArrayOutputStream stream = new ByteArrayOutputStream();
122 | encrypt(data, stream);
123 | return stream.toByteArray();
124 | }
125 |
126 | public byte[] decrypt(byte[] data) {
127 | ByteArrayOutputStream stream = new ByteArrayOutputStream();
128 | decrypt(data, stream);
129 | return stream.toByteArray();
130 | }
131 |
132 | @Override
133 | public void encrypt(byte[] data, ByteArrayOutputStream stream) {
134 | synchronized (encLock) {
135 | stream.reset();
136 | if (!_encryptIVSet) {
137 | _encryptIVSet = true;
138 | byte[] iv = new byte[_ivLength];
139 | new SecureRandom().nextBytes(iv);
140 | setIV(iv, true);
141 | try {
142 | stream.write(iv);
143 | } catch (IOException e) {
144 | logger.info(e.toString());
145 | }
146 |
147 | }
148 |
149 | _encrypt(data, stream);
150 | }
151 | }
152 |
153 | @Override
154 | public void encrypt(byte[] data, int length, ByteArrayOutputStream stream) {
155 | byte[] d = new byte[length];
156 | System.arraycopy(data, 0, d, 0, length);
157 | encrypt(d, stream);
158 | }
159 |
160 | @Override
161 | public void decrypt(byte[] data, ByteArrayOutputStream stream) {
162 | byte[] temp;
163 |
164 | synchronized (decLock) {
165 | stream.reset();
166 | if (!_decryptIVSet) {
167 | _decryptIVSet = true;
168 | setIV(data, false);
169 | temp = new byte[data.length - _ivLength];
170 | System.arraycopy(data, _ivLength, temp, 0, data.length - _ivLength);
171 | } else {
172 | temp = data;
173 | }
174 |
175 | _decrypt(temp, stream);
176 | }
177 | }
178 |
179 | @Override
180 | public void decrypt(byte[] data, int length, ByteArrayOutputStream stream) {
181 | byte[] d = new byte[length];
182 | System.arraycopy(data, 0, d, 0, length);
183 | decrypt(d, stream);
184 | }
185 |
186 |
187 | public static byte[] md5Digest(byte[] input) {
188 | try {
189 | MessageDigest md5 = MessageDigest.getInstance("MD5");
190 | return md5.digest(input);
191 | } catch (Exception e) {
192 | throw new RuntimeException(e);
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/CryptFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Blake
3 | * All Rights Reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | *
11 | * 2. Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | *
15 | * 3. The name of the author may not be used to endorse or promote
16 | * products derived from this software without specific prior
17 | * written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | * POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package com.vm.shadowsocks.tunnel.shadowsocks;
33 |
34 | import java.lang.reflect.Constructor;
35 | import java.util.ArrayList;
36 | import java.util.Collections;
37 | import java.util.HashMap;
38 | import java.util.List;
39 | import java.util.Map;
40 | import java.util.logging.Logger;
41 |
42 | /**
43 | * Crypt factory
44 | */
45 | public class CryptFactory {
46 | private static final Map crypts = new HashMap() {{
47 | putAll(AesCrypt.getCiphers());
48 | putAll(CamelliaCrypt.getCiphers());
49 | putAll(BlowFishCrypt.getCiphers());
50 | putAll(SeedCrypt.getCiphers());
51 | putAll(Chacha20Crypt.getCiphers());
52 | putAll(Rc4Md5Crypt.getCiphers());
53 | // TODO: other crypts
54 | }};
55 | private static Logger logger = Logger.getLogger(CryptFactory.class.getName());
56 |
57 | public static boolean isCipherExisted(String name) {
58 | return (crypts.get(name) != null);
59 | }
60 |
61 | public static ICrypt get(String name, String password) {
62 | try {
63 | Object obj = getObj(crypts.get(name), String.class, name, String.class, password);
64 | return (ICrypt) obj;
65 |
66 | } catch (Exception e) {
67 | logger.info(e.getMessage());
68 | }
69 |
70 | return null;
71 | }
72 |
73 | public static List getSupportedCiphers() {
74 | List sortedKeys = new ArrayList(crypts.keySet());
75 | Collections.sort(sortedKeys);
76 | return sortedKeys;
77 | }
78 |
79 | public static Object getObj(String className, Object... args) {
80 | Object retValue = null;
81 | try {
82 | Class c = Class.forName(className);
83 | if (args.length == 0) {
84 | retValue = c.newInstance();
85 | } else if ((args.length & 1) == 0) {
86 | // args should come with pairs, for example
87 | // String.class, "arg1_value", String.class, "arg2_value"
88 | Class[] oParam = new Class[args.length / 2];
89 | for (int arg_i = 0, i = 0; arg_i < args.length; arg_i += 2, i++) {
90 | oParam[i] = (Class) args[arg_i];
91 | }
92 |
93 | Constructor constructor = c.getConstructor(oParam);
94 | Object[] paramObjs = new Object[args.length / 2];
95 | for (int arg_i = 1, i = 0; arg_i < args.length; arg_i += 2, i++) {
96 | paramObjs[i] = args[arg_i];
97 | }
98 | retValue = constructor.newInstance(paramObjs);
99 | }
100 | } catch (Exception e) {
101 | e.printStackTrace();
102 | }
103 |
104 | return retValue;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/ICrypt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Blake
3 | * All Rights Reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | *
11 | * 2. Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | *
15 | * 3. The name of the author may not be used to endorse or promote
16 | * products derived from this software without specific prior
17 | * written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | * POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package com.vm.shadowsocks.tunnel.shadowsocks;
33 |
34 | import java.io.ByteArrayOutputStream;
35 |
36 | /**
37 | * Interface of crypt
38 | */
39 | public interface ICrypt {
40 | byte[] encrypt(byte[] data);
41 |
42 | byte[] decrypt(byte[] data);
43 |
44 | void encrypt(byte[] data, ByteArrayOutputStream stream);
45 |
46 | void encrypt(byte[] data, int length, ByteArrayOutputStream stream);
47 |
48 | void decrypt(byte[] data, ByteArrayOutputStream stream);
49 |
50 | void decrypt(byte[] data, int length, ByteArrayOutputStream stream);
51 |
52 | int getIVLength();
53 |
54 | int getKeyLength();
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/Rc4Md5Crypt.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tunnel.shadowsocks;
2 |
3 | import org.bouncycastle.crypto.CipherParameters;
4 | import org.bouncycastle.crypto.StreamCipher;
5 | import org.bouncycastle.crypto.engines.RC4Engine;
6 | import org.bouncycastle.crypto.params.KeyParameter;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.security.InvalidAlgorithmParameterException;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | import javax.crypto.SecretKey;
14 | import javax.crypto.spec.SecretKeySpec;
15 |
16 |
17 | public class Rc4Md5Crypt extends CryptBase {
18 | public static String CIPHER_RC4_MD5 = "rc4-md5";
19 |
20 | public static Map getCiphers() {
21 | Map ciphers = new HashMap();
22 | ciphers.put(CIPHER_RC4_MD5, Rc4Md5Crypt.class.getName());
23 | return ciphers;
24 | }
25 |
26 | public Rc4Md5Crypt(String name, String password) {
27 | super(name, password);
28 | }
29 |
30 | @Override
31 | protected StreamCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
32 | return new RC4Engine();
33 | }
34 |
35 | @Override
36 | protected SecretKey getKey() {
37 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
38 | }
39 |
40 | @Override
41 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
42 | int noBytesProcessed;
43 | byte[] buffer = new byte[data.length];
44 |
45 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
46 | stream.write(buffer, 0, noBytesProcessed);
47 | }
48 |
49 | @Override
50 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
51 | int noBytesProcessed;
52 | byte[] buffer = new byte[data.length];
53 |
54 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
55 | stream.write(buffer, 0, noBytesProcessed);
56 | }
57 |
58 | @Override
59 | public int getIVLength() {
60 | return 16;
61 | }
62 |
63 | @Override
64 | public int getKeyLength() {
65 | return 16;
66 | }
67 |
68 | @Override
69 | protected CipherParameters getCipherParameters(byte[] iv) {
70 | byte[] bts = new byte[_keyLength + _ivLength];
71 | System.arraycopy(_key.getEncoded(), 0, bts, 0, _keyLength);
72 | System.arraycopy(iv, 0, bts, _keyLength, _ivLength);
73 | return new KeyParameter(md5Digest(bts));
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/SeedCrypt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Blake
3 | * All Rights Reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | *
11 | * 2. Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | *
15 | * 3. The name of the author may not be used to endorse or promote
16 | * products derived from this software without specific prior
17 | * written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | * POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package com.vm.shadowsocks.tunnel.shadowsocks;
33 |
34 | import org.bouncycastle.crypto.StreamBlockCipher;
35 | import org.bouncycastle.crypto.engines.SEEDEngine;
36 | import org.bouncycastle.crypto.modes.CFBBlockCipher;
37 |
38 | import java.io.ByteArrayOutputStream;
39 | import java.security.InvalidAlgorithmParameterException;
40 | import java.util.HashMap;
41 | import java.util.Map;
42 |
43 | import javax.crypto.SecretKey;
44 | import javax.crypto.spec.SecretKeySpec;
45 |
46 | /**
47 | * Seed cipher implementation
48 | */
49 | public class SeedCrypt extends CryptBase {
50 |
51 | public final static String CIPHER_SEED_CFB = "seed-cfb";
52 |
53 | public static Map getCiphers() {
54 | Map ciphers = new HashMap();
55 | ciphers.put(CIPHER_SEED_CFB, SeedCrypt.class.getName());
56 |
57 | return ciphers;
58 | }
59 |
60 | public SeedCrypt(String name, String password) {
61 | super(name, password);
62 | }
63 |
64 | @Override
65 | public int getKeyLength() {
66 | return 16;
67 | }
68 |
69 | @Override
70 | protected StreamBlockCipher getCipher(boolean isEncrypted) throws InvalidAlgorithmParameterException {
71 | SEEDEngine engine = new SEEDEngine();
72 | StreamBlockCipher cipher;
73 |
74 | if (_name.equals(CIPHER_SEED_CFB)) {
75 | cipher = new CFBBlockCipher(engine, getIVLength() * 8);
76 | } else {
77 | throw new InvalidAlgorithmParameterException(_name);
78 | }
79 |
80 | return cipher;
81 | }
82 |
83 | @Override
84 | public int getIVLength() {
85 | return 16;
86 | }
87 |
88 | @Override
89 | protected SecretKey getKey() {
90 | return new SecretKeySpec(_ssKey.getEncoded(), "AES");
91 | }
92 |
93 | @Override
94 | protected void _encrypt(byte[] data, ByteArrayOutputStream stream) {
95 | int noBytesProcessed;
96 | byte[] buffer = new byte[data.length];
97 |
98 | noBytesProcessed = encCipher.processBytes(data, 0, data.length, buffer, 0);
99 | stream.write(buffer, 0, noBytesProcessed);
100 | }
101 |
102 | @Override
103 | protected void _decrypt(byte[] data, ByteArrayOutputStream stream) {
104 | int noBytesProcessed;
105 | byte[] buffer = new byte[data.length];
106 |
107 | noBytesProcessed = decCipher.processBytes(data, 0, data.length, buffer, 0);
108 | stream.write(buffer, 0, noBytesProcessed);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/ShadowSocksKey.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Blake
3 | * All Rights Reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright notice,
9 | * this list of conditions and the following disclaimer.
10 | *
11 | * 2. Redistributions in binary form must reproduce the above copyright notice,
12 | * this list of conditions and the following disclaimer in the documentation
13 | * and/or other materials provided with the distribution.
14 | *
15 | * 3. The name of the author may not be used to endorse or promote
16 | * products derived from this software without specific prior
17 | * written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | * POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | package com.vm.shadowsocks.tunnel.shadowsocks;
33 |
34 | import java.io.UnsupportedEncodingException;
35 | import java.security.MessageDigest;
36 | import java.util.logging.Logger;
37 |
38 | import javax.crypto.SecretKey;
39 |
40 | /**
41 | * Shadowsocks key generator
42 | */
43 | public class ShadowSocksKey implements SecretKey {
44 |
45 | private Logger logger = Logger.getLogger(ShadowSocksKey.class.getName());
46 | private final static int KEY_LENGTH = 32;
47 | private byte[] _key;
48 | private int _length;
49 |
50 | public ShadowSocksKey(String password) {
51 | _length = KEY_LENGTH;
52 | _key = init(password);
53 | }
54 |
55 | public ShadowSocksKey(String password, int length) {
56 | // TODO: Invalid key length
57 | _length = length;
58 | _key = init(password);
59 | }
60 |
61 | private byte[] init(String password) {
62 | MessageDigest md = null;
63 | byte[] keys = new byte[KEY_LENGTH];
64 | byte[] temp = null;
65 | byte[] hash = null;
66 | byte[] passwordBytes = null;
67 | int i = 0;
68 |
69 | try {
70 | md = MessageDigest.getInstance("MD5");
71 | passwordBytes = password.getBytes("UTF-8");
72 | } catch (UnsupportedEncodingException e) {
73 | logger.info("ShadowSocksKey: Unsupported string encoding");
74 | } catch (Exception e) {
75 | return null;
76 | }
77 |
78 | while (i < keys.length) {
79 | if (i == 0) {
80 | hash = md.digest(passwordBytes);
81 | temp = new byte[passwordBytes.length + hash.length];
82 | } else {
83 | System.arraycopy(hash, 0, temp, 0, hash.length);
84 | System.arraycopy(passwordBytes, 0, temp, hash.length, passwordBytes.length);
85 | hash = md.digest(temp);
86 | }
87 | System.arraycopy(hash, 0, keys, i, hash.length);
88 | i += hash.length;
89 | }
90 |
91 | if (_length != KEY_LENGTH) {
92 | byte[] keysl = new byte[_length];
93 | System.arraycopy(keys, 0, keysl, 0, _length);
94 | return keysl;
95 | }
96 | return keys;
97 | }
98 |
99 | @Override
100 | public String getAlgorithm() {
101 | return "shadowsocks";
102 | }
103 |
104 | @Override
105 | public String getFormat() {
106 | return "RAW";
107 | }
108 |
109 | @Override
110 | public byte[] getEncoded() {
111 | return _key;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/ShadowsocksConfig.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tunnel.shadowsocks;
2 |
3 | import android.net.Uri;
4 | import android.util.Base64;
5 |
6 | import com.vm.shadowsocks.tunnel.Config;
7 |
8 | import java.net.InetSocketAddress;
9 |
10 | public class ShadowsocksConfig extends Config {
11 | public String EncryptMethod;
12 | public String Password;
13 |
14 | public static ShadowsocksConfig parse(String proxyInfo) throws Exception {
15 | ShadowsocksConfig config = new ShadowsocksConfig();
16 | Uri uri = Uri.parse(proxyInfo);
17 | if (uri.getPort() == -1) {
18 | String base64String = uri.getHost();
19 | proxyInfo = "ss://" + new String(Base64.decode(base64String.getBytes("ASCII"), Base64.DEFAULT));
20 | uri = Uri.parse(proxyInfo);
21 | }
22 |
23 | String userInfoString = uri.getUserInfo();
24 | if (userInfoString != null) {
25 | String[] userStrings = userInfoString.split(":");
26 | config.EncryptMethod = userStrings[0];
27 | if (userStrings.length >= 2) {
28 | config.Password = userStrings[1];
29 | }
30 | }
31 | if (!CryptFactory.isCipherExisted(config.EncryptMethod)) {
32 | throw new Exception(String.format("Method: %s does not support", config.EncryptMethod));
33 | }
34 | config.ServerAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
35 | return config;
36 | }
37 |
38 | @Override
39 | public boolean equals(Object o) {
40 | if (o == null)
41 | return false;
42 | return this.toString().equals(o.toString());
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return String.format("ss://%s:%s@%s", EncryptMethod, Password, ServerAddress);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/tunnel/shadowsocks/ShadowsocksTunnel.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.tunnel.shadowsocks;
2 |
3 | import com.vm.shadowsocks.tunnel.Tunnel;
4 |
5 | import java.nio.ByteBuffer;
6 | import java.nio.channels.Selector;
7 |
8 | public class ShadowsocksTunnel extends Tunnel {
9 |
10 | private ICrypt m_Encryptor;
11 | private ShadowsocksConfig m_Config;
12 | private boolean m_TunnelEstablished;
13 |
14 | public ShadowsocksTunnel(ShadowsocksConfig config, Selector selector) throws Exception {
15 | super(config.ServerAddress, selector);
16 | m_Config = config;
17 | m_Encryptor = CryptFactory.get(m_Config.EncryptMethod, m_Config.Password);
18 |
19 | }
20 |
21 | @Override
22 | protected void onConnected(ByteBuffer buffer) throws Exception {
23 |
24 | buffer.clear();
25 | // https://shadowsocks.org/en/spec/protocol.html
26 |
27 | buffer.put((byte) 0x03);//domain
28 | byte[] domainBytes = m_DestAddress.getHostName().getBytes();
29 | buffer.put((byte) domainBytes.length);//domain length;
30 | buffer.put(domainBytes);
31 | buffer.putShort((short) m_DestAddress.getPort());
32 | buffer.flip();
33 | byte[] _header = new byte[buffer.limit()];
34 | buffer.get(_header);
35 |
36 | buffer.clear();
37 | buffer.put(m_Encryptor.encrypt(_header));
38 | buffer.flip();
39 |
40 | if (write(buffer, true)) {
41 | m_TunnelEstablished = true;
42 | onTunnelEstablished();
43 | } else {
44 | m_TunnelEstablished = true;
45 | this.beginReceive();
46 | }
47 | }
48 |
49 | @Override
50 | protected boolean isTunnelEstablished() {
51 | return m_TunnelEstablished;
52 | }
53 |
54 | @Override
55 | protected void beforeSend(ByteBuffer buffer) throws Exception {
56 |
57 | byte[] bytes = new byte[buffer.limit()];
58 | buffer.get(bytes);
59 |
60 | byte[] newbytes = m_Encryptor.encrypt(bytes);
61 |
62 | buffer.clear();
63 | buffer.put(newbytes);
64 | buffer.flip();
65 | }
66 |
67 | @Override
68 | protected void afterReceived(ByteBuffer buffer) throws Exception {
69 | byte[] bytes = new byte[buffer.limit()];
70 | buffer.get(bytes);
71 | byte[] newbytes = m_Encryptor.decrypt(bytes);
72 | String s = new String(newbytes);
73 | buffer.clear();
74 | buffer.put(newbytes);
75 | buffer.flip();
76 | }
77 |
78 | @Override
79 | protected void onDispose() {
80 | m_Config = null;
81 | m_Encryptor = null;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/ui/AppManager.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.ui;
2 |
3 | import android.animation.Animator;
4 | import android.app.ActionBar;
5 | import android.app.Activity;
6 | import android.content.Intent;
7 | import android.content.pm.PackageManager;
8 | import android.content.pm.ResolveInfo;
9 | import android.graphics.drawable.Drawable;
10 | import android.os.Bundle;
11 | import android.support.annotation.Nullable;
12 | import android.support.v7.widget.DefaultItemAnimator;
13 | import android.support.v7.widget.LinearLayoutManager;
14 | import android.support.v7.widget.RecyclerView;
15 | import android.view.LayoutInflater;
16 | import android.view.MenuItem;
17 | import android.view.View;
18 | import android.view.ViewGroup;
19 | import android.widget.ImageView;
20 | import android.widget.Switch;
21 |
22 | import com.futuremind.recyclerviewfastscroll.FastScroller;
23 | import com.futuremind.recyclerviewfastscroll.SectionTitleProvider;
24 | import com.vm.shadowsocks.R;
25 | import com.vm.shadowsocks.core.AppInfo;
26 | import com.vm.shadowsocks.core.AppProxyManager;
27 |
28 | import java.util.Collections;
29 | import java.util.List;
30 |
31 | import io.reactivex.Observable;
32 | import io.reactivex.ObservableEmitter;
33 | import io.reactivex.ObservableOnSubscribe;
34 | import io.reactivex.Observer;
35 | import io.reactivex.disposables.Disposable;
36 | import io.reactivex.schedulers.Schedulers;
37 | import io.reactivex.android.schedulers.AndroidSchedulers;
38 |
39 | /**
40 | * Created by so898 on 2017/5/3.
41 | */
42 |
43 | public class AppManager extends Activity{
44 | private View loadingView;
45 | private RecyclerView appListView;
46 | private FastScroller fastScroller;
47 | private AppManagerAdapter adapter;
48 |
49 | @Override
50 | public void onCreate(@Nullable Bundle savedInstanceState) {
51 | super.onCreate(savedInstanceState);
52 | this.setContentView(R.layout.layout_apps);
53 |
54 | ActionBar actionBar = getActionBar();
55 | if (actionBar != null)
56 | actionBar.setDisplayHomeAsUpEnabled(true);
57 |
58 | loadingView = findViewById(R.id.loading);
59 | appListView = (RecyclerView)findViewById(R.id.list);
60 | appListView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
61 | appListView.setItemAnimator(new DefaultItemAnimator());
62 | fastScroller = (FastScroller)findViewById(R.id.fastscroller);
63 |
64 | Observable> observable = Observable.create(new ObservableOnSubscribe>() {
65 | @Override
66 | public void subscribe(ObservableEmitter> appInfo) throws Exception {
67 | queryAppInfo();
68 | adapter = new AppManagerAdapter();
69 | appInfo.onComplete();
70 | }
71 | }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
72 | Observer> observer = new Observer>() {
73 | @Override
74 | public void onSubscribe(Disposable d) {}
75 |
76 | @Override
77 | public void onNext(List aLong) {}
78 |
79 | @Override
80 | public void onError(Throwable e) {}
81 |
82 | @Override
83 | public void onComplete() {
84 | appListView.setAdapter(adapter);
85 | fastScroller.setRecyclerView(appListView);
86 | long shortAnimTime = 1;
87 | appListView.setAlpha(0);
88 | appListView.setVisibility(View.VISIBLE);
89 | appListView.animate().alpha(1).setDuration(shortAnimTime);
90 | loadingView.animate().alpha(0).setDuration(shortAnimTime).setListener(new Animator.AnimatorListener() {
91 | @Override
92 | public void onAnimationStart(Animator animator) {}
93 |
94 | @Override
95 | public void onAnimationEnd(Animator animator) {
96 | loadingView.setVisibility(View.GONE);
97 | }
98 |
99 | @Override
100 | public void onAnimationCancel(Animator animator) {}
101 |
102 | @Override
103 | public void onAnimationRepeat(Animator animator) {}
104 | });
105 | }
106 | };
107 | observable.subscribe(observer);
108 | }
109 |
110 | public void queryAppInfo() {
111 | PackageManager pm = this.getPackageManager(); // 获得PackageManager对象
112 | Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
113 | mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
114 | List resolveInfos = pm.queryIntentActivities(mainIntent, 0);
115 | Collections.sort(resolveInfos, new ResolveInfo.DisplayNameComparator(pm));
116 | if (AppProxyManager.Instance.mlistAppInfo != null) {
117 | AppProxyManager.Instance.mlistAppInfo.clear();
118 | for (ResolveInfo reInfo : resolveInfos) {
119 | String pkgName = reInfo.activityInfo.packageName; // 获得应用程序的包名
120 | String appLabel = (String) reInfo.loadLabel(pm); // 获得应用程序的Label
121 | Drawable icon = reInfo.loadIcon(pm); // 获得应用程序图标
122 | AppInfo appInfo = new AppInfo();
123 | appInfo.setAppLabel(appLabel);
124 | appInfo.setPkgName(pkgName);
125 | appInfo.setAppIcon(icon);
126 | if (!appInfo.getPkgName().equals("com.vm.shadowsocks"))//App本身会强制加入代理列表
127 | AppProxyManager.Instance.mlistAppInfo.add(appInfo);
128 | }
129 | }
130 | }
131 |
132 | @Override
133 | public boolean onOptionsItemSelected(MenuItem item) {
134 | switch (item.getItemId()) {
135 | case android.R.id.home:
136 | finish();
137 | return true;
138 | default:
139 | return super.onOptionsItemSelected(item);
140 | }
141 | }
142 |
143 | }
144 |
145 | class AppViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
146 | private ImageView icon = (ImageView)itemView.findViewById(R.id.itemicon);
147 | private Switch check = (Switch)itemView.findViewById(R.id.itemcheck);
148 | private AppInfo item;
149 | private Boolean proxied = false;
150 |
151 | AppViewHolder(View itemView) {
152 | super(itemView);
153 | itemView.setOnClickListener(this);
154 | }
155 |
156 | void bind(AppInfo app) {
157 | this.item = app;
158 | proxied = AppProxyManager.Instance.isAppProxy(app.getPkgName());
159 | icon.setImageDrawable(app.getAppIcon());
160 | check.setText(app.getAppLabel());
161 | check.setChecked(proxied);
162 | }
163 |
164 | @Override
165 | public void onClick(View view) {
166 | if (proxied) {
167 | AppProxyManager.Instance.removeProxyApp(item.getPkgName());
168 | check.setChecked(false);
169 | } else {
170 | AppProxyManager.Instance.addProxyApp(item.getPkgName());
171 | check.setChecked(true);
172 | }
173 | proxied = !proxied;
174 | }
175 | }
176 |
177 | class AppManagerAdapter extends RecyclerView.Adapter implements SectionTitleProvider {
178 |
179 |
180 | @Override
181 | public AppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
182 | return new AppViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_apps_item, parent, false));
183 | }
184 |
185 | @Override
186 | public void onBindViewHolder(AppViewHolder holder, int position) {
187 | AppInfo appInfo = AppProxyManager.Instance.mlistAppInfo.get(position);
188 | holder.bind(appInfo);
189 | }
190 |
191 | @Override
192 | public int getItemCount() {
193 | return AppProxyManager.Instance.mlistAppInfo.size();
194 | }
195 |
196 | @Override
197 | public String getSectionTitle(int position) {
198 | AppInfo appInfo = AppProxyManager.Instance.mlistAppInfo.get(position);
199 | return appInfo.getAppLabel();
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vm/shadowsocks/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.vm.shadowsocks.ui;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.app.AlertDialog;
6 | import android.content.DialogInterface;
7 | import android.content.DialogInterface.OnClickListener;
8 | import android.content.Intent;
9 | import android.content.SharedPreferences;
10 | import android.content.SharedPreferences.Editor;
11 | import android.content.pm.PackageManager;
12 | import android.net.Uri;
13 | import android.os.Bundle;
14 | import android.text.InputType;
15 | import android.text.TextUtils;
16 | import android.util.Log;
17 | import android.view.Menu;
18 | import android.view.MenuItem;
19 | import android.view.View;
20 | import android.view.ViewGroup;
21 | import android.widget.CompoundButton;
22 | import android.widget.CompoundButton.OnCheckedChangeListener;
23 | import android.widget.EditText;
24 | import android.widget.ScrollView;
25 | import android.widget.Switch;
26 | import android.widget.TextView;
27 | import android.widget.Toast;
28 |
29 | import com.google.zxing.integration.android.IntentIntegrator;
30 | import com.google.zxing.integration.android.IntentResult;
31 | import com.vm.shadowsocks.R;
32 | import com.vm.shadowsocks.core.AppInfo;
33 | import com.vm.shadowsocks.core.AppProxyManager;
34 | import com.vm.shadowsocks.core.LocalVpnService;
35 | import com.vm.shadowsocks.core.ProxyConfig;
36 |
37 | import java.util.Calendar;
38 |
39 | public class MainActivity extends Activity implements
40 | View.OnClickListener,
41 | OnCheckedChangeListener,
42 | LocalVpnService.onStatusChangedListener {
43 |
44 | private static String GL_HISTORY_LOGS;
45 |
46 | private static final String TAG = MainActivity.class.getSimpleName();
47 |
48 | private static final String CONFIG_URL_KEY = "CONFIG_URL_KEY";
49 |
50 | private static final int START_VPN_SERVICE_REQUEST_CODE = 1985;
51 |
52 | private Switch switchProxy;
53 | private TextView textViewLog;
54 | private ScrollView scrollViewLog;
55 | private TextView textViewProxyUrl, textViewProxyApp;
56 | private Calendar mCalendar;
57 |
58 | @Override
59 | protected void onCreate(Bundle savedInstanceState) {
60 | super.onCreate(savedInstanceState);
61 | setContentView(R.layout.activity_main);
62 |
63 | scrollViewLog = (ScrollView) findViewById(R.id.scrollViewLog);
64 | textViewLog = (TextView) findViewById(R.id.textViewLog);
65 | findViewById(R.id.ProxyUrlLayout).setOnClickListener(this);
66 | findViewById(R.id.AppSelectLayout).setOnClickListener(this);
67 |
68 | textViewProxyUrl = (TextView) findViewById(R.id.textViewProxyUrl);
69 | String ProxyUrl = readProxyUrl();
70 | if (TextUtils.isEmpty(ProxyUrl)) {
71 | textViewProxyUrl.setText(R.string.config_not_set_value);
72 | } else {
73 | textViewProxyUrl.setText(ProxyUrl);
74 | }
75 |
76 | textViewLog.setText(GL_HISTORY_LOGS);
77 | scrollViewLog.fullScroll(ScrollView.FOCUS_DOWN);
78 |
79 | mCalendar = Calendar.getInstance();
80 | LocalVpnService.addOnStatusChangedListener(this);
81 |
82 | //Pre-App Proxy
83 | if (AppProxyManager.isLollipopOrAbove){
84 | new AppProxyManager(this);
85 | textViewProxyApp = (TextView) findViewById(R.id.textViewAppSelectDetail);
86 | } else {
87 | ((ViewGroup) findViewById(R.id.AppSelectLayout).getParent()).removeView(findViewById(R.id.AppSelectLayout));
88 | ((ViewGroup) findViewById(R.id.textViewAppSelectLine).getParent()).removeView(findViewById(R.id.textViewAppSelectLine));
89 | }
90 | }
91 |
92 | String readProxyUrl() {
93 | SharedPreferences preferences = getSharedPreferences("shadowsocksProxyUrl", MODE_PRIVATE);
94 | return preferences.getString(CONFIG_URL_KEY, "");
95 | }
96 |
97 | void setProxyUrl(String ProxyUrl) {
98 | SharedPreferences preferences = getSharedPreferences("shadowsocksProxyUrl", MODE_PRIVATE);
99 | Editor editor = preferences.edit();
100 | editor.putString(CONFIG_URL_KEY, ProxyUrl);
101 | editor.apply();
102 | }
103 |
104 | String getVersionName() {
105 | PackageManager packageManager = getPackageManager();
106 | if (packageManager == null) {
107 | Log.e(TAG, "null package manager is impossible");
108 | return null;
109 | }
110 |
111 | try {
112 | return packageManager.getPackageInfo(getPackageName(), 0).versionName;
113 | } catch (PackageManager.NameNotFoundException e) {
114 | Log.e(TAG, "package not found is impossible", e);
115 | return null;
116 | }
117 | }
118 |
119 | boolean isValidUrl(String url) {
120 | try {
121 | if (url == null || url.isEmpty())
122 | return false;
123 |
124 | if (url.startsWith("ss://")) {//file path
125 | return true;
126 | } else { //url
127 | Uri uri = Uri.parse(url);
128 | if (!"http".equals(uri.getScheme()) && !"https".equals(uri.getScheme()))
129 | return false;
130 | if (uri.getHost() == null)
131 | return false;
132 | }
133 | return true;
134 | } catch (Exception e) {
135 | return false;
136 | }
137 | }
138 |
139 | @Override
140 | public void onClick(View v) {
141 | if (switchProxy.isChecked()) {
142 | return;
143 | }
144 |
145 | if (v.getTag().toString().equals("ProxyUrl")){
146 | new AlertDialog.Builder(this)
147 | .setTitle(R.string.config_url)
148 | .setItems(new CharSequence[]{
149 | getString(R.string.config_url_scan),
150 | getString(R.string.config_url_manual)
151 | }, new OnClickListener() {
152 | @Override
153 | public void onClick(DialogInterface dialogInterface, int i) {
154 | switch (i) {
155 | case 0:
156 | scanForProxyUrl();
157 | break;
158 | case 1:
159 | showProxyUrlInputDialog();
160 | break;
161 | }
162 | }
163 | })
164 | .show();
165 | } else if (v.getTag().toString().equals("AppSelect")){
166 | System.out.println("abc");
167 | startActivity(new Intent(this, AppManager.class));
168 | }
169 | }
170 |
171 | private void scanForProxyUrl() {
172 | new IntentIntegrator(this)
173 | .setPrompt(getString(R.string.config_url_scan_hint))
174 | .initiateScan(IntentIntegrator.QR_CODE_TYPES);
175 | }
176 |
177 | private void showProxyUrlInputDialog() {
178 | final EditText editText = new EditText(this);
179 | editText.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
180 | editText.setHint(getString(R.string.config_url_hint));
181 | editText.setText(readProxyUrl());
182 |
183 | new AlertDialog.Builder(this)
184 | .setTitle(R.string.config_url)
185 | .setView(editText)
186 | .setPositiveButton(R.string.btn_ok, new OnClickListener() {
187 | @Override
188 | public void onClick(DialogInterface dialog, int which) {
189 | if (editText.getText() == null) {
190 | return;
191 | }
192 |
193 | String ProxyUrl = editText.getText().toString().trim();
194 | if (isValidUrl(ProxyUrl)) {
195 | setProxyUrl(ProxyUrl);
196 | textViewProxyUrl.setText(ProxyUrl);
197 | } else {
198 | Toast.makeText(MainActivity.this, R.string.err_invalid_url, Toast.LENGTH_SHORT).show();
199 | }
200 | }
201 | })
202 | .setNegativeButton(R.string.btn_cancel, null)
203 | .show();
204 | }
205 |
206 | @SuppressLint("DefaultLocale")
207 | @Override
208 | public void onLogReceived(String logString) {
209 | mCalendar.setTimeInMillis(System.currentTimeMillis());
210 | logString = String.format("[%1$02d:%2$02d:%3$02d] %4$s\n",
211 | mCalendar.get(Calendar.HOUR_OF_DAY),
212 | mCalendar.get(Calendar.MINUTE),
213 | mCalendar.get(Calendar.SECOND),
214 | logString);
215 |
216 | System.out.println(logString);
217 |
218 | if (textViewLog.getLineCount() > 200) {
219 | textViewLog.setText("");
220 | }
221 | textViewLog.append(logString);
222 | scrollViewLog.fullScroll(ScrollView.FOCUS_DOWN);
223 | GL_HISTORY_LOGS = textViewLog.getText() == null ? "" : textViewLog.getText().toString();
224 | }
225 |
226 | @Override
227 | public void onStatusChanged(String status, Boolean isRunning) {
228 | switchProxy.setEnabled(true);
229 | switchProxy.setChecked(isRunning);
230 | onLogReceived(status);
231 | Toast.makeText(this, status, Toast.LENGTH_SHORT).show();
232 | }
233 |
234 | @Override
235 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
236 | if (LocalVpnService.IsRunning != isChecked) {
237 | switchProxy.setEnabled(false);
238 | if (isChecked) {
239 | Intent intent = LocalVpnService.prepare(this);
240 | if (intent == null) {
241 | startVPNService();
242 | } else {
243 | startActivityForResult(intent, START_VPN_SERVICE_REQUEST_CODE);
244 | }
245 | } else {
246 | LocalVpnService.IsRunning = false;
247 | }
248 | }
249 | }
250 |
251 | private void startVPNService() {
252 | String ProxyUrl = readProxyUrl();
253 | if (!isValidUrl(ProxyUrl)) {
254 | Toast.makeText(this, R.string.err_invalid_url, Toast.LENGTH_SHORT).show();
255 | switchProxy.post(new Runnable() {
256 | @Override
257 | public void run() {
258 | switchProxy.setChecked(false);
259 | switchProxy.setEnabled(true);
260 | }
261 | });
262 | return;
263 | }
264 |
265 | textViewLog.setText("");
266 | GL_HISTORY_LOGS = null;
267 | onLogReceived("starting...");
268 | LocalVpnService.ProxyUrl = ProxyUrl;
269 | startService(new Intent(this, LocalVpnService.class));
270 | }
271 |
272 | @Override
273 | protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
274 | if (requestCode == START_VPN_SERVICE_REQUEST_CODE) {
275 | if (resultCode == RESULT_OK) {
276 | startVPNService();
277 | } else {
278 | switchProxy.setChecked(false);
279 | switchProxy.setEnabled(true);
280 | onLogReceived("canceled.");
281 | }
282 | return;
283 | }
284 |
285 | IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
286 | if (scanResult != null) {
287 | String ProxyUrl = scanResult.getContents();
288 | if (isValidUrl(ProxyUrl)) {
289 | setProxyUrl(ProxyUrl);
290 | textViewProxyUrl.setText(ProxyUrl);
291 | } else {
292 | Toast.makeText(MainActivity.this, R.string.err_invalid_url, Toast.LENGTH_SHORT).show();
293 | }
294 | return;
295 | }
296 |
297 | super.onActivityResult(requestCode, resultCode, intent);
298 | }
299 |
300 | @Override
301 | public boolean onCreateOptionsMenu(Menu menu) {
302 | getMenuInflater().inflate(R.menu.main_activity_actions, menu);
303 |
304 | MenuItem menuItem = menu.findItem(R.id.menu_item_switch);
305 | if (menuItem == null) {
306 | return false;
307 | }
308 |
309 | switchProxy = (Switch) menuItem.getActionView();
310 | if (switchProxy == null) {
311 | return false;
312 | }
313 |
314 | switchProxy.setChecked(LocalVpnService.IsRunning);
315 | switchProxy.setOnCheckedChangeListener(this);
316 |
317 | return true;
318 | }
319 |
320 | @Override
321 | public boolean onOptionsItemSelected(MenuItem item) {
322 | switch (item.getItemId()) {
323 | case R.id.menu_item_about:
324 | new AlertDialog.Builder(this)
325 | .setTitle(getString(R.string.app_name) + getVersionName())
326 | .setMessage(R.string.about_info)
327 | .setPositiveButton(R.string.btn_ok, null)
328 | .setNegativeButton(R.string.btn_more, new OnClickListener() {
329 | @Override
330 | public void onClick(DialogInterface dialog, int which) {
331 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/dawei101/shadowsocks-android-java")));
332 | }
333 | })
334 | .show();
335 |
336 | return true;
337 | case R.id.menu_item_exit:
338 | if (!LocalVpnService.IsRunning) {
339 | finish();
340 | return true;
341 | }
342 |
343 | new AlertDialog.Builder(this)
344 | .setTitle(R.string.menu_item_exit)
345 | .setMessage(R.string.exit_confirm_info)
346 | .setPositiveButton(R.string.btn_ok, new OnClickListener() {
347 | @Override
348 | public void onClick(DialogInterface dialog, int which) {
349 | LocalVpnService.IsRunning = false;
350 | LocalVpnService.Instance.disconnectVPN();
351 | stopService(new Intent(MainActivity.this, LocalVpnService.class));
352 | System.runFinalization();
353 | System.exit(0);
354 | }
355 | })
356 | .setNegativeButton(R.string.btn_cancel, null)
357 | .show();
358 |
359 | return true;
360 | case R.id.menu_item_toggle_global:
361 | ProxyConfig.Instance.globalMode = !ProxyConfig.Instance.globalMode;
362 | if (ProxyConfig.Instance.globalMode) {
363 | onLogReceived("Proxy global mode is on");
364 | } else {
365 | onLogReceived("Proxy global mode is off");
366 | }
367 | default:
368 | return super.onOptionsItemSelected(item);
369 | }
370 | }
371 |
372 | @Override
373 | protected void onResume() {
374 | super.onResume();
375 | if (AppProxyManager.isLollipopOrAbove) {
376 | if (AppProxyManager.Instance.proxyAppInfo.size() != 0) {
377 | String tmpString = "";
378 | for (AppInfo app : AppProxyManager.Instance.proxyAppInfo) {
379 | tmpString += app.getAppLabel() + ", ";
380 | }
381 | textViewProxyApp.setText(tmpString);
382 | }
383 | }
384 | }
385 |
386 | @Override
387 | protected void onDestroy() {
388 | LocalVpnService.removeOnStatusChangedListener(this);
389 | super.onDestroy();
390 | }
391 |
392 | }
393 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/shadowsocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawei101/shadowsocks-android-java/0c38e592dd10a4fad3dbe7dca351b4cd27b2f1bf/app/src/main/res/drawable-xxhdpi/shadowsocks.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
20 |
21 |
27 |
28 |
29 |
30 |
37 |
38 |
45 |
46 |
52 |
53 |
59 |
60 |
61 |
62 |
70 |
71 |
75 |
76 |
80 |
81 |
87 |
88 |
89 |
90 |
91 |
94 |
95 |
98 |
99 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_apps.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
13 |
17 |
22 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_apps_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
18 |
19 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_activity_actions.xml:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/ipmask:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawei101/shadowsocks-android-java/0c38e592dd10a4fad3dbe7dca351b4cd27b2f1bf/app/src/main/res/raw/ipmask
--------------------------------------------------------------------------------
/app/src/main/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sw720dp-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | 128dp
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Shadowsocks
4 | 代理
5 | 开/关全局模式
6 | 关于
7 | 退出
8 | 配置地址
9 | 未设置
10 | 扫描二维码
11 | 手动输入
12 | 扫描配置地址的二维码
13 | 选择使用代理的软件
14 | 未选择
15 | 使用代理的软件
16 | 确定
17 | 取消
18 | 更多…
19 | 已连接
20 | 已断开
21 | Shadowsocks android 由dawei开发维护,可以在任意Android 4.0+上使用,无需ROOT权限。它是基于SmartProxy与Shadowsocks-Java改装。
22 | 这将会断开VPN连接,确定要退出吗?
23 | 无效的配置地址
24 | ss://method:password@host:port
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Shadowsocks
4 | Toggle global mode
5 | Proxy
6 | About
7 | Exit
8 | Config URL
9 | Not set
10 | Scan an QR code
11 | Type manually
12 | Scan the QR code of your config url
13 | Select App
14 | No
15 | Per-App Proxy
16 | OK
17 | Cancel
18 | More…
19 | connected
20 | disconnected
21 | Shadowsocks-android is writen by dawei. It could run on any Android 4.0+ device , no need to root, and it is modified based on SmartProxy and Shadowsocks-Java
22 | This will disconnect the VPN connection, Are you sure to exit?
23 | The config URL is invalid.
24 | ss://method:password@host:port
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dawei101/shadowsocks-android-java/0c38e592dd10a4fad3dbe7dca351b4cd27b2f1bf/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Aug 16 23:07:29 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------