├── .gitignore ├── .settings ├── org.eclipse.jdt.core.prefs └── org.eclipse.jdt.ui.prefs ├── .travis.yml ├── LICENSE ├── README.md ├── Smart Proxy X ├── .gitignore ├── build.gradle ├── proguard-rules.txt └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── me │ │ └── smartproxy │ │ ├── core │ │ ├── ChinaIpMaskManager.java │ │ ├── DnsProxy.java │ │ ├── HttpHostHeaderParser.java │ │ ├── LocalVpnService.java │ │ ├── NatSession.java │ │ ├── NatSessionManager.java │ │ ├── ProxyConfig.java │ │ ├── TcpProxyServer.java │ │ ├── TmpConfig.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 │ │ ├── IEncryptor.java │ │ ├── RawTunnel.java │ │ ├── Tunnel.java │ │ ├── httpconnect │ │ │ ├── HttpConnectConfig.java │ │ │ └── HttpConnectTunnel.java │ │ └── shadowsocks │ │ │ ├── EncryptorFactory.java │ │ │ ├── ShadowsocksConfig.java │ │ │ ├── ShadowsocksTunnel.java │ │ │ └── TableEncryptor.java │ │ └── ui │ │ ├── MainActivity.java │ │ └── QuickSettingsService.java │ ├── jniLibs │ ├── arm64-v8a │ │ └── libnghttpx.so │ ├── libnghttpx.so │ ├── x86 │ │ └── libnghttpx.so │ └── x86_64 │ │ └── libnghttpx.so │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ └── activity_main.xml │ ├── menu │ └── main_activity_actions.xml │ ├── raw │ └── ipmask │ ├── values-sw600dp │ └── dimens.xml │ ├── values-sw720dp-land │ └── dimens.xml │ ├── values-v11 │ └── styles.xml │ ├── values-v14 │ └── 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 | .DS_Store 2 | .gradle 3 | .idea 4 | 5 | /local.properties 6 | 7 | *.iml 8 | release 9 | build 10 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 3 | org.eclipse.jdt.core.compiler.compliance=1.6 4 | org.eclipse.jdt.core.compiler.source=1.6 5 | org.eclipse.jdt.core.formatter.align_type_members_on_columns=false 6 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 7 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 8 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=0 9 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 10 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 11 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 12 | org.eclipse.jdt.core.formatter.alignment_for_assignment=0 13 | org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 14 | org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 15 | org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 16 | org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 17 | org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 18 | org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 19 | org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 20 | org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=0 21 | org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=0 22 | org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 23 | org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 24 | org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 25 | org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=0 26 | org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 27 | org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=0 28 | org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=0 29 | org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 30 | org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 31 | org.eclipse.jdt.core.formatter.blank_lines_after_package=1 32 | org.eclipse.jdt.core.formatter.blank_lines_before_field=0 33 | org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 34 | org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 35 | org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 36 | org.eclipse.jdt.core.formatter.blank_lines_before_method=1 37 | org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 38 | org.eclipse.jdt.core.formatter.blank_lines_before_package=0 39 | org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 40 | org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 41 | org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line 42 | org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line 43 | org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line 44 | org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line 45 | org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line 46 | org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line 47 | org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line 48 | org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line 49 | org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line 50 | org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line 51 | org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line 52 | org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false 53 | org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false 54 | org.eclipse.jdt.core.formatter.comment.format_block_comments=true 55 | org.eclipse.jdt.core.formatter.comment.format_header=false 56 | org.eclipse.jdt.core.formatter.comment.format_html=true 57 | org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true 58 | org.eclipse.jdt.core.formatter.comment.format_line_comments=true 59 | org.eclipse.jdt.core.formatter.comment.format_source_code=true 60 | org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true 61 | org.eclipse.jdt.core.formatter.comment.indent_root_tags=true 62 | org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert 63 | org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert 64 | org.eclipse.jdt.core.formatter.comment.line_length=80 65 | org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true 66 | org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true 67 | org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false 68 | org.eclipse.jdt.core.formatter.compact_else_if=true 69 | org.eclipse.jdt.core.formatter.continuation_indentation=2 70 | org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 71 | org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off 72 | org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on 73 | org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false 74 | org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true 75 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true 76 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true 77 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true 78 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true 79 | org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true 80 | org.eclipse.jdt.core.formatter.indent_empty_lines=false 81 | org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true 82 | org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true 83 | org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true 84 | org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false 85 | org.eclipse.jdt.core.formatter.indentation.size=4 86 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert 87 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert 88 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert 89 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert 90 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert 91 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert 92 | org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert 93 | org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert 94 | org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert 95 | org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert 96 | org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert 97 | org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert 98 | org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert 99 | org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert 100 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert 101 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert 102 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert 103 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert 104 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert 105 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert 106 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert 107 | org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert 108 | org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert 109 | org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert 110 | org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert 111 | org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert 112 | org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert 113 | org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert 114 | org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert 115 | org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert 116 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert 117 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert 118 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert 119 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert 120 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert 121 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert 122 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert 123 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert 124 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert 125 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert 126 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert 127 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert 128 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert 129 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert 130 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert 131 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert 132 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert 133 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert 134 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert 135 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert 136 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert 137 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert 138 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert 139 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert 140 | org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert 141 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert 142 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert 143 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert 144 | org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert 145 | org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert 146 | org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert 147 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert 148 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert 149 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert 150 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert 151 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert 152 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert 153 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert 154 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert 155 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert 156 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert 157 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert 158 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert 159 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert 160 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert 161 | org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert 162 | org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert 163 | org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert 164 | org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert 165 | org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert 166 | org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert 167 | org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert 168 | org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert 169 | org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert 170 | org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert 171 | org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert 172 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert 173 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert 174 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert 175 | org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert 176 | org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert 177 | org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert 178 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert 179 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert 180 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert 181 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert 182 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert 183 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert 184 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert 185 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert 186 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert 187 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert 188 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert 189 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert 190 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert 191 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert 192 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert 193 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert 194 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert 195 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert 196 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert 197 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert 198 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert 199 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert 200 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert 201 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert 202 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert 203 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert 204 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert 205 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert 206 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert 207 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert 208 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert 209 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert 210 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert 211 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert 212 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert 213 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert 214 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert 215 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert 216 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert 217 | org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert 218 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert 219 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert 220 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert 221 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert 222 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert 223 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert 224 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert 225 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert 226 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert 227 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert 228 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert 229 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert 230 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert 231 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert 232 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert 233 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert 234 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert 235 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert 236 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert 237 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert 238 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert 239 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert 240 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert 241 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert 242 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert 243 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert 244 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert 245 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert 246 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert 247 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert 248 | org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert 249 | org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert 250 | org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert 251 | org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert 252 | org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert 253 | org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert 254 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert 255 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert 256 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert 257 | org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert 258 | org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert 259 | org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert 260 | org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert 261 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert 262 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert 263 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert 264 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert 265 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert 266 | org.eclipse.jdt.core.formatter.join_lines_in_comments=true 267 | org.eclipse.jdt.core.formatter.join_wrapped_lines=false 268 | org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false 269 | org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false 270 | org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false 271 | org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false 272 | org.eclipse.jdt.core.formatter.lineSplit=800 273 | org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false 274 | org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false 275 | org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 276 | org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 277 | org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true 278 | org.eclipse.jdt.core.formatter.tabulation.char=tab 279 | org.eclipse.jdt.core.formatter.tabulation.size=4 280 | org.eclipse.jdt.core.formatter.use_on_off_tags=false 281 | org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false 282 | org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true 283 | org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true 284 | org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=false 285 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.ui.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | formatter_profile=_test 3 | formatter_settings_version=12 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | sudo: false 3 | android: 4 | components: 5 | # Uncomment the lines below if you want to 6 | # use the latest revision of Android SDK Tools 7 | - tools 8 | - platform-tools 9 | 10 | # The BuildTools version used by your project 11 | - build-tools-28.0.2 12 | 13 | # The SDK version used to compile your project 14 | - android-27 15 | 16 | # Additional components 17 | - extra-google-m2repository 18 | - extra-android-m2repository 19 | 20 | # Specify at least one system image, 21 | # if you need to run emulator(s) during your tests 22 | - sys-img-armeabi-v7a-android-27 23 | 24 | script: ./gradlew assembleDebug check 25 | before_cache: 26 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 27 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 28 | cache: 29 | directories: 30 | - $HOME/.gradle/caches/ 31 | - $HOME/.gradle/wrapper/ 32 | - $HOME/.android/build-cache 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SmartProxy 2 | ========== 3 | [![Build Status](https://travis-ci.org/zxc111/SmartProxy.svg?branch=h2)](https://travis-ci.org/zxc111/SmartProxy) 4 | 5 | http2 proxy client for android 6 | 7 | base on [https://github.com/oldman1977/SmartProxy](https://github.com/oldman1977/SmartProxy) 8 | 9 | 服务器端配合[nghttpx](https://github.com/nghttp2/nghttp2)食用。(自行编译) 10 | 11 | 简单粗暴将nghttpx打包进apk中 12 | 13 | #### ChangeLog 14 | 15 | 1. 增加通过 QuickSetting 启动 16 | 2. 调整nghttpx的参数,提升传输效率 17 | 18 | #### 已知问题 19 | 1. ~~要按保存+on才能开始运行。保存开启nghttpx转发,on开启vpnService截取包。~~ 20 | 2. ~~性能略差。100k/s吧大概~~ 21 | 3. 切换配置麻烦 22 | 4. 远端服务器暂时只支持ip 23 | 5. 有点费电,暂时无解。 24 | 6. 目前放进去的是nghttpx是编译的arm平台的,不能跑在x86下,也就是说emulator下无法真正的测试通信 25 | 7. ~~文件编码有些问题~~ 26 | 8. 先这么多吧 27 | -------------------------------------------------------------------------------- /Smart Proxy X/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Smart Proxy X/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:7.0.4' 8 | } 9 | } 10 | apply plugin: 'com.android.application' 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | } 16 | 17 | android { 18 | compileSdkVersion 30 19 | // buildToolsVersion '30.0.2' 20 | 21 | defaultConfig { 22 | minSdkVersion 24 23 | targetSdkVersion 30 24 | versionCode 7 25 | versionName "1.6" 26 | 27 | ndk { 28 | abiFilters 'armeabi', 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' 29 | } 30 | } 31 | 32 | lintOptions { 33 | abortOnError false 34 | } 35 | signingConfigs { 36 | release { 37 | storeFile file("[android.keystore]") 38 | storePassword System.getenv("KSTOREPWD") 39 | keyAlias "[smartproxy]" 40 | keyPassword System.getenv("KEYPWD") 41 | } 42 | } 43 | 44 | buildTypes { 45 | release { 46 | //runProguard false 47 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 48 | signingConfig signingConfigs.release 49 | } 50 | } 51 | } 52 | 53 | dependencies { 54 | // api 'com.embarkmobile:zxing-android-minimal:3.5.0@aar' 55 | // api 'com.embarkmobile:zxing-android-integration:3.5.0@aar' 56 | // api 'com.google.zxing:core:3.5.0' 57 | } 58 | -------------------------------------------------------------------------------- /Smart Proxy X/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/opt/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 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 | #} -------------------------------------------------------------------------------- /Smart Proxy X/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc111/SmartProxy/24a12530cab7fe6f6e00c048d973e507da919ab3/Smart Proxy X/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/ChinaIpMaskManager.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.core; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | import me.smartproxy.tcpip.CommonMethods; 7 | import android.util.SparseIntArray; 8 | 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 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/DnsProxy.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.core; 2 | 3 | import java.io.IOException; 4 | import java.net.DatagramPacket; 5 | import java.net.DatagramSocket; 6 | import java.net.InetSocketAddress; 7 | import java.nio.ByteBuffer; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | import me.smartproxy.dns.DnsPacket; 11 | import me.smartproxy.dns.Question; 12 | import me.smartproxy.dns.Resource; 13 | import me.smartproxy.dns.ResourcePointer; 14 | import me.smartproxy.tcpip.CommonMethods; 15 | import me.smartproxy.tcpip.IPHeader; 16 | import me.smartproxy.tcpip.UDPHeader; 17 | 18 | import android.util.SparseArray; 19 | 20 | 21 | public class DnsProxy implements Runnable { 22 | 23 | private class QueryState 24 | { 25 | public short ClientQueryID; 26 | public long QueryNanoTime; 27 | public int ClientIP; 28 | public short ClientPort; 29 | public int RemoteIP; 30 | public short RemotePort; 31 | } 32 | 33 | public boolean Stopped; 34 | private static final ConcurrentHashMap IPDomainMaps= new ConcurrentHashMap(); 35 | private static final ConcurrentHashMap DomainIPMaps= new ConcurrentHashMap(); 36 | private final long QUERY_TIMEOUT_NS=10*1000000000L; 37 | private DatagramSocket m_Client; 38 | private Thread m_ReceivedThread; 39 | private short m_QueryID; 40 | private SparseArray m_QueryArray; 41 | 42 | public DnsProxy() throws IOException { 43 | m_QueryArray = new SparseArray(); 44 | m_Client = new DatagramSocket(0); 45 | } 46 | 47 | public static String reverseLookup(int ip){ 48 | return IPDomainMaps.get(ip); 49 | } 50 | 51 | public void start(){ 52 | m_ReceivedThread = new Thread(this); 53 | m_ReceivedThread.setName("DnsProxyThread"); 54 | m_ReceivedThread.start(); 55 | System.out.println("dns server start"); 56 | } 57 | 58 | public void stop(){ 59 | Stopped=true; 60 | if( m_Client!=null){ 61 | m_Client.close(); 62 | m_Client=null; 63 | } 64 | } 65 | 66 | @Override 67 | public void run() { 68 | System.out.println("dns server run"); 69 | 70 | try { 71 | byte[] RECEIVE_BUFFER = new byte[2000]; 72 | IPHeader ipHeader=new IPHeader(RECEIVE_BUFFER, 0); 73 | ipHeader.Default(); 74 | UDPHeader udpHeader=new UDPHeader(RECEIVE_BUFFER, 20); 75 | 76 | ByteBuffer dnsBuffer=ByteBuffer.wrap(RECEIVE_BUFFER); 77 | dnsBuffer.position(28); 78 | dnsBuffer=dnsBuffer.slice(); 79 | 80 | DatagramPacket packet = new DatagramPacket(RECEIVE_BUFFER,28, RECEIVE_BUFFER.length-28); 81 | 82 | while (m_Client!=null&&!m_Client.isClosed()){ 83 | System.out.println("123123123"); 84 | 85 | packet.setLength(RECEIVE_BUFFER.length-28); 86 | m_Client.receive(packet); 87 | System.out.println("111 start"); 88 | 89 | dnsBuffer.clear(); 90 | dnsBuffer.limit(packet.getLength()); 91 | try { 92 | DnsPacket dnsPacket=DnsPacket.FromBytes(dnsBuffer); 93 | if(dnsPacket!=null){ 94 | OnDnsResponseReceived(ipHeader,udpHeader,dnsPacket); 95 | System.out.println("111 stop"); 96 | } 97 | } catch (Exception e) { 98 | e.printStackTrace(); 99 | LocalVpnService.Instance.writeLog("Parse dns error: %s", e); 100 | } 101 | } 102 | } catch (Exception e) { 103 | e.printStackTrace(); 104 | } finally{ 105 | System.out.println("DnsResolver Thread Exited."); 106 | this.stop(); 107 | } 108 | } 109 | 110 | private int getFirstIP(DnsPacket dnsPacket){ 111 | for (int i = 0; i < dnsPacket.Header.ResourceCount; i++) { 112 | Resource resource=dnsPacket.Resources[i]; 113 | if(resource.Type==1){ 114 | int ip=CommonMethods.readInt(resource.Data, 0); 115 | return ip; 116 | } 117 | } 118 | return 0; 119 | } 120 | 121 | private void tamperDnsResponse(byte[] rawPacket,DnsPacket dnsPacket,int newIP){ 122 | Question question=dnsPacket.Questions[0]; 123 | 124 | dnsPacket.Header.setResourceCount((short)1); 125 | dnsPacket.Header.setAResourceCount((short)0); 126 | dnsPacket.Header.setEResourceCount((short)0); 127 | 128 | ResourcePointer rPointer=new ResourcePointer(rawPacket, question.Offset()+question.Length()); 129 | rPointer.setDomain((short)0xC00C); 130 | rPointer.setType(question.Type); 131 | rPointer.setClass(question.Class); 132 | rPointer.setTTL(ProxyConfig.Instance.getDnsTTL()); 133 | rPointer.setDataLength((short)4); 134 | rPointer.setIP(newIP); 135 | 136 | dnsPacket.Size=12+question.Length()+16; 137 | } 138 | 139 | private int getOrCreateFakeIP(String domainString){ 140 | Integer fakeIP=DomainIPMaps.get(domainString); 141 | if(fakeIP==null){ 142 | int hashIP=domainString.hashCode(); 143 | do{ 144 | fakeIP=ProxyConfig.FAKE_NETWORK_IP | (hashIP&0x0000FFFF); 145 | hashIP++; 146 | }while(IPDomainMaps.containsKey(fakeIP)); 147 | 148 | DomainIPMaps.put(domainString,fakeIP); 149 | IPDomainMaps.put(fakeIP, domainString); 150 | } 151 | return fakeIP; 152 | } 153 | 154 | private boolean dnsPollution(byte[] rawPacket,DnsPacket dnsPacket){ 155 | if(dnsPacket.Header.QuestionCount>0){ 156 | Question question=dnsPacket.Questions[0]; 157 | if(question.Type==1){ 158 | int realIP=getFirstIP(dnsPacket); 159 | if(ProxyConfig.Instance.needProxy(question.Domain, realIP)){ 160 | int fakeIP=getOrCreateFakeIP(question.Domain); 161 | tamperDnsResponse(rawPacket,dnsPacket,fakeIP); 162 | if(ProxyConfig.IS_DEBUG) 163 | System.out.printf("FakeDns: %s=>%s(%s)\n",question.Domain,CommonMethods.ipIntToString(realIP),CommonMethods.ipIntToString(fakeIP)); 164 | return true; 165 | } 166 | } 167 | } 168 | return false; 169 | } 170 | 171 | private void OnDnsResponseReceived(IPHeader ipHeader,UDPHeader udpHeader,DnsPacket dnsPacket) { 172 | QueryState state =null; 173 | synchronized (m_QueryArray) { 174 | state=m_QueryArray.get(dnsPacket.Header.ID); 175 | if(state!=null){ 176 | m_QueryArray.remove(dnsPacket.Header.ID); 177 | } 178 | } 179 | 180 | if (state != null) { 181 | //DNS污染,默认污染海外网站 182 | dnsPollution(udpHeader.m_Data,dnsPacket); 183 | 184 | dnsPacket.Header.setID(state.ClientQueryID); 185 | ipHeader.setSourceIP(state.RemoteIP); 186 | ipHeader.setDestinationIP(state.ClientIP); 187 | ipHeader.setProtocol(IPHeader.UDP); 188 | ipHeader.setTotalLength(20+8+dnsPacket.Size); 189 | udpHeader.setSourcePort(state.RemotePort); 190 | udpHeader.setDestinationPort(state.ClientPort); 191 | udpHeader.setTotalLength(8+dnsPacket.Size); 192 | 193 | LocalVpnService.Instance.sendUDPPacket(ipHeader, udpHeader); 194 | } 195 | } 196 | 197 | private int getIPFromCache(String domain){ 198 | Integer ip=DomainIPMaps.get(domain); 199 | if(ip==null){ 200 | return 0; 201 | } 202 | else { 203 | return ip; 204 | } 205 | } 206 | 207 | private boolean interceptDns(IPHeader ipHeader,UDPHeader udpHeader,DnsPacket dnsPacket){ 208 | Question question=dnsPacket.Questions[0]; 209 | System.out.println("DNS Qeury "+question.Domain); 210 | if(question.Type==1){ 211 | if(ProxyConfig.Instance.needProxy(question.Domain, getIPFromCache(question.Domain))){ 212 | int fakeIP=getOrCreateFakeIP(question.Domain); 213 | tamperDnsResponse(ipHeader.m_Data,dnsPacket,fakeIP); 214 | 215 | if(ProxyConfig.IS_DEBUG) 216 | System.out.printf("interceptDns FakeDns: %s=>%s\n",question.Domain,CommonMethods.ipIntToString(fakeIP)); 217 | 218 | int sourceIP=ipHeader.getSourceIP(); 219 | short sourcePort=udpHeader.getSourcePort(); 220 | ipHeader.setSourceIP(ipHeader.getDestinationIP()); 221 | ipHeader.setDestinationIP(sourceIP); 222 | ipHeader.setTotalLength(20+8+dnsPacket.Size); 223 | udpHeader.setSourcePort(udpHeader.getDestinationPort()); 224 | udpHeader.setDestinationPort(sourcePort); 225 | udpHeader.setTotalLength(8+dnsPacket.Size); 226 | LocalVpnService.Instance.sendUDPPacket(ipHeader, udpHeader); 227 | return true; 228 | } 229 | } 230 | return false; 231 | } 232 | 233 | private void clearExpiredQueries(){ 234 | long now=System.nanoTime(); 235 | for (int i = m_QueryArray.size()-1; i>=0; i--) { 236 | QueryState state=m_QueryArray.valueAt(i); 237 | if ((now - state.QueryNanoTime)> QUERY_TIMEOUT_NS){ 238 | m_QueryArray.removeAt(i); 239 | } 240 | } 241 | } 242 | 243 | public void onDnsRequestReceived(IPHeader ipHeader,UDPHeader udpHeader,DnsPacket dnsPacket){ 244 | if(!interceptDns(ipHeader,udpHeader,dnsPacket)){ 245 | //转发DNS 246 | QueryState state = new QueryState(); 247 | state.ClientQueryID =dnsPacket.Header.ID; 248 | state.QueryNanoTime = System.nanoTime(); 249 | state.ClientIP = ipHeader.getSourceIP(); 250 | state.ClientPort = udpHeader.getSourcePort(); 251 | state.RemoteIP = ipHeader.getDestinationIP(); 252 | state.RemotePort = udpHeader.getDestinationPort(); 253 | 254 | // 转换QueryID 255 | m_QueryID++;// 增加ID 256 | dnsPacket.Header.setID(m_QueryID); 257 | 258 | synchronized (m_QueryArray) { 259 | clearExpiredQueries();//清空过期的查询,减少内存开销。 260 | m_QueryArray.put(m_QueryID, state);// 关联数据 261 | } 262 | 263 | InetSocketAddress remoteAddress = new InetSocketAddress(CommonMethods.ipIntToInet4Address(state.RemoteIP ), state.RemotePort); 264 | DatagramPacket packet = new DatagramPacket(udpHeader.m_Data, udpHeader.m_Offset+8, dnsPacket.Size); 265 | packet.setSocketAddress(remoteAddress); 266 | 267 | try { 268 | if(LocalVpnService.Instance.protect(m_Client)){ 269 | System.out.println("111 send"); 270 | m_Client.send(packet); 271 | System.out.println("111 send end"); 272 | }else { 273 | System.err.println("VPN protect udp socket failed."); 274 | } 275 | } catch (IOException e) { 276 | // TODO Auto-generated catch block 277 | e.printStackTrace(); 278 | } 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/HttpHostHeaderParser.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.core; 2 | 3 | import java.util.Locale; 4 | 5 | import me.smartproxy.tcpip.CommonMethods; 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 | } 101 | else { 102 | offset += length; 103 | } 104 | } 105 | 106 | System.err.println("TLS Client Hello packet doesn't contains Host field info."); 107 | return null; 108 | } 109 | else { 110 | System.err.println("Bad TLS Client Hello packet."); 111 | return null; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/LocalVpnService.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.core; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.lang.reflect.Method; 7 | import java.nio.ByteBuffer; 8 | import java.util.ArrayList; 9 | import java.util.Map; 10 | import java.util.UUID; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | import me.smartproxy.core.ProxyConfig.IPAddress; 14 | import me.smartproxy.dns.DnsPacket; 15 | import me.smartproxy.tcpip.CommonMethods; 16 | import me.smartproxy.tcpip.IPHeader; 17 | import me.smartproxy.tcpip.TCPHeader; 18 | import me.smartproxy.tcpip.UDPHeader; 19 | import me.smartproxy.ui.MainActivity; 20 | import me.smartproxy.R; 21 | 22 | import android.app.PendingIntent; 23 | import android.content.Intent; 24 | import android.content.SharedPreferences; 25 | import android.content.SharedPreferences.Editor; 26 | import android.content.pm.PackageInfo; 27 | import android.content.pm.PackageManager; 28 | import android.net.VpnService; 29 | import android.os.Build; 30 | import android.os.Handler; 31 | import android.os.ParcelFileDescriptor; 32 | 33 | public class LocalVpnService extends VpnService implements Runnable { 34 | 35 | public static LocalVpnService Instance; 36 | public static String ConfigUrl; 37 | public static boolean IsRunning = false; 38 | 39 | private static int ID; 40 | private static int LOCAL_IP; 41 | private static ConcurrentHashMap m_OnStatusChangedListeners = new ConcurrentHashMap(); 42 | 43 | private Thread m_VPNThread; 44 | private ParcelFileDescriptor m_VPNInterface; 45 | private TcpProxyServer m_TcpProxyServer; 46 | private DnsProxy m_DnsProxy; 47 | private FileOutputStream m_VPNOutputStream; 48 | 49 | private byte[] m_Packet; 50 | private IPHeader m_IPHeader; 51 | private TCPHeader m_TCPHeader; 52 | private UDPHeader m_UDPHeader; 53 | private ByteBuffer m_DNSBuffer; 54 | private Handler m_Handler; 55 | private long m_SentBytes; 56 | private long m_ReceivedBytes; 57 | 58 | public LocalVpnService() { 59 | ID++; 60 | m_Handler = new Handler(); 61 | m_Packet = new byte[1024 * 256]; 62 | m_IPHeader = new IPHeader(m_Packet, 0); 63 | m_TCPHeader = new TCPHeader(m_Packet, 20); 64 | m_UDPHeader = new UDPHeader(m_Packet, 20); 65 | m_DNSBuffer = ((ByteBuffer) ByteBuffer.wrap(m_Packet).position(28)).slice(); 66 | Instance = this; 67 | 68 | System.out.printf("New VPNService(%d)\n", ID); 69 | } 70 | 71 | @Override 72 | public void onCreate() { 73 | System.out.printf("VPNService(%s) created.\n", ID); 74 | // Start a new session by creating a new thread. 75 | m_VPNThread = new Thread(this, "VPNServiceThread"); 76 | m_VPNThread.start(); 77 | super.onCreate(); 78 | } 79 | 80 | @Override 81 | public int onStartCommand(Intent intent, int flags, int startId) { 82 | IsRunning = true; 83 | return super.onStartCommand(intent, flags, startId); 84 | } 85 | 86 | public interface onStatusChangedListener { 87 | public void onStatusChanged(String status, Boolean isRunning); 88 | 89 | public void onLogReceived(String logString); 90 | } 91 | 92 | public static void addOnStatusChangedListener(onStatusChangedListener listener) { 93 | if (!m_OnStatusChangedListeners.containsKey(listener)) { 94 | m_OnStatusChangedListeners.put(listener, 1); 95 | } 96 | } 97 | 98 | public static void removeOnStatusChangedListener(onStatusChangedListener listener) { 99 | if (m_OnStatusChangedListeners.containsKey(listener)) { 100 | m_OnStatusChangedListeners.remove(listener); 101 | } 102 | } 103 | 104 | private void onStatusChanged(final String status, final boolean isRunning) { 105 | m_Handler.post(new Runnable() { 106 | @Override 107 | public void run() { 108 | for (Map.Entry entry : m_OnStatusChangedListeners.entrySet()) { 109 | entry.getKey().onStatusChanged(status, isRunning); 110 | } 111 | } 112 | }); 113 | } 114 | 115 | public void writeLog(final String format, Object... args) { 116 | final String logString = String.format(format, args); 117 | m_Handler.post(new Runnable() { 118 | @Override 119 | public void run() { 120 | for (Map.Entry entry : m_OnStatusChangedListeners.entrySet()) { 121 | entry.getKey().onLogReceived(logString); 122 | } 123 | } 124 | }); 125 | } 126 | 127 | public void sendUDPPacket(IPHeader ipHeader, UDPHeader udpHeader) { 128 | try { 129 | CommonMethods.ComputeUDPChecksum(ipHeader, udpHeader); 130 | this.m_VPNOutputStream.write(ipHeader.m_Data, ipHeader.m_Offset, ipHeader.getTotalLength()); 131 | } catch (IOException e) { 132 | e.printStackTrace(); 133 | } 134 | } 135 | 136 | String getAppInstallID() { 137 | SharedPreferences preferences = getSharedPreferences("SmartProxy", MODE_PRIVATE); 138 | String appInstallID = preferences.getString("AppInstallID", null); 139 | if (appInstallID == null || appInstallID.isEmpty()) { 140 | appInstallID = UUID.randomUUID().toString(); 141 | Editor editor = preferences.edit(); 142 | editor.putString("AppInstallID", appInstallID); 143 | editor.commit(); 144 | } 145 | return appInstallID; 146 | } 147 | 148 | String getVersionName() { 149 | try { 150 | PackageManager packageManager = getPackageManager(); 151 | // getPackageName()是你当前类的包名,0代表是获取版本信息 152 | PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0); 153 | String version = packInfo.versionName; 154 | return version; 155 | } catch (Exception e) { 156 | return "0.0"; 157 | } 158 | } 159 | 160 | @Override 161 | public synchronized void run() { 162 | try { 163 | System.out.printf("VPNService(%s) work thread is runing...\n", ID); 164 | 165 | ProxyConfig.AppInstallID = getAppInstallID();//获取安装ID 166 | ProxyConfig.AppVersion = getVersionName();//获取版本号 167 | System.out.printf("AppInstallID: %s\n", ProxyConfig.AppInstallID); 168 | 169 | writeLog("Android version: %s", Build.VERSION.RELEASE); 170 | writeLog("App version: %s", ProxyConfig.AppVersion); 171 | 172 | 173 | ChinaIpMaskManager.loadFromFile(getResources().openRawResource(R.raw.ipmask));//加载中国的IP段,用于IP分流。 174 | waitUntilPreapred();//检查是否准备完毕。 175 | 176 | m_TcpProxyServer = new TcpProxyServer(0); 177 | m_TcpProxyServer.start(); 178 | writeLog("LocalTcpServer started."); 179 | 180 | m_DnsProxy = new DnsProxy(); 181 | m_DnsProxy.start(); 182 | writeLog("LocalDnsProxy started."); 183 | 184 | while (true) { 185 | if (IsRunning) { 186 | //加载配置文件 187 | 188 | writeLog("Load config from %s ...", ConfigUrl); 189 | try { 190 | 191 | ProxyConfig.Instance.loadFromUrl(ConfigUrl); 192 | 193 | if (ProxyConfig.Instance.getDefaultProxy() == null) { 194 | 195 | throw new Exception("Invalid config file."); 196 | } 197 | writeLog("PROXY %s", ProxyConfig.Instance.getDefaultProxy()); 198 | } catch (Exception e) { 199 | 200 | String errString = e.getMessage(); 201 | writeLog("Load config err %s", errString); 202 | if (errString == null || errString.isEmpty()) { 203 | errString = e.toString(); 204 | } 205 | System.out.println(errString); 206 | 207 | IsRunning = false; 208 | onStatusChanged(errString, false); 209 | continue; 210 | } 211 | 212 | writeLog("Load config success."); 213 | String welcomeInfoString = ProxyConfig.Instance.getWelcomeInfo(); 214 | if (welcomeInfoString != null && !welcomeInfoString.isEmpty()) { 215 | writeLog("%s", ProxyConfig.Instance.getWelcomeInfo()); 216 | } 217 | runVPN(); 218 | } else { 219 | Thread.sleep(100); 220 | } 221 | } 222 | } catch (InterruptedException e) { 223 | 224 | System.out.println(e); 225 | } catch (Exception e) { 226 | 227 | e.printStackTrace(); 228 | writeLog("Fatal error: %s", e.toString()); 229 | } finally { 230 | 231 | writeLog("SmartProxy terminated."); 232 | dispose(); 233 | } 234 | } 235 | 236 | private void runVPN() throws Exception { 237 | 238 | this.m_VPNInterface = establishVPN(); 239 | this.m_VPNOutputStream = new FileOutputStream(m_VPNInterface.getFileDescriptor()); 240 | 241 | FileInputStream in = new FileInputStream(m_VPNInterface.getFileDescriptor()); 242 | int size = 0; 243 | 244 | while (size != -1 && IsRunning) { 245 | while ((size = in.read(m_Packet)) > 0 && IsRunning) { 246 | if (m_DnsProxy.Stopped || m_TcpProxyServer.Stopped) { 247 | in.close(); 248 | throw new Exception("LocalServer stopped."); 249 | } 250 | onIPPacketReceived(m_IPHeader, size); 251 | } 252 | Thread.sleep(100); 253 | } 254 | in.close(); 255 | disconnectVPN(); 256 | } 257 | 258 | void onIPPacketReceived(IPHeader ipHeader, int size) throws IOException { 259 | switch (ipHeader.getProtocol()) { 260 | case IPHeader.TCP: 261 | TCPHeader tcpHeader = m_TCPHeader; 262 | tcpHeader.m_Offset = ipHeader.getHeaderLength(); 263 | // System.out.printf( 264 | // "receive ip: %s to ip: %s\n", 265 | // CommonMethods.ipIntToString(ipHeader.getSourceIP()), 266 | // CommonMethods.ipIntToString(ipHeader.getDestinationIP()) 267 | // ); 268 | 269 | if (ipHeader.getSourceIP() == LOCAL_IP) { 270 | if (tcpHeader.getSourcePort() == m_TcpProxyServer.Port) {// 收到本地TCP服务器数据 271 | NatSession session = NatSessionManager.getSession(tcpHeader.getDestinationPort()); 272 | if (session != null) { 273 | ipHeader.setSourceIP(ipHeader.getDestinationIP()); 274 | tcpHeader.setSourcePort(session.RemotePort); 275 | ipHeader.setDestinationIP(LOCAL_IP); 276 | 277 | CommonMethods.ComputeTCPChecksum(ipHeader, tcpHeader); 278 | m_VPNOutputStream.write(ipHeader.m_Data, ipHeader.m_Offset, size); 279 | m_ReceivedBytes += size; 280 | } else { 281 | System.out.printf("NoSession: %s %s\n", ipHeader.toString(), tcpHeader.toString()); 282 | } 283 | } else { 284 | 285 | // 添加端口映射 286 | int portKey = tcpHeader.getSourcePort(); 287 | NatSession session = NatSessionManager.getSession(portKey); 288 | if (session == null || session.RemoteIP != ipHeader.getDestinationIP() || session.RemotePort != tcpHeader.getDestinationPort()) { 289 | session = NatSessionManager.createSession(portKey, ipHeader.getDestinationIP(), tcpHeader.getDestinationPort()); 290 | } 291 | 292 | session.LastNanoTime = System.nanoTime(); 293 | session.PacketSent++;//注意顺序 294 | 295 | int tcpDataSize = ipHeader.getDataLength() - tcpHeader.getHeaderLength(); 296 | if (session.PacketSent == 3) { 297 | System.out.println(123); 298 | } 299 | if (session.PacketSent == 2 && tcpDataSize == 0) { 300 | return;//丢弃tcp握手的第二个ACK报文。因为客户端发数据的时候也会带上ACK,这样可以在服务器Accept之前分析出HOST信息。 301 | } 302 | 303 | //分析数据,找到host 304 | if (session.BytesSent == 0 && tcpDataSize > 10) { 305 | int dataOffset = tcpHeader.m_Offset + tcpHeader.getHeaderLength(); 306 | String host = HttpHostHeaderParser.parseHost(tcpHeader.m_Data, dataOffset, tcpDataSize); 307 | if (host != null) { 308 | session.RemoteHost = host; 309 | System.out.println("host: " + host); 310 | } 311 | } 312 | 313 | // 转发给本地TCP服务器 314 | ipHeader.setSourceIP(ipHeader.getDestinationIP()); 315 | ipHeader.setDestinationIP(LOCAL_IP); 316 | tcpHeader.setDestinationPort(m_TcpProxyServer.Port); 317 | 318 | CommonMethods.ComputeTCPChecksum(ipHeader, tcpHeader); 319 | m_VPNOutputStream.write(ipHeader.m_Data, ipHeader.m_Offset, size); 320 | session.BytesSent += tcpDataSize;//注意顺序 321 | m_SentBytes += size; 322 | } 323 | } 324 | break; 325 | case IPHeader.UDP: 326 | // 转发DNS数据包: 327 | UDPHeader udpHeader = m_UDPHeader; 328 | udpHeader.m_Offset = ipHeader.getHeaderLength(); 329 | if (ipHeader.getSourceIP() == LOCAL_IP && udpHeader.getDestinationPort() == 53) { 330 | // if (ipHeader.getSourceIP() == LOCAL_IP) { 331 | m_DNSBuffer.clear(); 332 | m_DNSBuffer.limit(ipHeader.getDataLength() - 8); 333 | DnsPacket dnsPacket = DnsPacket.FromBytes(m_DNSBuffer); 334 | if (dnsPacket != null && dnsPacket.Header.QuestionCount > 0) { 335 | m_DnsProxy.onDnsRequestReceived(ipHeader, udpHeader, dnsPacket); 336 | } 337 | } 338 | break; 339 | } 340 | } 341 | 342 | private void waitUntilPreapred() { 343 | while (prepare(this) != null) { 344 | try { 345 | Thread.sleep(100); 346 | } catch (InterruptedException e) { 347 | e.printStackTrace(); 348 | } 349 | } 350 | } 351 | 352 | private ParcelFileDescriptor establishVPN() throws Exception { 353 | 354 | Builder builder = new Builder(); 355 | 356 | builder.setMtu(ProxyConfig.Instance.getMTU()); 357 | 358 | if (ProxyConfig.IS_DEBUG) 359 | System.out.printf("setMtu: %d\n", ProxyConfig.Instance.getMTU()); 360 | 361 | IPAddress ipAddress = ProxyConfig.Instance.getDefaultLocalIP(); 362 | LOCAL_IP = CommonMethods.ipStringToInt(ipAddress.Address); 363 | builder.addAddress(ipAddress.Address, ipAddress.PrefixLength); 364 | builder.addAllowedApplication("me.smartproxy"); 365 | builder.addAllowedApplication("com.android.chrome"); 366 | if (ProxyConfig.IS_DEBUG) 367 | System.out.printf("addAddress: %s/%d\n", ipAddress.Address, ipAddress.PrefixLength); 368 | 369 | for (ProxyConfig.IPAddress dns : ProxyConfig.Instance.getDnsList()) { 370 | builder.addDnsServer(dns.Address); 371 | if (ProxyConfig.IS_DEBUG) 372 | System.out.printf("addDnsServer: %s\n", dns.Address); 373 | } 374 | 375 | if (ProxyConfig.Instance.getRouteList().size() > 0) { 376 | for (ProxyConfig.IPAddress routeAddress : ProxyConfig.Instance.getRouteList()) { 377 | builder.addRoute(routeAddress.Address, routeAddress.PrefixLength); 378 | if (ProxyConfig.IS_DEBUG) 379 | System.out.printf("addRoute: %s/%d\n", routeAddress.Address, routeAddress.PrefixLength); 380 | } 381 | builder.addRoute(CommonMethods.ipIntToString(ProxyConfig.FAKE_NETWORK_IP), 16); 382 | 383 | if (ProxyConfig.IS_DEBUG) 384 | System.out.printf("addRoute for FAKE_NETWORK: %s/%d\n", CommonMethods.ipIntToString(ProxyConfig.FAKE_NETWORK_IP), 16); 385 | } else { 386 | builder.addRoute("0.0.0.0", 0); 387 | if (ProxyConfig.IS_DEBUG) 388 | System.out.printf("addDefaultRoute: 0.0.0.0/0\n"); 389 | } 390 | 391 | 392 | Class SystemProperties = Class.forName("android.os.SystemProperties"); 393 | Method method = SystemProperties.getMethod("get", new Class[]{String.class}); 394 | ArrayList servers = new ArrayList(); 395 | for (String name : new String[]{"net.dns1", "net.dns2", "net.dns3", "net.dns4",}) { 396 | // for (String name : new String[] { "223.5.5.5", "114.114.114.114", "192.168.50.1"}) { 397 | String value = (String) method.invoke(null, name); 398 | // String value = name; 399 | if (value != null && !"".equals(value) && !servers.contains(value)) { 400 | servers.add(value); 401 | builder.addRoute(value, 32); 402 | if (ProxyConfig.IS_DEBUG) 403 | System.out.printf("%s=%s\n, 111111", name, value); 404 | } 405 | } 406 | 407 | Intent intent = new Intent(this, MainActivity.class); 408 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); 409 | builder.setConfigureIntent(pendingIntent); 410 | 411 | builder.setSession(ProxyConfig.Instance.getSessionName()); 412 | ParcelFileDescriptor pfdDescriptor = builder.establish(); 413 | onStatusChanged(ProxyConfig.Instance.getSessionName() + getString(R.string.vpn_connected_status), true); 414 | return pfdDescriptor; 415 | } 416 | 417 | public void disconnectVPN() { 418 | try { 419 | if (m_VPNInterface != null) { 420 | m_VPNInterface.close(); 421 | m_VPNInterface = null; 422 | } 423 | } catch (Exception e) { 424 | // ignore 425 | } 426 | onStatusChanged(ProxyConfig.Instance.getSessionName() + getString(R.string.vpn_disconnected_status), false); 427 | this.m_VPNOutputStream = null; 428 | } 429 | 430 | private synchronized void dispose() { 431 | // 断开VPN 432 | disconnectVPN(); 433 | 434 | // 停止TcpServer 435 | if (m_TcpProxyServer != null) { 436 | m_TcpProxyServer.stop(); 437 | m_TcpProxyServer = null; 438 | writeLog("LocalTcpServer stopped."); 439 | } 440 | 441 | // 停止DNS解析器 442 | if (m_DnsProxy != null) { 443 | m_DnsProxy.stop(); 444 | m_DnsProxy = null; 445 | writeLog("LocalDnsProxy stopped."); 446 | } 447 | 448 | stopSelf(); 449 | IsRunning = false; 450 | System.exit(0); 451 | } 452 | 453 | @Override 454 | public void onDestroy() { 455 | System.out.printf("VPNService(%s) destoried.\n", ID); 456 | if (m_VPNThread != null) { 457 | m_VPNThread.interrupt(); 458 | } 459 | } 460 | 461 | } 462 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/NatSession.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/NatSessionManager.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.core; 2 | 3 | import me.smartproxy.tcpip.CommonMethods; 4 | import android.util.SparseArray; 5 | 6 | public class NatSessionManager { 7 | 8 | static final int MAX_SESSION_COUNT=60; 9 | static final long SESSION_TIMEOUT_NS=60*1000000000L; 10 | static final SparseArray Sessions = new SparseArray(); 11 | 12 | public static NatSession getSession(int portKey){ 13 | return Sessions.get(portKey); 14 | } 15 | 16 | public static int getSessionCount() { 17 | return Sessions.size(); 18 | } 19 | 20 | static void clearExpiredSessions(){ 21 | long now=System.nanoTime(); 22 | for (int i = Sessions.size()-1; i>=0; i--) { 23 | NatSession session=Sessions.valueAt(i); 24 | if(now- session.LastNanoTime>SESSION_TIMEOUT_NS){ 25 | Sessions.removeAt(i); 26 | } 27 | } 28 | } 29 | 30 | public static NatSession createSession(int portKey,int remoteIP,short remotePort){ 31 | if(Sessions.size()>MAX_SESSION_COUNT){ 32 | clearExpiredSessions();//清理过期的会话。 33 | } 34 | 35 | NatSession session=new NatSession(); 36 | session.LastNanoTime=System.nanoTime(); 37 | session.RemoteIP=remoteIP; 38 | session.RemotePort=remotePort; 39 | 40 | if(ProxyConfig.isFakeIP(remoteIP)){ 41 | session.RemoteHost=DnsProxy.reverseLookup(remoteIP); 42 | } 43 | 44 | if(session.RemoteHost==null){ 45 | session.RemoteHost=CommonMethods.ipIntToString(remoteIP); 46 | } 47 | Sessions.put(portKey, session); 48 | return session; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/ProxyConfig.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.core; 2 | 3 | import android.annotation.SuppressLint; 4 | 5 | import java.io.FileInputStream; 6 | import java.net.InetAddress; 7 | import java.net.InetSocketAddress; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.Locale; 11 | import java.util.Timer; 12 | import java.util.TimerTask; 13 | import java.util.regex.Matcher; 14 | import java.util.regex.Pattern; 15 | 16 | import me.smartproxy.tcpip.CommonMethods; 17 | import me.smartproxy.tunnel.Config; 18 | import me.smartproxy.tunnel.httpconnect.HttpConnectConfig; 19 | import me.smartproxy.tunnel.shadowsocks.ShadowsocksConfig; 20 | 21 | public class ProxyConfig { 22 | public static final ProxyConfig Instance = new ProxyConfig(); 23 | public final static boolean IS_DEBUG = true; 24 | public static String AppInstallID; 25 | public static String AppVersion; 26 | public final static int FAKE_NETWORK_MASK = CommonMethods.ipStringToInt("255.255.0.0"); 27 | public final static int FAKE_NETWORK_IP = CommonMethods.ipStringToInt("10.231.0.0"); 28 | 29 | ArrayList m_IpList; 30 | ArrayList m_DnsList; 31 | ArrayList m_RouteList; 32 | ArrayList m_ProxyList; 33 | HashMap m_DomainMap; 34 | 35 | int m_dns_ttl; 36 | String m_welcome_info; 37 | String m_session_name; 38 | String m_user_agent; 39 | boolean m_outside_china_use_proxy = true; 40 | boolean m_isolate_http_host_header = true; 41 | int m_mtu; 42 | 43 | Timer m_Timer; 44 | 45 | public class IPAddress { 46 | public final String Address; 47 | public final int PrefixLength; 48 | 49 | public IPAddress(String address, int prefixLength) { 50 | this.Address = address; 51 | this.PrefixLength = prefixLength; 52 | } 53 | 54 | public IPAddress(String ipAddresString) { 55 | String[] arrStrings = ipAddresString.split("/"); 56 | String address = arrStrings[0]; 57 | int prefixLength = 32; 58 | if (arrStrings.length > 1) { 59 | prefixLength = Integer.parseInt(arrStrings[1]); 60 | } 61 | this.Address = address; 62 | this.PrefixLength = prefixLength; 63 | } 64 | 65 | @SuppressLint("DefaultLocale") 66 | @Override 67 | public String toString() { 68 | return String.format("%s/%d", Address, PrefixLength); 69 | } 70 | 71 | @Override 72 | public boolean equals(Object o) { 73 | if (o == null) { 74 | return false; 75 | } else { 76 | return this.toString().equals(o.toString()); 77 | } 78 | } 79 | } 80 | 81 | public ProxyConfig() { 82 | m_IpList = new ArrayList(); 83 | m_DnsList = new ArrayList(); 84 | m_RouteList = new ArrayList(); 85 | m_ProxyList = new ArrayList(); 86 | m_DomainMap = new HashMap(); 87 | 88 | m_Timer = new Timer(); 89 | m_Timer.schedule(m_Task, 120000, 120000);//每两分钟刷新一次。 90 | } 91 | 92 | TimerTask m_Task = new TimerTask() { 93 | @Override 94 | public void run() { 95 | refreshProxyServer();//定时更新dns缓存 96 | } 97 | 98 | //定时更新dns缓存 99 | void refreshProxyServer() { 100 | try { 101 | for (int i = 0; i < m_ProxyList.size(); i++) { 102 | try { 103 | Config config = m_ProxyList.get(0); 104 | InetAddress address = InetAddress.getByName(config.ServerAddress.getHostName()); 105 | if (address != null && !address.equals(config.ServerAddress.getAddress())) { 106 | config.ServerAddress = new InetSocketAddress(address, config.ServerAddress.getPort()); 107 | } 108 | } catch (Exception e) { 109 | } 110 | } 111 | } catch (Exception e) { 112 | 113 | } 114 | } 115 | }; 116 | 117 | 118 | public static boolean isFakeIP(int ip) { 119 | return (ip & ProxyConfig.FAKE_NETWORK_MASK) == ProxyConfig.FAKE_NETWORK_IP; 120 | } 121 | 122 | public Config getDefaultProxy() { 123 | if (m_ProxyList.size() > 0) { 124 | return m_ProxyList.get(0); 125 | } else { 126 | return null; 127 | } 128 | } 129 | 130 | public Config getDefaultTunnelConfig(InetSocketAddress destAddress) { 131 | return getDefaultProxy(); 132 | } 133 | 134 | public IPAddress getDefaultLocalIP() { 135 | if (m_IpList.size() > 0) { 136 | return m_IpList.get(0); 137 | } else { 138 | return new IPAddress("10.8.0.2", 32); 139 | } 140 | } 141 | 142 | public ArrayList getDnsList() { 143 | return m_DnsList; 144 | } 145 | 146 | public ArrayList getRouteList() { 147 | return m_RouteList; 148 | } 149 | 150 | public int getDnsTTL() { 151 | if (m_dns_ttl < 30) { 152 | m_dns_ttl = 30; 153 | } 154 | return m_dns_ttl; 155 | } 156 | 157 | public String getWelcomeInfo() { 158 | return m_welcome_info; 159 | } 160 | 161 | public String getSessionName() { 162 | if (m_session_name == null) { 163 | m_session_name = getDefaultProxy().ServerAddress.getHostName(); 164 | } 165 | return m_session_name; 166 | } 167 | 168 | public String getUserAgent() { 169 | if (m_user_agent == null || m_user_agent.isEmpty()) { 170 | m_user_agent = System.getProperty("http.agent"); 171 | } 172 | return m_user_agent; 173 | } 174 | 175 | public int getMTU() { 176 | if (m_mtu > 1400 && m_mtu <= 20000) { 177 | return m_mtu; 178 | } else { 179 | return 1400; 180 | } 181 | } 182 | 183 | private Boolean getDomainState(String domain) { 184 | domain = domain.toLowerCase(); 185 | while (domain.length() > 0) { 186 | Boolean stateBoolean = m_DomainMap.get(domain); 187 | if (stateBoolean != null) { 188 | return stateBoolean; 189 | } else { 190 | int start = domain.indexOf('.') + 1; 191 | if (start > 0 && start < domain.length()) { 192 | domain = domain.substring(start); 193 | } else { 194 | return null; 195 | } 196 | } 197 | } 198 | return null; 199 | } 200 | 201 | public boolean needProxy(String host, int ip) { 202 | System.out.println(String.format("%s %s ", host, ip)); 203 | if (host != null) { 204 | Boolean stateBoolean = getDomainState(host); 205 | if (stateBoolean != null) { 206 | return stateBoolean.booleanValue(); 207 | } 208 | } 209 | 210 | if (isFakeIP(ip)) 211 | return true; 212 | 213 | if (m_outside_china_use_proxy && ip != 0) { 214 | return !ChinaIpMaskManager.isIPInChina(ip); 215 | } 216 | return false; 217 | } 218 | 219 | public boolean isIsolateHttpHostHeader() { 220 | return m_isolate_http_host_header; 221 | } 222 | 223 | 224 | private String[] downloadConfig(String url) throws Exception { 225 | try { 226 | /* HttpClient client=new DefaultHttpClient(); 227 | HttpGet requestGet=new HttpGet(url); 228 | 229 | requestGet.addHeader("X-Android-MODEL", Build.MODEL); 230 | requestGet.addHeader("X-Android-SDK_INT",Integer.toString(Build.VERSION.SDK_INT)); 231 | requestGet.addHeader("X-Android-RELEASE", Build.VERSION.RELEASE); 232 | requestGet.addHeader("X-App-Version", AppVersion); 233 | requestGet.addHeader("X-App-Install-ID", AppInstallID); 234 | requestGet.setHeader("User-Agent", System.getProperty("http.agent")); 235 | HttpResponse response=client.execute(requestGet); 236 | 237 | String configString=EntityUtils.toString(response.getEntity(),"UTF-8");*/ 238 | String user_pwd = ""; 239 | if (!TmpConfig.UserName.equals("")) { 240 | user_pwd = String.format("%s:%s@", TmpConfig.UserName, TmpConfig.Password); 241 | } 242 | //String configString = String.format("proxy http://%s%s:%s", user_pwd, TmpConfig.remoteIp, TmpConfig.remotePort); 243 | String configString = String.format("proxy http://%s127.0.0.1:9000", user_pwd); 244 | String[] lines = configString.split("\\n"); 245 | return lines; 246 | } catch (Exception e) { 247 | throw new Exception(String.format("Download config file from %s failed.", url)); 248 | } 249 | } 250 | 251 | private String[] readConfigFromFile(String path) throws Exception { 252 | StringBuilder sBuilder = new StringBuilder(); 253 | FileInputStream inputStream = null; 254 | try { 255 | byte[] buffer = new byte[8192]; 256 | int count = 0; 257 | inputStream = new FileInputStream(path); 258 | while ((count = inputStream.read(buffer)) > 0) { 259 | sBuilder.append(new String(buffer, 0, count, "UTF-8")); 260 | } 261 | return sBuilder.toString().split("\\n"); 262 | } catch (Exception e) { 263 | throw new Exception(String.format("Can't read config file: %s", path)); 264 | } finally { 265 | if (inputStream != null) { 266 | try { 267 | inputStream.close(); 268 | } catch (Exception e2) { 269 | } 270 | } 271 | } 272 | } 273 | 274 | public void loadFromUrl(String url) throws Exception { 275 | String[] lines = null; 276 | if (url.charAt(0) == '/') { 277 | lines = readConfigFromFile(url); 278 | } else { 279 | lines = downloadConfig(url); 280 | } 281 | 282 | m_IpList.clear(); 283 | m_DnsList.clear(); 284 | m_RouteList.clear(); 285 | m_ProxyList.clear(); 286 | m_DomainMap.clear(); 287 | 288 | int lineNumber = 0; 289 | for (String line : lines) { 290 | lineNumber++; 291 | String[] items = line.split("\\s+"); 292 | if (items.length < 2) { 293 | continue; 294 | } 295 | 296 | String tagString = items[0].toLowerCase(Locale.ENGLISH).trim(); 297 | try { 298 | if (!tagString.startsWith("#")) { 299 | if (ProxyConfig.IS_DEBUG) 300 | System.out.println(line); 301 | 302 | if (tagString.equals("ip")) { 303 | addIPAddressToList(items, 1, m_IpList); 304 | } else if (tagString.equals("dns")) { 305 | addIPAddressToList(items, 1, m_DnsList); 306 | } else if (tagString.equals("route")) { 307 | addIPAddressToList(items, 1, m_RouteList); 308 | } else if (tagString.equals("proxy")) { 309 | addProxyToList(items, 1); 310 | } else if (tagString.equals("direct_domain")) { 311 | addDomainToHashMap(items, 1, false); 312 | } else if (tagString.equals("proxy_domain")) { 313 | addDomainToHashMap(items, 1, true); 314 | } else if (tagString.equals("dns_ttl")) { 315 | m_dns_ttl = Integer.parseInt(items[1]); 316 | } else if (tagString.equals("welcome_info")) { 317 | m_welcome_info = line.substring(line.indexOf(" ")).trim(); 318 | } else if (tagString.equals("session_name")) { 319 | m_session_name = items[1]; 320 | } else if (tagString.equals("user_agent")) { 321 | m_user_agent = line.substring(line.indexOf(" ")).trim(); 322 | } else if (tagString.equals("outside_china_use_proxy")) { 323 | m_outside_china_use_proxy = convertToBool(items[1]); 324 | } else if (tagString.equals("isolate_http_host_header")) { 325 | m_isolate_http_host_header = convertToBool(items[1]); 326 | } else if (tagString.equals("mtu")) { 327 | m_mtu = Integer.parseInt(items[1]); 328 | } 329 | } 330 | } catch (Exception e) { 331 | throw new Exception(String.format("SmartProxy config file parse error: line:%d, tag:%s, error:%s", lineNumber, tagString, e)); 332 | } 333 | 334 | } 335 | 336 | //查找默认代理。 337 | if (m_ProxyList.size() == 0) { 338 | tryAddProxy(lines); 339 | } 340 | } 341 | 342 | private void tryAddProxy(String[] lines) { 343 | for (String line : lines) { 344 | Pattern p = Pattern.compile("proxy\\s+([^:]+):(\\d+)", Pattern.CASE_INSENSITIVE); 345 | Matcher m = p.matcher(line); 346 | while (m.find()) { 347 | HttpConnectConfig config = new HttpConnectConfig(); 348 | config.ServerAddress = new InetSocketAddress(m.group(1), Integer.parseInt(m.group(2))); 349 | if (!m_ProxyList.contains(config)) { 350 | m_ProxyList.add(config); 351 | m_DomainMap.put(config.ServerAddress.getHostName(), false); 352 | } 353 | } 354 | } 355 | } 356 | 357 | private void addProxyToList(String[] items, int offset) throws Exception { 358 | for (int i = offset; i < items.length; i++) { 359 | String proxyString = items[i].trim(); 360 | Config config = null; 361 | if (proxyString.startsWith("ss://")) { 362 | config = ShadowsocksConfig.parse(proxyString); 363 | } else { 364 | if (!proxyString.toLowerCase().startsWith("http://")) { 365 | proxyString = "http://" + proxyString; 366 | } 367 | config = HttpConnectConfig.parse(proxyString); 368 | } 369 | if (!m_ProxyList.contains(config)) { 370 | m_ProxyList.add(config); 371 | m_DomainMap.put(config.ServerAddress.getHostName(), false); 372 | } 373 | } 374 | } 375 | 376 | private void addDomainToHashMap(String[] items, int offset, Boolean state) { 377 | for (int i = offset; i < items.length; i++) { 378 | String domainString = items[i].toLowerCase().trim(); 379 | if (domainString.charAt(0) == '.') { 380 | domainString = domainString.substring(1); 381 | } 382 | m_DomainMap.put(domainString, state); 383 | } 384 | } 385 | 386 | private boolean convertToBool(String valueString) { 387 | if (valueString == null || valueString.isEmpty()) 388 | return false; 389 | valueString = valueString.toLowerCase(Locale.ENGLISH).trim(); 390 | if (valueString.equals("on") || valueString.equals("1") || valueString.equals("true") || valueString.equals("yes")) { 391 | return true; 392 | } else { 393 | return false; 394 | } 395 | } 396 | 397 | 398 | private void addIPAddressToList(String[] items, int offset, ArrayList list) { 399 | for (int i = offset; i < items.length; i++) { 400 | String item = items[i].trim().toLowerCase(); 401 | if (item.startsWith("#")) { 402 | break; 403 | } else { 404 | IPAddress ip = new IPAddress(item); 405 | if (!list.contains(ip)) { 406 | list.add(ip); 407 | } 408 | } 409 | } 410 | } 411 | 412 | } 413 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/TcpProxyServer.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.core; 2 | 3 | import java.io.IOException; 4 | import java.net.Inet4Address; 5 | import java.net.InetAddress; 6 | import java.net.InetSocketAddress; 7 | import java.nio.channels.SelectionKey; 8 | import java.nio.channels.Selector; 9 | import java.nio.channels.ServerSocketChannel; 10 | import java.nio.channels.SocketChannel; 11 | import java.util.Iterator; 12 | 13 | import me.smartproxy.tcpip.CommonMethods; 14 | import me.smartproxy.tunnel.Tunnel; 15 | 16 | public class TcpProxyServer implements Runnable { 17 | 18 | public boolean Stopped; 19 | public short Port; 20 | 21 | Selector m_Selector; 22 | ServerSocketChannel m_ServerSocketChannel; 23 | Thread m_ServerThread; 24 | 25 | public TcpProxyServer(int port) throws IOException { 26 | m_Selector = Selector.open(); 27 | m_ServerSocketChannel = ServerSocketChannel.open(); 28 | m_ServerSocketChannel.configureBlocking(false); 29 | InetSocketAddress addr = new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0); 30 | m_ServerSocketChannel.socket().bind(addr); 31 | m_ServerSocketChannel.register(m_Selector, SelectionKey.OP_ACCEPT); 32 | this.Port=(short) m_ServerSocketChannel.socket().getLocalPort(); 33 | System.out.printf("AsyncTcpServer listen on %d success.\n", this.Port&0xFFFF); 34 | System.out.println(m_ServerSocketChannel.getLocalAddress()); 35 | } 36 | 37 | public void start(){ 38 | m_ServerThread=new Thread(this); 39 | m_ServerThread.setName("TcpProxyServerThread"); 40 | m_ServerThread.start(); 41 | } 42 | 43 | public void stop(){ 44 | this.Stopped=true; 45 | if(m_Selector!=null){ 46 | try { 47 | m_Selector.close(); 48 | m_Selector=null; 49 | } catch (Exception e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | 54 | if(m_ServerSocketChannel!=null){ 55 | try { 56 | m_ServerSocketChannel.close(); 57 | m_ServerSocketChannel=null; 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | } 63 | 64 | @Override 65 | public void run() { 66 | try { 67 | while (true) { 68 | m_Selector.select(); 69 | Iterator keyIterator = m_Selector.selectedKeys().iterator(); 70 | while (keyIterator.hasNext()) { 71 | SelectionKey key = keyIterator.next(); 72 | if (key.isValid()) { 73 | try { 74 | if (key.isReadable()) { 75 | ((Tunnel)key.attachment()).onReadable(key); 76 | } 77 | else if(key.isWritable()){ 78 | ((Tunnel)key.attachment()).onWritable(key); 79 | } 80 | else if (key.isConnectable()) { 81 | ((Tunnel)key.attachment()).onConnectable(); 82 | } 83 | else if (key.isAcceptable()) { 84 | onAccepted(key); 85 | } 86 | } catch (Exception e) { 87 | System.out.println(e.toString()); 88 | } 89 | } 90 | keyIterator.remove(); 91 | } 92 | } 93 | } catch (Exception e) { 94 | e.printStackTrace(); 95 | }finally{ 96 | this.stop(); 97 | System.out.println("TcpServer thread exited."); 98 | } 99 | } 100 | 101 | InetSocketAddress getDestAddress(SocketChannel localChannel){ 102 | short portKey=(short)localChannel.socket().getPort(); 103 | NatSession session =NatSessionManager.getSession(portKey); 104 | if (session != null) { 105 | // 判断是否走代理 106 | if(ProxyConfig.Instance.needProxy(session.RemoteHost, session.RemoteIP) || !TmpConfig.bypass){ 107 | // libnghttpx.so 往远端发不走代理 108 | if (session.RemoteHost.equals(TmpConfig.remoteIp)){ 109 | return new InetSocketAddress(localChannel.socket().getInetAddress(),session.RemotePort&0xFFFF); 110 | } 111 | 112 | if(ProxyConfig.IS_DEBUG) 113 | System.out.printf("%d/%d:[PROXY] %s=>%s:%d\n",NatSessionManager.getSessionCount(), Tunnel.SessionCount,session.RemoteHost,CommonMethods.ipIntToString(session.RemoteIP),session.RemotePort&0xFFFF); 114 | return InetSocketAddress.createUnresolved(session.RemoteHost, session.RemotePort&0xFFFF); 115 | }else { 116 | // 直接放行 117 | return new InetSocketAddress(localChannel.socket().getInetAddress(),session.RemotePort&0xFFFF); 118 | } 119 | } 120 | return null; 121 | } 122 | 123 | void onAccepted(SelectionKey key){ 124 | Tunnel localTunnel =null; 125 | try { 126 | SocketChannel localChannel=m_ServerSocketChannel.accept(); 127 | localTunnel=TunnelFactory.wrap(localChannel, m_Selector); 128 | 129 | InetSocketAddress destAddress=getDestAddress(localChannel); 130 | if(destAddress!=null){ 131 | Tunnel remoteTunnel=TunnelFactory.createTunnelByConfig(destAddress,m_Selector); 132 | remoteTunnel.setBrotherTunnel(localTunnel);//关联兄弟 133 | localTunnel.setBrotherTunnel(remoteTunnel);//关联兄弟 134 | remoteTunnel.connect(destAddress);//开始连接 135 | } 136 | else { 137 | LocalVpnService.Instance.writeLog("Error: socket(%s:%d) target host is null.",localChannel.socket().getInetAddress().toString(),localChannel.socket().getPort()); 138 | localTunnel.dispose(); 139 | } 140 | } catch (Exception e) { 141 | e.printStackTrace(); 142 | LocalVpnService.Instance.writeLog("Error: remote socket create failed: %s",e.toString()); 143 | if(localTunnel!=null){ 144 | localTunnel.dispose(); 145 | } 146 | } 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/TmpConfig.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.core; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | 12 | import static android.app.PendingIntent.getActivity; 13 | import static android.content.Context.MODE_PRIVATE; 14 | 15 | /** 16 | * Created by gho on 17-11-4. 17 | */ 18 | 19 | public class TmpConfig { 20 | public static String UserName, Password, remoteIp, remotePort; 21 | public static boolean bypass = true; // 是否绕过国内 22 | 23 | public static final String UserKey = "UserNameKey"; 24 | public static final String PasswordKey = "PasswordKey"; 25 | public static final String IpKey = "IpKey"; 26 | public static final String PortKey = "PortKey"; 27 | public static final String ByPassKey = "ByPass"; 28 | 29 | public static final String CONFIG_URL_KEY = "CONFIG_URL_KEY"; 30 | 31 | public static String nghttpxCmd = ""; 32 | 33 | // public static String exePath = "/data/data/me.smartproxy/"; 34 | 35 | private static String username = "", pwd = "", ip = "", port = ""; 36 | 37 | private static void checkConfig(Context context) { 38 | UserName = username = readConfigKey(UserKey, context); 39 | Password = pwd = readConfigKey(PasswordKey, context); 40 | remoteIp = ip = readConfigKey(IpKey, context); 41 | remotePort = port = readConfigKey(PortKey, context); 42 | if (readConfigKey(ByPassKey, context).equals("true")) { 43 | bypass = true; 44 | } else { 45 | bypass = false; 46 | } 47 | } 48 | 49 | public static String getConfig(Context context) { 50 | 51 | String user_pwd = ""; 52 | checkConfig(context); 53 | if (!username.equals("")) { 54 | user_pwd = String.format("%s:%s@", username, pwd); 55 | } 56 | return String.format("http://%s%s:%s", user_pwd, ip, port); 57 | 58 | } 59 | 60 | public static String readConfigKey(String Key, Context context) { 61 | SharedPreferences preferences = context.getSharedPreferences("SmartProxy", MODE_PRIVATE); 62 | return preferences.getString(Key, ""); 63 | } 64 | 65 | public static void CopyAndStart(Context context) { 66 | checkConfig(context); 67 | try { 68 | String filePath = context.getFilesDir().getPath() + "libnghttpx.so"; 69 | System.out.println(filePath); 70 | File f = new File(filePath); 71 | 72 | if (!f.exists()) { 73 | copyDataToSD(filePath, "libnghttpx.so", context); 74 | } 75 | File exeFile = new File(filePath); 76 | exeFile.setExecutable(true, true); 77 | 78 | nghttpxCmd = context.getApplicationInfo().nativeLibraryDir + "/libnghttpx.so"; 79 | } catch (IOException e1) { 80 | e1.printStackTrace(); 81 | } 82 | startNghttpx(); 83 | // setBypass(); // 绕过国内 84 | } 85 | 86 | public static void startNghttpx() { 87 | Runnable runnable = new Runnable() { 88 | @Override 89 | public void run() { 90 | try { 91 | execCmd(); 92 | } catch (IOException e) { 93 | e.printStackTrace(); 94 | LocalVpnService.Instance.writeLog(e.toString()); 95 | } 96 | } 97 | }; 98 | final Thread thread = new Thread(runnable); 99 | thread.start(); 100 | 101 | /* Runnable stop = new Runnable() { 102 | @Override 103 | public void run() { 104 | while (true) { 105 | if (!LocalVpnService.IsRunning){ 106 | if (thread.isAlive()) { 107 | thread.interrupt(); 108 | } 109 | break; 110 | } 111 | try { 112 | Thread.sleep(1000); 113 | } catch (InterruptedException e) { 114 | e.printStackTrace(); 115 | } 116 | } 117 | } 118 | }; 119 | final Thread stopThread = new Thread(stop); 120 | stopThread.start();*/ 121 | } 122 | 123 | private static void execCmd() throws IOException { 124 | Runtime runtime = Runtime.getRuntime(); 125 | String backendConfig = String.format("--backend=%s,%s;;tls;proto=h2", ip, port); 126 | 127 | System.out.println(backendConfig); 128 | 129 | Process process = runtime.exec(new String[]{ 130 | TmpConfig.nghttpxCmd, 131 | "-k", 132 | "--frontend=0.0.0.0,9000;no-tls", 133 | backendConfig, 134 | "--http2-proxy", 135 | "--workers=4", 136 | "--backend-http2-window-size=524288", 137 | }); 138 | 139 | try { 140 | process.waitFor(); 141 | InputStream error = process.getErrorStream(); 142 | String err_msg = ""; 143 | 144 | for (int i = 0; i < 2048; i++) { 145 | err_msg += (char) error.read(); 146 | } 147 | System.out.println(err_msg); 148 | 149 | } catch (InterruptedException e) { 150 | e.printStackTrace(); 151 | } 152 | 153 | } 154 | 155 | private static void copyDataToSD(String strOutFileName, String assertFileName, Context context) throws IOException { 156 | InputStream myInput; 157 | OutputStream myOutput = new FileOutputStream(strOutFileName); 158 | myInput = context.getAssets().open(assertFileName); 159 | byte[] buffer = new byte[1024]; 160 | int length = myInput.read(buffer); 161 | while (length > 0) { 162 | myOutput.write(buffer, 0, length); 163 | length = myInput.read(buffer); 164 | } 165 | myOutput.flush(); 166 | myInput.close(); 167 | myOutput.close(); 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/core/TunnelFactory.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.core; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.nio.channels.Selector; 5 | import java.nio.channels.SocketChannel; 6 | 7 | import me.smartproxy.tunnel.Config; 8 | import me.smartproxy.tunnel.RawTunnel; 9 | import me.smartproxy.tunnel.Tunnel; 10 | import me.smartproxy.tunnel.httpconnect.HttpConnectConfig; 11 | import me.smartproxy.tunnel.httpconnect.HttpConnectTunnel; 12 | import me.smartproxy.tunnel.shadowsocks.ShadowsocksConfig; 13 | import me.smartproxy.tunnel.shadowsocks.ShadowsocksTunnel; 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 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/dns/DnsFlags.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/dns/DnsHeader.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.dns; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | import me.smartproxy.tcpip.CommonMethods; 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 | { 29 | buffer.putShort(this.ID); 30 | buffer.putShort(this.Flags.ToShort()); 31 | buffer.putShort(this.QuestionCount); 32 | buffer.putShort(this.ResourceCount); 33 | buffer.putShort(this.AResourceCount); 34 | buffer.putShort(this.EResourceCount); 35 | } 36 | 37 | static final short offset_ID=0; 38 | static final short offset_Flags=2; 39 | static final short offset_QuestionCount=4; 40 | static final short offset_ResourceCount=6; 41 | static final short offset_AResourceCount=8; 42 | static final short offset_EResourceCount=10; 43 | 44 | public byte[] Data; 45 | public int Offset; 46 | 47 | public DnsHeader(byte[] data,int offset){ 48 | this.Offset=offset; 49 | this.Data=data; 50 | } 51 | 52 | public short getID() { 53 | return CommonMethods.readShort(Data, Offset+offset_ID); 54 | } 55 | 56 | public short getFlags() { 57 | return CommonMethods.readShort(Data, Offset+offset_Flags); 58 | } 59 | 60 | public short getQuestionCount() { 61 | return CommonMethods.readShort(Data, Offset+offset_QuestionCount); 62 | } 63 | 64 | public short getResourceCount() { 65 | return CommonMethods.readShort(Data, Offset+offset_ResourceCount); 66 | } 67 | 68 | public short getAResourceCount() { 69 | return CommonMethods.readShort(Data, Offset+offset_AResourceCount); 70 | } 71 | 72 | public short getEResourceCount() { 73 | return CommonMethods.readShort(Data, Offset+offset_EResourceCount); 74 | } 75 | 76 | public void setID(short value) { 77 | CommonMethods.writeShort(Data, Offset+offset_ID,value); 78 | } 79 | 80 | public void setFlags(short value) { 81 | CommonMethods.writeShort(Data, Offset+offset_Flags,value); 82 | } 83 | 84 | public void setQuestionCount(short value) { 85 | CommonMethods.writeShort(Data, Offset+offset_QuestionCount,value); 86 | } 87 | 88 | public void setResourceCount(short value) { 89 | CommonMethods.writeShort(Data, Offset+offset_ResourceCount,value); 90 | } 91 | 92 | public void setAResourceCount(short value) { 93 | CommonMethods.writeShort(Data, Offset+offset_AResourceCount,value); 94 | } 95 | 96 | public void setEResourceCount(short value) { 97 | CommonMethods.writeShort(Data, Offset+offset_EResourceCount,value); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/dns/DnsPacket.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | { 16 | if (buffer.limit() < 12) 17 | return null; 18 | if (buffer.limit() > 512) 19 | return null; 20 | 21 | DnsPacket packet = new DnsPacket(); 22 | packet.Size=buffer.limit(); 23 | packet.Header = DnsHeader.FromBytes(buffer); 24 | 25 | if (packet.Header.QuestionCount > 2 || packet.Header.ResourceCount > 50 || packet.Header.AResourceCount > 50 || packet.Header.EResourceCount > 50) 26 | { 27 | return null; 28 | } 29 | 30 | packet.Questions = new Question[packet.Header.QuestionCount]; 31 | packet.Resources = new Resource[packet.Header.ResourceCount]; 32 | packet.AResources = new Resource[packet.Header.AResourceCount]; 33 | packet.EResources = new Resource[packet.Header.EResourceCount]; 34 | 35 | for (int i = 0; i < packet.Questions.length; i++) 36 | { 37 | packet.Questions[i] = Question.FromBytes(buffer); 38 | } 39 | 40 | for (int i = 0; i < packet.Resources.length; i++) 41 | { 42 | packet.Resources[i] = Resource.FromBytes(buffer); 43 | } 44 | 45 | for (int i = 0; i < packet.AResources.length; i++) 46 | { 47 | packet.AResources[i] = Resource.FromBytes(buffer); 48 | } 49 | 50 | for (int i = 0; i < packet.EResources.length; i++) 51 | { 52 | packet.EResources[i] = Resource.FromBytes(buffer); 53 | } 54 | 55 | return packet; 56 | } 57 | 58 | public void ToBytes(ByteBuffer buffer) 59 | { 60 | Header.QuestionCount = 0; 61 | Header.ResourceCount = 0; 62 | Header.AResourceCount = 0; 63 | Header.EResourceCount = 0; 64 | 65 | if (Questions != null) 66 | Header.QuestionCount = (short) Questions.length; 67 | if (Resources != null) 68 | Header.ResourceCount = (short) Resources.length; 69 | if (AResources != null) 70 | Header.AResourceCount = (short) AResources.length; 71 | if (EResources != null) 72 | Header.EResourceCount = (short) EResources.length; 73 | 74 | this.Header.ToBytes(buffer); 75 | 76 | for (int i = 0; i < Header.QuestionCount; i++) 77 | { 78 | this.Questions[i].ToBytes(buffer); 79 | } 80 | 81 | for (int i = 0; i < Header.ResourceCount; i++) 82 | { 83 | this.Resources[i].ToBytes(buffer); 84 | } 85 | 86 | for (int i = 0; i < Header.AResourceCount; i++) 87 | { 88 | this.AResources[i].ToBytes(buffer); 89 | } 90 | 91 | for (int i = 0; i < Header.EResourceCount; i++) 92 | { 93 | this.EResources[i].ToBytes(buffer); 94 | } 95 | } 96 | 97 | public static String ReadDomain(ByteBuffer buffer,int dnsHeaderOffset) 98 | { 99 | StringBuilder sb = new StringBuilder(); 100 | int len = 0; 101 | while (buffer.hasRemaining() && (len = (buffer.get() & 0xFF)) > 0) 102 | { 103 | if ((len & 0xc0) == 0xc0)// pointer 高2位为11表示是指针。如:1100 0000 104 | { 105 | // 指针的取值是前一字节的后6位加后一字节的8位共14位的值。 106 | int pointer = buffer.get() & 0xFF;// 低8位 107 | pointer |= (len & 0x3F) << 8;// 高6位 108 | 109 | ByteBuffer newBuffer = ByteBuffer.wrap(buffer.array(), dnsHeaderOffset + pointer, dnsHeaderOffset+buffer.limit()); 110 | sb.append(ReadDomain(newBuffer,dnsHeaderOffset)); 111 | return sb.toString(); 112 | } 113 | else 114 | { 115 | while (len > 0 && buffer.hasRemaining()) 116 | { 117 | sb.append((char) (buffer.get() & 0xFF)); 118 | len--; 119 | } 120 | sb.append('.'); 121 | } 122 | } 123 | 124 | if(len==0&&sb.length()>0){ 125 | sb.deleteCharAt(sb.length()-1);//去掉末尾的点(.) 126 | } 127 | return sb.toString(); 128 | } 129 | 130 | public static void WriteDomain(String domain, ByteBuffer buffer) 131 | { 132 | if (domain == null || domain == "") 133 | { 134 | buffer.put((byte) 0); 135 | return; 136 | } 137 | 138 | String[] arr = domain.split("\\."); 139 | for (String item : arr) 140 | { 141 | if (arr.length > 1) 142 | { 143 | buffer.put((byte) item.length()); 144 | } 145 | 146 | for (int i = 0; i < item.length(); i++) 147 | { 148 | buffer.put((byte) item.codePointAt(i)); 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/dns/Question.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | public int Offset() { 12 | return offset; 13 | } 14 | 15 | private int length; 16 | public int Length() { 17 | return length; 18 | } 19 | 20 | public static Question FromBytes(ByteBuffer buffer){ 21 | Question q=new Question(); 22 | q.offset=buffer.arrayOffset()+buffer.position(); 23 | q.Domain = DnsPacket.ReadDomain(buffer,buffer.arrayOffset()); 24 | q.Type=buffer.getShort(); 25 | q.Class=buffer.getShort(); 26 | q.length=buffer.arrayOffset()+buffer.position()-q.offset; 27 | return q; 28 | } 29 | 30 | public void ToBytes(ByteBuffer buffer) 31 | { 32 | this.offset=buffer.position(); 33 | DnsPacket.WriteDomain(this.Domain, buffer); 34 | buffer.putShort(this.Type); 35 | buffer.putShort(this.Class); 36 | this.length=buffer.position()-this.offset; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/dns/Resource.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | public int Offset() { 15 | return offset; 16 | } 17 | 18 | private int length; 19 | public int Length() { 20 | return length; 21 | } 22 | 23 | public static Resource FromBytes(ByteBuffer buffer){ 24 | 25 | Resource r = new Resource(); 26 | r.offset=buffer.arrayOffset()+ buffer.position(); 27 | r.Domain = DnsPacket.ReadDomain(buffer,buffer.arrayOffset()); 28 | r.Type=buffer.getShort(); 29 | r.Class=buffer.getShort(); 30 | r.TTL=buffer.getInt(); 31 | r.DataLength=buffer.getShort(); 32 | r.Data = new byte[r.DataLength&0xFFFF]; 33 | buffer.get(r.Data); 34 | r.length=buffer.arrayOffset()+ buffer.position()-r.offset; 35 | return r; 36 | } 37 | 38 | public void ToBytes(ByteBuffer buffer) 39 | { 40 | if(this.Data==null){ 41 | this.Data=new byte[0]; 42 | } 43 | this.DataLength=(short)this.Data.length; 44 | 45 | this.offset=buffer.position(); 46 | DnsPacket.WriteDomain(this.Domain, buffer); 47 | buffer.putShort(this.Type); 48 | buffer.putShort(this.Class); 49 | buffer.putInt(this.TTL); 50 | 51 | buffer.putShort(this.DataLength); 52 | buffer.put(this.Data); 53 | this.length=buffer.position()-this.offset; 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/dns/ResourcePointer.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.dns; 2 | 3 | import me.smartproxy.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 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tcpip/CommonMethods.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | { 109 | sum += (buf[offset] & 0xFF) << 8; 110 | } 111 | return sum; 112 | } 113 | 114 | // 计算IP包的校验和 115 | public static boolean ComputeIPChecksum(IPHeader ipHeader) { 116 | short oldCrc = ipHeader.getCrc(); 117 | ipHeader.setCrc((short) 0);// 计算前置零 118 | short newCrc = CommonMethods.checksum(0, ipHeader.m_Data, 119 | ipHeader.m_Offset, ipHeader.getHeaderLength()); 120 | ipHeader.setCrc(newCrc); 121 | return oldCrc == newCrc; 122 | } 123 | 124 | // 计算TCP或UDP的校验和 125 | public static boolean ComputeTCPChecksum(IPHeader ipHeader,TCPHeader tcpHeader) { 126 | ComputeIPChecksum(ipHeader);//计算IP校验和 127 | int ipData_len = ipHeader.getTotalLength() - ipHeader.getHeaderLength();// IP数据长度 128 | if (ipData_len < 0) 129 | return false; 130 | // 计算为伪首部和 131 | long sum = getsum(ipHeader.m_Data, ipHeader.m_Offset 132 | + IPHeader.offset_src_ip, 8); 133 | sum += ipHeader.getProtocol()&0xFF; 134 | sum += ipData_len; 135 | 136 | short oldCrc = tcpHeader.getCrc(); 137 | tcpHeader.setCrc((short) 0);// 计算前置0 138 | 139 | short newCrc = checksum(sum, tcpHeader.m_Data, tcpHeader.m_Offset, ipData_len);// 计算校验和 140 | 141 | tcpHeader.setCrc(newCrc); 142 | return oldCrc == newCrc; 143 | } 144 | 145 | // 计算TCP或UDP的校验和 146 | public static boolean ComputeUDPChecksum(IPHeader ipHeader,UDPHeader udpHeader) { 147 | ComputeIPChecksum(ipHeader);//计算IP校验和 148 | int ipData_len = ipHeader.getTotalLength() - ipHeader.getHeaderLength();// IP数据长度 149 | if (ipData_len < 0) 150 | return false; 151 | // 计算为伪首部和 152 | long sum = getsum(ipHeader.m_Data, ipHeader.m_Offset 153 | + IPHeader.offset_src_ip, 8); 154 | sum += ipHeader.getProtocol()&0xFF; 155 | sum += ipData_len; 156 | 157 | short oldCrc = udpHeader.getCrc(); 158 | udpHeader.setCrc((short) 0);// 计算前置0 159 | 160 | short newCrc = checksum(sum, udpHeader.m_Data, udpHeader.m_Offset, ipData_len);// 计算校验和 161 | 162 | udpHeader.setCrc(newCrc); 163 | return oldCrc == newCrc; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tcpip/IPHeader.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tcpip/TCPHeader.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tcpip/UDPHeader.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/Config.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.tunnel; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.nio.channels.Selector; 5 | 6 | public abstract class Config { 7 | public InetSocketAddress ServerAddress; 8 | public IEncryptor Encryptor; 9 | } 10 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/IEncryptor.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.tunnel; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | public interface IEncryptor { 6 | 7 | void encrypt(ByteBuffer buffer); 8 | void decrypt(ByteBuffer buffer); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/RawTunnel.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/Tunnel.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.tunnel; 2 | 3 | import android.annotation.SuppressLint; 4 | 5 | import java.io.IOException; 6 | import java.net.InetSocketAddress; 7 | import java.nio.ByteBuffer; 8 | import java.nio.channels.SelectionKey; 9 | import java.nio.channels.Selector; 10 | import java.nio.channels.SocketChannel; 11 | 12 | import me.smartproxy.core.LocalVpnService; 13 | import me.smartproxy.core.ProxyConfig; 14 | 15 | public abstract class Tunnel { 16 | 17 | final static ByteBuffer GL_BUFFER=ByteBuffer.allocate(1024*256); 18 | public static long SessionCount; 19 | 20 | protected abstract void onConnected(ByteBuffer buffer) throws Exception; 21 | protected abstract boolean isTunnelEstablished(); 22 | protected abstract void beforeSend(ByteBuffer buffer) throws Exception; 23 | protected abstract void afterReceived(ByteBuffer buffer) throws Exception; 24 | protected abstract void onDispose(); 25 | 26 | private SocketChannel m_InnerChannel; 27 | private ByteBuffer m_SendRemainBuffer; 28 | private Selector m_Selector; 29 | private Tunnel m_BrotherTunnel; 30 | private boolean m_Disposed; 31 | private InetSocketAddress m_ServerEP; 32 | protected InetSocketAddress m_DestAddress; 33 | 34 | public Tunnel(SocketChannel innerChannel,Selector selector){ 35 | this.m_InnerChannel=innerChannel; 36 | this.m_Selector=selector; 37 | SessionCount++; 38 | } 39 | 40 | public Tunnel(InetSocketAddress serverAddress,Selector selector) throws IOException{ 41 | SocketChannel innerChannel=SocketChannel.open(); 42 | innerChannel.configureBlocking(false); 43 | this.m_InnerChannel=innerChannel; 44 | this.m_Selector=selector; 45 | this.m_ServerEP=serverAddress; 46 | SessionCount++; 47 | } 48 | 49 | public void setBrotherTunnel(Tunnel brotherTunnel){ 50 | m_BrotherTunnel=brotherTunnel; 51 | } 52 | 53 | public void connect(InetSocketAddress destAddress) throws Exception{ 54 | if(LocalVpnService.Instance.protect(m_InnerChannel.socket())){//保护socket不走vpn 55 | m_DestAddress=destAddress; 56 | m_InnerChannel.register(m_Selector, SelectionKey.OP_CONNECT,this);//注册连接事件 57 | m_InnerChannel.connect(m_ServerEP);//连接目标 58 | }else { 59 | throw new Exception("VPN protect socket failed."); 60 | } 61 | } 62 | 63 | protected void beginReceive() throws Exception{ 64 | if(m_InnerChannel.isBlocking()){ 65 | m_InnerChannel.configureBlocking(false); 66 | } 67 | m_InnerChannel.register(m_Selector, SelectionKey.OP_READ,this);//注册读事件 68 | } 69 | 70 | 71 | protected boolean write(ByteBuffer buffer,boolean copyRemainData) throws Exception { 72 | int bytesSent; 73 | while (buffer.hasRemaining()) { 74 | bytesSent=m_InnerChannel.write(buffer); 75 | if(bytesSent==0){ 76 | break;//不能再发送了,终止循环 77 | } 78 | } 79 | 80 | if(buffer.hasRemaining()){//数据没有发送完毕 81 | if(copyRemainData){//拷贝剩余数据,然后侦听写入事件,待可写入时写入。 82 | //拷贝剩余数据 83 | if(m_SendRemainBuffer==null){ 84 | m_SendRemainBuffer=ByteBuffer.allocate(buffer.capacity()); 85 | } 86 | m_SendRemainBuffer.clear(); 87 | m_SendRemainBuffer.put(buffer); 88 | m_SendRemainBuffer.flip(); 89 | m_InnerChannel.register(m_Selector,SelectionKey.OP_WRITE, this);//注册写事件 90 | } 91 | return false; 92 | } 93 | else {//发送完毕了 94 | return true; 95 | } 96 | } 97 | 98 | protected void onTunnelEstablished() throws Exception{ 99 | this.beginReceive();//开始接收数据 100 | m_BrotherTunnel.beginReceive();//兄弟也开始收数据吧 101 | } 102 | 103 | @SuppressLint("DefaultLocale") 104 | public void onConnectable(){ 105 | try { 106 | if(m_InnerChannel.finishConnect()){//连接成功 107 | onConnected(GL_BUFFER);//通知子类TCP已连接,子类可以根据协议实现握手等。 108 | }else {//连接失败 109 | LocalVpnService.Instance.writeLog("Error: connect to %s failed.",m_ServerEP); 110 | this.dispose(); 111 | } 112 | } catch (Exception e) { 113 | LocalVpnService.Instance.writeLog("Error: connect to %s failed: %s", m_ServerEP,e); 114 | this.dispose(); 115 | } 116 | } 117 | 118 | public void onReadable(SelectionKey key){ 119 | try { 120 | ByteBuffer buffer=GL_BUFFER; 121 | buffer.clear(); 122 | int bytesRead=m_InnerChannel.read(buffer); 123 | if(bytesRead>0){ 124 | buffer.flip(); 125 | afterReceived(buffer);//先让子类处理,例如解密数据。 126 | if(isTunnelEstablished()&&buffer.hasRemaining()){//将读到的数据,转发给兄弟。 127 | m_BrotherTunnel.beforeSend(buffer);//发送之前,先让子类处理,例如做加密等。 128 | if(!m_BrotherTunnel.write(buffer,true)){ 129 | key.cancel();//兄弟吃不消,就取消读取事件。 130 | if(ProxyConfig.IS_DEBUG) 131 | System.out.printf("%s can not read more.\n", m_ServerEP); 132 | } 133 | } 134 | }else if(bytesRead<0) { 135 | this.dispose();//连接已关闭,释放资源。 136 | } 137 | } catch (Exception e) { 138 | e.printStackTrace(); 139 | this.dispose(); 140 | } 141 | } 142 | 143 | public void onWritable(SelectionKey key){ 144 | try { 145 | this.beforeSend(m_SendRemainBuffer);//发送之前,先让子类处理,例如做加密等。 146 | if(this.write(m_SendRemainBuffer, false)) {//如果剩余数据已经发送完毕 147 | key.cancel();//取消写事件。 148 | if(isTunnelEstablished()){ 149 | m_BrotherTunnel.beginReceive();//这边数据发送完毕,通知兄弟可以收数据了。 150 | }else { 151 | this.beginReceive();//开始接收代理服务器响应数据 152 | } 153 | } 154 | } catch (Exception e) { 155 | this.dispose(); 156 | } 157 | } 158 | 159 | public void dispose(){ 160 | disposeInternal(true); 161 | } 162 | 163 | void disposeInternal(boolean disposeBrother) { 164 | if(m_Disposed){ 165 | return; 166 | } 167 | else { 168 | try { 169 | m_InnerChannel.close(); 170 | } catch (Exception e) { 171 | } 172 | 173 | if(m_BrotherTunnel!=null&&disposeBrother){ 174 | m_BrotherTunnel.disposeInternal(false);//把兄弟的资源也释放了。 175 | } 176 | 177 | m_InnerChannel=null; 178 | m_SendRemainBuffer=null; 179 | m_Selector=null; 180 | m_BrotherTunnel=null; 181 | m_Disposed=true; 182 | SessionCount--; 183 | 184 | onDispose(); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/httpconnect/HttpConnectConfig.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.tunnel.httpconnect; 2 | 3 | import java.net.InetSocketAddress; 4 | 5 | import android.net.Uri; 6 | import me.smartproxy.tunnel.Config; 7 | 8 | public class HttpConnectConfig extends Config { 9 | public String UserName; 10 | public String Password; 11 | 12 | public static HttpConnectConfig parse(String proxyInfo){ 13 | HttpConnectConfig config=new HttpConnectConfig(); 14 | Uri uri=Uri.parse(proxyInfo); 15 | String userInfoString=uri.getUserInfo(); 16 | if(userInfoString!=null){ 17 | String[] userStrings=userInfoString.split(":"); 18 | config.UserName=userStrings[0]; 19 | if(userStrings.length>=2){ 20 | config.Password=userStrings[1]; 21 | } 22 | } 23 | config.ServerAddress=new InetSocketAddress(uri.getHost(), uri.getPort()); 24 | return config; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if(o==null) 30 | return false; 31 | return this.toString().equals(o.toString()); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return String.format("http://%s:%s@%s", UserName,Password,ServerAddress); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/httpconnect/HttpConnectTunnel.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.tunnel.httpconnect; 2 | 3 | import android.util.Base64; 4 | 5 | import java.io.IOException; 6 | import java.nio.ByteBuffer; 7 | import java.nio.channels.Selector; 8 | 9 | import me.smartproxy.core.ProxyConfig; 10 | import me.smartproxy.core.TmpConfig; 11 | import me.smartproxy.tunnel.Tunnel; 12 | 13 | public class HttpConnectTunnel extends Tunnel { 14 | 15 | private boolean m_TunnelEstablished; 16 | private HttpConnectConfig m_Config; 17 | 18 | public HttpConnectTunnel(HttpConnectConfig config,Selector selector) throws IOException { 19 | super(config.ServerAddress,selector); 20 | m_Config=config; 21 | } 22 | 23 | @Override 24 | protected void onConnected(ByteBuffer buffer) throws Exception { 25 | String auth = String.format("%s:%s", TmpConfig.UserName, TmpConfig.Password); 26 | if (auth.equals("")){ 27 | auth = ""; 28 | } else { 29 | auth = Base64.encodeToString(auth.getBytes("UTF-8"), Base64.NO_PADDING); 30 | auth = auth.replace("\n", ""); 31 | auth = String.format("\r\nProxy-Authorization: Basic %s", auth); 32 | 33 | } 34 | System.out.print(m_DestAddress.getHostName()); 35 | String request = String.format("CONNECT %s:%d HTTP/1.1\r\nHOST: %s:%d\r\nAccept: */*\r\nProxy-Connection: keep-alive\r\nUser-Agent: %s\r\nX-App-Install-ID: %s%s\r\n\r\n", 36 | m_DestAddress.getHostName(), 37 | m_DestAddress.getPort(), 38 | m_DestAddress.getHostName(), 39 | m_DestAddress.getPort(), 40 | ProxyConfig.Instance.getUserAgent(), 41 | ProxyConfig.AppInstallID, 42 | auth 43 | ); 44 | 45 | buffer.clear(); 46 | buffer.put(request.getBytes()); 47 | buffer.flip(); 48 | if(this.write(buffer,true)){//发送连接请求到代理服务器 49 | this.beginReceive();//开始接收代理服务器响应数据 50 | } 51 | } 52 | 53 | void trySendPartOfHeader(ByteBuffer buffer) throws Exception { 54 | int bytesSent=0; 55 | int _size = 1024*64; 56 | if(buffer.remaining()>_size){ 57 | int pos=buffer.position()+buffer.arrayOffset(); 58 | String firString=new String(buffer.array(),pos,_size).toUpperCase(); 59 | 60 | int limit=buffer.limit(); 61 | buffer.limit(buffer.position()+_size); 62 | super.write(buffer,false); 63 | bytesSent=_size-buffer.remaining(); 64 | buffer.limit(limit); 65 | if(ProxyConfig.IS_DEBUG) 66 | System.out.printf("Send %d bytes(%s) to %s\n",bytesSent,firString,m_DestAddress); 67 | } 68 | } 69 | 70 | 71 | @Override 72 | protected void beforeSend(ByteBuffer buffer) throws Exception { 73 | if(ProxyConfig.Instance.isIsolateHttpHostHeader()){ 74 | trySendPartOfHeader(buffer);//尝试发送请求头的一部分,让请求头的host在第二个包里面发送,从而绕过机房的白名单机制。 75 | } 76 | } 77 | 78 | @Override 79 | protected void afterReceived(ByteBuffer buffer) throws Exception { 80 | if(!m_TunnelEstablished){ 81 | //收到代理服务器响应数据 82 | //分析响应并判断是否连接成功 83 | String response=new String(buffer.array(),buffer.position(),12); 84 | System.out.println(response); 85 | 86 | if(response.matches("^HTTP/1.[01] [0-9]+$")) { 87 | buffer.limit(buffer.position()); 88 | }else { 89 | throw new Exception(String.format("Proxy server responsed an error: %s",response)); 90 | } 91 | 92 | m_TunnelEstablished=true; 93 | super.onTunnelEstablished(); 94 | } 95 | } 96 | 97 | @Override 98 | protected boolean isTunnelEstablished() { 99 | return m_TunnelEstablished; 100 | } 101 | 102 | @Override 103 | protected void onDispose() { 104 | m_Config=null; 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/shadowsocks/EncryptorFactory.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.tunnel.shadowsocks; 2 | 3 | import java.util.HashMap; 4 | 5 | import me.smartproxy.tunnel.IEncryptor; 6 | 7 | public class EncryptorFactory { 8 | 9 | private static HashMap EncryptorCache=new HashMap(); 10 | 11 | public static IEncryptor createEncryptorByConfig(ShadowsocksConfig config) throws Exception{ 12 | if("table".equals(config.EncryptMethod)){ 13 | IEncryptor tableEncryptor=EncryptorCache.get(config.toString()); 14 | if(tableEncryptor==null){ 15 | tableEncryptor= new TableEncryptor(config.Password); 16 | if(EncryptorCache.size()>2){ 17 | EncryptorCache.clear(); 18 | } 19 | EncryptorCache.put(config.toString(), tableEncryptor); 20 | } 21 | return tableEncryptor; 22 | } 23 | throw new Exception(String.format("Does not support the '%s' method. Only 'table' encrypt method was supported.", config.EncryptMethod)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/shadowsocks/ShadowsocksConfig.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.tunnel.shadowsocks; 2 | 3 | import java.net.InetSocketAddress; 4 | 5 | import android.net.Uri; 6 | import android.util.Base64; 7 | import me.smartproxy.tunnel.Config; 8 | 9 | public class ShadowsocksConfig extends Config { 10 | public String EncryptMethod; 11 | public String Password; 12 | 13 | public static ShadowsocksConfig parse(String proxyInfo) throws Exception{ 14 | ShadowsocksConfig config=new ShadowsocksConfig(); 15 | Uri uri=Uri.parse(proxyInfo); 16 | if(uri.getPort()==-1){ 17 | String base64String=uri.getHost(); 18 | proxyInfo="ss://"+ new String(Base64.decode(base64String.getBytes("ASCII"),Base64.DEFAULT)); 19 | uri=Uri.parse(proxyInfo); 20 | } 21 | 22 | String userInfoString=uri.getUserInfo(); 23 | if(userInfoString!=null){ 24 | String[] userStrings=userInfoString.split(":"); 25 | config.EncryptMethod=userStrings[0]; 26 | if(userStrings.length>=2){ 27 | config.Password=userStrings[1]; 28 | } 29 | } 30 | config.ServerAddress=new InetSocketAddress(uri.getHost(), uri.getPort()); 31 | config.Encryptor=EncryptorFactory.createEncryptorByConfig(config); 32 | return config; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if(o==null) 38 | return false; 39 | return this.toString().equals(o.toString()); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return String.format("ss://%s:%s@%s", EncryptMethod,Password,ServerAddress); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/shadowsocks/ShadowsocksTunnel.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.tunnel.shadowsocks; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.nio.channels.Selector; 5 | 6 | import me.smartproxy.tunnel.IEncryptor; 7 | import me.smartproxy.tunnel.Tunnel; 8 | 9 | public class ShadowsocksTunnel extends Tunnel { 10 | 11 | private IEncryptor m_Encryptor; 12 | private ShadowsocksConfig m_Config; 13 | private boolean m_TunnelEstablished; 14 | 15 | public ShadowsocksTunnel(ShadowsocksConfig config,Selector selector) throws Exception { 16 | super(config.ServerAddress,selector); 17 | if(config.Encryptor==null){ 18 | throw new Exception("Error: The Encryptor for ShadowsocksTunnel is null."); 19 | } 20 | m_Config=config; 21 | m_Encryptor=config.Encryptor; 22 | } 23 | 24 | @Override 25 | protected void onConnected(ByteBuffer buffer) throws Exception { 26 | 27 | //构造socks5请求(跳过前3个字节) 28 | buffer.clear(); 29 | buffer.put((byte)0x03);//domain 30 | byte[] domainBytes=m_DestAddress.getHostName().getBytes(); 31 | buffer.put((byte)domainBytes.length);//domain length; 32 | buffer.put(domainBytes); 33 | buffer.putShort((short)m_DestAddress.getPort()); 34 | buffer.flip(); 35 | 36 | m_Encryptor.encrypt(buffer); 37 | if(write(buffer, true)){ 38 | m_TunnelEstablished=true; 39 | onTunnelEstablished(); 40 | }else { 41 | m_TunnelEstablished=true; 42 | this.beginReceive(); 43 | } 44 | } 45 | 46 | @Override 47 | protected boolean isTunnelEstablished() { 48 | return m_TunnelEstablished; 49 | } 50 | 51 | @Override 52 | protected void beforeSend(ByteBuffer buffer) throws Exception { 53 | m_Encryptor.encrypt(buffer); 54 | } 55 | 56 | @Override 57 | protected void afterReceived(ByteBuffer buffer) throws Exception { 58 | m_Encryptor.decrypt(buffer); 59 | } 60 | 61 | @Override 62 | protected void onDispose() { 63 | m_Config=null; 64 | m_Encryptor=null; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/tunnel/shadowsocks/TableEncryptor.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.tunnel.shadowsocks; 2 | 3 | import java.nio.Buffer; 4 | import java.nio.ByteBuffer; 5 | import java.security.MessageDigest; 6 | 7 | import me.smartproxy.tunnel.IEncryptor; 8 | 9 | public class TableEncryptor implements IEncryptor { 10 | 11 | private byte[] encryptTable = new byte[256]; 12 | private byte[] decryptTable = new byte[256]; 13 | 14 | public TableEncryptor(String password){ 15 | 16 | long a = passwordToInt64(password); 17 | for (int i = 0; i < 256; i++) 18 | { 19 | encryptTable[i] = (byte)i; 20 | } 21 | 22 | System.out.println("mergeSort...."); 23 | long startTime=System.nanoTime(); 24 | encryptTable = mergeSort(encryptTable, a); 25 | long endTime=System.nanoTime(); 26 | System.out.printf("mergeSort: %3fms\n", (endTime-startTime)/1000D/1000D); 27 | 28 | for (int i = 0; i < 256; i++) 29 | { 30 | decryptTable[encryptTable[i]&0xFF] = (byte)i; 31 | } 32 | } 33 | 34 | long passwordToInt64(String password){ 35 | try { 36 | byte[] passwordBytes=password.getBytes("UTF-8"); 37 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 38 | byte[] hashPwd=md5.digest(passwordBytes); 39 | long a = bytesToInt64(hashPwd); 40 | return a; 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | return 0; 44 | } 45 | } 46 | 47 | long bytesToInt64(byte[] data){ 48 | long value=data[0]; 49 | value|=((long)(data[1]&0xFF)<<8); 50 | value|=((long)(data[2]&0xFF)<<16); 51 | value|=((long)(data[3]&0xFF)<<24); 52 | value|=((long)(data[4]&0xFF)<<32); 53 | value|=((long)(data[5]&0xFF)<<40); 54 | value|=((long)(data[6]&0xFF)<<48); 55 | value|=((long)(data[7]&0xFF)<<56); 56 | return value; 57 | } 58 | 59 | private byte[] mergeSort(byte[] srcArray , long a ){ 60 | byte[] dstArray=new byte[256]; 61 | long a1=Long.MAX_VALUE; 62 | long a2=(a&Long.MAX_VALUE); 63 | int stepSize,leftOffset,leftMaxOffset,rightOffset,rightMaxOffset,dstOffset,leftValue,rightValue; 64 | 65 | for (int i = 1; i < 1024; i++) { 66 | for (stepSize = 1;stepSize < 256;stepSize<<=1){ 67 | for (dstOffset=0;dstOffset<256;){ 68 | leftOffset=dstOffset; 69 | leftMaxOffset = leftOffset + stepSize; 70 | rightOffset=leftMaxOffset; 71 | rightMaxOffset = rightOffset + stepSize; 72 | for (;dstOffset0?((a%leftValue - a%rightValue) <=0):(((a1%leftValue+a2%leftValue+1)%leftValue - (a1%rightValue+a2%rightValue+1)%rightValue)<=0); 82 | if(isLeftSmallThanRight){ 83 | dstArray[dstOffset] = srcArray[leftOffset++]; 84 | }else { 85 | dstArray[dstOffset] = srcArray[rightOffset++]; 86 | } 87 | } 88 | } 89 | } 90 | 91 | byte[] temp = dstArray; 92 | dstArray = srcArray; 93 | srcArray = temp; 94 | } 95 | } 96 | return srcArray; 97 | } 98 | 99 | @Override 100 | public void encrypt(ByteBuffer buffer) { 101 | byte[] data=buffer.array(); 102 | for (int i = buffer.arrayOffset()+buffer.position(); i < buffer.limit(); i++) { 103 | data[i]=encryptTable[data[i]&0xFF]; 104 | } 105 | } 106 | 107 | @Override 108 | public void decrypt(ByteBuffer buffer) { 109 | byte[] data=buffer.array(); 110 | for (int i = buffer.arrayOffset()+buffer.position(); i < buffer.limit(); i++) { 111 | data[i]=decryptTable[data[i]&0xFF]; 112 | } 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.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.widget.*; 21 | import android.widget.CompoundButton.OnCheckedChangeListener; 22 | 23 | import me.smartproxy.R; 24 | import me.smartproxy.core.LocalVpnService; 25 | import me.smartproxy.core.TmpConfig; 26 | 27 | import java.util.Calendar; 28 | 29 | public class MainActivity extends Activity implements 30 | View.OnClickListener, 31 | OnCheckedChangeListener, 32 | LocalVpnService.onStatusChangedListener { 33 | 34 | private static String GL_HISTORY_LOGS; 35 | 36 | private static final String TAG = MainActivity.class.getSimpleName(); 37 | 38 | private static final int START_VPN_SERVICE_REQUEST_CODE = 1985; 39 | 40 | private Switch switchProxy; 41 | private TextView textViewLog; 42 | private ScrollView scrollViewLog; 43 | private TextView textViewConfigUrl; 44 | private Calendar mCalendar; 45 | private CheckBox byPass; 46 | 47 | @Override 48 | protected void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | setContentView(R.layout.activity_main); 51 | 52 | scrollViewLog = (ScrollView) findViewById(R.id.scrollViewLog); 53 | textViewLog = (TextView) findViewById(R.id.textViewLog); 54 | 55 | textViewLog.setText(GL_HISTORY_LOGS); 56 | scrollViewLog.fullScroll(ScrollView.FOCUS_DOWN); 57 | 58 | mCalendar = Calendar.getInstance(); 59 | LocalVpnService.addOnStatusChangedListener(this); 60 | 61 | byPass = (CheckBox) findViewById(R.id.bypassChina); 62 | 63 | // TmpConfig.bypass = byPass.isChecked(); 64 | 65 | final TextView UserNameField = (TextView) findViewById(R.id.UserName); 66 | final TextView PasswordField = (TextView) findViewById(R.id.Password); 67 | final TextView IpField = (TextView) findViewById(R.id.remoteIp); 68 | final TextView PortField = (TextView) findViewById(R.id.remotePort); 69 | 70 | String UserNameFromDB = readConfigKey(TmpConfig.UserKey); 71 | String PasswordDB = readConfigKey(TmpConfig.PasswordKey); 72 | String IpDB = readConfigKey(TmpConfig.IpKey); 73 | String PortDB = readConfigKey(TmpConfig.PortKey); 74 | String ByPassDB = readConfigKey(TmpConfig.ByPassKey); 75 | 76 | 77 | if (TextUtils.isEmpty(UserNameFromDB) == false) { 78 | UserNameField.setText(UserNameFromDB); 79 | TmpConfig.UserName = UserNameFromDB; 80 | } 81 | if (TextUtils.isEmpty(PasswordDB) == false) { 82 | PasswordField.setText(PasswordDB); 83 | TmpConfig.Password = PasswordDB; 84 | } 85 | if (TextUtils.isEmpty(IpDB)) { 86 | IpField.setText("remote_ip"); 87 | } else { 88 | IpField.setText(IpDB); 89 | TmpConfig.remoteIp = IpDB; 90 | } 91 | if (TextUtils.isEmpty(PortDB)) { 92 | PortField.setText("remote_port"); 93 | } else { 94 | PortField.setText(PortDB); 95 | TmpConfig.remotePort = PortDB; 96 | } 97 | 98 | if (ByPassDB.equals("true")) { 99 | TmpConfig.bypass = true; 100 | } else { 101 | TmpConfig.bypass = false; 102 | } 103 | byPass.setChecked(TmpConfig.bypass); 104 | 105 | 106 | Button button = (Button) findViewById(R.id.confirm); 107 | button.setOnClickListener(new View.OnClickListener() { 108 | @Override 109 | public void onClick(View v) { 110 | String UserName = (String) UserNameField.getText().toString(); 111 | setConfigKey(TmpConfig.UserKey, UserName); 112 | TmpConfig.UserName = UserName; 113 | String Password = (String) PasswordField.getText().toString(); 114 | setConfigKey(TmpConfig.PasswordKey, Password); 115 | TmpConfig.Password = Password; 116 | String Ip = (String) IpField.getText().toString(); 117 | setConfigKey(TmpConfig.IpKey, Ip); 118 | TmpConfig.remoteIp = Ip; 119 | String Port = (String) PortField.getText().toString(); 120 | setConfigKey(TmpConfig.PortKey, Port); 121 | TmpConfig.remotePort = Port; 122 | boolean bypass = (boolean) byPass.isChecked(); 123 | 124 | if (bypass) { 125 | setConfigKey(TmpConfig.ByPassKey, "true"); 126 | } else { 127 | setConfigKey(TmpConfig.ByPassKey, "false"); 128 | } 129 | TmpConfig.bypass = bypass; 130 | TmpConfig.remotePort = Port; 131 | } 132 | }); 133 | } 134 | 135 | String readConfigUrl() { 136 | SharedPreferences preferences = getSharedPreferences("SmartProxy", MODE_PRIVATE); 137 | return preferences.getString(TmpConfig.CONFIG_URL_KEY, ""); 138 | } 139 | 140 | void setConfigUrl(String configUrl) { 141 | SharedPreferences preferences = getSharedPreferences("SmartProxy", MODE_PRIVATE); 142 | Editor editor = preferences.edit(); 143 | editor.putString(TmpConfig.CONFIG_URL_KEY, configUrl); 144 | editor.commit(); 145 | } 146 | 147 | String readConfigKey(String Key) { 148 | SharedPreferences preferences = getSharedPreferences("SmartProxy", MODE_PRIVATE); 149 | return preferences.getString(Key, ""); 150 | } 151 | 152 | public void setConfigKey(String Key, String Value) { 153 | SharedPreferences preferences = getSharedPreferences("SmartProxy", MODE_PRIVATE); 154 | Editor editor = preferences.edit(); 155 | 156 | editor.putString(Key, Value); 157 | editor.commit(); 158 | } 159 | 160 | String getVersionName() { 161 | PackageManager packageManager = getPackageManager(); 162 | if (packageManager == null) { 163 | Log.e(TAG, "null package manager is impossible"); 164 | return null; 165 | } 166 | 167 | try { 168 | return packageManager.getPackageInfo(getPackageName(), 0).versionName; 169 | } catch (PackageManager.NameNotFoundException e) { 170 | Log.e(TAG, "package not found is impossible", e); 171 | return null; 172 | } 173 | } 174 | 175 | boolean isValidUrl(String url) { 176 | try { 177 | // TODO clean this 178 | return true; 179 | } catch (Exception e) { 180 | return false; 181 | } 182 | } 183 | 184 | @Override 185 | public void onClick(View v) { 186 | if (switchProxy.isChecked()) { 187 | return; 188 | } 189 | 190 | new AlertDialog.Builder(this) 191 | .setTitle(R.string.config_url) 192 | .setItems(new CharSequence[]{ 193 | getString(R.string.config_url_scan), 194 | getString(R.string.config_url_manual) 195 | }, new OnClickListener() { 196 | @Override 197 | public void onClick(DialogInterface dialogInterface, int i) { 198 | switch (i) { 199 | case 0: 200 | break; 201 | case 1: 202 | showConfigUrlInputDialog(); 203 | break; 204 | } 205 | } 206 | }) 207 | .show(); 208 | } 209 | 210 | private void showConfigUrlInputDialog() { 211 | final EditText editText = new EditText(this); 212 | editText.setInputType(InputType.TYPE_TEXT_VARIATION_URI); 213 | editText.setHint(getString(R.string.config_url_hint)); 214 | editText.setText(readConfigUrl()); 215 | 216 | new AlertDialog.Builder(this) 217 | .setTitle(R.string.config_url) 218 | .setView(editText) 219 | .setPositiveButton(R.string.btn_ok, new OnClickListener() { 220 | @Override 221 | public void onClick(DialogInterface dialog, int which) { 222 | if (editText.getText() == null) { 223 | return; 224 | } 225 | 226 | String configUrl = editText.getText().toString().trim(); 227 | 228 | if (isValidUrl(configUrl)) { 229 | setConfigUrl(configUrl); 230 | textViewConfigUrl.setText(configUrl); 231 | } else { 232 | Toast.makeText(MainActivity.this, R.string.err_invalid_url, Toast.LENGTH_SHORT).show(); 233 | } 234 | } 235 | }) 236 | .setNegativeButton(R.string.btn_cancel, null) 237 | .show(); 238 | } 239 | 240 | @SuppressLint("DefaultLocale") 241 | @Override 242 | public void onLogReceived(String logString) { 243 | mCalendar.setTimeInMillis(System.currentTimeMillis()); 244 | logString = String.format("[%1$02d:%2$02d:%3$02d] %4$s\n", 245 | mCalendar.get(Calendar.HOUR_OF_DAY), 246 | mCalendar.get(Calendar.MINUTE), 247 | mCalendar.get(Calendar.SECOND), 248 | logString); 249 | 250 | System.out.println(logString); 251 | 252 | if (textViewLog.getLineCount() > 200) { 253 | textViewLog.setText(""); 254 | } 255 | textViewLog.append(logString); 256 | scrollViewLog.fullScroll(ScrollView.FOCUS_DOWN); 257 | GL_HISTORY_LOGS = textViewLog.getText() == null ? "" : textViewLog.getText().toString(); 258 | } 259 | 260 | @Override 261 | public void onStatusChanged(String status, Boolean isRunning) { 262 | switchProxy.setEnabled(true); 263 | switchProxy.setChecked(isRunning); 264 | onLogReceived(status); 265 | Toast.makeText(this, status, Toast.LENGTH_SHORT).show(); 266 | } 267 | 268 | @Override 269 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 270 | if (LocalVpnService.IsRunning != isChecked) { 271 | switchProxy.setEnabled(false); 272 | 273 | if (isChecked) { 274 | TmpConfig.CopyAndStart(this); 275 | startVpn(); 276 | } else { 277 | LocalVpnService.IsRunning = false; 278 | } 279 | } 280 | } 281 | 282 | public void startVpn() { 283 | Intent intent = LocalVpnService.prepare(this); 284 | if (intent == null) { 285 | startVPNService(); 286 | } else { 287 | startActivityForResult(intent, START_VPN_SERVICE_REQUEST_CODE); 288 | } 289 | } 290 | 291 | private void startVPNService() { 292 | String configUrl = TmpConfig.getConfig(this); 293 | if (!isValidUrl(configUrl)) { 294 | Toast.makeText(this, R.string.err_invalid_url, Toast.LENGTH_SHORT).show(); 295 | switchProxy.post(new Runnable() { 296 | @Override 297 | public void run() { 298 | switchProxy.setChecked(false); 299 | switchProxy.setEnabled(true); 300 | } 301 | }); 302 | return; 303 | } 304 | 305 | textViewLog.setText(""); 306 | GL_HISTORY_LOGS = null; 307 | onLogReceived("starting..."); 308 | LocalVpnService.ConfigUrl = configUrl; 309 | startService(new Intent(this, LocalVpnService.class)); 310 | } 311 | 312 | @Override 313 | protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 314 | if (requestCode == START_VPN_SERVICE_REQUEST_CODE) { 315 | if (resultCode == RESULT_OK) { 316 | startVPNService(); 317 | } else { 318 | switchProxy.setChecked(false); 319 | switchProxy.setEnabled(true); 320 | onLogReceived("canceled."); 321 | } 322 | return; 323 | } 324 | 325 | super.onActivityResult(requestCode, resultCode, intent); 326 | } 327 | 328 | @Override 329 | public boolean onCreateOptionsMenu(Menu menu) { 330 | getMenuInflater().inflate(R.menu.main_activity_actions, menu); 331 | 332 | MenuItem menuItem = menu.findItem(R.id.menu_item_switch); 333 | if (menuItem == null) { 334 | return false; 335 | } 336 | 337 | switchProxy = (Switch) menuItem.getActionView(); 338 | if (switchProxy == null) { 339 | return false; 340 | } 341 | 342 | switchProxy.setChecked(LocalVpnService.IsRunning); 343 | switchProxy.setOnCheckedChangeListener(this); 344 | 345 | return true; 346 | } 347 | 348 | @Override 349 | public boolean onOptionsItemSelected(MenuItem item) { 350 | switch (item.getItemId()) { 351 | case R.id.menu_item_about: 352 | new AlertDialog.Builder(this) 353 | .setTitle(getString(R.string.app_name) + getVersionName()) 354 | .setMessage(R.string.about_info) 355 | .setPositiveButton(R.string.btn_ok, null) 356 | .setNegativeButton(R.string.btn_more, new OnClickListener() { 357 | @Override 358 | public void onClick(DialogInterface dialog, int which) { 359 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/zxc111/SmartProxy"))); 360 | } 361 | }) 362 | .show(); 363 | 364 | return true; 365 | case R.id.menu_item_exit: 366 | if (!LocalVpnService.IsRunning) { 367 | finish(); 368 | return true; 369 | } 370 | 371 | new AlertDialog.Builder(this) 372 | .setTitle(R.string.menu_item_exit) 373 | .setMessage(R.string.exit_confirm_info) 374 | .setPositiveButton(R.string.btn_ok, new OnClickListener() { 375 | @Override 376 | public void onClick(DialogInterface dialog, int which) { 377 | LocalVpnService.IsRunning = false; 378 | LocalVpnService.Instance.disconnectVPN(); 379 | stopService(new Intent(MainActivity.this, LocalVpnService.class)); 380 | System.runFinalization(); 381 | System.exit(0); 382 | } 383 | }) 384 | .setNegativeButton(R.string.btn_cancel, null) 385 | .show(); 386 | 387 | return true; 388 | default: 389 | return super.onOptionsItemSelected(item); 390 | } 391 | } 392 | 393 | @Override 394 | protected void onDestroy() { 395 | LocalVpnService.removeOnStatusChangedListener(this); 396 | super.onDestroy(); 397 | } 398 | 399 | } 400 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/java/me/smartproxy/ui/QuickSettingsService.java: -------------------------------------------------------------------------------- 1 | package me.smartproxy.ui; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Intent; 5 | import android.graphics.drawable.Icon; 6 | import android.os.Build; 7 | import android.service.quicksettings.TileService; 8 | import android.service.quicksettings.Tile; 9 | import android.util.Log; 10 | import android.widget.Switch; 11 | 12 | import me.smartproxy.core.TmpConfig; 13 | import me.smartproxy.R; 14 | import me.smartproxy.core.LocalVpnService; 15 | 16 | /** 17 | * Created by3/27. 18 | */ 19 | 20 | @TargetApi(Build.VERSION_CODES.N) 21 | public class QuickSettingsService extends TileService{ 22 | //当用户从Edit栏添加到快速设置中调用 23 | 24 | private final int STATE_OFF = 0; 25 | private final int STATE_ON = 1; 26 | private final String LOG_TAG = "QuickSettingService"; 27 | private int toggleState = STATE_OFF; 28 | private static final Boolean isrunning = true; 29 | 30 | private Switch switchProxy; 31 | @Override 32 | public void onTileAdded() { 33 | Log.d(LOG_TAG, "onTileAdded"); 34 | } 35 | //当用户从快速设置栏中移除的时候调用 36 | @Override 37 | public void onTileRemoved() { 38 | Log.d(LOG_TAG, "onTileRemoved"); 39 | } 40 | // 点击的时候 41 | @TargetApi(Build.VERSION_CODES.N) 42 | @Override 43 | public void onClick() { 44 | Icon icon; 45 | icon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_launcher); 46 | if (toggleState == STATE_ON) { 47 | toggleState = STATE_OFF; 48 | getQsTile().setState(Tile.STATE_INACTIVE);// 更改成非活跃状态 49 | // switchProxy.setChecked(false); 50 | if (LocalVpnService.IsRunning) { 51 | LocalVpnService.IsRunning = false; 52 | LocalVpnService.Instance.disconnectVPN(); 53 | // stopService(new Intent(this, LocalVpnService.class)); 54 | } 55 | } else { 56 | toggleState = STATE_ON; 57 | getQsTile().setState(Tile.STATE_ACTIVE);//更改成活跃状态 58 | TmpConfig.CopyAndStart(this); 59 | String configUrl = TmpConfig.getConfig(this); 60 | System.out.println(configUrl); 61 | LocalVpnService.ConfigUrl = configUrl; 62 | LocalVpnService.IsRunning = true; 63 | 64 | Intent intent = LocalVpnService.prepare(this); 65 | if (intent == null) { 66 | // startVPNService(); 67 | startService(new Intent(this, LocalVpnService.class)); 68 | } else { 69 | // startActivityForResult(intent, START_VPN_SERVICE_REQUEST_CODE); 70 | } 71 | 72 | } 73 | 74 | getQsTile().setIcon(icon);//设置图标 75 | getQsTile().updateTile();//更新Tile 76 | } 77 | 78 | // 打开下拉菜单的时候调用,当快速设置按钮并没有在编辑栏拖到设置栏中不会调用 79 | //在TleAdded之后会调用一次 80 | @Override 81 | public void onStartListening () { 82 | Icon icon; 83 | icon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_launcher); 84 | if (LocalVpnService.IsRunning) { 85 | toggleState = STATE_ON; 86 | getQsTile().setState(Tile.STATE_ACTIVE); 87 | } else { 88 | toggleState = STATE_OFF; 89 | getQsTile().setState(Tile.STATE_INACTIVE); 90 | } 91 | getQsTile().setIcon(icon);//设置图标 92 | getQsTile().updateTile();//更新Tile 93 | Log.d(LOG_TAG, "onStartListening"); 94 | } 95 | // 关闭下拉菜单的时候调用,当快速设置按钮并没有在编辑栏拖到设置栏中不会调用 96 | // 在onTileRemoved移除之前也会调用移除 97 | @Override 98 | public void onStopListening () { 99 | Log.d(LOG_TAG, "onStopListening"); 100 | } 101 | 102 | /*public void setStateOn() { 103 | String config = new TmpConfig().getConfig(this); 104 | System.out.println(config); 105 | } 106 | public void setStateOff() { 107 | 108 | }*/ 109 | public void QSService() { 110 | QuickSettingsService instance = this; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Smart Proxy X/src/main/jniLibs/arm64-v8a/libnghttpx.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc111/SmartProxy/24a12530cab7fe6f6e00c048d973e507da919ab3/Smart Proxy X/src/main/jniLibs/arm64-v8a/libnghttpx.so -------------------------------------------------------------------------------- /Smart Proxy X/src/main/jniLibs/libnghttpx.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc111/SmartProxy/24a12530cab7fe6f6e00c048d973e507da919ab3/Smart Proxy X/src/main/jniLibs/libnghttpx.so -------------------------------------------------------------------------------- /Smart Proxy X/src/main/jniLibs/x86/libnghttpx.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc111/SmartProxy/24a12530cab7fe6f6e00c048d973e507da919ab3/Smart Proxy X/src/main/jniLibs/x86/libnghttpx.so -------------------------------------------------------------------------------- /Smart Proxy X/src/main/jniLibs/x86_64/libnghttpx.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc111/SmartProxy/24a12530cab7fe6f6e00c048d973e507da919ab3/Smart Proxy X/src/main/jniLibs/x86_64/libnghttpx.so -------------------------------------------------------------------------------- /Smart Proxy X/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc111/SmartProxy/24a12530cab7fe6f6e00c048d973e507da919ab3/Smart Proxy X/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Smart Proxy X/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc111/SmartProxy/24a12530cab7fe6f6e00c048d973e507da919ab3/Smart Proxy X/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Smart Proxy X/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc111/SmartProxy/24a12530cab7fe6f6e00c048d973e507da919ab3/Smart Proxy X/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Smart Proxy X/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxc111/SmartProxy/24a12530cab7fe6f6e00c048d973e507da919ab3/Smart Proxy X/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Smart Proxy X/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 18 | 19 | 25 | 26 | 34 | 35 | 36 | 41 | 42 | 48 | 49 | 55 | 56 | 57 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 83 | 84 | 90 | 91 | 97 | 98 | 99 | 104 | 105 |