node icon indicating copy to clipboard operation
node copied to clipboard

Get udp recipent IP for incoming packet

Open HuJK opened this issue 6 months ago • 0 comments

What is the problem this feature will solve?

It's impossible to serve a UDP based server with nodejs on multiple IP server. A lot of cloud service provider or ISPs allowing us to purchase additional IP on it.

Assume I have a server with multiple IP on it.
In this case, I'm demo with 172.22.10.100 and 172.22.10.101 The 172.22.10.100 is marked as primary, and 172.22.10.101 is marked as secondary by kernel.

2: eth0@if141: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 2e:98:22:63:a9:1c brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.22.10.100/20 scope global eth0
       valid_lft forever preferred_lft forever
    inet 172.22.10.101/20 scope global secondary eth0
       valid_lft forever preferred_lft forever

This is a simple udp server, it replys "pong" message to every client.

var dgram = require('dgram');

var PORT = 55567;
var HOST = '0.0.0.0';

        // UDP Server
server = dgram.createSocket('udp4');

server.on('listening', function () {
    var address = this.address();
        console.log('UDP Server listening on ' + address.address + ":" + address.port);
    }.bind(server));

server.on('close', function() {
    console.log('udp socket closed..');
});

server.on('message', function (message, remote) {
    console.log('Data received from client : ' + message.toString());
    this.send(new Buffer("pong"), remote.port, remote.address, function(err, bytes) {
    if (err) throw err;
        console.log(`UDP message sent to ${remote.address}:${remote.port}`);
    });

}.bind(server));

server.bind(PORT, HOST);

If the client connect to 172.22.10.100.55567, it replies with src ip 172.22.10.100.55567

tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
13:01:39.892849 eth0  In  IP 172.22.3.2.36202 > 172.22.10.100.55567: UDP, length 4
13:01:39.893467 eth0  Out IP 172.22.10.100.55567 > 172.22.3.2.36202: UDP, length 4
13:01:43.951945 eth0  In  IP 172.22.3.2.57033 > 172.22.10.100.55567: UDP, length 4
13:01:43.952904 eth0  Out IP 172.22.10.100.55567 > 172.22.3.2.57033: UDP, length 4

But if the client connect to 172.22.10.101.55567, the server still reply with src ip 172.22.10.100.55567 which make it unuseable on IP 172.22.10.101

13:02:48.927441 eth0  In  IP 172.22.3.2.51876 > 172.22.10.101.55567: UDP, length 4
13:02:48.928299 eth0  Out IP 172.22.10.100.55567 > 172.22.3.2.51876: UDP, length 4
13:02:50.387984 eth0  In  IP 172.22.3.2.46488 > 172.22.10.101.55567: UDP, length 4
13:02:50.388810 eth0  Out IP 172.22.10.100.55567 > 172.22.3.2.46488: UDP, length 4

This bug happens is because linux selects source address with following rules:

  1. User specified (with bind or write)
  2. Tracked by kernel (not happen for udp listen)
  3. Hit routes by destination IP 3-1. If pref-src is set on the route, use it 3-2. if the route points a NIC, select an IP from it 3-3. If multiple IP on the NIC, use the ip marked as primary.

TCP socket get covered by system at rule 2 but udp is not. If the selection hits rule 3, it's basically guess. Because it lost the information of which IP is the client connect to.

Different from TCP session, system won't track udp session for user, we have to handle it by our self. We have to bind specfic source IP on udp each session otherwise system just select the primary IP from NIC based on default table. So that it brokes at system have multiple route table or secondary IP on the NIC.

What is the feature you are proposing to solve the problem?

In order to reply message with correct source address, we have to get the information of destnation IP at incoming packet. Unfortunately we can't do it in nodejs right now so that there is no way to implement workable udp server with nodejs on multiple IP server.

There are similar issue, but all get closed and locked. And all these are talking something about broadcast address. https://github.com/nodejs/node-v0.x-archive/issues/8788 https://github.com/nodejs/node-v0.x-archive/issues/6589

But in my use case, it irrelevant to broadcast address or overlapping subnets etc.
It's just a try to create a regular udp server application on a multiple IP server.
So I want nodejs developers reconsider this feature.

The another issue is get destnation IP for incoming packet is platform specific. We have to write a lot of code to hendle it.

In linux (include Android) based platform, we have to use IP_PKTINFO In BSD based platform (include MacOS and iOS), we need IP_RECVDSTADDR In Windows platform, we need WSAIoctl and WSARecvMsg For these 3 platform, it covers 99% of our scenarios.

But this is a generial purpose requirement. It would be nice if we can make a abstraction for this feature at nodejs, instead of implement it with platform specific code with C binding.

What alternatives have you considered?

Uses native C api to retrive the destnation IP at incoming packet, then we can reply reply with correct source address. Which make the code more complex, unreadable and unportable.

HuJK avatar Aug 21 '24 13:08 HuJK