Created
April 14, 2014 07:42
-
-
Save indutny/10624955 to your computer and use it in GitHub Desktop.
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
diff --git a/lib/child_process.js b/lib/child_process.js | |
index c8e9e37..390f435 100644 | |
--- a/lib/child_process.js | |
+++ b/lib/child_process.js | |
@@ -140,6 +140,11 @@ var handleConversion = { | |
handle.onread = function() {}; | |
socket._handle = null; | |
+ // Kill happy eyeball | |
+ if (socket._eyeball) | |
+ socket._eyeball.destroy(); | |
+ socket._eyeball = null; | |
+ | |
return handle; | |
}, | |
@@ -465,7 +470,9 @@ function setupChannel(target, channel) { | |
if (obj.simultaneousAccepts) { | |
net._setSimultaneousAccepts(handle); | |
} | |
- } else if (this._handleQueue) { | |
+ } else if (this._handleQueue && | |
+ message && | |
+ message.cmd !== 'NODE_HANDLE_ACK') { | |
// Queue request anyway to avoid out-of-order messages. | |
this._handleQueue.push({ message: message, handle: null }); | |
return; | |
diff --git a/lib/dns.js b/lib/dns.js | |
index ab80deb..2a34177 100644 | |
--- a/lib/dns.js | |
+++ b/lib/dns.js | |
@@ -90,7 +90,14 @@ function onlookup(err, addresses) { | |
if (err) { | |
return this.callback(errnoException(err, 'getaddrinfo', this.hostname)); | |
} | |
- if (this.family) { | |
+ if (this.raw) { | |
+ this.callback(null, addresses.map(function(host) { | |
+ return { | |
+ host: host, | |
+ family: host.indexOf(':') >= 0 ? 6 : 4 | |
+ }; | |
+ })); | |
+ } else if (this.family) { | |
this.callback(null, addresses[0], this.family); | |
} else { | |
this.callback(null, addresses[0], addresses[0].indexOf(':') >= 0 ? 6 : 4); | |
@@ -100,11 +107,19 @@ function onlookup(err, addresses) { | |
// Easy DNS A/AAAA look up | |
// lookup(hostname, [family,] callback) | |
+// lookup(options, callback) | |
exports.lookup = function(hostname, family, callback) { | |
+ var raw = false; | |
+ | |
// parse arguments | |
if (arguments.length === 2) { | |
callback = family; | |
family = 0; | |
+ if (hostname !== null && typeof hostname === 'object') { | |
+ raw = hostname.raw; | |
+ family = hostname.family || 0; | |
+ hostname = hostname.host; | |
+ } | |
} else if (!family) { | |
family = 0; | |
} else { | |
@@ -116,13 +131,19 @@ exports.lookup = function(hostname, family, callback) { | |
callback = makeAsync(callback); | |
if (!hostname) { | |
- callback(null, null, family === 6 ? 6 : 4); | |
+ if (raw) | |
+ callback(null, []); | |
+ else | |
+ callback(null, null, family === 6 ? 6 : 4); | |
return {}; | |
} | |
var matchedFamily = net.isIP(hostname); | |
if (matchedFamily) { | |
- callback(null, hostname, matchedFamily); | |
+ if (raw) | |
+ callback(null, [{ host: hostname, family: matchedFamily }]); | |
+ else | |
+ callback(null, hostname, matchedFamily); | |
return {}; | |
} | |
@@ -130,7 +151,8 @@ exports.lookup = function(hostname, family, callback) { | |
callback: callback, | |
family: family, | |
hostname: hostname, | |
- oncomplete: onlookup | |
+ oncomplete: onlookup, | |
+ raw: raw | |
}; | |
var err = cares.getaddrinfo(req, hostname, family); | |
if (err) throw errnoException(err, 'getaddrinfo', hostname); | |
diff --git a/lib/net.js b/lib/net.js | |
index 84c6833..4874610 100644 | |
--- a/lib/net.js | |
+++ b/lib/net.js | |
@@ -79,7 +79,7 @@ exports.createServer = function() { | |
// connect(port, [host], [cb]) | |
// connect(path, [cb]); | |
// | |
-exports.connect = exports.createConnection = function() { | |
+var publicConnect = exports.connect = exports.createConnection = function() { | |
var args = normalizeConnectArgs(arguments); | |
debug('createConnection', args); | |
var s = new Socket(args[0]); | |
@@ -132,6 +132,7 @@ function Socket(options) { | |
if (!(this instanceof Socket)) return new Socket(options); | |
this._connecting = false; | |
+ this._eyeball = null; | |
this._hadError = false; | |
this._handle = null; | |
this._host = null; | |
@@ -786,6 +787,7 @@ function afterWrite(status, handle, req, err) { | |
function connect(self, address, port, addressType, localAddress, localPort) { | |
+ debug('connect', address, port, addressType, localAddress, localPort); | |
// TODO return promise from Socket.prototype.connect which | |
// wraps _connectReq. | |
@@ -837,18 +839,17 @@ function connect(self, address, port, addressType, localAddress, localPort) { | |
} | |
var req = { oncomplete: afterConnect }; | |
+ port = port | 0; | |
+ | |
if (addressType === 6 || addressType === 4) { | |
- port = port | 0; | |
if (port <= 0 || port > 65535) | |
throw new RangeError('Port should be > 0 and < 65536'); | |
+ } | |
- if (addressType === 6) { | |
- err = self._handle.connect6(req, address, port); | |
- } else if (addressType === 4) { | |
- err = self._handle.connect(req, address, port); | |
- } | |
+ if (addressType === 6) { | |
+ err = self._handle.connect6(req, address, port); | |
} else { | |
- err = self._handle.connect(req, address, afterConnect); | |
+ err = self._handle.connect(req, address, port); | |
} | |
if (err) { | |
@@ -857,6 +858,75 @@ function connect(self, address, port, addressType, localAddress, localPort) { | |
} | |
+function tryEyeball(socket, options, addr) { | |
+ debug('trying eyeball connect to ' + addr.host); | |
+ | |
+ var opts = util._extend({}, options); | |
+ opts.host = addr.host; | |
+ | |
+ var eyeball = publicConnect(opts, function() { | |
+ // Looks like primary IP was slower or failed | |
+ if (socket._connecting && !socket.destroyed) { | |
+ switchEyeball(socket); | |
+ } else if (socket._eyeball) { | |
+ assert(socket._eyeball === eyeball); | |
+ | |
+ // Cleanup | |
+ socket._eyeball = null; | |
+ eyeball.destroy(); | |
+ } | |
+ }); | |
+ | |
+ // Cleanup on error | |
+ function cleanup() { | |
+ if (socket._eyeball) { | |
+ assert(socket._eyeball === eyeball); | |
+ socket._eyeball = null; | |
+ eyeball.destroy(); | |
+ } | |
+ } | |
+ eyeball.on('error', cleanup); | |
+ | |
+ socket._eyeball = eyeball; | |
+} | |
+ | |
+ | |
+function switchEyeball(socket) { | |
+ var eyeball = socket._eyeball; | |
+ if (!eyeball) | |
+ return false; | |
+ socket._eyeball = null; | |
+ | |
+ debug('switching eyeball'); | |
+ | |
+ // Socket was transferred via cluster module or just closed | |
+ if (!socket._handle) | |
+ return; | |
+ | |
+ var old = socket._handle; | |
+ | |
+ // Ignore errors on it | |
+ old.owner = eyeball; | |
+ | |
+ // Replace with a new one | |
+ eyeball._handle.owner = socket; | |
+ eyeball.removeAllListeners('error'); | |
+ eyeball.removeAllListeners('connect'); | |
+ socket._connecting = eyeball._connecting; | |
+ socket._handle = eyeball._handle; | |
+ socket._host = eyeball._host; | |
+ | |
+ // Close old handle | |
+ try { | |
+ eyeball._handle = old; | |
+ old.close(); | |
+ } catch(e) { | |
+ } | |
+ | |
+ return true; | |
+} | |
+ | |
+ | |
Socket.prototype.connect = function(options, cb) { | |
if (this.write !== Socket.prototype.write) | |
this.write = Socket.prototype.write; | |
@@ -901,18 +971,42 @@ Socket.prototype.connect = function(options, cb) { | |
if (pipe) { | |
connect(self, options.path); | |
- } else if (!options.host) { | |
- debug('connect: missing host'); | |
- self._host = '127.0.0.1'; | |
- connect(self, self._host, options.port, 4); | |
- | |
} else { | |
- var host = options.host; | |
- var family = options.family || 4; | |
+ var host = options.host || 'localhost'; | |
+ var family = options.family; | |
debug('connect: find host ' + host); | |
self._host = host; | |
- require('dns').lookup(host, family, function(err, ip, addressType) { | |
+ require('dns').lookup({ | |
+ host: host, | |
+ family: family, | |
+ raw: true | |
+ }, function(err, results) { | |
+ if (!err) { | |
+ var ip = results[0].host; | |
+ var addressType = results[0].family; | |
+ | |
+ var alternate = null; | |
+ for (var i = 1; i < results.length; i++) { | |
+ if (results[i].family === addressType) | |
+ continue; | |
+ alternate = results[i]; | |
+ break; | |
+ } | |
+ } | |
+ | |
+ // Opt-out of eyeballs, if env variable is set | |
+ if (process.env.NODE_NET_HAPPY_EYEBALLS == '0' && alternate) { | |
+ // Default to ipv4 as in v0.10 | |
+ if (addressType === 6) { | |
+ ip = alternate.host; | |
+ addressType = 4; | |
+ } | |
+ alternate = null; | |
+ } | |
+ | |
self.emit('lookup', err, ip, addressType); | |
+ if (alternate) | |
+ self.emit('lookup', err, alternate.host, alternate.family); | |
// It's possible we were destroyed while looking this up. | |
// XXX it would be great if we could cancel the promise returned by | |
@@ -931,8 +1025,6 @@ Socket.prototype.connect = function(options, cb) { | |
} else { | |
timers._unrefActive(self); | |
- addressType = addressType || 4; | |
- | |
// node_net.cc handles null host names graciously but user land | |
// expects remoteAddress to have a meaningful value | |
ip = ip || (addressType === 4 ? '127.0.0.1' : '0:0:0:0:0:0:0:1'); | |
@@ -943,6 +1035,10 @@ Socket.prototype.connect = function(options, cb) { | |
addressType, | |
options.localAddress, | |
options.localPort); | |
+ | |
+ // Try happy eyeballs | |
+ if (alternate) | |
+ tryEyeball(self, options, alternate); | |
} | |
}); | |
} | |
@@ -966,15 +1062,21 @@ Socket.prototype.unref = function() { | |
function afterConnect(status, handle, req, readable, writable) { | |
var self = handle.owner; | |
+ // Kill eyeball on success anyway | |
+ if (status == 0) { | |
+ if (self._eyeball) | |
+ self._eyeball.destroy(); | |
+ self._eyeball = null; | |
+ } | |
+ | |
// callback may come after call to destroy | |
if (self.destroyed) { | |
return; | |
} | |
+ debug('afterConnect', status); | |
assert(handle === self._handle, 'handle != self._handle'); | |
- debug('afterConnect'); | |
- | |
assert.ok(self._connecting); | |
self._connecting = false; | |
@@ -991,8 +1093,8 @@ function afterConnect(status, handle, req, readable, writable) { | |
self.read(0); | |
} else { | |
- self._connecting = false; | |
- self._destroy(errnoException(status, 'connect')); | |
+ if (!switchEyeball(self)) | |
+ self._destroy(errnoException(status, 'connect')); | |
} | |
} | |
diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc | |
index a7bfeda..f774c17 100644 | |
--- a/src/cares_wrap.cc | |
+++ b/src/cares_wrap.cc | |
@@ -898,19 +898,7 @@ void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) { | |
Local<String> s = OneByteString(env->isolate(), ip); | |
results->Set(n, s); | |
n++; | |
- } | |
- | |
- // Increment | |
- address = address->ai_next; | |
- } | |
- | |
- // Iterate over the IPv6 responses putting them in the array. | |
- address = res; | |
- while (address) { | |
- assert(address->ai_socktype == SOCK_STREAM); | |
- | |
- // Ignore random ai_family types. | |
- if (address->ai_family == AF_INET6) { | |
+ } else if (address->ai_family == AF_INET6) { | |
// Juggle pointers | |
addr = reinterpret_cast<char*>(&(reinterpret_cast<struct sockaddr_in6*>( | |
address->ai_addr)->sin6_addr)); | |
@@ -931,7 +919,6 @@ void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) { | |
address = address->ai_next; | |
} | |
- | |
argv[1] = results; | |
} | |
diff --git a/test/simple/test-asynclistener-error-net.js b/test/simple/test-asynclistener-error-net.js | |
index 26a337a..60f5619 100644 | |
--- a/test/simple/test-asynclistener-error-net.js | |
+++ b/test/simple/test-asynclistener-error-net.js | |
@@ -91,7 +91,7 @@ var server = net.createServer(function(c) { | |
}); | |
expectCaught += iter; | |
-server.listen(common.PORT, function() { | |
+server.listen(common.PORT, '127.0.0.1', function() { | |
for (var i = 0; i < iter; i++) | |
clientConnect(); | |
}); | |
diff --git a/test/simple/test-http-parser-free.js b/test/simple/test-http-parser-free.js | |
index 7b35781..1a71ea7 100644 | |
--- a/test/simple/test-http-parser-free.js | |
+++ b/test/simple/test-http-parser-free.js | |
@@ -38,7 +38,7 @@ server.listen(common.PORT, function() { | |
if (!parser) { | |
parser = req.parser; | |
} else { | |
- assert.strictEqual(req.parser, parser); | |
+ assert(req.parser === parser); | |
} | |
if (++responses === N) { | |
diff --git a/test/simple/test-https-invalid-key.js b/test/simple/test-https-invalid-key.js | |
index 82e05a2..5b57dd4 100644 | |
--- a/test/simple/test-https-invalid-key.js | |
+++ b/test/simple/test-https-invalid-key.js | |
@@ -46,7 +46,7 @@ server.on('clientError', function(err) { | |
server.close(); | |
}); | |
-server.listen(common.PORT, function() { | |
+server.listen(common.PORT, '127.0.0.1', function() { | |
var req = https.get({port: common.PORT}, function(res) { | |
assert(false); | |
}); | |
diff --git a/test/simple/test-net-pause-resume-connecting.js b/test/simple/test-net-pause-resume-connecting.js | |
index 264dc96..63ad485 100644 | |
--- a/test/simple/test-net-pause-resume-connecting.js | |
+++ b/test/simple/test-net-pause-resume-connecting.js | |
@@ -37,7 +37,7 @@ var server = net.createServer(function(conn) { | |
server.close(); | |
}); | |
-server.listen(common.PORT); | |
+server.listen(common.PORT, '127.0.0.1'); | |
// Client 1 | |
diff --git a/test/simple/test-net-pingpong.js b/test/simple/test-net-pingpong.js | |
index 1e7b668..0ee269a 100644 | |
--- a/test/simple/test-net-pingpong.js | |
+++ b/test/simple/test-net-pingpong.js | |
@@ -26,6 +26,8 @@ var net = require('net'); | |
var tests_run = 0; | |
+process.env.NODE_NET_HAPPY_EYEBALLS = '0'; | |
+ | |
function pingPongTest(port, host) { | |
var N = 1000; | |
var count = 0; | |
diff --git a/test/simple/test-net-remote-address-port.js b/test/simple/test-net-remote-address-port.js | |
index 5d1ae3c..3c2956f 100644 | |
--- a/test/simple/test-net-remote-address-port.js | |
+++ b/test/simple/test-net-remote-address-port.js | |
@@ -28,7 +28,8 @@ var conns = 0, conns_closed = 0; | |
var server = net.createServer(function(socket) { | |
conns++; | |
- assert.equal('127.0.0.1', socket.remoteAddress); | |
+ assert.ok(/127\.0\.0\.1/.test(socket.remoteAddress) || | |
+ '::1' === socket.remoteAddress); | |
assert.ok(socket.remotePort); | |
assert.notEqual(socket.remotePort, common.PORT); | |
socket.on('end', function() { | |
diff --git a/test/simple/test-net-server-max-connections.js b/test/simple/test-net-server-max-connections.js | |
index 43bc2fe..2bea08a 100644 | |
--- a/test/simple/test-net-server-max-connections.js | |
+++ b/test/simple/test-net-server-max-connections.js | |
@@ -41,7 +41,7 @@ var server = net.createServer(function(connection) { | |
waits.push(function() { connection.end(); }); | |
}); | |
-server.listen(common.PORT, function() { | |
+server.listen(common.PORT, '127.0.0.1', function() { | |
makeConnection(0); | |
}); | |
diff --git a/test/simple/test-net-stream.js b/test/simple/test-net-stream.js | |
index 9f2db51..77b8da5 100644 | |
--- a/test/simple/test-net-stream.js | |
+++ b/test/simple/test-net-stream.js | |
@@ -57,7 +57,7 @@ var server = net.createServer(function(socket) { | |
} | |
socket.end(); | |
-}).listen(common.PORT, function() { | |
+}).listen(common.PORT, '127.0.0.1', function() { | |
var conn = net.connect(common.PORT); | |
conn.on('data', function(buf) { | |
conn.pause(); | |
diff --git a/test/simple/test-net-write-after-close.js b/test/simple/test-net-write-after-close.js | |
index 3b98bbc..fa63c6d 100644 | |
--- a/test/simple/test-net-write-after-close.js | |
+++ b/test/simple/test-net-write-after-close.js | |
@@ -48,7 +48,7 @@ var server = net.createServer(function(socket) { | |
}, 250); | |
}); | |
-server.listen(common.PORT, function() { | |
+server.listen(common.PORT, '127.0.0.1', function() { | |
var client = net.connect(common.PORT, function() { | |
client.end(); | |
}); | |
diff --git a/test/simple/test-pump-file2tcp-noexist.js b/test/simple/test-pump-file2tcp-noexist.js | |
index 8017db7..01e39d3 100644 | |
--- a/test/simple/test-pump-file2tcp-noexist.js | |
+++ b/test/simple/test-pump-file2tcp-noexist.js | |
@@ -45,7 +45,7 @@ var server = net.createServer(function(stream) { | |
}); | |
}); | |
-server.listen(common.PORT, function() { | |
+server.listen(common.PORT, '127.0.0.1', function() { | |
var conn = net.createConnection(common.PORT); | |
conn.setEncoding('utf8'); | |
conn.on('data', function(chunk) { | |
diff --git a/test/simple/test-pump-file2tcp.js b/test/simple/test-pump-file2tcp.js | |
index 60c0fdd..3d28e60 100644 | |
--- a/test/simple/test-pump-file2tcp.js | |
+++ b/test/simple/test-pump-file2tcp.js | |
@@ -38,7 +38,7 @@ var server = net.createServer(function(stream) { | |
}); | |
}); | |
-server.listen(common.PORT, function() { | |
+server.listen(common.PORT, '127.0.0.1', function() { | |
var conn = net.createConnection(common.PORT); | |
conn.setEncoding('utf8'); | |
conn.on('data', function(chunk) { | |
diff --git a/test/simple/test-socket-write-after-fin.js b/test/simple/test-socket-write-after-fin.js | |
index 88d780b..c7289d2 100644 | |
--- a/test/simple/test-socket-write-after-fin.js | |
+++ b/test/simple/test-socket-write-after-fin.js | |
@@ -38,7 +38,7 @@ var server = net.createServer({ allowHalfOpen: true }, function(sock) { | |
server.close(); | |
}); | |
}); | |
-server.listen(common.PORT); | |
+server.listen(common.PORT, '127.0.0.1'); | |
var sock = net.connect(common.PORT); | |
sock.setEncoding('utf8'); | |
diff --git a/test/simple/test-tls-over-http-tunnel.js b/test/simple/test-tls-over-http-tunnel.js | |
index 9fa82ae..480a157 100644 | |
--- a/test/simple/test-tls-over-http-tunnel.js | |
+++ b/test/simple/test-tls-over-http-tunnel.js | |
@@ -91,7 +91,8 @@ var proxy = net.createServer(function(clientSocket) { | |
}); | |
clientSocket.on('end', function() { | |
- serverSocket.destroy(); | |
+ if (serverSocket) | |
+ serverSocket.destroy(); | |
}); | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment