Skip to content

http.get option localAddress not working #3625

Closed

Description

@k1tzu

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.

Activity

  1. added
    httpIssues and PRs related to the http subsystem.
    on Nov 2, 2015
  2. mscdex commented on Nov 2, 2015

    @mscdex
    Contributor

    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.

  3. k1tzu commented on Nov 2, 2015

    @k1tzu
    Author

    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

  4. bnoordhuis commented on Nov 2, 2015

    @bnoordhuis
    Member

    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));
  5. k1tzu commented on Nov 2, 2015

    @k1tzu
    Author

    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);
        });
    });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestIssues requesting new Node.js features.httpIssues and PRs related to the http subsystem.questionIssues asking questions about Node.js.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions