This repository has been archived by the owner on Feb 4, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 106
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(connection): attempt both ipv6 and ipv4 when no family entered (#…
…260) If the user does not specify an IP family, we will attempt to connect first via IPv6, and fall back to IPv4 if that fails Fixes NODE-1222
- Loading branch information
1 parent
867b080
commit 107bae5
Showing
2 changed files
with
236 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
'use strict'; | ||
|
||
const bson = require('bson'); | ||
const expect = require('chai').expect; | ||
const mock = require('../../mock'); | ||
const Connection = require('../../../lib/connection/connection'); | ||
|
||
describe('Connection', function() { | ||
const noop = () => {}; | ||
let server; | ||
afterEach(() => mock.cleanup()); | ||
|
||
function testCase(name, options) { | ||
const config = options.config; | ||
const args = { | ||
metadata: { requires: { topology: ['single'] } }, | ||
test: function(done) { | ||
const connection = new Connection( | ||
noop, | ||
Object.assign( | ||
{ | ||
bson, | ||
port: server.port | ||
}, | ||
config | ||
) | ||
); | ||
|
||
const cleanup = err => { | ||
connection.destroy(); | ||
done(err); | ||
}; | ||
|
||
const errorHandler = options.error | ||
? err => { | ||
try { | ||
options.error(err); | ||
cleanup(); | ||
} catch (e) { | ||
cleanup(e); | ||
} | ||
} | ||
: cleanup; | ||
|
||
const connectHandler = options.connect | ||
? () => { | ||
try { | ||
options.connect(connection); | ||
cleanup(); | ||
} catch (e) { | ||
cleanup(e); | ||
} | ||
} | ||
: () => { | ||
cleanup(new Error('Expected test to not connect, but it connected successfully')); | ||
}; | ||
|
||
connection.on('error', errorHandler); | ||
connection.on('connect', connectHandler); | ||
connection.connect(); | ||
} | ||
}; | ||
|
||
if (options.skip) { | ||
it.skip(name, args); | ||
} else if (options.only) { | ||
it.only(name, args); | ||
} else { | ||
it(name, args); | ||
} | ||
} | ||
|
||
describe('IPv4', function() { | ||
beforeEach(() => mock.createServer(0, '127.0.0.1').then(_server => (server = _server))); | ||
|
||
testCase('should connect with no family', { | ||
config: { host: 'localhost' }, | ||
connect: connection => { | ||
expect(connection.connection.remotePort).to.equal(server.port); | ||
expect(connection.connection.remoteFamily).to.equal('IPv4'); | ||
} | ||
}); | ||
|
||
testCase('should connect with family=4', { | ||
config: { host: 'localhost', family: 4 }, | ||
connect: connection => { | ||
expect(connection.connection.remotePort).to.equal(server.port); | ||
expect(connection.connection.remoteFamily).to.equal('IPv4'); | ||
} | ||
}); | ||
|
||
testCase('should error with family=6', { | ||
config: { host: 'localhost', family: 6 }, | ||
error: err => expect(err).to.be.an.instanceOf(Error) | ||
}); | ||
}); | ||
|
||
describe('IPv6', function() { | ||
beforeEach(() => mock.createServer(0, '::').then(_server => (server = _server))); | ||
|
||
testCase('should connect with no family', { | ||
config: { host: 'localhost' }, | ||
connect: connection => { | ||
expect(connection.connection.remotePort).to.equal(server.port); | ||
expect(connection.connection.remoteFamily).to.equal('IPv6'); | ||
} | ||
}); | ||
|
||
// NOTE: this test is currently being skipped b/c of a "feature" in | ||
// most operating systems where listening on an IPv6 port | ||
// also listens on an IPv4 port. Don't want to spend time working around | ||
// this. See https://github.com/nodejs/node/issues/9390 for more info. | ||
testCase('should error with family=4', { | ||
skip: true, | ||
config: { host: 'localhost', family: 4 }, | ||
error: err => expect(err).to.be.an.instanceOf(Error) | ||
}); | ||
|
||
testCase('should connect with family=6', { | ||
config: { host: 'localhost', family: 6 }, | ||
connect: connection => { | ||
expect(connection.connection.remotePort).to.equal(server.port); | ||
expect(connection.connection.remoteFamily).to.equal('IPv6'); | ||
} | ||
}); | ||
}); | ||
}); |
107bae5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I traced down Automattic/mongoose#6566 to this change. While I can understand the rationale for this change, perhaps we can try ipv4 and v6 in parallel rather than doing
family: 6
first and falling back tofamily: 4
if that fails?107bae5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vkarpov15 agreed, I think we should achieve this by implementing happy eyeballs rather than our current approach. I made NODE-1580 to track this.
107bae5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another possibility would be allowing specifying
family
in the connection string. It is non-standard, but would make it easier to switch between ipv4 for local vs ipv6 for prod or vice versa.