Skip to content

Commit ac1316e

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

File tree

6 files changed

+94
-18
lines changed

6 files changed

+94
-18
lines changed

Diff for: package-lock.json

+11-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+7-3
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": ">=6.0.0-alpha.0 <7",
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": {
@@ -102,6 +105,7 @@
102105
"sinon": "^15.0.4",
103106
"sinon-chai": "^3.7.0",
104107
"snappy": "^7.2.2",
108+
"socks": "^2.7.1",
105109
"source-map-support": "^0.5.21",
106110
"ts-node": "^10.9.1",
107111
"tsd": "^0.28.1",

Diff for: src/client-side-encryption/stateMachine.js

+16-2
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 },

Diff for: src/cmap/connect.ts

+21-3
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',

Diff for: src/deps.ts

+35
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-var-requires */
22
import type { Document } from './bson';
3+
import { type Stream } from './cmap/connect';
34
import type { ProxyOptions } from './cmap/connection';
45
import { MongoMissingDependencyError } from './error';
56
import type { MongoClient } from './mongo_client';
@@ -157,6 +158,40 @@ export function getSnappy(): SnappyLib | { kModuleError: MongoMissingDependencyE
157158
}
158159
}
159160

161+
export type SocksLib = {
162+
SocksClient: {
163+
createConnection(options: {
164+
command: 'connect';
165+
destination: { host: string; port: number };
166+
proxy: {
167+
/** host and port are ignored because we pass existing_socket */
168+
host: 'iLoveJavaScript';
169+
port: 0;
170+
type: 5;
171+
userId?: string;
172+
password?: string;
173+
};
174+
timeout?: number;
175+
/** We always create our own socket, and pass it to this API for proxy negotiation */
176+
existing_socket: Stream;
177+
}): Promise<{ socket: Stream }>;
178+
};
179+
};
180+
181+
export function getSocks(): SocksLib | { kModuleError: MongoMissingDependencyError } {
182+
try {
183+
// Ensure you always wrap an optional require in the try block NODE-3199
184+
const value = require('socks');
185+
return value;
186+
} catch (cause) {
187+
const kModuleError = new MongoMissingDependencyError(
188+
'Optional module `socks` not found. Please install it to connections over a SOCKS5 proxy',
189+
{ cause }
190+
);
191+
return { kModuleError };
192+
}
193+
}
194+
160195
export let saslprep: typeof import('saslprep') | { kModuleError: MongoMissingDependencyError } =
161196
makeErrorModule(
162197
new MongoMissingDependencyError(

Diff for: test/action/dependency.test.ts

+4-6
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)