├── .codacy.yml
├── .gitignore
├── .travis.yml
├── CodeAnalysisRules.ruleset
├── LICENSE.md
├── README.md
├── SocksRelayServer.sln
├── src
├── ConnectionInfo.cs
├── Dns
│ ├── DefaultDnsResolver.cs
│ └── IDnsResolver.cs
├── Exception
│ ├── Socks4Exception.cs
│ ├── Socks5Exception.cs
│ └── SocksRelayServerException.cs
├── ISocksRelayServer.cs
├── Protocol
│ ├── Socks4.cs
│ └── Socks5.cs
├── Relay
│ ├── Helpers.cs
│ └── SocketRelay.cs
├── Socks5Client.cs
├── SocksRelayServer.cs
└── SocksRelayServer.csproj
└── tests
├── CustomDnsResolver.cs
├── SocksRelayServerTests.cs
├── SocksRelayServerTests.csproj
└── TestHelpers.cs
/.codacy.yml:
--------------------------------------------------------------------------------
1 | exclude_paths:
2 | - tests/**
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 |
56 | # StyleCop
57 | StyleCopReport.xml
58 |
59 | # Files built by Visual Studio
60 | *_i.c
61 | *_p.c
62 | *_h.h
63 | *.ilk
64 | *.meta
65 | *.obj
66 | *.iobj
67 | *.pch
68 | *.pdb
69 | *.ipdb
70 | *.pgc
71 | *.pgd
72 | *.rsp
73 | *.sbr
74 | *.tlb
75 | *.tli
76 | *.tlh
77 | *.tmp
78 | *.tmp_proj
79 | *_wpftmp.csproj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush personal settings
295 | .cr/personal
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
332 | # Local History for Visual Studio
333 | .localhistory/
334 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | mono: none
3 | dotnet: 2.2.402
4 | dist: xenial
5 |
6 | services:
7 | - docker
8 |
9 | before_install:
10 | - docker pull wernight/dante
11 | - docker run --name dante -d -p 1080:1080 wernight/dante
12 |
13 | script:
14 | - dotnet test tests/SocksRelayServerTests.csproj
15 | - docker logs dante
16 |
--------------------------------------------------------------------------------
/CodeAnalysisRules.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | > MIT License
2 | >
3 | > Copyright (c) 2018 Outis Nemo Ltd.
4 | >
5 | > Permission is hereby granted, free of charge, to any person obtaining
6 | > a copy of this software and associated documentation files (the
7 | > "Software"), to deal in the Software without restriction, including
8 | > without limitation the rights to use, copy, modify, merge, publish,
9 | > distribute, sublicense, and/or sell copies of the Software, and to
10 | > permit persons to whom the Software is furnished to do so, subject to
11 | > the following conditions:
12 | >
13 | > The above copyright notice and this permission notice shall be
14 | > included in all copies or substantial portions of the Software.
15 | >
16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SocksRelayServer
2 |
3 | ## About the project
4 | [](https://travis-ci.org/OutisNemo/SocksRelayServer) [](https://app.codacy.com/app/brnbs/SocksRelayServer?utm_source=github.com&utm_medium=referral&utm_content=OutisNemo/SocksRelayServer&utm_campaign=Badge_Grade_Dashboard)
5 |
6 | A simple SOCKS v4a proxy server written in .NET Standard 2.0 which forwards all traffic to a SOCKS v5 server. The proxy server does not support authentication however it can connect to a SOCKS v5 server using username and password. UDP and Bind commands are not supported. It also provides an interface for custom DNS resolving and a default DNS resolver as well. It can also use the upstream proxy for resolving the hostnames.
7 |
8 | ## How to install
9 | You can easily install this package to your project using NuGet.
10 |
11 | See the [NuGet page](https://www.nuget.org/packages/OutisNemo.SocksRelayServer/)
12 |
13 | ## How to use
14 | You can find detailed usage examples in the `tests/SocksRelayServerTests.csproj` project.
15 |
16 | ## How to write a custom DNS resolver
17 | All you need to do is implement the `IDnsResolver` interface and pass your implementation to the `SocksRelayServer` instance using it's `DnsResolver` property. You can see a working example in the `tests/SocksRelayServerTests.cs` file.
18 |
19 | ## License
20 | See the [LICENSE file](LICENSE.md) in this repository.
21 |
--------------------------------------------------------------------------------
/SocksRelayServer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocksRelayServer", "src\SocksRelayServer.csproj", "{7D567A22-294A-4494-833C-D563F0A3C4B5}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocksRelayServerTests", "tests\SocksRelayServerTests.csproj", "{D53096CC-6C09-4F20-89F5-439417F86B58}"
9 | EndProject
10 | Global
11 | GlobalSection(Performance) = preSolution
12 | HasPerformanceSessions = true
13 | EndGlobalSection
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|Any CPU = Debug|Any CPU
16 | Debug|x64 = Debug|x64
17 | Debug|x86 = Debug|x86
18 | Release|Any CPU = Release|Any CPU
19 | Release|x64 = Release|x64
20 | Release|x86 = Release|x86
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Debug|x64.ActiveCfg = Debug|Any CPU
26 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Debug|x64.Build.0 = Debug|Any CPU
27 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Debug|x86.ActiveCfg = Debug|Any CPU
28 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Debug|x86.Build.0 = Debug|Any CPU
29 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Release|x64.ActiveCfg = Release|Any CPU
32 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Release|x64.Build.0 = Release|Any CPU
33 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Release|x86.ActiveCfg = Release|Any CPU
34 | {7D567A22-294A-4494-833C-D563F0A3C4B5}.Release|x86.Build.0 = Release|Any CPU
35 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Debug|x64.ActiveCfg = Debug|Any CPU
38 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Debug|x64.Build.0 = Debug|Any CPU
39 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Debug|x86.ActiveCfg = Debug|Any CPU
40 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Debug|x86.Build.0 = Debug|Any CPU
41 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Release|x64.ActiveCfg = Release|Any CPU
44 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Release|x64.Build.0 = Release|Any CPU
45 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Release|x86.ActiveCfg = Release|Any CPU
46 | {D53096CC-6C09-4F20-89F5-439417F86B58}.Release|x86.Build.0 = Release|Any CPU
47 | EndGlobalSection
48 | GlobalSection(SolutionProperties) = preSolution
49 | HideSolutionNode = FALSE
50 | EndGlobalSection
51 | GlobalSection(ExtensibilityGlobals) = postSolution
52 | SolutionGuid = {1A44A179-3BE4-4CE3-BE3D-AD3DDB9FE2C1}
53 | EndGlobalSection
54 | EndGlobal
55 |
--------------------------------------------------------------------------------
/src/ConnectionInfo.cs:
--------------------------------------------------------------------------------
1 | using SocksRelayServer.Relay;
2 |
3 | namespace SocksRelayServer
4 | {
5 | internal class ConnectionInfo
6 | {
7 | public System.Net.Sockets.Socket LocalSocket { get; set; }
8 |
9 | public System.Net.Sockets.Socket RemoteSocket { get; set; }
10 |
11 | public void Terminate()
12 | {
13 | LocalSocket.TryDispose();
14 | RemoteSocket.TryDispose();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Dns/DefaultDnsResolver.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 |
4 | namespace SocksRelayServer.Dns
5 | {
6 | using System.Net;
7 |
8 | internal class DefaultDnsResolver : IDnsResolver
9 | {
10 | public Task TryResolve(string hostname)
11 | {
12 | IPAddress result = null;
13 |
14 | if (IPAddress.TryParse(hostname, out var address))
15 | {
16 | return Task.FromResult(address);
17 | }
18 |
19 | try
20 | {
21 | result = Dns.GetHostAddresses(hostname).FirstOrDefault();
22 | }
23 | catch
24 | {
25 | // ignore
26 | }
27 |
28 | return Task.FromResult(result);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Dns/IDnsResolver.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Threading.Tasks;
3 |
4 | namespace SocksRelayServer.Dns
5 | {
6 | public interface IDnsResolver
7 | {
8 | Task TryResolve(string hostname);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Exception/Socks4Exception.cs:
--------------------------------------------------------------------------------
1 | namespace SocksRelayServer.Exception
2 | {
3 | public class Socks4Exception : SocksRelayServerException
4 | {
5 | public Socks4Exception(string message)
6 | : base(message)
7 | {
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Exception/Socks5Exception.cs:
--------------------------------------------------------------------------------
1 | namespace SocksRelayServer.Exception
2 | {
3 | public class Socks5Exception : SocksRelayServerException
4 | {
5 | public Socks5Exception(string message)
6 | : base(message)
7 | {
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Exception/SocksRelayServerException.cs:
--------------------------------------------------------------------------------
1 | namespace SocksRelayServer.Exception
2 | {
3 | public class SocksRelayServerException : System.Exception
4 | {
5 | public SocksRelayServerException(string message)
6 | : base(message)
7 | {
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/ISocksRelayServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using SocksRelayServer.Dns;
4 |
5 | namespace SocksRelayServer
6 | {
7 | public interface ISocksRelayServer : IDisposable
8 | {
9 | event EventHandler OnLocalConnect;
10 |
11 | event EventHandler OnRemoteConnect;
12 |
13 | event EventHandler OnLogMessage;
14 |
15 | ///
16 | /// Set this to handle custom DNS resolutions. The default DNS reoslution method uses the built-in method of the OS.
17 | ///
18 | IDnsResolver DnsResolver { get; set; }
19 |
20 | ///
21 | /// The username used for authenticate against the upstream proxy.
22 | ///
23 | string Username { get; set; }
24 |
25 | ///
26 | /// The password used for authenticate against the upstream proxy.
27 | ///
28 | string Password { get; set; }
29 |
30 | ///
31 | /// Set the buffer size used for communicating in both ways. The default is set to 8096.
32 | ///
33 | int BufferSize { get; set; }
34 |
35 | ///
36 | /// Set this to true if you want to pass the unresolved hostnames to the upstream proxy. In this case the DnsResolver property is ignored.
37 | ///
38 | bool ResolveHostnamesRemotely { get; set; }
39 |
40 | ///
41 | /// The local endpoint where this server is listening.
42 | ///
43 | IPEndPoint LocalEndPoint { get; }
44 |
45 | ///
46 | /// The remote endpoint where this server is relaying the requests.
47 | ///
48 | IPEndPoint RemotEndPoint { get; }
49 |
50 | ///
51 | /// The sockets time-out value, in milliseconds. If you set the property with a value between 1 and 499, the value will be changed to 500.
52 | /// The default value is 0, which indicates an infinite time-out period. Specifying -1 also indicates an infinite time-out period.
53 | ///
54 | int SendTimeout { get; set; }
55 |
56 | ///
57 | /// The sockets time-out value, in milliseconds. The default value is 0, which indicates an infinite time-out period. Specifying -1 also
58 | /// indicates an infinite time-out period.
59 | ///
60 | int ReceiveTimeout { get; set; }
61 |
62 | void Start();
63 |
64 | void Stop();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Protocol/Socks4.cs:
--------------------------------------------------------------------------------
1 | namespace SocksRelayServer.Protocol
2 | {
3 | internal static class Socks4
4 | {
5 | public const byte Version = 0x04;
6 |
7 | public const byte CommandStreamConnection = 0x01;
8 | public const byte CommandBindingConnection = 0x02;
9 |
10 | public const byte StatusRequestGranted = 0x5A;
11 | public const byte StatusRequestFailed = 0x5B;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Protocol/Socks5.cs:
--------------------------------------------------------------------------------
1 | namespace SocksRelayServer.Protocol
2 | {
3 | internal static class Socks5
4 | {
5 | public const byte Version = 0x05;
6 |
7 | public const byte CommandStreamConnection = 0x01;
8 | public const byte CommandBindingConnection = 0x02;
9 | public const byte CommandUdpConnection = 0x03;
10 |
11 | public const byte AuthenticationUsernamePassword = 0x02;
12 | public const byte AuthenticationNone = 0x00;
13 | public const byte AuthenticationNoMethodAccepted = 0xFF;
14 |
15 | public const byte AddressTypeIPv4 = 0x01;
16 | public const byte AddressTypeIPv6 = 0x04;
17 | public const byte AddressTypeDomain = 0x03;
18 |
19 | public const byte StatusRequestGranted = 0x00;
20 | public const byte StatusGeneralFailure = 0x01;
21 | public const byte StatusConnectionNotAllowed = 0x02;
22 | public const byte StatusNetworkUnreachable = 0x03;
23 | public const byte StatusHostUnreachable = 0x04;
24 | public const byte StatusConnectionRefused = 0x05;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Relay/Helpers.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Sockets;
2 |
3 | namespace SocksRelayServer.Relay
4 | {
5 | internal static class Helpers
6 | {
7 | public static void TryDispose(this Socket socket)
8 | {
9 | if (socket is null)
10 | {
11 | return;
12 | }
13 |
14 | if (socket.Connected)
15 | {
16 | try
17 | {
18 | socket.Shutdown(SocketShutdown.Send);
19 | }
20 | catch
21 | {
22 | // ignored
23 | }
24 | }
25 |
26 | try
27 | {
28 | socket.Close();
29 | }
30 | catch
31 | {
32 | // ignored
33 | }
34 | }
35 |
36 | public static void TryDispose(this SocketAsyncEventArgs saea)
37 | {
38 | if (saea is null)
39 | {
40 | return;
41 | }
42 |
43 | try
44 | {
45 | saea.UserToken = null;
46 | saea.AcceptSocket = null;
47 |
48 | saea.Dispose();
49 | }
50 | catch
51 | {
52 | // ignored
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Relay/SocketRelay.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Sockets;
3 | using System.Threading.Tasks;
4 |
5 | namespace SocksRelayServer.Relay
6 | {
7 | internal class SocketRelay
8 | {
9 | private bool _receiving;
10 | private SocketRelay _other;
11 |
12 | private SocketAsyncEventArgs _recSaea;
13 | private SocketAsyncEventArgs _sendSaea;
14 | private Socket _source;
15 | private Socket _target;
16 | private byte[] _buffer;
17 |
18 | private int _received;
19 | private int _sendingOffset;
20 |
21 | private bool _disposed = false;
22 | private bool _shouldDispose = false;
23 |
24 | private SocketRelay(Socket source, Socket target)
25 | {
26 | _source = source;
27 | _target = target;
28 | _buffer = new byte[8192];
29 | _recSaea = new SocketAsyncEventArgs { UserToken = this };
30 | _sendSaea = new SocketAsyncEventArgs { UserToken = this };
31 | _recSaea.SetBuffer(_buffer, 0, _buffer.Length);
32 | _sendSaea.SetBuffer(_buffer, 0, _buffer.Length);
33 | _recSaea.Completed += OnAsyncOperationCompleted;
34 | _sendSaea.Completed += OnAsyncOperationCompleted;
35 | _receiving = true;
36 | }
37 |
38 | public static void RelayBiDirectionally(Socket s1, Socket s2)
39 | {
40 | var relayOne = new SocketRelay(s1, s2);
41 | var relayTwo = new SocketRelay(s2, s1);
42 |
43 | relayOne._other = relayTwo;
44 | relayTwo._other = relayOne;
45 |
46 | Task.Run(new Action(relayOne.Process));
47 | Task.Run(new Action(relayTwo.Process));
48 | }
49 |
50 | private static void OnAsyncOperationCompleted(object o, SocketAsyncEventArgs saea)
51 | {
52 | if (saea.UserToken is SocketRelay relay)
53 | {
54 | relay.Process();
55 | }
56 | }
57 |
58 | private void OnCleanup()
59 | {
60 | if (_disposed)
61 | {
62 | return;
63 | }
64 |
65 | _disposed = _shouldDispose = true;
66 |
67 | _other._shouldDispose = true;
68 | _other = null;
69 |
70 | _source.TryDispose();
71 | _target.TryDispose();
72 | _recSaea.TryDispose();
73 | _sendSaea.TryDispose();
74 |
75 | _source = _target = null;
76 | _recSaea = _sendSaea = null;
77 | _buffer = null;
78 | }
79 |
80 | private void Process()
81 | {
82 | try
83 | {
84 | while (true)
85 | {
86 | if (_shouldDispose)
87 | {
88 | OnCleanup();
89 | return;
90 | }
91 |
92 | if (_receiving)
93 | {
94 | _receiving = false;
95 | _sendingOffset = -1;
96 |
97 | if (_source.ReceiveAsync(_recSaea))
98 | {
99 | return;
100 | }
101 | }
102 | else
103 | {
104 | if (_sendingOffset == -1)
105 | {
106 | _received = _recSaea.BytesTransferred;
107 | _sendingOffset = 0;
108 |
109 | if (_received == 0)
110 | {
111 | _shouldDispose = true;
112 | continue;
113 | }
114 | }
115 | else
116 | {
117 | _sendingOffset += _sendSaea.BytesTransferred;
118 | }
119 |
120 | if (_sendingOffset != _received)
121 | {
122 | _sendSaea.SetBuffer(_buffer, _sendingOffset, _received - _sendingOffset);
123 |
124 | if (_target.SendAsync(_sendSaea))
125 | {
126 | return;
127 | }
128 | }
129 | else
130 | {
131 | _receiving = true;
132 | }
133 | }
134 | }
135 | }
136 | catch
137 | {
138 | OnCleanup();
139 | }
140 | }
141 | }
142 | }
--------------------------------------------------------------------------------
/src/Socks5Client.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Text;
5 | using SocksRelayServer.Exception;
6 |
7 | namespace SocksRelayServer
8 | {
9 | internal class Socks5Client
10 | {
11 | private readonly string _socksAddr;
12 | private readonly int _socksPort;
13 | private readonly string _destAddr;
14 | private readonly int _destPort;
15 | private readonly string _username;
16 | private readonly string _password;
17 | private readonly Socket _socket;
18 |
19 | private Socks5Client(string socksAddress, int socksPort, string destAddress, int destPort, string username, string password, int sendTimeout, int receiveTimeout)
20 | {
21 | _socksAddr = socksAddress;
22 | _socksPort = socksPort;
23 | _destAddr = destAddress;
24 | _destPort = destPort;
25 | _username = username;
26 | _password = password;
27 |
28 | _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
29 | {
30 | SendTimeout = sendTimeout,
31 | ReceiveTimeout = receiveTimeout,
32 | };
33 | }
34 |
35 | public static Socket Connect(string socksAddress, int socksPort, string destAddress, int destPort, string username, string password, int sendTimeout, int receiveTimeout)
36 | {
37 | var client = new Socks5Client(socksAddress, socksPort, destAddress, destPort, username, password, sendTimeout, receiveTimeout);
38 | return client.Connect();
39 | }
40 |
41 | private static byte GetAddressType(string destAddr)
42 | {
43 | var result = IPAddress.TryParse(destAddr, out var address);
44 |
45 | if (!result)
46 | {
47 | return Protocol.Socks5.AddressTypeDomain;
48 | }
49 |
50 | switch (address.AddressFamily)
51 | {
52 | case AddressFamily.InterNetwork:
53 | return Protocol.Socks5.AddressTypeIPv4;
54 | case AddressFamily.InterNetworkV6:
55 | return Protocol.Socks5.AddressTypeIPv6;
56 | default:
57 | throw new Socks5Exception("Upstream connection failed: Unknown address type");
58 | }
59 | }
60 |
61 | private static byte[] GetDestAddressBytes(byte addressType, string host)
62 | {
63 | switch (addressType)
64 | {
65 | case Protocol.Socks5.AddressTypeIPv4:
66 | case Protocol.Socks5.AddressTypeIPv6:
67 | return IPAddress.Parse(host).GetAddressBytes();
68 | case Protocol.Socks5.AddressTypeDomain:
69 | var bytes = new byte[host.Length + 1];
70 | bytes[0] = Convert.ToByte(host.Length);
71 | Encoding.ASCII.GetBytes(host).CopyTo(bytes, 1);
72 | return bytes;
73 | default:
74 | return null;
75 | }
76 | }
77 |
78 | private static byte[] GetDestPortBytes(int value)
79 | {
80 | var array = new byte[2];
81 | array[0] = Convert.ToByte(value / 256);
82 | array[1] = Convert.ToByte(value % 256);
83 | return array;
84 | }
85 |
86 | private Socket Connect()
87 | {
88 | byte[] buffer;
89 | if (!string.IsNullOrEmpty(_username) && _password != null)
90 | {
91 | buffer = new byte[4]
92 | {
93 | Protocol.Socks5.Version,
94 | 2,
95 | Protocol.Socks5.AuthenticationNone,
96 | Protocol.Socks5.AuthenticationUsernamePassword,
97 | };
98 | }
99 | else
100 | {
101 | buffer = new byte[3]
102 | {
103 | Protocol.Socks5.Version,
104 | 1,
105 | Protocol.Socks5.AuthenticationNone,
106 | };
107 | }
108 |
109 | _socket.Connect(_socksAddr, _socksPort);
110 | _socket.Send(buffer);
111 | _socket.Receive(buffer, 0, 2, SocketFlags.None);
112 |
113 | if (buffer[1] == Protocol.Socks5.AuthenticationNoMethodAccepted)
114 | {
115 | _socket.Close();
116 | throw new Socks5Exception("No authentication method is accepted");
117 | }
118 |
119 | if (buffer[1] == Protocol.Socks5.AuthenticationUsernamePassword)
120 | {
121 | var credentials = new byte[_username.Length + _password.Length + 3];
122 | credentials[0] = 1;
123 | credentials[1] = (byte)_username.Length;
124 | Encoding.ASCII.GetBytes(_username).CopyTo(credentials, 2);
125 | credentials[_username.Length + 2] = (byte)_password.Length;
126 | Encoding.ASCII.GetBytes(_password).CopyTo(credentials, _username.Length + 3);
127 |
128 | _socket.Send(credentials, credentials.Length, SocketFlags.None);
129 | buffer = new byte[2];
130 | _socket.Receive(buffer, buffer.Length, SocketFlags.None);
131 | if (buffer[1] != Protocol.Socks5.StatusRequestGranted)
132 | {
133 | throw new Socks5Exception("Invalid username or password");
134 | }
135 | }
136 |
137 | var addrType = GetAddressType(_destAddr);
138 | var address = GetDestAddressBytes(addrType, _destAddr);
139 | var port = GetDestPortBytes(_destPort);
140 |
141 | buffer = new byte[4 + port.Length + address.Length];
142 | buffer[0] = Protocol.Socks5.Version;
143 | buffer[1] = Protocol.Socks5.CommandStreamConnection;
144 | buffer[2] = 0x00;
145 | buffer[3] = addrType;
146 |
147 | address.CopyTo(buffer, 4);
148 | port.CopyTo(buffer, 4 + address.Length);
149 | _socket.Send(buffer);
150 | buffer = new byte[255];
151 | _socket.Receive(buffer, buffer.Length, SocketFlags.None);
152 |
153 | if (buffer[1] != Protocol.Socks5.StatusRequestGranted)
154 | {
155 | switch (buffer[1])
156 | {
157 | case Protocol.Socks5.StatusGeneralFailure:
158 | throw new Socks5Exception("Upstream connection failed: StatusGeneralFailure");
159 | case Protocol.Socks5.StatusConnectionNotAllowed:
160 | throw new Socks5Exception("Upstream connection failed: StatusConnectionNotAllowed");
161 | case Protocol.Socks5.StatusNetworkUnreachable:
162 | throw new Socks5Exception("Upstream connection failed: StatusNetworkUnreachable");
163 | case Protocol.Socks5.StatusHostUnreachable:
164 | throw new Socks5Exception("Upstream connection failed: StatusHostUnreachable");
165 | case Protocol.Socks5.StatusConnectionRefused:
166 | throw new Socks5Exception("Upstream connection failed: StatusConnectionRefused");
167 | default:
168 | throw new Socks5Exception("Upstream connection failed: Unknown error");
169 | }
170 | }
171 |
172 | return _socket;
173 | }
174 | }
175 | }
--------------------------------------------------------------------------------
/src/SocksRelayServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Net.Sockets;
5 | using System.Text;
6 | using System.Threading;
7 | using SocksRelayServer.Dns;
8 | using SocksRelayServer.Exception;
9 | using SocksRelayServer.Relay;
10 |
11 | namespace SocksRelayServer
12 | {
13 | public class SocksRelayServer : ISocksRelayServer
14 | {
15 | private Socket _serverSocket;
16 | private Thread _acceptThread;
17 | private bool _serverStarted;
18 |
19 | public SocksRelayServer(IPEndPoint localEndPoint, IPEndPoint remoteProxyEndPoint)
20 | {
21 | if (Equals(localEndPoint, remoteProxyEndPoint))
22 | {
23 | throw new SocksRelayServerException("LocalEndPoint and RemoteEndPoint cannot be the same");
24 | }
25 |
26 | BufferSize = 8192;
27 | LocalEndPoint = localEndPoint;
28 | RemotEndPoint = remoteProxyEndPoint;
29 | SendTimeout = 0;
30 | ReceiveTimeout = 0;
31 |
32 | ResolveHostnamesRemotely = false;
33 | DnsResolver = new DefaultDnsResolver();
34 | }
35 |
36 | public event EventHandler OnLocalConnect;
37 |
38 | public event EventHandler OnRemoteConnect;
39 |
40 | public event EventHandler OnLogMessage;
41 |
42 | public IDnsResolver DnsResolver { get; set; }
43 |
44 | public string Username { get; set; }
45 |
46 | public string Password { get; set; }
47 |
48 | public int BufferSize { get; set; }
49 |
50 | public bool ResolveHostnamesRemotely { get; set; }
51 |
52 | public IPEndPoint LocalEndPoint { get; }
53 |
54 | public IPEndPoint RemotEndPoint { get; }
55 |
56 | public int SendTimeout { get; set; }
57 |
58 | public int ReceiveTimeout { get; set; }
59 |
60 | public void Start()
61 | {
62 | if (!ResolveHostnamesRemotely && DnsResolver == null)
63 | {
64 | throw new SocksRelayServerException("DnsResolver property cannot be null when using Local DNS resolution");
65 | }
66 |
67 | SetupServerSocket();
68 |
69 | _serverStarted = true;
70 | _acceptThread = new Thread(AcceptConnections) { IsBackground = true };
71 | _acceptThread.Start();
72 | }
73 |
74 | public void Stop()
75 | {
76 | _serverStarted = false;
77 | }
78 |
79 | public void Dispose()
80 | {
81 | Stop();
82 | }
83 |
84 | private static void SendSocks4Reply(Socket socket, byte statusCode, IReadOnlyList address, IReadOnlyList portNumber)
85 | {
86 | var response = new byte[]
87 | {
88 | 0x00,
89 | statusCode,
90 | portNumber[0], portNumber[1],
91 | address[0], address[1], address[2], address[3],
92 | };
93 |
94 | socket.Send(response);
95 | }
96 |
97 | private static bool IsSocks4AProtocol(IReadOnlyList ip)
98 | {
99 | return ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] > 0;
100 | }
101 |
102 | private void SetupServerSocket()
103 | {
104 | // Create the socket, bind it, and start listening
105 | _serverSocket = new Socket(LocalEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
106 | _serverSocket.Bind(LocalEndPoint);
107 | _serverSocket.Listen(1024);
108 | }
109 |
110 | private void AcceptConnections()
111 | {
112 | while (_serverStarted)
113 | {
114 | // Accept a connection
115 | var connection = new ConnectionInfo();
116 |
117 | var socket = _serverSocket.Accept();
118 | socket.ReceiveTimeout = ReceiveTimeout;
119 | socket.SendTimeout = SendTimeout;
120 |
121 | connection.LocalSocket = socket;
122 | connection.RemoteSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
123 | {
124 | SendTimeout = SendTimeout,
125 | ReceiveTimeout = ReceiveTimeout,
126 | };
127 |
128 | // Create the thread for the receive
129 | var thread = new Thread(ProcessLocalConnection) { IsBackground = true };
130 | thread.Start(connection);
131 |
132 | var remoteIpEndPoint = (IPEndPoint)connection.LocalSocket.RemoteEndPoint;
133 | OnLocalConnect?.Invoke(this, new DnsEndPoint(remoteIpEndPoint.Address.ToString(), remoteIpEndPoint.Port, remoteIpEndPoint.AddressFamily));
134 | }
135 |
136 | _serverSocket.Close();
137 | }
138 |
139 | private async void ProcessLocalConnection(object state)
140 | {
141 | var connection = (ConnectionInfo)state;
142 | var buffer = new byte[BufferSize];
143 |
144 | try
145 | {
146 | var bytesRead = connection.LocalSocket.Receive(buffer);
147 | if (bytesRead < 1 || buffer[0] != Protocol.Socks4.Version)
148 | {
149 | connection.Terminate();
150 | return;
151 | }
152 |
153 | OnLogMessage?.Invoke(this, $"Got {bytesRead} bytes from ");
154 | }
155 | catch (SocketException ex)
156 | {
157 | OnLogMessage?.Invoke(this, $"Caught SocketException in ProcessLocalConnection with error code {ex.SocketErrorCode}");
158 | }
159 |
160 | try
161 | {
162 | switch (buffer[1])
163 | {
164 | case Protocol.Socks4.CommandStreamConnection:
165 | {
166 | var portBuffer = new[] { buffer[2], buffer[3] };
167 | var port = (ushort)(portBuffer[0] << 8 | portBuffer[1]);
168 |
169 | var address = new[] { buffer[4], buffer[5], buffer[6], buffer[7] };
170 | var destAddress = new IPAddress(address).ToString();
171 | var destAddressFamily = AddressFamily.InterNetwork;
172 |
173 | if (IsSocks4AProtocol(address))
174 | {
175 | var hostBuffer = new byte[256];
176 | Buffer.BlockCopy(buffer, 9, hostBuffer, 0, 100);
177 |
178 | // Resolve hostname, fallback to remote proxy dns resolution
179 | var hostname = Encoding.ASCII.GetString(hostBuffer).TrimEnd((char)0);
180 | if (!ResolveHostnamesRemotely)
181 | {
182 | var resolvedHostname = await DnsResolver.TryResolve(hostname);
183 | if (resolvedHostname == null)
184 | {
185 | OnLogMessage?.Invoke(this, $"DNS resolution failed for {hostname}");
186 | SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestGranted, address, portBuffer);
187 | connection.Terminate();
188 | break;
189 | }
190 |
191 | destAddress = resolvedHostname.ToString();
192 | destAddressFamily = resolvedHostname.AddressFamily;
193 | }
194 | else
195 | {
196 | destAddress = hostname;
197 | destAddressFamily = AddressFamily.Unspecified;
198 | }
199 | }
200 |
201 | connection.RemoteSocket = Socks5Client.Connect(
202 | RemotEndPoint.Address.ToString(),
203 | RemotEndPoint.Port,
204 | destAddress,
205 | port,
206 | Username,
207 | Password,
208 | SendTimeout,
209 | ReceiveTimeout);
210 |
211 | OnRemoteConnect?.Invoke(this, new DnsEndPoint(destAddress, port, destAddressFamily));
212 |
213 | if (connection.RemoteSocket.Connected)
214 | {
215 | OnLogMessage?.Invoke(this, "RelayBiDirectionally between server and client started");
216 | SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestGranted, address, portBuffer);
217 | SocketRelay.RelayBiDirectionally(connection.RemoteSocket, connection.LocalSocket);
218 | }
219 | else
220 | {
221 | OnLogMessage?.Invoke(this, "RemoteSocket connection failed");
222 | SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestFailed, address, portBuffer);
223 | connection.Terminate();
224 | }
225 |
226 | break;
227 | }
228 |
229 | case Protocol.Socks4.CommandBindingConnection:
230 | {
231 | var portBuffer = new[] { buffer[2], buffer[3] };
232 | var address = new[] { buffer[4], buffer[5], buffer[6], buffer[7] };
233 |
234 | // TCP/IP port binding not supported
235 | SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestFailed, address, portBuffer);
236 | connection.Terminate();
237 | break;
238 | }
239 |
240 | default:
241 | OnLogMessage?.Invoke(this, "Unknown protocol on LocalSocket");
242 | connection.Terminate();
243 | break;
244 | }
245 | }
246 | catch (SocketException ex)
247 | {
248 | OnLogMessage?.Invoke(this, $"Caught SocketException in ProcessLocalConnection with error code {ex.SocketErrorCode.ToString()}");
249 | }
250 | catch (Socks5Exception ex)
251 | {
252 | var portBuffer = new[] { buffer[2], buffer[3] };
253 | var address = new[] { buffer[4], buffer[5], buffer[6], buffer[7] };
254 |
255 | SendSocks4Reply(connection.LocalSocket, Protocol.Socks4.StatusRequestFailed, address, portBuffer);
256 | connection.Terminate();
257 |
258 | OnLogMessage?.Invoke(this, $"Caught Socks5Exception in ProcessLocalConnection with message {ex.Message}");
259 | }
260 | }
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/src/SocksRelayServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | ..\CodeAnalysisRules.ruleset
6 |
7 |
8 |
9 | OutisNemo.SocksRelayServer
10 | OutisNemo
11 | brnbs, deaktomi
12 | SocksRelayServer
13 | A simple SOCKS v4a proxy server written in C# (.NET Standard 2.0) which forwards all traffic to a SOCKS v5 server.
14 | A simple SOCKS v4a proxy server written in C# which forwards all traffic to a SOCKS v5 server. The proxy server does not support authentication however it can connect to a SOCKS v5 server using username and password. UDP and Bind commands are not supported. It also provides an interface for custom DNS resolving and a default DNS resolver as well. It can also use the upstream proxy for resolving the hostnames.
15 | Copyright ©2020 Outis Nemo
16 | https://github.com/OutisNemo/SocksRelayServer
17 | MIT
18 | https://github.com/OutisNemo/SocksRelayServer
19 | git
20 | false
21 | false
22 | socks socks5 socks4a socks4 fiddler fiddlercore proxy-server dotnet-standard2 dotnet-core
23 | 1.2.2
24 |
25 |
26 |
27 |
28 | all
29 | runtime; build; native; contentfiles; analyzers
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/CustomDnsResolver.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Net;
3 | using System.Threading.Tasks;
4 | using DNS.Client;
5 | using SocksRelayServer.Dns;
6 |
7 | namespace SocksRelayServerTests
8 | {
9 | public class CustomDnsResolver : IDnsResolver
10 | {
11 | private readonly DnsClient _client;
12 |
13 | public CustomDnsResolver()
14 | {
15 | _client = new DnsClient("1.1.1.1");
16 | }
17 |
18 | public async Task TryResolve(string hostname)
19 | {
20 | IList ips = null;
21 |
22 | if (IPAddress.TryParse(hostname, out var address))
23 | {
24 | return address;
25 | }
26 |
27 | try
28 | {
29 | ips = await _client.Lookup(hostname);
30 | }
31 | catch
32 | {
33 | // ignore
34 | }
35 |
36 | return ips?.Count > 0 ? ips[0] : null;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/SocksRelayServerTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Net.Http;
6 | using System.Net.Sockets;
7 | using System.Threading.Tasks;
8 | using Microsoft.VisualStudio.TestTools.UnitTesting;
9 | using SocksRelayServer;
10 | using SocksSharp;
11 | using SocksSharp.Proxy;
12 |
13 | namespace SocksRelayServerTests
14 | {
15 | [TestClass]
16 | public class SocksRelayServerTests
17 | {
18 | private static IPAddress _remoteProxyAddress = IPAddress.Parse("192.168.0.101");
19 | private static int _remoteProxyPort = 1080;
20 |
21 | public SocksRelayServerTests()
22 | {
23 | var remoteProxyAddress = Environment.GetEnvironmentVariable("REMOTE_PROXY_ADDRESS");
24 | if (!string.IsNullOrEmpty(remoteProxyAddress))
25 | {
26 | _remoteProxyAddress = IPAddress.Parse(remoteProxyAddress);
27 | }
28 |
29 | var remoteProxyPort = Environment.GetEnvironmentVariable("REMOTE_PROXY_PORT");
30 | if (!string.IsNullOrEmpty(remoteProxyPort))
31 | {
32 | _remoteProxyPort = int.Parse(remoteProxyPort);
33 | }
34 | }
35 |
36 | [TestInitialize]
37 | public void IsRemoteProxyListening()
38 | {
39 | using (var client = new TcpClient())
40 | {
41 | try
42 | {
43 | client.Connect(_remoteProxyAddress, _remoteProxyPort);
44 | }
45 | catch (SocketException)
46 | {
47 | Assert.Fail($"Remote proxy server is not running on {_remoteProxyAddress}:{_remoteProxyPort}, check configured IP and port");
48 | }
49 |
50 | client.Close();
51 | }
52 | }
53 |
54 | [TestMethod]
55 | public void CheckIfTrafficIsNotMalformed()
56 | {
57 | using (var relay = CreateRelayServer())
58 | {
59 | relay.ResolveHostnamesRemotely = false;
60 | relay.Start();
61 |
62 | var tasks = new List();
63 | for (var i = 0; i < 8; i++)
64 | {
65 | tasks.Add(TestHelpers.DoTestRequest(relay.LocalEndPoint, "https://httpbin.org/headers"));
66 | }
67 |
68 | Task.WaitAll(tasks.ToArray());
69 | }
70 | }
71 |
72 | [TestMethod]
73 | public async Task CheckIfLargeFilesAreDownloaded()
74 | {
75 | using (var relay = CreateRelayServer())
76 | {
77 | relay.ResolveHostnamesRemotely = false;
78 | relay.Start();
79 |
80 | var settings = new ProxySettings
81 | {
82 | Host = relay.LocalEndPoint.Address.ToString(),
83 | Port = relay.LocalEndPoint.Port,
84 | ConnectTimeout = 15000,
85 | ReadWriteTimeOut = 15000,
86 | };
87 |
88 | using (var proxyClientHandler = new ProxyClientHandler(settings))
89 | {
90 | using (var httpClient = new HttpClient(proxyClientHandler))
91 | {
92 | var request = new HttpRequestMessage
93 | {
94 | Version = HttpVersion.Version11,
95 | Method = HttpMethod.Get,
96 | RequestUri = new Uri("https://updates.tdesktop.com/tsetup/tportable.1.8.15.zip"),
97 | };
98 |
99 | var response = await httpClient.SendAsync(request);
100 | var content = await response.Content.ReadAsByteArrayAsync();
101 |
102 | var contentLengthHeader = long.Parse(response.Content.Headers.First(h => h.Key.Equals("Content-Length")).Value.First());
103 | Assert.IsTrue(contentLengthHeader > 5 * 1024 * 1024); // at least 5 MB
104 |
105 | Assert.AreEqual(contentLengthHeader, content.Length);
106 | }
107 | }
108 | }
109 | }
110 |
111 | [TestMethod]
112 | public async Task CheckRelayingToIpv4Address()
113 | {
114 | using (var relay = CreateRelayServer())
115 | {
116 | relay.Start();
117 |
118 | await TestHelpers.DoTestRequest(relay.LocalEndPoint, "http://172.217.18.78/");
119 | }
120 | }
121 |
122 | [TestMethod]
123 | public async Task CheckRelayingToHostnameResolveLocally()
124 | {
125 | using (var relay = CreateRelayServer())
126 | {
127 | relay.ResolveHostnamesRemotely = false;
128 | relay.Start();
129 |
130 | await TestHelpers.DoTestRequest(relay.LocalEndPoint, "https://www.thinkwithgoogle.com/intl/de-de/");
131 | }
132 | }
133 |
134 | [TestMethod]
135 | public async Task CheckRelayingToHostnameResolveRemotely()
136 | {
137 | using (var relay = CreateRelayServer())
138 | {
139 | relay.ResolveHostnamesRemotely = true;
140 | relay.Start();
141 |
142 | await TestHelpers.DoTestRequest(relay.LocalEndPoint, "https://www.thinkwithgoogle.com/intl/de-de/");
143 | }
144 | }
145 |
146 | [TestMethod]
147 | public async Task CheckRelayingToNonExistentIpAddress()
148 | {
149 | using (var relay = CreateRelayServer())
150 | {
151 | relay.Start();
152 |
153 | var settings = new ProxySettings
154 | {
155 | Host = relay.LocalEndPoint.Address.ToString(),
156 | Port = relay.LocalEndPoint.Port,
157 | ConnectTimeout = 15000,
158 | ReadWriteTimeOut = 15000,
159 | };
160 |
161 | using (var proxyClientHandler = new ProxyClientHandler(settings))
162 | {
163 | using (var httpClient = new HttpClient(proxyClientHandler))
164 | {
165 | try
166 | {
167 | await httpClient.GetAsync("http://0.1.2.3");
168 | Assert.Fail();
169 | }
170 | catch (ProxyException e)
171 | {
172 | Assert.AreEqual("Request rejected or failed", e.Message);
173 | }
174 | }
175 | }
176 | }
177 | }
178 |
179 | [TestMethod]
180 | public async Task CheckRelayingToNonExistentHostnameResolveLocally()
181 | {
182 | using (var relay = CreateRelayServer())
183 | {
184 | relay.ResolveHostnamesRemotely = false;
185 | relay.Start();
186 |
187 | var settings = new ProxySettings
188 | {
189 | Host = relay.LocalEndPoint.Address.ToString(),
190 | Port = relay.LocalEndPoint.Port,
191 | ConnectTimeout = 15000,
192 | ReadWriteTimeOut = 15000,
193 | };
194 |
195 | using (var proxyClientHandler = new ProxyClientHandler(settings))
196 | {
197 | using (var httpClient = new HttpClient(proxyClientHandler))
198 | {
199 | try
200 | {
201 | await httpClient.GetAsync("https://nonexists-subdomain.google.com");
202 | Assert.Fail();
203 | }
204 | catch (ProxyException)
205 | {
206 | // this is expected
207 | }
208 | }
209 | }
210 | }
211 | }
212 |
213 | [TestMethod]
214 | public async Task CheckRelayingToNonExistentHostnameUsingCustomResolver()
215 | {
216 | using (var relay = CreateRelayServer())
217 | {
218 | relay.ResolveHostnamesRemotely = false;
219 | relay.DnsResolver = new CustomDnsResolver();
220 | relay.Start();
221 |
222 | var settings = new ProxySettings
223 | {
224 | Host = relay.LocalEndPoint.Address.ToString(),
225 | Port = relay.LocalEndPoint.Port,
226 | ConnectTimeout = 15000,
227 | ReadWriteTimeOut = 15000,
228 | };
229 |
230 | using (var proxyClientHandler = new ProxyClientHandler(settings))
231 | {
232 | using (var httpClient = new HttpClient(proxyClientHandler))
233 | {
234 | try
235 | {
236 | await httpClient.GetAsync("https://nonexists-subdomain.google.com");
237 | Assert.Fail();
238 | }
239 | catch (ProxyException)
240 | {
241 | // this is expected
242 | }
243 | }
244 | }
245 | }
246 | }
247 |
248 | [TestMethod]
249 | public void CheckRelayingToHostnameUsingCustomResolver()
250 | {
251 | using (var relay = CreateRelayServer())
252 | {
253 | relay.ResolveHostnamesRemotely = false;
254 | relay.DnsResolver = new CustomDnsResolver();
255 | relay.Start();
256 |
257 | var tasks = new List();
258 | for (var i = 0; i < 8; i++)
259 | {
260 | tasks.Add(TestHelpers.DoTestRequest(relay.LocalEndPoint, "https://www.thinkwithgoogle.com/intl/de-de/"));
261 | }
262 |
263 | Task.WaitAll(tasks.ToArray());
264 | }
265 | }
266 |
267 | [TestMethod]
268 | public async Task CheckRelayingToNonExistentHostnameResolveRemotely()
269 | {
270 | using (var relay = CreateRelayServer())
271 | {
272 | relay.ResolveHostnamesRemotely = true;
273 | relay.Start();
274 |
275 | var settings = new ProxySettings
276 | {
277 | Host = relay.LocalEndPoint.Address.ToString(),
278 | Port = relay.LocalEndPoint.Port,
279 | ConnectTimeout = 15000,
280 | ReadWriteTimeOut = 15000,
281 | };
282 |
283 | using (var proxyClientHandler = new ProxyClientHandler(settings))
284 | {
285 | using (var httpClient = new HttpClient(proxyClientHandler))
286 | {
287 | try
288 | {
289 | await httpClient.GetAsync("https://nonexists-subdomain.google.com");
290 | Assert.Fail();
291 | }
292 | catch (ProxyException e)
293 | {
294 | Assert.AreEqual("Request rejected or failed", e.Message);
295 | }
296 | }
297 | }
298 | }
299 | }
300 |
301 | private static ISocksRelayServer CreateRelayServer()
302 | {
303 | ISocksRelayServer relay = new SocksRelayServer.SocksRelayServer(new IPEndPoint(IPAddress.Loopback, TestHelpers.GetFreeTcpPort()), new IPEndPoint(_remoteProxyAddress, _remoteProxyPort));
304 |
305 | relay.OnLogMessage += (sender, s) => Console.WriteLine($"OnLogMessage: {s}");
306 | relay.OnLocalConnect += (sender, endpoint) => Console.WriteLine($"Accepted connection from {endpoint}");
307 | relay.OnRemoteConnect += (sender, endpoint) => Console.WriteLine($"Opened connection to {endpoint}");
308 |
309 | Console.WriteLine($"Created new instance of RelayServer on {relay.LocalEndPoint}");
310 |
311 | return relay;
312 | }
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/tests/SocksRelayServerTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | ..\CodeAnalysisRules.ruleset
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | all
17 | runtime; build; native; contentfiles; analyzers
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/TestHelpers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Http;
4 | using System.Net.Sockets;
5 | using System.Threading.Tasks;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 | using SocksSharp;
8 | using SocksSharp.Proxy;
9 |
10 | namespace SocksRelayServerTests
11 | {
12 | public static class TestHelpers
13 | {
14 | public static int GetFreeTcpPort()
15 | {
16 | var l = new TcpListener(IPAddress.Loopback, 0);
17 | l.Start();
18 | var port = ((IPEndPoint)l.LocalEndpoint).Port;
19 | l.Stop();
20 |
21 | return port;
22 | }
23 |
24 | public static async Task DoTestRequest(IPEndPoint relayEndPoint, string url)
25 | where T : IProxy
26 | {
27 | var settings = new ProxySettings
28 | {
29 | Host = relayEndPoint.Address.ToString(),
30 | Port = relayEndPoint.Port,
31 | ConnectTimeout = 15000,
32 | ReadWriteTimeOut = 15000,
33 | };
34 |
35 | string responseContentWithProxy;
36 | using (var proxyClientHandler = new ProxyClientHandler(settings))
37 | {
38 | using (var httpClient = new HttpClient(proxyClientHandler))
39 | {
40 | var response = await httpClient.SendAsync(GenerateRequestMessageForTestRequest(url));
41 | responseContentWithProxy = await response.Content.ReadAsStringAsync();
42 | }
43 | }
44 |
45 | string responseContentWithoutProxy;
46 | using (var handler = new HttpClientHandler())
47 | {
48 | handler.AllowAutoRedirect = false;
49 |
50 | using (var httpClient = new HttpClient(handler))
51 | {
52 | var response = await httpClient.SendAsync(GenerateRequestMessageForTestRequest(url));
53 | responseContentWithoutProxy = await response.Content.ReadAsStringAsync();
54 | }
55 | }
56 |
57 | Assert.AreEqual(responseContentWithoutProxy, responseContentWithProxy);
58 | }
59 |
60 | private static HttpRequestMessage GenerateRequestMessageForTestRequest(string url)
61 | {
62 | var requestMessage = new HttpRequestMessage
63 | {
64 | Version = HttpVersion.Version10,
65 | Method = HttpMethod.Get,
66 | RequestUri = new Uri(url),
67 | };
68 |
69 | requestMessage.Headers.TryAddWithoutValidation(
70 | "User-Agent",
71 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36");
72 |
73 | return requestMessage;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------