From b78df149fd55d11125a35af7e2366c0a36ed4c74 Mon Sep 17 00:00:00 2001 From: Michael Sun <47126816+MichaelSun90@users.noreply.github.com> Date: Thu, 20 Jul 2023 09:42:50 -0700 Subject: [PATCH] feat: add redirect info to connection errors (#1527) Co-authored with @arthurschreiber Co-authored with @mShan0 --- src/connection.ts | 18 +++++++- test/unit/rerouting-test.js | 83 ++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index dcf9d8177..704386af9 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -2129,7 +2129,14 @@ class Connection extends EventEmitter { * @private */ connectTimeout() { - const message = `Failed to connect to ${this.config.server}${this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`} in ${this.config.options.connectTimeout}ms`; + const hostPostfix = this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`; + // If we have routing data stored, this connection has been redirected + const server = this.routingData ? this.routingData.server : this.config.server; + const port = this.routingData ? `:${this.routingData.port}` : hostPostfix; + // Grab the target host from the connection configration, and from a redirect message + // otherwise, leave the message empty. + const routingMessage = this.routingData ? ` (redirected from ${this.config.server}${hostPostfix})` : ''; + const message = `Failed to connect to ${server}${port}${routingMessage} in ${this.config.options.connectTimeout}ms`; this.debug.log(message); this.emit('connect', new ConnectionError(message, 'ETIMEOUT')); this.connectTimer = undefined; @@ -2258,7 +2265,14 @@ class Connection extends EventEmitter { */ socketError(error: Error) { if (this.state === this.STATE.CONNECTING || this.state === this.STATE.SENT_TLSSSLNEGOTIATION) { - const message = `Failed to connect to ${this.config.server}:${this.config.options.port} - ${error.message}`; + const hostPostfix = this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`; + // If we have routing data stored, this connection has been redirected + const server = this.routingData ? this.routingData.server : this.config.server; + const port = this.routingData ? `:${this.routingData.port}` : hostPostfix; + // Grab the target host from the connection configration, and from a redirect message + // otherwise, leave the message empty. + const routingMessage = this.routingData ? ` (redirected from ${this.config.server}${hostPostfix})` : ''; + const message = `Failed to connect to ${server}${port}${routingMessage} - ${error.message}`; this.debug.log(message); this.emit('connect', new ConnectionError(message, 'ESOCKET')); } else { diff --git a/test/unit/rerouting-test.js b/test/unit/rerouting-test.js index a793aa4be..22d3cf3cd 100644 --- a/test/unit/rerouting-test.js +++ b/test/unit/rerouting-test.js @@ -1,7 +1,7 @@ const { assert } = require('chai'); const net = require('net'); -const { Connection } = require('../../src/tedious'); +const { Connection, ConnectionError } = require('../../src/tedious'); const IncomingMessageStream = require('../../src/incoming-message-stream'); const OutgoingMessageStream = require('../../src/outgoing-message-stream'); const Debug = require('../../src/debug'); @@ -381,4 +381,85 @@ describe('Connecting to a server that sends a re-routing information', function( connection.close(); } }); + + it('it should throw an error with redirect information when targetserver connection failed', async function() { + routingServer.on('connection', async (connection) => { + const debug = new Debug(); + const incomingMessageStream = new IncomingMessageStream(debug); + const outgoingMessageStream = new OutgoingMessageStream(debug, { packetSize: 4 * 1024 }); + + connection.pipe(incomingMessageStream); + outgoingMessageStream.pipe(connection); + + try { + const messageIterator = incomingMessageStream[Symbol.asyncIterator](); + + // PRELOGIN + { + const { value: message } = await messageIterator.next(); + assert.strictEqual(message.type, 0x12); + + const chunks = []; + for await (const data of message) { + chunks.push(data); + } + + const responsePayload = new PreloginPayload({ encrypt: false, version: { major: 0, minor: 0, build: 0, subbuild: 0 } }); + const responseMessage = new Message({ type: 0x12 }); + responseMessage.end(responsePayload.data); + outgoingMessageStream.write(responseMessage); + } + + // LOGIN7 + { + const { value: message } = await messageIterator.next(); + assert.strictEqual(message.type, 0x10); + + const chunks = []; + for await (const data of message) { + chunks.push(data); + } + + const responseMessage = new Message({ type: 0x04 }); + responseMessage.write(buildLoginAckToken()); + responseMessage.end(buildRoutingEnvChangeToken('test.invalid', targetServer.address().port)); + outgoingMessageStream.write(responseMessage); + } + + // No further messages, connection closed on remote + { + const { done } = await messageIterator.next(); + assert.isTrue(done); + } + } catch (err) { + process.nextTick(() => { + throw err; + }); + } finally { + connection.end(); + } + }); + + const connection = new Connection({ + server: routingServer.address().address, + options: { + port: routingServer.address().port, + encrypt: false + } + }); + + try { + await new Promise((resolve, reject) => { + connection.connect((err) => { + err ? reject(err) : resolve(err); + }); + }); + } catch (err) { + assert.instanceOf(err, ConnectionError); + const message = `Failed to connect to test.invalid:${targetServer.address().port} (redirected from ${routingServer.address().address}:${routingServer.address().port})`; + assert.include(err.message, message); + } finally { + connection.close(); + } + }); });