Skip to content

Commit 54efb7d

Browse files
authored
feat(NODE-5754): allow auto select family options (#4185)
1 parent b26c328 commit 54efb7d

14 files changed

+249
-22
lines changed

Diff for: .evergreen/run-typescript.sh

100644100755
File mode changed.

Diff for: src/client-side-encryption/auto_encrypter.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
type MongoCryptConstructor,
44
type MongoCryptOptions
55
} from 'mongodb-client-encryption';
6+
import * as net from 'net';
67

78
import { deserialize, type Document, serialize } from '../bson';
89
import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
@@ -11,6 +12,7 @@ import { getMongoDBClientEncryption } from '../deps';
1112
import { MongoRuntimeError } from '../error';
1213
import { MongoClient, type MongoClientOptions } from '../mongo_client';
1314
import { MongoDBCollectionNamespace } from '../utils';
15+
import { autoSelectSocketOptions } from './client_encryption';
1416
import * as cryptoCallbacks from './crypto_callbacks';
1517
import { MongoCryptInvalidArgumentError } from './errors';
1618
import { MongocryptdManager } from './mongocryptd_manager';
@@ -297,10 +299,20 @@ export class AutoEncrypter {
297299
serverSelectionTimeoutMS: 10000
298300
};
299301

300-
if (options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') {
302+
if (
303+
(options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') &&
304+
!net.getDefaultAutoSelectFamily
305+
) {
306+
// Only set family if autoSelectFamily options are not supported.
301307
clientOptions.family = 4;
302308
}
303309

310+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
311+
// @ts-ignore: TS complains as this always returns true on versions where it is present.
312+
if (net.getDefaultAutoSelectFamily) {
313+
Object.assign(clientOptions, autoSelectSocketOptions(this._client.options));
314+
}
315+
304316
this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, clientOptions);
305317
}
306318
}
@@ -379,7 +391,8 @@ export class AutoEncrypter {
379391
promoteValues: false,
380392
promoteLongs: false,
381393
proxyOptions: this._proxyOptions,
382-
tlsOptions: this._tlsOptions
394+
tlsOptions: this._tlsOptions,
395+
socketOptions: autoSelectSocketOptions(this._client.options)
383396
});
384397

