Skip to content

Commit 1c6fdcb

Browse files
committed
feat(NODE-5409)!: allow socks to be installed optionally
1 parent c7c5dae commit 1c6fdcb

File tree

6 files changed

+94
-18
lines changed

6 files changed

+94
-18
lines changed

package-lock.json

Lines changed: 11 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
},
2727
"dependencies": {
2828
"bson": "^5.4.0",
29-
"mongodb-connection-string-url": "^2.6.0",
30-
"socks": "^2.7.1"
29+
"mongodb-connection-string-url": "^2.6.0"
3130
},
3231
"optionalDependencies": {
3332
"saslprep": "^1.0.3"
@@ -38,7 +37,8 @@
3837
"gcp-metadata": "^5.2.0",
3938
"kerberos": "^2.0.1",
4039
"mongodb-client-encryption": ">=2.3.0 <3",
41-
"snappy": "^7.2.2"
40+
"snappy": "^7.2.2",
41+
"socks": "^2.7.1"
4242
},
4343
"peerDependenciesMeta": {
4444
"@aws-sdk/credential-providers": {
@@ -58,6 +58,9 @@
5858
},
5959
"gcp-metadata": {
6060
"optional": true
61+
},
62+
"socks": {
63+
"optional": true
6164
}
6265
},
6366
"devDependencies": {
@@ -101,6 +104,7 @@
101104
"sinon": "^15.0.4",
102105
"sinon-chai": "^3.7.0",
103106
"snappy": "^7.2.2",
107+
"socks": "^2.7.1",
104108
"source-map-support": "^0.5.21",
105109
"ts-node": "^10.9.1",
106110
"tsd": "^0.28.1",

src/client-side-encryption/stateMachine.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,25 @@ import * as tls from 'tls';
44
import * as net from 'net';
55
import * as fs from 'fs';
66
import { once } from 'events';
7-
import { SocksClient } from 'socks';
87
import { MongoNetworkTimeoutError } from '../error';
98
import { debug, databaseNamespace, collectionNamespace } from './common';
109
import { MongoCryptError } from './errors';
1110
import { BufferPool } from './buffer_pool';
1211
import { serialize, deserialize } from '../bson';
12+
import { getSocks } from '../deps';
13+
14+
/** @type {import('../deps').SocksLib | null} */
15+
let socks = null;
16+
function loadSocks() {
17+
if (socks == null) {
18+
const socksImport = getSocks();
19+
if ('kModuleError' in socksImport) {
20+
throw socksImport.kModuleError;
21+
}
22+
socks = socksImport;
23+
}
24+
return socks;
25+
}
1326

1427
// libmongocrypt states
1528
const MONGOCRYPT_CTX_ERROR = 0;
@@ -289,8 +302,9 @@ class StateMachine {
289302
rawSocket.on('error', onerror);
290303
try {
291304
await once(rawSocket, 'connect');
305+
socks ??= loadSocks();
292306
options.socket = (
293-
await SocksClient.createConnection({
307+
await socks.SocksClient.createConnection({
294308
existing_socket: rawSocket,
295309
command: 'connect',
296310
destination: { host: options.host, port: options.port },

src/cmap/connect.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { Socket, SocketConnectOpts } from 'net';
22
import * as net from 'net';
3-
import { SocksClient } from 'socks';
43
import type { ConnectionOptions as TLSConnectionOpts, TLSSocket } from 'tls';
54
import * as tls from 'tls';
65

76
import type { Document } from '../bson';
87
import { LEGACY_HELLO_COMMAND } from '../constants';
8+
import { getSocks, type SocksLib } from '../deps';
99
import {
1010
MongoCompatibilityError,
1111
MongoError,
@@ -419,6 +419,18 @@ function makeConnection(options: MakeConnectionOptions, _callback: Callback<Stre
419419
}
420420
}
421421

422+
let socks: SocksLib | null = null;
423+
function loadSocks() {
424+
if (socks == null) {
425+
const socksImport = getSocks();
426+
if ('kModuleError' in socksImport) {
427+
throw socksImport.kModuleError;
428+
}
429+
socks = socksImport;
430+
}
431+
return socks;
432+
}
433+
422434
function makeSocks5Connection(options: MakeConnectionOptions, callback: Callback<Stream>) {
423435
const hostAddress = HostAddress.fromHostPort(
424436
options.proxyHost ?? '', // proxyHost is guaranteed to set here
@@ -434,7 +446,7 @@ function makeSocks5Connection(options: MakeConnectionOptions, callback: Callback
434446
proxyHost: undefined
435447
},
436448
(err, rawSocket) => {
437-
if (err) {
449+
if (err || !rawSocket) {
438450
return callback(err);
439451
}
440452

@@ -445,8 +457,14 @@ function makeSocks5Connection(options: MakeConnectionOptions, callback: Callback
445457
);
446458
}
447459

460+
try {
461+
socks ??= loadSocks();
462+
} catch (error) {
463+
return callback(error);
464+
}
465+
448466
// Then, establish the Socks5 proxy connection:
449-
SocksClient.createConnection({
467+
socks.SocksClient.createConnection({
450468
existing_socket: rawSocket,
451469
timeout: options.connectTimeoutMS,
452470
command: 'connect',

src/deps.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-var-requires */
22
import type { Document } from './bson';
33
import type { AWSCredentials } from './cmap/auth/mongodb_aws';
4+
import { type Stream } from './cmap/connect';
45
import type { ProxyOptions } from './cmap/connection';
56
import { MongoMissingDependencyError } from './error';
67
import type { MongoClient } from './mongo_client';
@@ -126,6 +127,40 @@ export function getSnappy(): SnappyLib | { kModuleError: MongoMissingDependencyE
126127
}
127128
}
128129

130+
export type SocksLib = {
131+
SocksClient: {
132+
createConnection(options: {
133+
command: 'connect';
134+
destination: { host: string; port: number };
135+
proxy: {
136+
/** host and port are ignored because we pass existing_socket */
137+
host: 'iLoveJavaScript';
138+
port: 0;
139+
type: 5;
140+
userId?: string;
141+
password?: string;
142+
};
143+
timeout?: number;
144+
/** We always create our own socket, and pass it to this API for proxy negotiation */
145+
existing_socket: Stream;
146+
}): Promise<{ socket: Stream }>;
147+
};
148+
};
149+
150+
export function getSocks(): SocksLib | { kModuleError: MongoMissingDependencyError } {
151+
try {
152+
// Ensure you always wrap an optional require in the try block NODE-3199
153+
const value = require('socks');
154+
return value;
155+
} catch (cause) {
156+
const kModuleError = new MongoMissingDependencyError(
157+
'Optional module `socks` not found. Please install it to connections over a SOCKS5 proxy',
158+
{ cause }
159+
);
160+
return { kModuleError };
161+
}
162+
}
163+
129164
export let saslprep: typeof import('saslprep') | { kModuleError: MongoMissingDependencyError } =
130165
makeErrorModule(
131166
new MongoMissingDependencyError(

test/action/dependency.test.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import { expect } from 'chai';
77
import { dependencies, peerDependencies, peerDependenciesMeta } from '../../package.json';
88
import { itInNodeProcess } from '../tools/utils';
99

10-
const EXPECTED_DEPENDENCIES = ['bson', 'mongodb-connection-string-url', 'socks'];
10+
const EXPECTED_DEPENDENCIES = ['bson', 'mongodb-connection-string-url'];
1111
const EXPECTED_PEER_DEPENDENCIES = [
1212
'@aws-sdk/credential-providers',
1313
'@mongodb-js/zstd',
1414
'kerberos',
1515
'snappy',
1616
'mongodb-client-encryption',
17-
'gcp-metadata'
17+
'gcp-metadata',
18+
'socks'
1819
];
1920

2021
describe('package.json', function () {
@@ -119,10 +120,7 @@ describe('package.json', function () {
119120
'mongodb-connection-string-url',
120121
'whatwg-url',
121122
'webidl-conversions',
122-
'tr46',
123-
'socks',
124-
'ip',
125-
'smart-buffer'
123+
'tr46'
126124
];
127125

128126
describe('mongodb imports', () => {

0 commit comments

Comments
 (0)