I can bind to localAddress just fine when using HTTP, but as soon as I switch to HTTPS I get an error: bind EINVAL. Please consider this code:
var http = require('http');
var https = require('https');
var options = { host:'icanhazip.com',path:'/',localAddress:'2604:a880:1:20::27:a00f',family:6 };
callback = function(response) {
var data = '';
response.on('data',function(chunk) { data+= chunk; });
response.on('error',function(error) { console.log("error: "+error.message); });
response.on('end',function() {
console.log(data);
});
}
http.request(options,callback).end(); // Works. IP:2604:a880:1:20::27:a00f
https.request(options,callback).end(); // Fails. IP:2604:a880:1:20::27:a00f
https.request({host:'icanhazip.com',path:'/',family:6},callback).end(); // Works. IP:2604:a880:1:20::27:a00f
Here's the error while running node v5.0.0:
Error: bind EINVAL 2604:a880:1:20::27:a00f
at Object.exports._errnoException (util.js:860:11)
at exports._exceptionWithHostPort (util.js:883:20)
at connect (net.js:809:16)
at net.js:984:7
at GetAddrInfoReqWrap.asyncCallback [as callback] (dns.js:63:16)
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:82:10)
The only difference between the working and the failing code is setting localAddress and ironically, the last example binds to the correct IP address, but won't let you do it using localAddress. The problem here is I have to make a request from a completely separate IPv6 address under my use case, and it works fine with HTTP but I need this to work for HTTPS requests. Currently I can only make this work while using cURL. Could you please provide some insight here? Please help! :hearts:
Here's some working code using node-libcurl just to prove I'm not crazy:
var Curl = require('node-libcurl').Curl, curl = new Curl();
curl.setOpt(Curl.option.URL,'https://icanhazip.com');
curl.setOpt(Curl.option.INTERFACE, '2604:a880:1:20::27:a00f');
curl.setOpt(Curl.option.IPRESOLVE, 2);
curl.setOpt(Curl.option.FOLLOWLOCATION, 1);
curl.setOpt(Curl.option.DNS_CACHE_TIMEOUT, 86400);
curl.on('end',function(statusCode, data, headers) {
//console.info( statusCode );
console.info(data);
console.info(this.getInfo( 'TOTAL_TIME'));
this.close();
});
curl.on('error', curl.close.bind(curl));
curl.perform();
_HTTPS https.request() only has the issue when using IPv6. HTTP http.request() and localAddress work fine._
Possibly related to https://github.com/nodejs/node/issues/3625
@nodejs/crypto
The EINVAL error most likely means that you need to specify the interface to bind to, e.g. 2604:a880:1:20::27:a00f%eth0 instead of just 2604:a880:1:20::27:a00f. I suspect libcurl simply tries interfaces until it finds one that accepts the address but node doesn't do that for you.
@bnoordhuis, thanks for the suggestion. Didn't know you could do that. However, I can confirm that on node v5.5.0 and v6.2.0 that doesn't work: (eth0 is correct)
events.js:160
throw er; // Unhandled 'error' event
^
Error: bind EINVAL 2604:a880:1:20::27:a006%eth0
at Object.exports._errnoException (util.js:953:11)
at exports._exceptionWithHostPort (util.js:976:20)
at connect (net.js:839:18)
at net.js:1003:7
at GetAddrInfoReqWrap.asyncCallback [as callback] (dns.js:65:16)
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:84:10)
var https = require('https');
var options = { host:'icanhazip.com',path:'/',localAddress:'2604:a880:1:20::27:a006%eth0',family:6 };
callback = function(response) {
var data = '';
response.on('data',function(chunk) { data+= chunk; });
response.on('error',function(error) { console.log("error: "+error.message); });
response.on('end',function() {
console.log(data);
});
}
https.request(options,callback).end();
@jaggedsoft Can you post the output of strace -e bind node script.js?
(I assume you're using Linux because the interface is eth0.)
Hi @bnoordhuis,
The only difference I see is +++ exited with 1 +++
strace -e bind node ipv6.js
events.js:160
throw er; // Unhandled 'error' event
^
Error: bind EINVAL 2604:a880:1:20::27:a00f%eth0
at Object.exports._errnoException (util.js:953:11)
at exports._exceptionWithHostPort (util.js:976:20)
at connect (net.js:839:18)
at net.js:1003:7
at GetAddrInfoReqWrap.asyncCallback [as callback] (dns.js:65:16)
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:84:10)
+++ exited with 1 +++
Running Node v6.2.0 and Debian:
Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u4 (2016-02-29) x86_64 GNU/Linux
Thank you for helping to look into this. Here is the output when I use 'http' instead of 'https' module:
strace -e bind node ipv6.js
bind(12, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "2604:a880:1:20::27:a00f", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
request took: 121.931ms
http works, returned ip: 2604:a880:1:20::27:a00f
+++ exited with 0 +++
Okay, can you try strace -e \!mmap,munmap,mprotect,brk,futex,rt_sigaction node script.js? Add -o trace.log to write it to a file.
@bnoordhuis Good luck with this, and thank you again for your help:
http://pastebin.com/raw/QtquexbR
Edit: I also tried process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; with the same result.
It turns out this is the same bug as #6440, for which I have a fix in #6654. Unfortunately, that pull request is blocked on CI configuration issues.
Excellent! Been wondering about this for 7 months, and @bnoordhuis comes to the rescue and solves the problem in a day. Thank you!! :+1:
Most helpful comment
It turns out this is the same bug as #6440, for which I have a fix in #6654. Unfortunately, that pull request is blocked on CI configuration issues.