385398
return deserialize(await stateMachine.execute(this, context), {
@@ -399,7 +412,8 @@ export class AutoEncrypter {
399412
const stateMachine = new StateMachine({
400413
...options,
401414
proxyOptions: this._proxyOptions,
402-
tlsOptions: this._tlsOptions
415+
tlsOptions: this._tlsOptions,
416+
socketOptions: autoSelectSocketOptions(this._client.options)
403417
});
404418

405419
return await stateMachine.execute(this, context);

Diff for: src/client-side-encryption/client_encryption.ts

+32-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { type Collection } from '../collection';
1212
import { type FindCursor } from '../cursor/find_cursor';
1313
import { type Db } from '../db';
1414
import { getMongoDBClientEncryption } from '../deps';
15-
import { type MongoClient } from '../mongo_client';
15+
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
1616
import { type Filter, type WithId } from '../mongo_types';
1717
import { type CreateCollectionOptions } from '../operations/create_collection';
1818
import { type DeleteResult } from '../operations/delete';
@@ -28,7 +28,11 @@ import {
2828
type KMSProviders,
2929
refreshKMSCredentials
3030
} from './providers/index';
31-
import { type CSFLEKMSTlsOptions, StateMachine } from './state_machine';
31+
import {
32+
type ClientEncryptionSocketOptions,
33+
type CSFLEKMSTlsOptions,
34+
StateMachine
35+
} from './state_machine';
3236

3337
/**
3438
* @public
@@ -199,7 +203,8 @@ export class ClientEncryption {
199203

200204
const stateMachine = new StateMachine({
201205
proxyOptions: this._proxyOptions,
202-
tlsOptions: this._tlsOptions
206+
tlsOptions: this._tlsOptions,
207+
socketOptions: autoSelectSocketOptions(this._client.options)
203208
});
204209

205210
const dataKey = deserialize(await stateMachine.execute(this, context)) as DataKey;
@@ -256,7 +261,8 @@ export class ClientEncryption {
256261
const context = this._mongoCrypt.makeRewrapManyDataKeyContext(filterBson, keyEncryptionKeyBson);
257262
const stateMachine = new StateMachine({
258263
proxyOptions: this._proxyOptions,
259-
tlsOptions: this._tlsOptions
264+
tlsOptions: this._tlsOptions,
265+
socketOptions: autoSelectSocketOptions(this._client.options)
260266
});
261267

262268
const { v: dataKeys } = deserialize(await stateMachine.execute(this, context));
@@ -637,7 +643,8 @@ export class ClientEncryption {
637643

638644
const stateMachine = new StateMachine({
639645
proxyOptions: this._proxyOptions,
640-
tlsOptions: this._tlsOptions
646+
tlsOptions: this._tlsOptions,
647+
socketOptions: autoSelectSocketOptions(this._client.options)
641648
});
642649

643650
const { v } = deserialize(await stateMachine.execute(this, context));
@@ -715,7 +722,8 @@ export class ClientEncryption {
715722
const valueBuffer = serialize({ v: value });
716723
const stateMachine = new StateMachine({
717724
proxyOptions: this._proxyOptions,
718-
tlsOptions: this._tlsOptions
725+
tlsOptions: this._tlsOptions,
726+
socketOptions: autoSelectSocketOptions(this._client.options)
719727
});
720728
const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
721729

@@ -957,3 +965,21 @@ export interface RangeOptions {
957965
sparsity: Long;
958966
precision?: number;
959967
}
968+
969+
/**
970+
* Get the socket options from the client.
971+
* @param baseOptions - The mongo client options.
972+
* @returns ClientEncryptionSocketOptions
973+
*/
974+
export function autoSelectSocketOptions(
975+
baseOptions: MongoClientOptions
976+
): ClientEncryptionSocketOptions {
977+
const options: ClientEncryptionSocketOptions = { autoSelectFamily: true };
978+
if ('autoSelectFamily' in baseOptions) {
979+
options.autoSelectFamily = baseOptions.autoSelectFamily;
980+
}
981+
if ('autoSelectFamilyAttemptTimeout' in baseOptions) {
982+
options.autoSelectFamilyAttemptTimeout = baseOptions.autoSelectFamilyAttemptTimeout;
983+
}
984+
return options;
985+
}

Diff for: src/client-side-encryption/state_machine.ts

+28-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { type ProxyOptions } from '../cmap/connection';
1414
import { getSocks, type SocksLib } from '../deps';
1515
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
1616
import { BufferPool, MongoDBCollectionNamespace, promiseWithResolvers } from '../utils';
17-
import { type DataKey } from './client_encryption';
17+
import { autoSelectSocketOptions, type DataKey } from './client_encryption';
1818
import { MongoCryptError } from './errors';
1919
import { type MongocryptdManager } from './mongocryptd_manager';
2020
import { type KMSProviders } from './providers';
@@ -114,6 +114,16 @@ export type CSFLEKMSTlsOptions = {
114114
[key: string]: ClientEncryptionTlsOptions | undefined;
115115
};
116116

117+
/**
118+
* @public
119+
*
120+
* Socket options to use for KMS requests.
121+
*/
122+
export type ClientEncryptionSocketOptions = Pick<
123+
MongoClientOptions,
124+
'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
125+
>;
126+
117127
/**
118128
* This is kind of a hack. For `rewrapManyDataKey`, we have tests that
119129
* guarantee that when there are no matching keys, `rewrapManyDataKey` returns
@@ -153,6 +163,9 @@ export type StateMachineOptions = {
153163

154164
/** TLS options for KMS requests, if set. */
155165
tlsOptions: CSFLEKMSTlsOptions;
166+
167+
/** Socket specific options we support. */
168+
socketOptions: ClientEncryptionSocketOptions;
156169
} & Pick<BSONSerializeOptions, 'promoteLongs' | 'promoteValues'>;
157170

158171
/**
@@ -289,10 +302,17 @@ export class StateMachine {
289302
async kmsRequest(request: MongoCryptKMSRequest): Promise<void> {
290303
const parsedUrl = request.endpoint.split(':');
291304
const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
292-
const options: tls.ConnectionOptions & { host: string; port: number } = {
305+
const socketOptions = autoSelectSocketOptions(this.options.socketOptions || {});
306+
const options: tls.ConnectionOptions & {
307+
host: string;
308+
port: number;
309+
autoSelectFamily?: boolean;
310+
autoSelectFamilyAttemptTimeout?: number;
311+
} = {
293312
host: parsedUrl[0],
294313
servername: parsedUrl[0],
295-
port
314+
port,
315+
...socketOptions
296316
};
297317
const message = request.message;
298318
const buffer = new BufferPool();
@@ -351,10 +371,12 @@ export class StateMachine {
351371

352372
try {
353373
if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
354-
netSocket.connect({
374+
const netSocketOptions = {
355375
host: this.options.proxyOptions.proxyHost,
356-
port: this.options.proxyOptions.proxyPort || 1080
357-
});
376+
port: this.options.proxyOptions.proxyPort || 1080,
377+
...socketOptions
378+
};
379+
netSocket.connect(netSocketOptions);
358380
await willConnect;
359381

360382
try {

Diff for: src/cmap/connect.ts

+2
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ export const LEGAL_TLS_SOCKET_OPTIONS = [
269269

270270
/** @public */
271271
export const LEGAL_TCP_SOCKET_OPTIONS = [
272+
'autoSelectFamily',
273+
'autoSelectFamilyAttemptTimeout',
272274
'family',
273275
'hints',
274276
'localAddress',

Diff for: src/connection_string.ts

+7
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,13 @@ export const OPTIONS = {
740740
autoEncryption: {
741741
type: 'record'
742742
},
743+
autoSelectFamily: {
744+
type: 'boolean',
745+
default: true
746+
},
747+
autoSelectFamilyAttemptTimeout: {
748+
type: 'uint'
749+
},
743750
bsonRegExp: {
744751
type: 'boolean'
745752
},

Diff for: src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ export type {
248248
LocalKMSProviderConfiguration
249249
} from './client-side-encryption/providers/index';
250250
export type {
251+
ClientEncryptionSocketOptions,
251252
ClientEncryptionTlsOptions,
252253
CSFLEKMSTlsOptions,
253254
StateMachineExecutable

Diff for: src/mongo_client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export type SupportedTLSSocketOptions = Pick<
104104

105105
/** @public */
106106
export type SupportedSocketOptions = Pick<
107-
TcpNetConnectOpts,
107+
TcpNetConnectOpts & { autoSelectFamily?: boolean; autoSelectFamilyAttemptTimeout?: number },
108108
(typeof LEGAL_TCP_SOCKET_OPTIONS)[number]
109109
>;
110110

Diff for: test/integration/node-specific/mongo_client.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect } from 'chai';
22
import { once } from 'events';
3+
import * as net from 'net';
34
import * as sinon from 'sinon';
45

56
import {
@@ -721,4 +722,56 @@ describe('class MongoClient', function () {
721722
});
722723
});
723724
});
725+
726+
context('when connecting', function () {
727+
let netSpy;
728+
729+
beforeEach(function () {
730+
netSpy = sinon.spy(net, 'createConnection');
731+
});
732+
733+
afterEach(function () {
734+
sinon.restore();
735+
});
736+
737+
context('when auto select options are provided', function () {
738+
beforeEach(function () {
739+
client = this.configuration.newClient({
740+
autoSelectFamily: false,
741+
autoSelectFamilyAttemptTimeout: 100
742+
});
743+
});
744+
745+
it('sets the provided options', {
746+
metadata: { requires: { topology: ['single'] } },
747+
test: async function () {
748+
await client.connect();
749+
expect(netSpy).to.have.been.calledWith({
750+
autoSelectFamily: false,
751+
autoSelectFamilyAttemptTimeout: 100,
752+
host: 'localhost',
753+
port: 27017
754+
});
755+
}
756+
});
757+
});
758+
759+
context('when auto select options are not provided', function () {
760+
beforeEach(function () {
761+
client = this.configuration.newClient();
762+
});
763+
764+
it('sets the default options', {
765+
metadata: { requires: { topology: ['single'] } },
766+
test: async function () {
767+
await client.connect();
768+
expect(netSpy).to.have.been.calledWith({
769+
autoSelectFamily: true,
770+
host: 'localhost',
771+
port: 27017
772+
});
773+
}
774+
});
775+
});
776+
});
724777
});

Diff for: test/manual/mocharc.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
2-
"require": "ts-node/register",
2+
"require": [
3+
"ts-node/register",
4+
"test/tools/runner/chai_addons.ts"
5+
],
36
"reporter": "test/tools/reporter/mongodb_reporter.js",
47
"failZero": true,
58
"color": true,

0 commit comments

Comments
 (0)