Node: http.get option localAddress not working

Created on 2 Nov 2015  路  4Comments  路  Source: nodejs/node

Hello! The binding to Local interface option is not working at all.
The case: Arch Linux system with two different ethernet interfaces.
eth0: 192.168.0.173/24
eth1: 192.168.0.182/24
routing table is
default via 192.168.0.1 dev eth0 metric 280
default via 192.168.0.1 dev eth1 metric 281

Every interface has a listening http server on other end with the address: 192.168.0.1
So I'm trying to send a request to each of them with nodejs.
Sample code:

var http = require('http');
var options = {};
options.port = 80;
options.host = "192.168.0.1";
options.path = '/index.html';
options.headers = {'Referer': 'http://' + options.host + ':' + options.port + '/index.html'};

options.localAddress = '192.168.0.182'; //NOT WORKING!! - REQUEST GOING FROM 192.168.0.173!

var creq = http.get(options);
creq.on('response', function (res) {
    res.on('data', function (chunk) {
        console.log(chunk);
    });
});

I've even tried creating a socket for that request with new net.Socket({ handle: net._createServerHandle( '192.168.0.182')}); and requests kept going from the wrong address.
There's workaround with 'curl --interface eth1 ...' that works, but it's not a good solution.

feature request http question

Most helpful comment

I've found another workaround that works. Using ffi and call to setsockopt. But I wish I could do that with nodejs only.

var http = require('http'),
    net = require('net'),
    util = require('util'),
    ffi = require('ffi');

var SOL_SOCKET = 1;
var SO_BINDTODEVICE = 25;
var current = ffi.Library(null, {
    'setsockopt': ['int', ['int', 'int', 'int', 'string', 'int']]
});

var remoteIp = "192.168.0.1";
var ourIp = "192.168.0.182";
var ourInterface = "eth1";

function bindingAgent(options) {
    http.Agent.call(this, options);
    this.createConnection = bindingCreateConnection;
}

util.inherits(bindingAgent, http.Agent);

function bindingCreateConnection(port, host, options) {
    var socket;
    socket = new net.Socket({handle: net._createServerHandle(ourIp)});
    var iface = ourInterface;
    var r = current.setsockopt(socket._handle.fd, SOL_SOCKET, SO_BINDTODEVICE, iface, 6);
    if (r === -1)
        throw new Error("getsockopt(SO_BINDTODEVICE) error");

    socket.connect(port, host);

    return socket;
}

var optionsAgent = {};
var ourBindingAgent = new bindingAgent(optionsAgent);

var httpReq = {};
httpReq.port = 80;
httpReq.host = remoteIp;
httpReq.path = '/index.html';
httpReq.headers = {'Referer': 'http://' + remoteIp + ':' + httpReq.port + '/index.html'};
httpReq.agent = ourBindingAgent;

var body = '';
var creq = http.get(httpReq);
creq.on('response', function (res) {
    res.setEncoding('utf8');
    res.on('data', function (chunk) {
        console.log(chunk);
    });
});

All 4 comments

IIRC routing on Linux uses the route with the _lowest_ metric value when multiple routes match, which in this case would agree with what you're experiencing.

Of cause it uses the route with the lowest metric! But that's the question!
I need to send packets to _both_ interfaces with nodejs.

In C language it is a simplest question to resolve with SO_BINDTODEVICE socket option. And I thought nodejs do this also in some intellectual way like

 //convert localAddress to the Interface name
#ifdef __linux__
     //SO_BINDTODEVICE to the interface
#elif __APPLE__
     //IP_BOUND_IF  to the interface
#endif

No problemo

Perhaps it's stating the obvious but the .localAddress option binds to an address, not an interface. There is no direct support for SO_BINDTODEVICE in node.js but you can jury-rig it with socat(1):

# so-bindtodevice needs root
$ socat TCP4:<address>:80,so-bindtodevice=<dev> EXEC:'node script.js',nofork,fdin=3,fdout=3

With script.js looking something like this:

var socket = require('net').Socket({ fd: 3, readable: true, writable: true });
var req = require('http').get({
  createConnection: () => socket,
  headers: { Host: 'example.com' },
}, res => res.pipe(process.stdout));

I've found another workaround that works. Using ffi and call to setsockopt. But I wish I could do that with nodejs only.

var http = require('http'),
    net = require('net'),
    util = require('util'),
    ffi = require('ffi');

var SOL_SOCKET = 1;
var SO_BINDTODEVICE = 25;
var current = ffi.Library(null, {
    'setsockopt': ['int', ['int', 'int', 'int', 'string', 'int']]
});

var remoteIp = "192.168.0.1";
var ourIp = "192.168.0.182";
var ourInterface = "eth1";

function bindingAgent(options) {
    http.Agent.call(this, options);
    this.createConnection = bindingCreateConnection;
}

util.inherits(bindingAgent, http.Agent);

function bindingCreateConnection(port, host, options) {
    var socket;
    socket = new net.Socket({handle: net._createServerHandle(ourIp)});
    var iface = ourInterface;
    var r = current.setsockopt(socket._handle.fd, SOL_SOCKET, SO_BINDTODEVICE, iface, 6);
    if (r === -1)
        throw new Error("getsockopt(SO_BINDTODEVICE) error");

    socket.connect(port, host);

    return socket;
}

var optionsAgent = {};
var ourBindingAgent = new bindingAgent(optionsAgent);

var httpReq = {};
httpReq.port = 80;
httpReq.host = remoteIp;
httpReq.path = '/index.html';
httpReq.headers = {'Referer': 'http://' + remoteIp + ':' + httpReq.port + '/index.html'};
httpReq.agent = ourBindingAgent;

var body = '';
var creq = http.get(httpReq);
creq.on('response', function (res) {
    res.setEncoding('utf8');
    res.on('data', function (chunk) {
        console.log(chunk);
    });
});
Was this page helpful?
0 / 5 - 0 ratings

Related issues

addaleax picture addaleax  路  146Comments

jonathanong picture jonathanong  路  93Comments

yury-s picture yury-s  路  89Comments

mikeal picture mikeal  路  197Comments

AkashaThorne picture AkashaThorne  路  207Comments