Skip to content

Commit f8bd6a3

Browse files
committed
Improvements plus initial tests
1 parent feaa948 commit f8bd6a3

File tree

6 files changed

+129
-19
lines changed

6 files changed

+129
-19
lines changed

src/MongoDB.Driver/ClusterRegistry.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ private TcpStreamSettings ConfigureTcp(TcpStreamSettings settings, ClusterKey cl
172172
receiveBufferSize: clusterKey.ReceiveBufferSize,
173173
sendBufferSize: clusterKey.SendBufferSize,
174174
writeTimeout: clusterKey.SocketTimeout);
175+
//TODO Maybe need to add proxy settings to clusterKey as well
175176
}
176177

177178
internal IClusterInternal GetOrCreateCluster(ClusterKey clusterKey)

src/MongoDB.Driver/Core/Configuration/ClusterBuilderExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ public static ClusterBuilder ConfigureWithConnectionString(
118118
builder = builder.ConfigureTcp(s => s.With(addressFamily: AddressFamily.InterNetworkV6));
119119
}
120120

121+
if (connectionString.ProxyHost != null)
122+
{
123+
builder = builder.ConfigureTcp(s => s.With(
124+
proxyHost: connectionString.ProxyHost,
125+
proxyPort: connectionString.ProxyPort,
126+
proxyUsername: connectionString.ProxyUsername,
127+
proxyPassword: connectionString.ProxyPassword));
128+
}
129+
121130
if (connectionString.SocketTimeout != null)
122131
{
123132
builder = builder.ConfigureTcp(s => s.With(

src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ public TimeSpan? WriteTimeout
205205
/// <param name="sendBufferSize">Size of the send buffer.</param>
206206
/// <param name="socketConfigurator">The socket configurator.</param>
207207
/// <param name="writeTimeout">The write timeout.</param>
208+
/// <param name="proxyHost"> //TODO Add docs</param>
209+
/// <param name="proxyPort"></param>
210+
/// <param name="proxyUsername"></param>
211+
/// <param name="proxyPassword"></param>
208212
/// <returns>A new TcpStreamSettings instance.</returns>
209213
public TcpStreamSettings With(
210214
Optional<AddressFamily> addressFamily = default(Optional<AddressFamily>),
@@ -213,8 +217,11 @@ public TcpStreamSettings With(
213217
Optional<int> receiveBufferSize = default(Optional<int>),
214218
Optional<int> sendBufferSize = default(Optional<int>),
215219
Optional<Action<Socket>> socketConfigurator = default(Optional<Action<Socket>>),
216-
Optional<TimeSpan?> writeTimeout = default(Optional<TimeSpan?>))
217-
//TODO Need to add proxy settings
220+
Optional<TimeSpan?> writeTimeout = default(Optional<TimeSpan?>),
221+
Optional<string> proxyHost = default(Optional<string>),
222+
Optional<int?> proxyPort = default(Optional<int?>),
223+
Optional<string> proxyUsername = default(Optional<string>),
224+
Optional<string> proxyPassword = default(Optional<string>))
218225
{
219226
return new TcpStreamSettings(
220227
addressFamily: addressFamily.WithDefault(_addressFamily),
@@ -223,7 +230,11 @@ public TcpStreamSettings With(
223230
receiveBufferSize: receiveBufferSize.WithDefault(_receiveBufferSize),
224231
sendBufferSize: sendBufferSize.WithDefault(_sendBufferSize),
225232
socketConfigurator: socketConfigurator.WithDefault(_socketConfigurator),
226-
writeTimeout: writeTimeout.WithDefault(_writeTimeout));
233+
writeTimeout: writeTimeout.WithDefault(_writeTimeout),
234+
proxyHost: proxyHost.WithDefault(_proxyHost),
235+
proxyPort: proxyPort.WithDefault(_proxyPort),
236+
proxyUsername: proxyUsername.WithDefault(_proxyUsername),
237+
proxyPassword: proxyPassword.WithDefault(_proxyPassword));
227238
}
228239
}
229240
}

src/MongoDB.Driver/Core/Connections/Socks5Helper.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using System.Net.Sockets;
2222
using System.Text;
2323
using System.Threading;
24+
using MongoDB.Driver.Core.Misc;
2425
using MongoDB.Driver.GridFS;
2526

2627
namespace MongoDB.Driver.Core.Connections
@@ -43,8 +44,11 @@ internal static class Socks5Helper
4344

4445
private const int BufferSize = 512;
4546

46-
public static void PerformSocks5Handshake(Stream stream, string targetHost, int targetPort, string proxyUsername, string proxyPassword, CancellationToken cancellationToken)
47+
//TODO Make an async version of this method
48+
public static void PerformSocks5Handshake(Stream stream, EndPoint endPoint, string proxyUsername, string proxyPassword, CancellationToken cancellationToken)
4749
{
50+
var (targetHost, targetPort) = endPoint.GetHostAndPort();
51+
4852
var buffer = ArrayPool<byte>.Shared.Rent(BufferSize);
4953
try
5054
{
@@ -100,9 +104,18 @@ public static void PerformSocks5Handshake(Stream stream, string targetHost, int
100104
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
101105
// +----+------+----------+------+----------+
102106
buffer[0] = SubnegotiationVersion;
107+
//TODO Maybe it can be extracted to a method?
108+
#if NET472
109+
var usernameLength = EncodeString(proxyUsername, buffer, 2, nameof(proxyUsername));
110+
#else
103111
var usernameLength = EncodeString(proxyUsername, buffer.AsSpan(2), nameof(proxyUsername));
112+
#endif
104113
buffer[1] = usernameLength;
114+
#if NET472
115+
var passwordLength = EncodeString(proxyPassword, buffer, 3 + usernameLength, nameof(proxyPassword));
116+
#else
105117
var passwordLength = EncodeString(proxyPassword, buffer.AsSpan(3 + usernameLength), nameof(proxyPassword));
118+
#endif
106119
buffer[2 + usernameLength] = passwordLength;
107120

108121
var authLength = 3 + usernameLength + passwordLength;
@@ -137,18 +150,23 @@ public static void PerformSocks5Handshake(Stream stream, string targetHost, int
137150
buffer[2] = 0x00;
138151
var addressLength = 0;
139152

153+
//TODO Can we avoid doing this...?
140154
if (IPAddress.TryParse(targetHost, out var ip))
141155
{
142156
switch (ip.AddressFamily)
143157
{
144158
case AddressFamily.InterNetwork:
145159
buffer[3] = AddressTypeIPv4;
160+
#if !NET472
146161
ip.TryWriteBytes(buffer.AsSpan(4), out _);
162+
#endif
147163
addressLength = 4;
148164
break;
149165
case AddressFamily.InterNetworkV6:
150166
buffer[3] = AddressTypeIPv6;
167+
#if !NET472
151168
ip.TryWriteBytes(buffer.AsSpan(4), out _);
169+
#endif
152170
addressLength = 16;
153171
break;
154172
default:
@@ -158,7 +176,11 @@ public static void PerformSocks5Handshake(Stream stream, string targetHost, int
158176
else
159177
{
160178
buffer[3] = AddressTypeDomain;
179+
#if NET472
180+
var hostLength = EncodeString(targetHost, buffer, 5, nameof(targetHost));
181+
#else
161182
var hostLength = EncodeString(targetHost, buffer.AsSpan(5), nameof(targetHost));
183+
#endif
162184
buffer[4] = hostLength;
163185
addressLength = hostLength + 1;
164186
}
@@ -206,16 +228,31 @@ private static void VerifyProtocolVersion(byte version)
206228
}
207229
}
208230

209-
private static byte EncodeString(ReadOnlySpan<char> chars, Span<byte> buffer, string parameterName)
231+
#if NET472
232+
private static byte EncodeString(string input, byte[] buffer, int offset, string parameterName)
210233
{
211234
try
212235
{
236+
var written = Encoding.UTF8.GetBytes(input, 0, input.Length, buffer, offset);
237+
return checked((byte)written);
238+
}
239+
catch
240+
{
241+
throw new IOException($"The {parameterName} could not be encoded as UTF-8.");
242+
}
243+
}
244+
#else
245+
private static byte EncodeString(ReadOnlySpan<char> chars, Span<byte> buffer, string parameterName)
246+
{
247+
try
248+
{ //TODO Maybe we should remove checked?
213249
return checked((byte)Encoding.UTF8.GetBytes(chars, buffer));
214250
}
215251
catch
216252
{
217253
throw new IOException($"The {parameterName} could not be encoded as UTF-8.");
218254
}
219255
}
256+
#endif
220257
}
221258
}

src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ public Stream CreateStream(EndPoint endPoint, CancellationToken cancellationToke
6565
var socket = CreateSocket(resolved[i]);
6666
Connect(socket, resolved[i], cancellationToken);
6767
var stream = CreateNetworkStream(socket);
68+
69+
//TODO Need to do the same for the async version and for net472
70+
if (_settings.UseProxy)
71+
{
72+
Socks5Helper.PerformSocks5Handshake(stream, endPoint, _settings.ProxyUsername, _settings.ProxyPassword, cancellationToken);
73+
}
6874
}
6975
catch
7076
{
@@ -262,20 +268,18 @@ private Socket CreateSocket(EndPoint endPoint)
262268

263269
private EndPoint[] ResolveEndPoints(EndPoint initial)
264270
{
265-
var dnsInitial = initial as DnsEndPoint;
266-
if (dnsInitial == null)
271+
if (initial is not DnsEndPoint dnsInitial)
267272
{
268-
return new[] { initial };
273+
return [initial];
269274
}
270275

271-
IPAddress address;
272-
if (IPAddress.TryParse(dnsInitial.Host, out address))
276+
if (IPAddress.TryParse(dnsInitial.Host, out var address))
273277
{
274-
return new[] { new IPEndPoint(address, dnsInitial.Port) };
278+
return [new IPEndPoint(address, dnsInitial.Port)];
275279
}
276280

277281
var preferred = initial.AddressFamily;
278-
if (preferred == AddressFamily.Unspecified || preferred == AddressFamily.Unknown)
282+
if (preferred is AddressFamily.Unspecified or AddressFamily.Unknown)
279283
{
280284
preferred = _settings.AddressFamily;
281285
}
@@ -289,20 +293,18 @@ private EndPoint[] ResolveEndPoints(EndPoint initial)
289293

290294
private async Task<EndPoint[]> ResolveEndPointsAsync(EndPoint initial)
291295
{
292-
var dnsInitial = initial as DnsEndPoint;
293-
if (dnsInitial == null)
296+
if (initial is not DnsEndPoint dnsInitial)
294297
{
295-
return new[] { initial };
298+
return [initial];
296299
}
297300

298-
IPAddress address;
299-
if (IPAddress.TryParse(dnsInitial.Host, out address))
301+
if (IPAddress.TryParse(dnsInitial.Host, out var address))
300302
{
301-
return new[] { new IPEndPoint(address, dnsInitial.Port) };
303+
return [new IPEndPoint(address, dnsInitial.Port)];
302304
}
303305

304306
var preferred = initial.AddressFamily;
305-
if (preferred == AddressFamily.Unspecified || preferred == AddressFamily.Unknown)
307+
if (preferred is AddressFamily.Unspecified or AddressFamily.Unknown)
306308
{
307309
preferred = _settings.AddressFamily;
308310
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using MongoDB.Bson;
18+
using MongoDB.Driver.Core.TestHelpers.Logging;
19+
using Xunit;
20+
using Xunit.Abstractions;
21+
22+
namespace MongoDB.Driver.Tests.Specifications.socks5_support
23+
{
24+
[Trait("Category", "Integration")]
25+
public class Socks5SupportProseTests(ITestOutputHelper testOutputHelper) : LoggableTestClass(testOutputHelper)
26+
{
27+
28+
[Theory]
29+
[InlineData("mongodb://<mappedhost>/?proxyHost=localhost&proxyPort=1080&directConnection=true", false)]
30+
[InlineData("mongodb://<mappedhost>/?proxyHost=localhost&proxyPort=1081&directConnection=true", true)]
31+
// [InlineData("mongodb://<replicaset>/?proxyHost=localhost&proxyPort=1080", false)]
32+
// [InlineData("mongodb://<replicaset>/?proxyHost=localhost&proxyPort=1081", true)]
33+
// [InlineData("mongodb://<mappedhost>/?proxyHost=localhost&proxyPort=1080&proxyUsername=nonexistentuser&proxyPassword=badauth&directConnection=true", false)]
34+
// [InlineData("mongodb://<mappedhost>/?proxyHost=localhost&proxyPort=1081&proxyUsername=nonexistentuser&proxyPassword=badauth&directConnection=true", true)]
35+
// [InlineData("mongodb://<replicaset>/?proxyHost=localhost&proxyPort=1081&proxyUsername=nonexistentuser&proxyPassword=badauth", true)]
36+
// [InlineData("mongodb://<mappedhost>/?proxyHost=localhost&proxyPort=1080&proxyUsername=username&proxyPassword=p4ssw0rd&directConnection=true", true)]
37+
// [InlineData("mongodb://<mappedhost>/?proxyHost=localhost&proxyPort=1081&directConnection=true", true)]
38+
// [InlineData("mongodb://<replicaset>/?proxyHost=localhost&proxyPort=1080&proxyUsername=username&proxyPassword=p4ssw0rd", true)]
39+
// [InlineData("mongodb://<replicaset>/?proxyHost=localhost&proxyPort=1081", true)]
40+
public void TestConnectionStrings(string connectionString, bool expectedResult)
41+
{
42+
var client = new MongoClient(connectionString);
43+
var database = client.GetDatabase("admin");
44+
45+
var command = new BsonDocument("hello", 1);
46+
var result = database.RunCommand<BsonDocument>(command);
47+
}
48+
49+
}
50+
}

0 commit comments

Comments
 (0)