├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── Socklient.sln └── Socklient ├── Constants.cs ├── Exceptions.cs ├── Extensions.cs ├── Socklient.csproj ├── Socklient.xml ├── SocksClient.cs └── UdpReceiveMemory.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | # 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行 2 | root = true 3 | 4 | # c# 文件 5 | [*.cs] 6 | 7 | #### Core EditorConfig 选项 #### 8 | 9 | # 缩进和间距 10 | indent_size = 4 11 | indent_style = space 12 | tab_width = 4 13 | 14 | # 新行首选项 15 | end_of_line = crlf 16 | insert_final_newline = false 17 | 18 | #### .NET 编码约定 #### 19 | 20 | # 组织 Using 21 | dotnet_separate_import_directive_groups = false 22 | dotnet_sort_system_directives_first = true 23 | file_header_template = unset 24 | 25 | # this. 和 Me. 首选项 26 | dotnet_style_qualification_for_event = false:silent 27 | dotnet_style_qualification_for_field = false:silent 28 | dotnet_style_qualification_for_method = false:silent 29 | dotnet_style_qualification_for_property = false:silent 30 | 31 | # 语言关键字与 bcl 类型首选项 32 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 33 | dotnet_style_predefined_type_for_member_access = true:silent 34 | 35 | # 括号首选项 36 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 37 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 38 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 39 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 40 | 41 | # 修饰符首选项 42 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 43 | 44 | # 表达式级首选项 45 | dotnet_style_coalesce_expression = true:suggestion 46 | dotnet_style_collection_initializer = true:suggestion 47 | dotnet_style_explicit_tuple_names = true:suggestion 48 | dotnet_style_null_propagation = true:suggestion 49 | dotnet_style_object_initializer = true:suggestion 50 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 51 | dotnet_style_prefer_auto_properties = true:silent 52 | dotnet_style_prefer_compound_assignment = true:suggestion 53 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 54 | dotnet_style_prefer_conditional_expression_over_return = true:silent 55 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 56 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 57 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 58 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 59 | dotnet_style_prefer_simplified_interpolation = true:suggestion 60 | 61 | # 字段首选项 62 | dotnet_style_readonly_field = true:suggestion 63 | 64 | # 参数首选项 65 | dotnet_code_quality_unused_parameters = all:suggestion 66 | 67 | # 禁止显示首选项 68 | dotnet_remove_unnecessary_suppression_exclusions = none 69 | 70 | #### c# 编码约定 #### 71 | 72 | # var 首选项 73 | csharp_style_var_elsewhere = false:silent 74 | csharp_style_var_for_built_in_types = false:silent 75 | csharp_style_var_when_type_is_apparent = false:silent 76 | 77 | # Expression-bodied 成员 78 | csharp_style_expression_bodied_accessors = true:silent 79 | csharp_style_expression_bodied_constructors = false:silent 80 | csharp_style_expression_bodied_indexers = true:silent 81 | csharp_style_expression_bodied_lambdas = true:silent 82 | csharp_style_expression_bodied_local_functions = false:silent 83 | csharp_style_expression_bodied_methods = false:silent 84 | csharp_style_expression_bodied_operators = false:silent 85 | csharp_style_expression_bodied_properties = true:silent 86 | 87 | # 模式匹配首选项 88 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 89 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 90 | csharp_style_prefer_not_pattern = true:suggestion 91 | csharp_style_prefer_pattern_matching = true:silent 92 | csharp_style_prefer_switch_expression = true:suggestion 93 | 94 | # Null 检查首选项 95 | csharp_style_conditional_delegate_call = true:suggestion 96 | 97 | # 修饰符首选项 98 | csharp_prefer_static_local_function = true:suggestion 99 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent 100 | 101 | # 代码块首选项 102 | csharp_prefer_braces = true:silent 103 | csharp_prefer_simple_using_statement = true:suggestion 104 | 105 | # 表达式级首选项 106 | csharp_prefer_simple_default_expression = true:suggestion 107 | csharp_style_deconstructed_variable_declaration = true:suggestion 108 | csharp_style_inlined_variable_declaration = true:suggestion 109 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 110 | csharp_style_prefer_index_operator = true:suggestion 111 | csharp_style_prefer_range_operator = true:suggestion 112 | csharp_style_throw_expression = true:suggestion 113 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 114 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 115 | 116 | # "using" 指令首选项 117 | csharp_using_directive_placement = outside_namespace:silent 118 | 119 | #### C# 格式规则 #### 120 | 121 | # 新行首选项 122 | csharp_new_line_before_catch = false 123 | csharp_new_line_before_else = false 124 | csharp_new_line_before_finally = false 125 | csharp_new_line_before_members_in_anonymous_types = true 126 | csharp_new_line_before_members_in_object_initializers = true 127 | csharp_new_line_before_open_brace = none 128 | csharp_new_line_between_query_expression_clauses = true 129 | 130 | # 缩进首选项 131 | csharp_indent_block_contents = true 132 | csharp_indent_braces = false 133 | csharp_indent_case_contents = true 134 | csharp_indent_case_contents_when_block = false 135 | csharp_indent_labels = one_less_than_current 136 | csharp_indent_switch_labels = true 137 | 138 | # 空格键首选项 139 | csharp_space_after_cast = false 140 | csharp_space_after_colon_in_inheritance_clause = true 141 | csharp_space_after_comma = true 142 | csharp_space_after_dot = false 143 | csharp_space_after_keywords_in_control_flow_statements = true 144 | csharp_space_after_semicolon_in_for_statement = true 145 | csharp_space_around_binary_operators = before_and_after 146 | csharp_space_around_declaration_statements = false 147 | csharp_space_before_colon_in_inheritance_clause = true 148 | csharp_space_before_comma = false 149 | csharp_space_before_dot = false 150 | csharp_space_before_open_square_brackets = false 151 | csharp_space_before_semicolon_in_for_statement = false 152 | csharp_space_between_empty_square_brackets = false 153 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 154 | csharp_space_between_method_call_name_and_opening_parenthesis = false 155 | csharp_space_between_method_call_parameter_list_parentheses = false 156 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 157 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 158 | csharp_space_between_method_declaration_parameter_list_parentheses = false 159 | csharp_space_between_parentheses = false 160 | csharp_space_between_square_brackets = false 161 | 162 | # 包装首选项 163 | csharp_preserve_single_line_blocks = true 164 | csharp_preserve_single_line_statements = true 165 | 166 | #### 命名样式 #### 167 | 168 | # 命名规则 169 | 170 | dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion 171 | dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface 172 | dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始 173 | 174 | dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion 175 | dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型 176 | dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 177 | 178 | dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion 179 | dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员 180 | dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 181 | 182 | dotnet_naming_rule.常量_should_be_帕斯卡拼写法.severity = suggestion 183 | dotnet_naming_rule.常量_should_be_帕斯卡拼写法.symbols = 常量 184 | dotnet_naming_rule.常量_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 185 | 186 | dotnet_naming_rule.静态字段_should_be_帕斯卡拼写法.severity = suggestion 187 | dotnet_naming_rule.静态字段_should_be_帕斯卡拼写法.symbols = 静态字段 188 | dotnet_naming_rule.静态字段_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 189 | 190 | dotnet_naming_rule.私有或内部字段_should_be_以下划线开始.severity = suggestion 191 | dotnet_naming_rule.私有或内部字段_should_be_以下划线开始.symbols = 私有或内部字段 192 | dotnet_naming_rule.私有或内部字段_should_be_以下划线开始.style = 以下划线开始 193 | 194 | # 符号规范 195 | 196 | dotnet_naming_symbols.interface.applicable_kinds = interface 197 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 198 | dotnet_naming_symbols.interface.required_modifiers = 199 | 200 | dotnet_naming_symbols.静态字段.applicable_kinds = field 201 | dotnet_naming_symbols.静态字段.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 202 | dotnet_naming_symbols.静态字段.required_modifiers = static 203 | 204 | dotnet_naming_symbols.私有或内部字段.applicable_kinds = field 205 | dotnet_naming_symbols.私有或内部字段.applicable_accessibilities = internal, private, private_protected 206 | dotnet_naming_symbols.私有或内部字段.required_modifiers = 207 | 208 | dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum 209 | dotnet_naming_symbols.类型.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 210 | dotnet_naming_symbols.类型.required_modifiers = 211 | 212 | dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method 213 | dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 214 | dotnet_naming_symbols.非字段成员.required_modifiers = 215 | 216 | dotnet_naming_symbols.常量.applicable_kinds = field 217 | dotnet_naming_symbols.常量.applicable_accessibilities = * 218 | dotnet_naming_symbols.常量.required_modifiers = const 219 | 220 | # 命名样式 221 | 222 | dotnet_naming_style.帕斯卡拼写法.required_prefix = 223 | dotnet_naming_style.帕斯卡拼写法.required_suffix = 224 | dotnet_naming_style.帕斯卡拼写法.word_separator = 225 | dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case 226 | 227 | dotnet_naming_style.以_i_开始.required_prefix = I 228 | dotnet_naming_style.以_i_开始.required_suffix = 229 | dotnet_naming_style.以_i_开始.word_separator = 230 | dotnet_naming_style.以_i_开始.capitalization = pascal_case 231 | 232 | dotnet_naming_style.以下划线开始.required_prefix = _ 233 | dotnet_naming_style.以下划线开始.required_suffix = 234 | dotnet_naming_style.以下划线开始.word_separator = 235 | dotnet_naming_style.以下划线开始.capitalization = camel_case 236 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 GF-Huang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Socklient 2 | ========= 3 | A SOCKS5 Client written in C# that implements RFC 1928 & 1929. 4 | 5 | Features 6 | ======== 7 | Support TCP (Connect) & UDP Associate 8 | 9 | NuGet 10 | ===== 11 | https://www.nuget.org/packages/Socklient 12 | 13 | Example 14 | ======= 15 | 16 | ### TCP 17 | ```cs 18 | using Socklient; 19 | 20 | 21 | using var client = new SocksClient(IPAddress.Parse("xxx.xxx.xxx.xxx") /* or some server hostname or domain */, 22 | 1080 /* or other ports */); 23 | await client.ConnectAsync("somewebsite.com", 80 /*or 443 or other ports*/); 24 | var stream = client.GetStream(); 25 | // Read and Write on this stream 26 | await stream.ReadAsync(...); 27 | await stream.WriteAsync(...); 28 | ``` 29 | 30 | ### UDP 31 | ```cs 32 | using Socklient; 33 | 34 | 35 | using var client = new SocksClient(IPAddress.Parse("xxx.xxx.xxx.xxx") /* or some server hostname or domain */, 36 | 1080 /* or other ports */); 37 | // Usually, all personal users are NAT, 38 | // so there is no way to determine the public IP and port they will use before sending. 39 | // In this case, the client MUST use a port number and address of all zeros. 40 | // More details read the this method comments, 41 | // or go to https://tools.ietf.org/html/rfc1928 then search "zeros" keyword. 42 | await client.UdpAssociateAsync(IPAddress.Any, 0); 43 | // Then Send and Receive on the client instance 44 | await client.SendAsync(...); 45 | UdpReceiveMemory result = await client.ReceiveAsync(); 46 | IPEndPoint remote = result.RemoteEndPoint; // the remote 47 | ReadOnlyMemory buffer = result.Memory; // the buffer contains the received data from remote 48 | ``` 49 | -------------------------------------------------------------------------------- /Socklient.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30907.101 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Socklient", "Socklient\Socklient.csproj", "{F7BD2FA4-1BA7-4E8C-88F4-CCC4F4D94291}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C1141084-81B2-4873-B514-FE047E5C7B7D}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {F7BD2FA4-1BA7-4E8C-88F4-CCC4F4D94291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {F7BD2FA4-1BA7-4E8C-88F4-CCC4F4D94291}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {F7BD2FA4-1BA7-4E8C-88F4-CCC4F4D94291}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {F7BD2FA4-1BA7-4E8C-88F4-CCC4F4D94291}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {7A8A0C15-4C63-4B14-B174-59549B73AD92} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /Socklient/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | 6 | namespace Socklient { 7 | enum Method : byte { 8 | NoAuthentication = 0x00, 9 | UsernamePassword = 0x02 10 | } 11 | 12 | enum Command : byte { 13 | Connect = 0x01, 14 | /// 15 | /// Unsupported yet 16 | /// 17 | Bind = 0x02, 18 | UdpAssociate = 0x03 19 | } 20 | 21 | enum AddressType : byte { 22 | IPv4 = 0x01, 23 | Domain = 0x03, 24 | IPv6 = 0x04 25 | } 26 | 27 | enum SocksStatus { 28 | /// 29 | /// Before handshake and authentication. 30 | /// 31 | Initial, 32 | /// 33 | /// After handshake, authentication, and send or command. 34 | /// 35 | Connected, 36 | /// 37 | /// Disposed, can not reuse. 38 | /// 39 | Disposed 40 | } 41 | 42 | /// 43 | /// Indicates the reply code of the server to the request. 44 | /// 45 | public enum Reply : byte { 46 | /// 47 | /// Succeeded. 48 | /// 49 | Successed = 0x00, 50 | /// 51 | /// General SOCKS server failure. 52 | /// 53 | GeneralFailure = 0x01, 54 | /// 55 | /// Connection not allowed by ruleset. 56 | /// 57 | ConnectionNotAllowed = 0x02, 58 | /// 59 | /// Network unreachable. 60 | /// 61 | NetworkUnreachable = 0x03, 62 | /// 63 | /// Host unreachable. 64 | /// 65 | HostUnreachable = 0x04, 66 | /// 67 | /// Connection refused. 68 | /// 69 | ConnectionRefused = 0x05, 70 | /// 71 | /// TTL expired. 72 | /// 73 | TTLExpired = 0x06, 74 | /// 75 | /// Command not supported. 76 | /// 77 | CommandNotSupported = 0x07, 78 | /// 79 | /// Address type not supported. 80 | /// 81 | AddressTypeNotSupported = 0x08 82 | } 83 | 84 | /// 85 | /// Determine the behavior when the client receive a ATYP. 86 | /// 87 | public enum DomainAddressBehavior { 88 | /// 89 | /// Throw a . 90 | /// 91 | ThrowException, 92 | /// 93 | /// Use the connected remote address as BND.ADDR. 94 | /// It is usually the address of the server specified when calling ConnectAsync or UdpAssociateAsync. 95 | /// 96 | UseConnectedAddress 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Socklient/Exceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | #pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释 6 | 7 | namespace Socklient { 8 | /// 9 | /// The exception that is thrown when the SOCKS5 server replies unexpected response. 10 | /// 11 | [Serializable] 12 | public class ProtocolErrorException : Exception { 13 | public ProtocolErrorException() { } 14 | public ProtocolErrorException(string message) : base(message) { } 15 | public ProtocolErrorException(string message, Exception inner) : base(message, inner) { } 16 | protected ProtocolErrorException( 17 | System.Runtime.Serialization.SerializationInfo info, 18 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 19 | } 20 | 21 | /// 22 | /// The exception that is thrown when authentication failed. 23 | /// 24 | [Serializable] 25 | public class AuthenticationException : Exception { 26 | public AuthenticationException() { } 27 | public AuthenticationException(string message) : base(message) { } 28 | public AuthenticationException(string message, Exception inner) : base(message, inner) { } 29 | protected AuthenticationException( 30 | System.Runtime.Serialization.SerializationInfo info, 31 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 32 | } 33 | 34 | /// 35 | /// The exception that is thrown when the REP field not equals to (0x00). 36 | /// 37 | [Serializable] 38 | public class ReplyException : Exception { 39 | public Reply Reply { get; } 40 | 41 | public ReplyException(Reply reply) : base($"Server reply error: {reply}.") => Reply = reply; 42 | public ReplyException(Reply reply, string message) : base(message) => Reply = reply; 43 | public ReplyException(Reply reply, string message, Exception inner) : base(message, inner) => Reply = reply; 44 | protected ReplyException( 45 | System.Runtime.Serialization.SerializationInfo info, 46 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 47 | } 48 | } 49 | 50 | #pragma warning restore CS1591 // 缺少对公共可见类型或成员的 XML 注释 -------------------------------------------------------------------------------- /Socklient/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Socklient { 10 | static class StreamExtension { 11 | public static async Task ReadRequiredAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken token = default) { 12 | var bytesReadTotal = 0; 13 | do { 14 | token.ThrowIfCancellationRequested(); 15 | 16 | var bytesRead = await stream.ReadAsync(buffer, offset + bytesReadTotal, count - bytesReadTotal, token); 17 | if (bytesRead == 0) 18 | throw new EndOfStreamException(); 19 | 20 | bytesReadTotal += bytesRead; 21 | 22 | } while (bytesReadTotal < count); 23 | } 24 | } 25 | 26 | static class AddressFamilyExtension { 27 | public static AddressType ToAddressType(this AddressFamily addressFamily) => addressFamily switch { 28 | AddressFamily.InterNetwork => AddressType.IPv4, 29 | AddressFamily.InterNetworkV6 => AddressType.IPv6, 30 | _ => throw new NotImplementedException() 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Socklient/Socklient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netstandard2.1 5 | 8.0 6 | enable 7 | Socklient 8 | 4.1.0 9 | GF-Huang 10 | 11 | A SOCKS5 client written in C# that implements RFC1928 & RFC1929. 12 | 13 | Supported SOCKS5 Commands: Connect & UDP Associate. 14 | https://github.com/GF-Huang/Socklient 15 | https://github.com/GF-Huang/Socklient 16 | git 17 | SOCKS SOCKS5 proxy client 18 | Add ShouldIgnoreBoundAddressCallback and DomainAddressBehavior properties for compatible with some strange SOCKS5 server implementations. 19 | 20 | 21 | 22 | D:\Code\Socklient\Socklient\Socklient.xml 23 | true 24 | snupkg 25 | true 26 | MIT 27 | 28 | 29 | 30 | 31 | 32 | all 33 | runtime; build; native; contentfiles; analyzers; buildtransitive 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Socklient/Socklient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Socklient 5 | 6 | 7 | 8 | 9 | Unsupported yet 10 | 11 | 12 | 13 | 14 | Before handshake and authentication. 15 | 16 | 17 | 18 | 19 | After handshake, authentication, and send or command. 20 | 21 | 22 | 23 | 24 | Disposed, can not reuse. 25 | 26 | 27 | 28 | 29 | Indicates the reply code of the server to the request. 30 | 31 | 32 | 33 | 34 | Succeeded. 35 | 36 | 37 | 38 | 39 | General SOCKS server failure. 40 | 41 | 42 | 43 | 44 | Connection not allowed by ruleset. 45 | 46 | 47 | 48 | 49 | Network unreachable. 50 | 51 | 52 | 53 | 54 | Host unreachable. 55 | 56 | 57 | 58 | 59 | Connection refused. 60 | 61 | 62 | 63 | 64 | TTL expired. 65 | 66 | 67 | 68 | 69 | Command not supported. 70 | 71 | 72 | 73 | 74 | Address type not supported. 75 | 76 | 77 | 78 | 79 | Determine the behavior when the client receive a ATYP. 80 | 81 | 82 | 83 | 84 | Throw a . 85 | 86 | 87 | 88 | 89 | Use the connected remote address as BND.ADDR. 90 | It is usually the address of the server specified when calling ConnectAsync or UdpAssociateAsync. 91 | 92 | 93 | 94 | 95 | The exception that is thrown when the SOCKS5 server replies unexpected response. 96 | 97 | 98 | 99 | 100 | The exception that is thrown when authentication failed. 101 | 102 | 103 | 104 | 105 | The exception that is thrown when the REP field not equals to (0x00). 106 | 107 | 108 | 109 | 110 | A SOCKS5 client. 111 | 112 | 113 | 114 | 115 | The BND.ADDR field of the response from server. 116 | 117 | The is not connected or associated. 118 | 119 | 120 | 121 | The BND.PORT field of the response from server. 122 | 123 | The is not connected or associated. 124 | 125 | 126 | 127 | Get underlying for more fine-grained control in CONNECT mode. 128 | 129 | 130 | 131 | 132 | Get underlying for more fine-grained control in UDP-ASSOCIATE mode. 133 | This property is null in CONNECT mode. 134 | 135 | 136 | 137 | 138 | Used to decide whether to ignore the BND.ADDR responded by UDP Associate command. Default return false. 139 | 140 | In the Internet world, a considerable number of SOCKS5 servers have incorrect UDP Associate implementation. 141 | 142 | 143 | According to the description of UDP Association in RFC 1928: "In the reply to a UDP ASSOCIATE request, the BND.PORT and BND.ADDR fields indicate the port number/address where the client MUST send UDP request messages to be relayed.", the server should respond its public IP address. If the server has multiple public IP addresses, the server should decide which public IP to respond according to its own strategy. 144 | 145 | 146 | However, most SOCKS5 servers implementations are very rough. They often use some private addresses as BND.ADDR respond to the client, such as 10.0.0.1, 172.16.1.1, 192.168.1.1 and so on. In this case, the UDP packet sent by the client cannot reach the server at all, unless the client and the server are in the same LAN. 147 | 148 | 149 | Therefore, through this callback, the client can according to the received BND.ADDR to determine whether this address is a private address. If true is returned, the client will send UDP packet to ServerAddress:BND.PORT; If false is returned, it will send UDP packet to BND.ADDR:BND.PORT. 150 | 151 | 152 | 153 | 154 | 155 | Determine the behavior when the client receive a ATYP. 156 | The default value is . 157 | 158 | Some SOCKS5 servers may hide the server's other IPs or other reasons, when responding to or request, they reply (0x03) as ATYP. 159 | This property determines what behavior the client should take in this case. 160 | 161 | 162 | Note: This property only effects and request. 163 | If UDP relay message header contains (0x03) ATYP, it will always throw a exception. 164 | 165 | 166 | 167 | 168 | 169 | Initializes a new instance of with the specified SOCKS5 and 170 | , is optional. 171 | 172 | The address of the SOCKS5 server. 173 | The port of the SOCKS5 server. 174 | Optional credential for username/password authentication. 175 | 176 | 177 | 178 | Initializes a new instance of with the specified SOCKS5 and 179 | , is optional. 180 | 181 | The hostname of the SOCKS5 server. 182 | The port of the SOCKS5 server. 183 | Optional credential for username/password authentication. 184 | 185 | 186 | 187 | Dispose the underlying and . 188 | 189 | 190 | 191 | 192 | Do handshake and authentication (if need), then send a command to the SOCKS5 server. 193 | 194 | The destination address to communicating via SOCKS5 server. 195 | The destination port to communicating via SOCKS5 server. 196 | The token to monitor for cancellation. The default value is . 197 | 198 | 199 | 200 | Do handshake and authentication (if need), then send a command to the SOCKS5 server. 201 | 202 | The destination domain to communicating via SOCKS5 server. 203 | The destination port to communicating via SOCKS5 server. 204 | The token to monitor for cancellation. The default value is . 205 | 206 | 207 | 208 | Get the of the underlying . 209 | 210 | 211 | 212 | 213 | Do handshake and authentication (if need), then send a command to the SOCKS5 server. 214 | 215 | The and fields contain the address and port that the client expects to use to send UDP datagrams on for the association. The server MAY use this information to limit access to the association. If the client is not in possesion of the information at the time of UDP Associate (for example, most home users are behind NAT, there is no way to determine the public IP and port they will use before sending), the client MUST use a port number and address of all zeros. 216 | 217 | 218 | The address that the client expects to use to send UDP datagrams on for the association. Alias of DST.ADDR defined in RFC 1928 UDP Associate. 219 | The port that the client expects to use to send UDP datagrams on for the association. Alias of DST.PORT defined in RFC 1928 UDP Associate. 220 | The token to monitor for cancellation. The default value is . 221 | 222 | 223 | 224 | Send datagram to destination domain and port via SOCKS server. 225 | 226 | The datagram to send. 227 | The destination domain. 228 | The destination port. 229 | 230 | 231 | 232 | Send datagram to destination address and port via SOCKS server. 233 | 234 | The datagram to send. 235 | The destination address. 236 | The destination port. 237 | 238 | 239 | 240 | Receive datagram via SOCKS server. 241 | 242 | 243 | 244 | 245 | Used to decide whether to ignore the BND.ADDR responded by UDP Associate command. 246 | 247 | In the Internet world, a considerable number of SOCKS5 servers have incorrect UDP Associate implementation. 248 | 249 | 250 | According to the description of UDP Association in RFC 1928: "In the reply to a UDP ASSOCIATE request, the BND.PORT and BND.ADDR fields indicate the port number/address where the client MUST send UDP request messages to be relayed.", the server should respond its public IP address. If the server has multiple public IP addresses, the server should decide which public IP to respond according to its own strategy. 251 | 252 | 253 | However, most SOCKS5 servers implementations are very rough. They often use some private addresses as BND.ADDR respond to the client, such as 10.0.0.1, 172.16.1.1, 192.168.1.1 and so on. In this case, the UDP packet sent by the client cannot reach the server at all, unless the client and the server are in the same LAN. 254 | 255 | 256 | Therefore, through this callback, the client can according to the received BND.ADDR to determine whether this address is a private address. If true is returned, the client will send UDP packet to ServerAddress:BND.PORT; If false is returned, it will send UDP packet to BND.ADDR:BND.PORT. 257 | 258 | 259 | The instance which calls the callback. 260 | The BND.ADDR of responded by UDP Associate command. 261 | 262 | 263 | 264 | 265 | Presents SOCKS5 server UDP replied result information from a call to the method. 266 | 267 | 268 | 269 | 270 | Gets the with the data received in the UDP packet. 271 | 272 | 273 | 274 | 275 | Gets the remote endpoint from which the UDP packet was received. 276 | 277 | 278 | 279 | 280 | Specifies that is allowed as an input even if the 281 | corresponding type disallows it. 282 | 283 | 284 | 285 | 286 | Initializes a new instance of the class. 287 | 288 | 289 | 290 | 291 | Specifies that is disallowed as an input even if the 292 | corresponding type allows it. 293 | 294 | 295 | 296 | 297 | Initializes a new instance of the class. 298 | 299 | 300 | 301 | 302 | Specifies that a method that will never return under any circumstance. 303 | 304 | 305 | 306 | 307 | Initializes a new instance of the class. 308 | 309 | 310 | 311 | 312 | 313 | Specifies that the method will not return if the associated 314 | parameter is passed the specified value. 315 | 316 | 317 | 318 | 319 | Gets the condition parameter value. 320 | Code after the method is considered unreachable by diagnostics if the argument 321 | to the associated parameter matches this value. 322 | 323 | 324 | 325 | 326 | Initializes a new instance of the 327 | class with the specified parameter value. 328 | 329 | 330 | The condition parameter value. 331 | Code after the method is considered unreachable by diagnostics if the argument 332 | to the associated parameter matches this value. 333 | 334 | 335 | 336 | 337 | Specifies that an output may be even if the 338 | corresponding type disallows it. 339 | 340 | 341 | 342 | 343 | Initializes a new instance of the class. 344 | 345 | 346 | 347 | 348 | Specifies that when a method returns , 349 | the parameter may be even if the corresponding type disallows it. 350 | 351 | 352 | 353 | 354 | Gets the return value condition. 355 | If the method returns this value, the associated parameter may be . 356 | 357 | 358 | 359 | 360 | Initializes the attribute with the specified return value condition. 361 | 362 | 363 | The return value condition. 364 | If the method returns this value, the associated parameter may be . 365 | 366 | 367 | 368 | 369 | Specifies that the method or property will ensure that the listed field and property members have 370 | not- values. 371 | 372 | 373 | 374 | 375 | Gets field or property member names. 376 | 377 | 378 | 379 | 380 | Initializes the attribute with a field or property member. 381 | 382 | 383 | The field or property member that is promised to be not-null. 384 | 385 | 386 | 387 | 388 | Initializes the attribute with the list of field and property members. 389 | 390 | 391 | The list of field and property members that are promised to be not-null. 392 | 393 | 394 | 395 | 396 | Specifies that the method or property will ensure that the listed field and property members have 397 | non- values when returning with the specified return value condition. 398 | 399 | 400 | 401 | 402 | Gets the return value condition. 403 | 404 | 405 | 406 | 407 | Gets field or property member names. 408 | 409 | 410 | 411 | 412 | Initializes the attribute with the specified return value condition and a field or property member. 413 | 414 | 415 | The return value condition. If the method returns this value, 416 | the associated parameter will not be . 417 | 418 | 419 | The field or property member that is promised to be not-. 420 | 421 | 422 | 423 | 424 | Initializes the attribute with the specified return value condition and list 425 | of field and property members. 426 | 427 | 428 | The return value condition. If the method returns this value, 429 | the associated parameter will not be . 430 | 431 | 432 | The list of field and property members that are promised to be not-null. 433 | 434 | 435 | 436 | 437 | Specifies that an output is not even if the 438 | corresponding type allows it. 439 | 440 | 441 | 442 | 443 | Initializes a new instance of the class. 444 | 445 | 446 | 447 | 448 | Specifies that the output will be non- if the 449 | named parameter is non-. 450 | 451 | 452 | 453 | 454 | Gets the associated parameter name. 455 | The output will be non- if the argument to the 456 | parameter specified is non-. 457 | 458 | 459 | 460 | 461 | Initializes the attribute with the associated parameter name. 462 | 463 | 464 | The associated parameter name. 465 | The output will be non- if the argument to the 466 | parameter specified is non-. 467 | 468 | 469 | 470 | 471 | Specifies that when a method returns , 472 | the parameter will not be even if the corresponding type allows it. 473 | 474 | 475 | 476 | 477 | Gets the return value condition. 478 | If the method returns this value, the associated parameter will not be . 479 | 480 | 481 | 482 | 483 | Initializes the attribute with the specified return value condition. 484 | 485 | 486 | The return value condition. 487 | If the method returns this value, the associated parameter will not be . 488 | 489 | 490 | 491 | 492 | -------------------------------------------------------------------------------- /Socklient/SocksClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Buffers.Binary; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace Socklient { 14 | /// 15 | /// A SOCKS5 client. 16 | /// 17 | public sealed class SocksClient : IDisposable { 18 | /// 19 | /// The BND.ADDR field of the response from server. 20 | /// 21 | /// The is not connected or associated. 22 | public IPAddress BoundAddress => _boundAddress ?? throw new InvalidOperationException($"The {GetType().FullName} is not connected or associated."); 23 | private IPAddress? _boundAddress; 24 | 25 | /// 26 | /// The BND.PORT field of the response from server. 27 | /// 28 | /// The is not connected or associated. 29 | public int BoundPort => _boundPort ?? throw new InvalidOperationException($"The {GetType().FullName} is not connected or associated."); 30 | private int? _boundPort; 31 | 32 | /// 33 | /// Get underlying for more fine-grained control in CONNECT mode. 34 | /// 35 | public TcpClient TcpClient { get; } = new TcpClient(); 36 | 37 | /// 38 | /// Get underlying for more fine-grained control in UDP-ASSOCIATE mode. 39 | /// This property is null in CONNECT mode. 40 | /// 41 | public UdpClient? UdpClient { get; private set; } 42 | 43 | /// 44 | /// Used to decide whether to ignore the BND.ADDR responded by UDP Associate command. Default return false. 45 | /// 46 | /// In the Internet world, a considerable number of SOCKS5 servers have incorrect UDP Associate implementation. 47 | /// 48 | /// 49 | /// According to the description of UDP Association in RFC 1928: "In the reply to a UDP ASSOCIATE request, the BND.PORT and BND.ADDR fields indicate the port number/address where the client MUST send UDP request messages to be relayed.", the server should respond its public IP address. If the server has multiple public IP addresses, the server should decide which public IP to respond according to its own strategy. 50 | /// 51 | /// 52 | /// However, most SOCKS5 servers implementations are very rough. They often use some private addresses as BND.ADDR respond to the client, such as 10.0.0.1, 172.16.1.1, 192.168.1.1 and so on. In this case, the UDP packet sent by the client cannot reach the server at all, unless the client and the server are in the same LAN. 53 | /// 54 | /// 55 | /// Therefore, through this callback, the client can according to the received BND.ADDR to determine whether this address is a private address. If true is returned, the client will send UDP packet to ServerAddress:BND.PORT; If false is returned, it will send UDP packet to BND.ADDR:BND.PORT. 56 | /// 57 | /// 58 | public ShouldIgnoreBoundAddressCallback ShouldIgnoreBoundAddressCallback { get; set; } = (s, a) => Task.FromResult(false); 59 | 60 | /// 61 | /// Determine the behavior when the client receive a ATYP. 62 | /// The default value is . 63 | /// 64 | /// Some SOCKS5 servers may hide the server's other IPs or other reasons, when responding to or request, they reply (0x03) as ATYP. 65 | /// This property determines what behavior the client should take in this case. 66 | /// 67 | /// 68 | /// Note: This property only effects and request. 69 | /// If UDP relay message header contains (0x03) ATYP, it will always throw a exception. 70 | /// 71 | /// 72 | public DomainAddressBehavior DomainAddressBehavior { get; set; } = DomainAddressBehavior.ThrowException; 73 | 74 | private IPAddress RemoteAddress => ((IPEndPoint)TcpClient.Client.RemoteEndPoint).Address; 75 | 76 | private const byte Version = 0x5; 77 | private const byte AuthenticationVersion = 0x1; 78 | private const byte UsernameMaxLength = 255; 79 | private const byte PasswordMaxLength = 255; 80 | private const byte IPv4AddressLength = 4; 81 | private const byte DomainLengthByteLength = 1; 82 | private const byte DomainMaxLength = 255; 83 | private const byte IPv6AddressLength = 16; 84 | private const byte PortLength = 2; 85 | private static readonly Method[] MethodsNoAuth = new[] { Method.NoAuthentication }; 86 | private static readonly Method[] MethodsNoAuthUsernamePassword = new[] { Method.NoAuthentication, Method.UsernamePassword }; 87 | 88 | private readonly IPAddress? _serverAddress; 89 | private readonly string? _serverHost; 90 | private readonly int _serverPort; 91 | private readonly NetworkCredential? _credential; 92 | private NetworkStream? _stream; 93 | private SocksStatus _status = SocksStatus.Initial; 94 | 95 | /// 96 | /// Initializes a new instance of with the specified SOCKS5 and 97 | /// , is optional. 98 | /// 99 | /// The address of the SOCKS5 server. 100 | /// The port of the SOCKS5 server. 101 | /// Optional credential for username/password authentication. 102 | public SocksClient(IPAddress server, int port, NetworkCredential? credential = null) { 103 | _serverAddress = server ?? throw new ArgumentNullException(nameof(server)); 104 | _serverPort = port; 105 | _credential = credential; 106 | 107 | if (credential?.UserName.Length > UsernameMaxLength) 108 | throw new ArgumentOutOfRangeException($"The {nameof(credential)}.{nameof(credential.UserName)} is longer than {UsernameMaxLength}"); 109 | if (credential?.Password.Length > PasswordMaxLength) 110 | throw new ArgumentOutOfRangeException($"The {nameof(credential)}.{nameof(credential.Password)} is longer than {PasswordMaxLength}"); 111 | } 112 | 113 | /// 114 | /// Initializes a new instance of with the specified SOCKS5 and 115 | /// , is optional. 116 | /// 117 | /// The hostname of the SOCKS5 server. 118 | /// The port of the SOCKS5 server. 119 | /// Optional credential for username/password authentication. 120 | public SocksClient(string server, int port, NetworkCredential? credential = null) { 121 | _serverHost = server ?? throw new ArgumentNullException(nameof(server)); 122 | _serverPort = port; 123 | _credential = credential; 124 | 125 | if (credential != null && Encoding.UTF8.GetByteCount(credential.UserName) > UsernameMaxLength) 126 | throw new ArgumentOutOfRangeException($"The {nameof(credential)}.{nameof(credential.UserName)} is longer than {UsernameMaxLength} after converted to UTF-8 bytes."); 127 | if (credential != null && Encoding.UTF8.GetByteCount(credential.Password) > PasswordMaxLength) 128 | throw new ArgumentOutOfRangeException($"The {nameof(credential)}.{nameof(credential.Password)} is longer than {PasswordMaxLength} after converted to UTF-8 bytes."); 129 | } 130 | 131 | /// 132 | /// Dispose the underlying and . 133 | /// 134 | public void Dispose() { 135 | if (_status != SocksStatus.Disposed) { 136 | TcpClient.Dispose(); 137 | UdpClient?.Dispose(); 138 | _stream?.Dispose(); 139 | 140 | _status = SocksStatus.Disposed; 141 | } 142 | } 143 | 144 | /// 145 | /// Do handshake and authentication (if need), then send a command to the SOCKS5 server. 146 | /// 147 | /// The destination address to communicating via SOCKS5 server. 148 | /// The destination port to communicating via SOCKS5 server. 149 | /// The token to monitor for cancellation. The default value is . 150 | public async Task ConnectAsync(IPAddress address, int port, CancellationToken token = default) { 151 | if (address is null) 152 | throw new ArgumentNullException(nameof(address)); 153 | if (_status == SocksStatus.Connected) 154 | throw new InvalidOperationException($"{GetType().FullName} already connected or associated."); 155 | if (_status == SocksStatus.Disposed) 156 | throw new ObjectDisposedException(GetType().FullName); 157 | 158 | await PrepareAsync(token).ConfigureAwait(false); 159 | await SendCommandAsync(Command.Connect, null, address, port, token).ConfigureAwait(false); 160 | 161 | _status = SocksStatus.Connected; 162 | } 163 | 164 | /// 165 | /// Do handshake and authentication (if need), then send a command to the SOCKS5 server. 166 | /// 167 | /// The destination domain to communicating via SOCKS5 server. 168 | /// The destination port to communicating via SOCKS5 server. 169 | /// The token to monitor for cancellation. The default value is . 170 | public async Task ConnectAsync(string domain, int port, CancellationToken token = default) { 171 | if (domain is null) 172 | throw new ArgumentNullException(nameof(domain)); 173 | if (Encoding.UTF8.GetByteCount(domain) > DomainMaxLength) 174 | throw new ArgumentOutOfRangeException(nameof(domain), $"The {nameof(domain)} is longer than {DomainMaxLength} after converted to UTF-8 bytes."); 175 | if (_status == SocksStatus.Connected) 176 | throw new InvalidOperationException($"{GetType().FullName} already connected or associated."); 177 | if (_status == SocksStatus.Disposed) 178 | throw new ObjectDisposedException(GetType().FullName); 179 | 180 | await PrepareAsync(token).ConfigureAwait(false); 181 | await SendCommandAsync(Command.Connect, domain, null, port, token).ConfigureAwait(false); 182 | 183 | _status = SocksStatus.Connected; 184 | } 185 | 186 | /// 187 | /// Get the of the underlying . 188 | /// 189 | public NetworkStream GetStream() { 190 | if (_status == SocksStatus.Disposed) 191 | throw new ObjectDisposedException(GetType().FullName); 192 | if (_status != SocksStatus.Connected) 193 | throw new InvalidOperationException($"The {GetType().FullName} is not connected or associated."); 194 | 195 | return _stream!; 196 | } 197 | 198 | /// 199 | /// Do handshake and authentication (if need), then send a command to the SOCKS5 server. 200 | /// 201 | /// The and fields contain the address and port that the client expects to use to send UDP datagrams on for the association. The server MAY use this information to limit access to the association. If the client is not in possesion of the information at the time of UDP Associate (for example, most home users are behind NAT, there is no way to determine the public IP and port they will use before sending), the client MUST use a port number and address of all zeros. 202 | /// 203 | /// 204 | /// The address that the client expects to use to send UDP datagrams on for the association. Alias of DST.ADDR defined in RFC 1928 UDP Associate. 205 | /// The port that the client expects to use to send UDP datagrams on for the association. Alias of DST.PORT defined in RFC 1928 UDP Associate. 206 | /// The token to monitor for cancellation. The default value is . 207 | public async Task UdpAssociateAsync(IPAddress address, int port, CancellationToken token = default) { 208 | if (_status == SocksStatus.Connected) 209 | throw new InvalidOperationException($"{GetType().FullName} already connected or associated."); 210 | if (_status == SocksStatus.Disposed) 211 | throw new ObjectDisposedException(GetType().FullName); 212 | 213 | await PrepareAsync(token).ConfigureAwait(false); 214 | await SendCommandAsync(Command.UdpAssociate, null, address, port, token).ConfigureAwait(false); 215 | 216 | if (BoundAddress.Equals(IPAddress.Any) || BoundAddress.Equals(IPAddress.IPv6Any)) 217 | _boundAddress = _serverAddress ?? RemoteAddress; 218 | if (BoundPort == 0) 219 | _boundPort = ((IPEndPoint)TcpClient.Client.RemoteEndPoint).Port; 220 | 221 | var ignoreBoundAddress = await ShouldIgnoreBoundAddressCallback(this, BoundAddress).ConfigureAwait(false); 222 | var addressToConnect = ignoreBoundAddress ? (_serverAddress ?? RemoteAddress) : BoundAddress; 223 | 224 | UdpClient = new UdpClient(AddressFamily.InterNetworkV6); 225 | UdpClient.Client.DualMode = true; 226 | UdpClient.Connect(addressToConnect, BoundPort); 227 | 228 | _status = SocksStatus.Connected; 229 | } 230 | 231 | /// 232 | /// Send datagram to destination domain and port via SOCKS server. 233 | /// 234 | /// The datagram to send. 235 | /// The destination domain. 236 | /// The destination port. 237 | public Task SendAsync(ReadOnlyMemory datagram, string domain, int port) { 238 | if (domain is null) 239 | throw new ArgumentNullException(nameof(domain)); 240 | 241 | return SendAsync(datagram, domain, address: null, port); 242 | } 243 | 244 | /// 245 | /// Send datagram to destination address and port via SOCKS server. 246 | /// 247 | /// The datagram to send. 248 | /// The destination address. 249 | /// The destination port. 250 | public Task SendAsync(ReadOnlyMemory datagram, IPAddress address, int port) { 251 | if (address is null) 252 | throw new ArgumentNullException(nameof(address)); 253 | 254 | return SendAsync(datagram, domain: null, address, port); 255 | } 256 | 257 | private async Task SendAsync(ReadOnlyMemory data, string? domain, IPAddress? address, int port) { 258 | if (UdpClient == null) 259 | throw new InvalidOperationException($"The {GetType().FullName} is not associated."); 260 | if (_status == SocksStatus.Disposed) 261 | throw new ObjectDisposedException(GetType().FullName); 262 | 263 | // +-----+------+------+----------+----------+----------+ 264 | // | RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 265 | // +-----+------+------+----------+----------+----------+ 266 | // | 2 | 1 | 1 | Variable | 2 | Variable | 267 | // +-----+------+------+----------+----------+----------+ 268 | var addressLength = GetAddressLength(domain, address); 269 | var bufferLength = 2 + 1 + 1 + (domain != null ? DomainLengthByteLength : 0) + addressLength + PortLength + data.Length; 270 | var buffer = ArrayPool.Shared.Rent(bufferLength); 271 | 272 | try { 273 | buffer[0] = buffer[1] = buffer[2] = 0; 274 | WriteAddressInfo(addressLength, domain, address, port, buffer, 3); 275 | data.Span.CopyTo(buffer.AsSpan(bufferLength - data.Length)); 276 | 277 | await UdpClient.SendAsync(buffer, bufferLength).ConfigureAwait(false); 278 | 279 | return bufferLength; 280 | 281 | } finally { 282 | ArrayPool.Shared.Return(buffer); 283 | } 284 | } 285 | 286 | /// 287 | /// Receive datagram via SOCKS server. 288 | /// 289 | public async Task ReceiveAsync() { 290 | if (UdpClient == null) 291 | throw new InvalidOperationException($"The {GetType().FullName} is not associated."); 292 | if (_status == SocksStatus.Disposed) 293 | throw new ObjectDisposedException(GetType().FullName); 294 | 295 | // +-----+------+------+----------+----------+----------+ 296 | // | RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 297 | // +-----+------+------+----------+----------+----------+ 298 | // | 2 | 1 | 1 | Variable | 2 | Variable | 299 | // +-----+------+------+----------+----------+----------+ 300 | // | HEADER | | 301 | // +----------------------------------------------------+ 302 | var result = await UdpClient.ReceiveAsync().ConfigureAwait(false); 303 | var buffer = result.Buffer; 304 | if (buffer.Length < 4) 305 | ThrowBufferTooSmall(buffer); 306 | 307 | var isIPv6 = (AddressType)buffer[3] switch { 308 | AddressType.IPv4 => false, 309 | AddressType.IPv6 => true, 310 | _ => throw new ProtocolErrorException($"Server replies unexpected ATYP: 0x{buffer[3]:X2}.") 311 | }; 312 | 313 | var headerLength = 4 + (isIPv6 ? IPv6AddressLength : IPv4AddressLength) + PortLength; 314 | if (buffer.Length <= headerLength) 315 | ThrowBufferTooSmall(buffer); 316 | 317 | var (address, port) = ReadAddressInfo(isIPv6, buffer.AsSpan(4, headerLength - 4)); 318 | 319 | return new UdpReceiveMemory(buffer.AsMemory(headerLength), new IPEndPoint(address, port)); 320 | } 321 | 322 | private async Task PrepareAsync(CancellationToken token) { 323 | token.ThrowIfCancellationRequested(); 324 | 325 | if (_serverAddress != null) 326 | await TcpClient.ConnectAsync(_serverAddress, _serverPort).ConfigureAwait(false); 327 | else if (_serverHost != null) 328 | await TcpClient.ConnectAsync(_serverHost, _serverPort).ConfigureAwait(false); 329 | 330 | _stream ??= TcpClient.GetStream(); 331 | 332 | var method = await HandshakeAsync(_credential == null ? MethodsNoAuth : MethodsNoAuthUsernamePassword, token).ConfigureAwait(false); 333 | 334 | if (method == Method.UsernamePassword) 335 | await AuthenticateAsync(token).ConfigureAwait(false); 336 | } 337 | 338 | private async Task HandshakeAsync(Method[] methods, CancellationToken token) { 339 | // +-----+----------+----------+ 340 | // | VER | NMETHODS | METHODS | 341 | // +-----+----------+----------+ 342 | // | 1 | 1 | 1 to 255 | 343 | // +-----+----------+----------+ 344 | var requestLength = 1 + 1 + methods.Length; 345 | var buffer = ArrayPool.Shared.Rent(requestLength); 346 | 347 | try { 348 | buffer[0] = Version; 349 | buffer[1] = (byte)methods.Length; 350 | MemoryMarshal.AsBytes(methods).CopyTo(buffer.AsSpan(2, requestLength)); 351 | 352 | await _stream!.WriteAsync(buffer, 0, requestLength, token).ConfigureAwait(false); 353 | 354 | // +-----+--------+ 355 | // | VER | METHOD | 356 | // +-----+--------+ 357 | // | 1 | 1 | 358 | // +-----+--------+ 359 | await _stream.ReadRequiredAsync(buffer, 0, 2, token).ConfigureAwait(false); 360 | 361 | if (buffer[0] != Version) 362 | throw new ProtocolErrorException($"Server replies incompatible version: 0x{buffer[0]:X2}."); 363 | 364 | var serverMethod = (Method)buffer[1]; 365 | if (!Enum.IsDefined(typeof(Method), serverMethod)) 366 | throw new ProtocolErrorException($"Server replies unsupported authentication method: 0x{(byte)serverMethod:X2}."); 367 | 368 | if (_credential == null && serverMethod == Method.UsernamePassword) 369 | throw new ProtocolErrorException($"Server replies unexpected authentication method: {serverMethod}."); 370 | 371 | return serverMethod; 372 | 373 | } finally { 374 | ArrayPool.Shared.Return(buffer); 375 | } 376 | } 377 | 378 | private async Task AuthenticateAsync(CancellationToken token) { 379 | // +-----+------+----------+------+----------+ 380 | // | VER | ULEN | UNAME | PLEN | PASSWD | 381 | // +-----+------+----------+------+----------+ 382 | // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 383 | // +-----+------+----------+------+----------+ 384 | var usernameLength = (byte)Encoding.UTF8.GetByteCount(_credential!.UserName); 385 | var passwordLength = (byte)Encoding.UTF8.GetByteCount(_credential.Password); 386 | var requestLength = 1 + 1 + usernameLength + 1 + passwordLength; 387 | var buffer = ArrayPool.Shared.Rent(requestLength); 388 | 389 | try { 390 | buffer[0] = AuthenticationVersion; 391 | buffer[1] = usernameLength; 392 | var bytesCount = Encoding.UTF8.GetBytes(_credential.UserName, 0, _credential.UserName.Length, buffer, 2); 393 | buffer[2 + bytesCount] = passwordLength; 394 | Encoding.UTF8.GetBytes(_credential.Password, 0, _credential.Password.Length, buffer, 2 + bytesCount + 1); 395 | 396 | await _stream!.WriteAsync(buffer, 0, requestLength, token).ConfigureAwait(false); 397 | 398 | // +-----+--------+ 399 | // | VER | STATUS | 400 | // +-----+--------+ 401 | // | 1 | 1 | 402 | // +-----+--------+ 403 | await _stream.ReadRequiredAsync(buffer, 0, 2, token).ConfigureAwait(false); 404 | 405 | if (buffer[1] != 0) 406 | throw new AuthenticationException($"Authentication failure with status code: 0x{buffer[1]:X}."); 407 | 408 | } finally { 409 | ArrayPool.Shared.Return(buffer); 410 | } 411 | } 412 | 413 | private async Task SendCommandAsync(Command command, string? domain, IPAddress? address, int port, CancellationToken token) { 414 | const int MaxRequestResponseLength = 1 + 1 + 1 + 1 + DomainLengthByteLength + DomainMaxLength + PortLength; 415 | var buffer = ArrayPool.Shared.Rent(MaxRequestResponseLength); 416 | 417 | // +-----+-----+-------+------+----------+----------+ 418 | // | VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 419 | // +-----+-----+-------+------+----------+----------+ 420 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 421 | // +-----+-----+-------+------+----------+----------+ 422 | var addressLength = GetAddressLength(domain, address); 423 | var requestLength = 1 + 1 + 1 + 1 + (domain != null ? DomainLengthByteLength : 0) + addressLength + PortLength; 424 | 425 | try { 426 | buffer[0] = Version; 427 | buffer[1] = (byte)command; 428 | buffer[2] = 0; 429 | WriteAddressInfo(addressLength, domain, address, port, buffer, 3); 430 | 431 | await _stream!.WriteAsync(buffer, 0, requestLength, token).ConfigureAwait(false); 432 | 433 | // +-----+-----+-------+------+----------+----------+ 434 | // | VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 435 | // +-----+-----+-------+------+----------+----------+ 436 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 437 | // +-----+-----+-------+------+----------+----------+ 438 | // 0 length domain in some servers rough implementation 439 | const int MinResponseLength = 1 + 1 + 1 + 1 + DomainLengthByteLength + 0 + PortLength; 440 | const int IPv4ResponseLength = 1 + 1 + 1 + 1 + IPv4AddressLength + PortLength; 441 | const int IPv6ResponseLength = 1 + 1 + 1 + 1 + IPv6AddressLength + PortLength; 442 | 443 | // At first, try to read the full response. 444 | var bytesRead = await _stream.ReadAsync(buffer, 0, MaxRequestResponseLength).ConfigureAwait(false); 445 | // read more until MinResponseLength 446 | if (bytesRead < MinResponseLength) { 447 | await _stream.ReadRequiredAsync(buffer, bytesRead, MinResponseLength - bytesRead, token).ConfigureAwait(false); 448 | bytesRead = MinResponseLength; 449 | } 450 | 451 | // Now, it is guaranteed that at least 7 (MinResponseLength) bytes have been read. 452 | if ((Reply)buffer[1] != Reply.Successed) 453 | throw new ReplyException((Reply)buffer[1]); 454 | 455 | switch ((AddressType)buffer[3]) { 456 | case AddressType.IPv4: 457 | if (bytesRead < IPv4ResponseLength) 458 | await _stream.ReadRequiredAsync(buffer, bytesRead, IPv4ResponseLength - bytesRead, token).ConfigureAwait(false); 459 | (_boundAddress, _boundPort) = ReadAddressInfo(isIPv6: false, buffer.AsSpan(4)); 460 | break; 461 | 462 | case AddressType.IPv6: 463 | if (bytesRead < IPv6ResponseLength) 464 | await _stream.ReadRequiredAsync(buffer, bytesRead, IPv6ResponseLength - bytesRead, token).ConfigureAwait(false); 465 | (_boundAddress, _boundPort) = ReadAddressInfo(isIPv6: true, buffer.AsSpan(4)); 466 | break; 467 | 468 | case AddressType.Domain: 469 | if (DomainAddressBehavior == DomainAddressBehavior.UseConnectedAddress) { 470 | var domainLength = buffer[4]; 471 | var domainResponseLength = 1 + 1 + 1 + 1 + DomainLengthByteLength + domainLength + PortLength; 472 | if (bytesRead < domainResponseLength) 473 | await _stream.ReadRequiredAsync(buffer, bytesRead, domainResponseLength - bytesRead, token) 474 | .ConfigureAwait(false); 475 | _boundAddress = RemoteAddress; 476 | _boundPort = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(domainResponseLength - PortLength, PortLength)); 477 | 478 | } else { // DomainAddressBehavior.ThrowException 479 | throw new ProtocolErrorException($"Server replies unexpected ATYP: 0x{buffer[3]:X2}."); 480 | } 481 | break; 482 | 483 | default: 484 | throw new ProtocolErrorException($"Server replies unknown ATYP: 0x{buffer[3]:X2}."); 485 | } 486 | 487 | } finally { 488 | ArrayPool.Shared.Return(buffer); 489 | } 490 | } 491 | 492 | private byte GetAddressLength(string? domain, IPAddress? address) { 493 | if (domain != null) { 494 | return (byte)Encoding.UTF8.GetByteCount(domain); 495 | 496 | } else { 497 | return address!.AddressFamily switch { 498 | AddressFamily.InterNetwork => IPv4AddressLength, 499 | AddressFamily.InterNetworkV6 => IPv6AddressLength, 500 | _ => throw new ArgumentOutOfRangeException(nameof(address)) 501 | }; 502 | } 503 | } 504 | 505 | private void WriteAddressInfo(byte addressLength, string? domain, IPAddress? address, int port, byte[] buffer, int offset) { 506 | // +------+----------+------+ 507 | // | ATYP | ADDR | PORT | 508 | // +------+----------+------+ 509 | // | 1 | Variable | 2 | 510 | // +------+----------+------+ 511 | buffer[offset++] = (byte)(domain != null ? AddressType.Domain : address!.AddressFamily.ToAddressType()); 512 | if (domain != null) { 513 | buffer[offset++] = addressLength; 514 | offset += Encoding.UTF8.GetBytes(domain, 0, domain.Length, buffer, offset); 515 | 516 | } else { 517 | #if NETSTANDARD2_0 518 | address!.GetAddressBytes().CopyTo(buffer.AsSpan(offset)); 519 | #elif NETSTANDARD2_1 520 | if (!address!.TryWriteBytes(buffer.AsSpan(offset), out _)) 521 | throw new InvalidOperationException("Address buffer insufficient."); 522 | #endif 523 | offset += addressLength; 524 | } 525 | 526 | BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(offset), (ushort)port); 527 | } 528 | 529 | private (IPAddress Address, int Port) ReadAddressInfo(bool isIPv6, ReadOnlySpan buffer) { 530 | // +----------+------+ 531 | // | ADDR | PORT | 532 | // +----------+------+ 533 | // | Variable | 2 | 534 | // +----------+------+ 535 | var addressLength = isIPv6 ? IPv6AddressLength : IPv4AddressLength; 536 | 537 | #if NETSTANDARD2_0 538 | var address = new IPAddress(buffer.Slice(0, addressLength).ToArray()); 539 | #elif NETSTANDARD2_1 540 | var address = new IPAddress(buffer[..addressLength]); 541 | #endif 542 | 543 | var port = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(addressLength, 2)); 544 | 545 | return (address, port); 546 | } 547 | 548 | private static void ThrowBufferTooSmall(byte[] buffer) => 549 | throw new ProtocolErrorException($"Server replies a packet that was smaller than expected: {BitConverter.ToString(buffer)}"); 550 | } 551 | 552 | /// 553 | /// Used to decide whether to ignore the BND.ADDR responded by UDP Associate command. 554 | /// 555 | /// In the Internet world, a considerable number of SOCKS5 servers have incorrect UDP Associate implementation. 556 | /// 557 | /// 558 | /// According to the description of UDP Association in RFC 1928: "In the reply to a UDP ASSOCIATE request, the BND.PORT and BND.ADDR fields indicate the port number/address where the client MUST send UDP request messages to be relayed.", the server should respond its public IP address. If the server has multiple public IP addresses, the server should decide which public IP to respond according to its own strategy. 559 | /// 560 | /// 561 | /// However, most SOCKS5 servers implementations are very rough. They often use some private addresses as BND.ADDR respond to the client, such as 10.0.0.1, 172.16.1.1, 192.168.1.1 and so on. In this case, the UDP packet sent by the client cannot reach the server at all, unless the client and the server are in the same LAN. 562 | /// 563 | /// 564 | /// Therefore, through this callback, the client can according to the received BND.ADDR to determine whether this address is a private address. If true is returned, the client will send UDP packet to ServerAddress:BND.PORT; If false is returned, it will send UDP packet to BND.ADDR:BND.PORT. 565 | /// 566 | /// 567 | /// The instance which calls the callback. 568 | /// The BND.ADDR of responded by UDP Associate command. 569 | /// 570 | public delegate Task ShouldIgnoreBoundAddressCallback(SocksClient sender, IPAddress address); 571 | } 572 | -------------------------------------------------------------------------------- /Socklient/UdpReceiveMemory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Text; 6 | 7 | namespace Socklient { 8 | /// 9 | /// Presents SOCKS5 server UDP replied result information from a call to the method. 10 | /// 11 | public readonly struct UdpReceiveMemory : IEquatable { 12 | /// 13 | /// Gets the with the data received in the UDP packet. 14 | /// 15 | public ReadOnlyMemory Memory { get; } 16 | 17 | /// 18 | /// Gets the remote endpoint from which the UDP packet was received. 19 | /// 20 | public IPEndPoint RemoteEndPoint { get; } 21 | 22 | internal UdpReceiveMemory(ReadOnlyMemory memory, IPEndPoint remoteEndPoint) { 23 | Memory = memory; 24 | RemoteEndPoint = remoteEndPoint; 25 | } 26 | 27 | #pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释 28 | public override bool Equals(object obj) => obj is UdpReceiveMemory other && Equals(other); 29 | 30 | public bool Equals(UdpReceiveMemory other) => Memory.Equals(other) && RemoteEndPoint.Equals(other.RemoteEndPoint); 31 | 32 | public override int GetHashCode() => Memory.GetHashCode() ^ RemoteEndPoint.GetHashCode(); 33 | 34 | public static bool operator ==(UdpReceiveMemory left, UdpReceiveMemory right) => left.Equals(right); 35 | 36 | public static bool operator !=(UdpReceiveMemory left, UdpReceiveMemory right) => !(left == right); 37 | #pragma warning restore CS1591 // 缺少对公共可见类型或成员的 XML 注释 38 | } 39 | } 40 | --------------------------------------------------------------------------------