Skip to content

Commit 8c7e440

Browse files
fix: handle timeouts during tls negotiation for strict encryption (#1564)
Co-authored-by: Arthur Schreiber <schreiber.arthur@gmail.com>
1 parent 4f3e210 commit 8c7e440

2 files changed

Lines changed: 73 additions & 9 deletions

File tree

src/connection.ts

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,7 +1931,8 @@ class Connection extends EventEmitter {
19311931
});
19321932
}, (err) => {
19331933
this.clearConnectTimer();
1934-
if (err.name === 'AbortError') {
1934+
1935+
if (signal.aborted) {
19351936
// Ignore the AbortError for now, this is still handled by the connectTimer firing
19361937
return;
19371938
}
@@ -2008,7 +2009,9 @@ class Connection extends EventEmitter {
20082009
this.transitionTo(this.STATE.SENT_PRELOGIN);
20092010
}
20102011

2011-
wrapWithTls(socket: net.Socket): Promise<tls.TLSSocket> {
2012+
wrapWithTls(socket: net.Socket, signal: AbortSignal): Promise<tls.TLSSocket> {
2013+
signal.throwIfAborted();
2014+
20122015
return new Promise((resolve, reject) => {
20132016
const secureContext = tls.createSecureContext(this.secureContextOptions);
20142017
// If connect to an ip address directly,
@@ -2023,12 +2026,41 @@ class Connection extends EventEmitter {
20232026
servername: this.config.options.serverName ? this.config.options.serverName : serverName,
20242027
};
20252028

2026-
const encryptsocket = tls.connect(encryptOptions, () => {
2027-
encryptsocket.removeListener('error', reject);
2029+
const encryptsocket = tls.connect(encryptOptions);
2030+
2031+
const onAbort = () => {
2032+
encryptsocket.removeListener('error', onError);
2033+
encryptsocket.removeListener('connect', onConnect);
2034+
2035+
encryptsocket.destroy();
2036+
2037+
reject(signal.reason);
2038+
};
2039+
2040+
const onError = (err: Error) => {
2041+
signal.removeEventListener('abort', onAbort);
2042+
2043+
encryptsocket.removeListener('error', onError);
2044+
encryptsocket.removeListener('connect', onConnect);
2045+
2046+
encryptsocket.destroy();
2047+
2048+
reject(err);
2049+
};
2050+
2051+
const onConnect = () => {
2052+
signal.removeEventListener('abort', onAbort);
2053+
2054+
encryptsocket.removeListener('error', onError);
2055+
encryptsocket.removeListener('connect', onConnect);
2056+
20282057
resolve(encryptsocket);
2029-
});
2058+
};
2059+
2060+
signal.addEventListener('abort', onAbort, { once: true });
20302061

2031-
encryptsocket.once('error', reject);
2062+
encryptsocket.on('error', onError);
2063+
encryptsocket.on('secureConnect', onConnect);
20322064
});
20332065
}
20342066

@@ -2047,7 +2079,7 @@ class Connection extends EventEmitter {
20472079
if (this.config.options.encrypt === 'strict') {
20482080
try {
20492081
// Wrap the socket with TLS for TDS 8.0
2050-
socket = await this.wrapWithTls(socket);
2082+
socket = await this.wrapWithTls(socket, signal);
20512083
} catch (err) {
20522084
socket.end();
20532085

@@ -2059,7 +2091,7 @@ class Connection extends EventEmitter {
20592091
})().catch((err) => {
20602092
this.clearConnectTimer();
20612093

2062-
if (err.name === 'AbortError') {
2094+
if (signal.aborted) {
20632095
return;
20642096
}
20652097

test/unit/connection-test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as net from 'net';
2-
import { Connection } from '../../src/tedious';
2+
import { Connection, ConnectionError } from '../../src/tedious';
33
import { assert } from 'chai';
44

55
describe('Using `strict` encryption', function() {
@@ -42,4 +42,36 @@ describe('Using `strict` encryption', function() {
4242
done();
4343
});
4444
});
45+
46+
it('handles connection timeout when performing tls handshake', function(done) {
47+
server.on('connection', (connection) => {
48+
setTimeout(() => {
49+
connection.destroy();
50+
}, 4000);
51+
});
52+
53+
const addressInfo = server.address() as net.AddressInfo;
54+
55+
const connection = new Connection({
56+
server: addressInfo?.address,
57+
options: {
58+
port: addressInfo?.port,
59+
encrypt: 'strict',
60+
connectTimeout: 3000
61+
}
62+
});
63+
64+
connection.connect((err) => {
65+
assert.instanceOf(err, ConnectionError);
66+
67+
const message = `Failed to connect to ${addressInfo?.address}:${addressInfo?.port} in 3000ms`;
68+
assert.equal(err!.message, message);
69+
70+
connection.close();
71+
});
72+
73+
connection.on('end', () => {
74+
done();
75+
});
76+
});
4577
});

0 commit comments

Comments
 (0)