Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop stubbing dns module #1103

Merged
merged 1 commit into from
Jun 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 26 additions & 39 deletions src/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ export class ParallelConnectionStrategy {
}

for (let i = 0, len = addresses.length; i < len; i++) {
const socket = sockets[i] = net.connect(Object.create(this.options, {
host: { value: addresses[i].address }
}));
const socket = sockets[i] = net.connect({
...this.options,
host: addresses[i].address,
family: addresses[i].family
});

socket.on('error', onError);
socket.on('connect', onConnect);
Expand All @@ -70,9 +72,11 @@ export class SequentialConnectionStrategy {
return callback(new Error('Could not connect (sequence)'));
}

const socket = net.connect(Object.create(this.options, {
host: { value: next.address }
}));
const socket = net.connect({
...this.options,
host: next.address,
family: next.family
});

const onError = (_err: Error) => {
socket.removeListener('error', onError);
Expand All @@ -95,48 +99,21 @@ export class SequentialConnectionStrategy {
}
}

type LookupFunction = (hostname: string, options: dns.LookupAllOptions, callback: (err: NodeJS.ErrnoException | null, addresses: dns.LookupAddress[]) => void) => void;

export class Connector {
options: { port: number, host: string, localAddress?: string };
multiSubnetFailover: boolean;
lookup: LookupFunction;

constructor(options: { port: number, host: string, localAddress?: string }, multiSubnetFailover: boolean) {
constructor(options: { port: number, host: string, localAddress?: string, lookup?: LookupFunction }, multiSubnetFailover: boolean) {
this.options = options;
this.lookup = options.lookup ?? dns.lookup;
this.multiSubnetFailover = multiSubnetFailover;
}

execute(cb: (err: Error | null, socket?: net.Socket) => void) {
if (net.isIP(this.options.host)) {
this.executeForIP(cb);
} else {
this.executeForHostname(cb);
}
}

executeForIP(cb: (err: Error | null, socket?: net.Socket) => void) {
const socket = net.connect(this.options);

const onError = (err: Error) => {
socket.removeListener('error', onError);
socket.removeListener('connect', onConnect);

socket.destroy();

cb(err);
};

const onConnect = () => {
socket.removeListener('error', onError);
socket.removeListener('connect', onConnect);

cb(null, socket);
};

socket.on('error', onError);
socket.on('connect', onConnect);
}

executeForHostname(cb: (err: Error | null, socket?: net.Socket) => void) {
dns.lookup(punycode.toASCII(this.options.host), { all: true }, (err, addresses) => {
this.lookupAllAddresses(this.options.host, (err, addresses) => {
if (err) {
return cb(err);
}
Expand All @@ -148,4 +125,14 @@ export class Connector {
}
});
}

lookupAllAddresses(host: string, callback: (err: NodeJS.ErrnoException | null, addresses: dns.LookupAddress[]) => void) {
if (net.isIPv6(host)) {
process.nextTick(callback, null, [{ address: host, family: 6 }]);
} else if (net.isIPv4(host)) {
process.nextTick(callback, null, [{ address: host, family: 4 }]);
} else {
this.lookup.call(null, punycode.toASCII(host), { all: true }, callback);
}
}
}
39 changes: 26 additions & 13 deletions src/instance-lookup.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { Sender } from './sender';
import dns from 'dns';

const SQL_SERVER_BROWSER_PORT = 1434;
const TIMEOUT = 2 * 1000;
const RETRIES = 3;
// There are three bytes at the start of the response, whose purpose is unknown.
const MYSTERY_HEADER_LENGTH = 3;

type LookupFunction = (hostname: string, options: dns.LookupAllOptions, callback: (err: NodeJS.ErrnoException | null, addresses: dns.LookupAddress[]) => void) => void;

// Most of the functionality has been determined from from jTDS's MSSqlServerInfo class.
export class InstanceLookup {
// Wrapper allows for stubbing Sender when unit testing instance-lookup.
createSender(host: string, port: number, request: Buffer) {
return new Sender(host, port, request);
createSender(host: string, port: number, lookup: LookupFunction, request: Buffer) {
return new Sender(host, port, lookup, request);
}

instanceLookup(options: { server: string, instanceName: string, timeout?: number, retries?: number }, callback: (message: string | undefined, port?: number) => void) {
instanceLookup(options: { server: string, instanceName: string, timeout?: number, retries?: number, port?: number, lookup?: LookupFunction }, callback: (message: string | undefined, port?: number) => void) {
const server = options.server;
if (typeof server !== 'string') {
throw new TypeError('Invalid arguments: "server" must be a string');
Expand All @@ -34,25 +37,37 @@ export class InstanceLookup {
throw new TypeError('Invalid arguments: "retries" must be a number');
}

if (options.lookup !== undefined && typeof options.lookup !== 'function') {
throw new TypeError('Invalid arguments: "lookup" must be a function');
}
const lookup = options.lookup ?? dns.lookup;

if (options.port !== undefined && typeof options.port !== 'number') {
throw new TypeError('Invalid arguments: "port" must be a number');
}
const port = options.port ?? SQL_SERVER_BROWSER_PORT;

if (typeof callback !== 'function') {
throw new TypeError('Invalid arguments: "callback" must be a function');
}

let sender: Sender;
let timer: NodeJS.Timeout;
let retriesLeft = retries;

const onTimeout = () => {
sender.cancel();
makeAttempt();
};

const makeAttempt = () => {
let sender: Sender;
let timer: NodeJS.Timeout;

const onTimeout = () => {
sender.cancel();
makeAttempt();
};

if (retriesLeft > 0) {
retriesLeft--;

const request = Buffer.from([0x02]);
sender = this.createSender(options.server, SQL_SERVER_BROWSER_PORT, request);
sender = this.createSender(options.server, port, lookup, request);
timer = setTimeout(onTimeout, timeout);
sender.execute((err, response) => {
clearTimeout(timer);
if (err) {
Expand All @@ -69,8 +84,6 @@ export class InstanceLookup {
}
}
});

timer = setTimeout(onTimeout, timeout);
} else {
callback('Failed to get response from SQL Server Browser on ' + server);
}
Expand Down
8 changes: 6 additions & 2 deletions src/sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import dns from 'dns';
import net from 'net';
import * as punycode from 'punycode';

type LookupFunction = (hostname: string, options: dns.LookupAllOptions, callback: (err: NodeJS.ErrnoException | null, addresses: dns.LookupAddress[]) => void) => void;

export class ParallelSendStrategy {
addresses: dns.LookupAddress[];
port: number;
Expand Down Expand Up @@ -106,11 +108,13 @@ export class Sender {
port: number;
request: Buffer;
parallelSendStrategy: ParallelSendStrategy | null;
lookup: LookupFunction;

constructor(host: string, port: number, request: Buffer) {
constructor(host: string, port: number, lookup: LookupFunction, request: Buffer) {
this.host = host;
this.port = port;
this.request = request;
this.lookup = lookup;

this.parallelSendStrategy = null;
}
Expand All @@ -129,7 +133,7 @@ export class Sender {

// Wrapper for stubbing. Sinon does not have support for stubbing module functions.
invokeLookupAll(host: string, cb: (error: Error | null, addresses?: dns.LookupAddress[]) => void) {
dns.lookup(punycode.toASCII(host), { all: true }, cb);
this.lookup.call(null, punycode.toASCII(host), { all: true }, cb);
}

executeForHostname(cb: (error: Error | null, message?: Buffer) => void) {
Expand Down
Loading