Linux UDP server always show 127.0.0.1 as source IP

Haocheng

I'm trying to send UDP packet from a different loopback IP, say 127.0.0.100.

There is no problem receiving the packet, while it always shows source IP of 127.0.0.1, and replies to there.

Is there a way to make it showing correct source IP?

Server side

$ nc -ul 0.0.0.0 27430
ACK
Ncat: Connection refused.

Client side

$ (echo "TEST" && sleep 1) | nc -u 127.0.0.100 27430

TCP Dump

$ sudo tcpdump -nn --interface lo -s 0  | grep 27430
21:46:37.311031 IP 127.0.0.1.46033 > 127.0.0.100.27430: UDP, length 5
21:46:39.834975 IP 127.0.0.1.27430 > 127.0.0.1.46033: UDP, length 4 <-- Replying from 127.0.0.1, instead of 127.0.0.100

while it shows correct IP of 127.0.0.100 from macOS:

Server side

$ nc -ul 0.0.0.0 27430
TEST
ACK

Client side

$ (echo "TEST" && sleep 1) | nc -u 127.0.0.100 27430

TCP Dump

$ sudo tcpdump --interface lo0 -s 0 | grep 27430
14:44:28.982553 IP 127.0.0.100.60482 > 127.0.0.100.27430: UDP, length 5
14:44:31.101434 IP 127.0.0.100.27430 > 127.0.0.100.60482: UDP, length 4 <-- Replying from 127.0.0.100
A.B

The netcat tool (all of its variants, including nmap's netcat) has no feature to handle this for UDP.

Due to how the BSD socket API is implemented, contrary to TCP, by default UDP doesn't keep state of received UDP datagrams on a socket when replying to them. In particular, where TCP after using accept(2) gets a new socket in connected state and can use getsockname(2) on the new socket to know what address the initial connection was made to, and would anyway reply with the correct source address (eg: 127.0.0.100), getsockname(2) on the unique UDP socket will just give back the address it is bound to: here 0.0.0.0.

When an UDP datagram is received, by default, there's no way to know which of the multiple possible addresses it was received on. When replying to this source, the routing stack is queried to complete the source IP address from the "unset" 0.0.0.0. This resolves to 127.0.0.1 on the loopback interface, because that's the primary address set on it. The information 127.0.0.100 is already lost. The same issue happens when a multi-homed system receives an UDP datagram to an IP address through an interface this address wasn't configured on: the reply will usually be emitted using the same interface (because the routing stack determined that the reply destination will use this interface) and will again choose the wrong source address: the one on the interface rather than the address the initial UDP datagram was send to.


For UDP to be able to handle this correctly there are two methods:

  • bind multiple times separately to all possible addresses where a connection can be made

    Then if data is received on one of these sockets, the reply will use the bound address (just as getsockname(2) would tell) which can only be the correct source, since this is a reply to data received on this socket.

    This is impractical for two reasons here: that means this would require multiple netcat commands, one per bind, and more importantly for the loopback interface there are ~ 2^24 possibilities. Anyway for OP's specific example this would work for the server side (below ncat is nmap's netcat, ie OP's nc):

    $ echo ACK | ncat -ul 127.0.0.100 27430
    TEST
    
    $ (echo "TEST" && sleep 1) | ncat -u 127.0.0.100 27430
    ACK
    $ 
    

    That's what usually does the DNS server bind 9 from ISC: it binds to every system's addresses (so just 127.0.0.1 for the loopback interface, a query to 127.0.0.100:53 would simply fail with ICMP port unreachable since nothing would be bound to it nor INADDR_ANY) and will adjust dynamically when addresses appear or disappear.

  • use the IP_PKTINFO socket option on the UDP socket to have additional information through socket ancillary messages.

    (on *BSD the more or less equivalent socket option would be IP_RECVDSTADDR)

    When this option is set, and the UDP socket is not used with connect(2) + read(2)/write(2) but with recvmsg(2)/sendmsg(2)/cmsg(3), this gives access to additional information through msghdr->msg_control. IP_PKTINFO will allow to know the local (destination) address and interface the UDP datagram was receved on, and allows to specify the local (source) address and/or the interface to use when replying back rather than let the routing stack choose.

    That's what will do the DNS server unbound from NLnet Labs when enabling the interface-automatic option.


The tool socat, having much more features than netcat has a built-in mechanism to use IP_PKTINFO.

Instead of:

nc -ul 0.0.0.0 27430

One can use:

socat -u udp4-recvfrom:27430,ip-pktinfo,reuseport,fork \
    system:'socat -u exec\:\"echo ACK\" udp4-datagram\:$SOCAT_PEERADDR\:$SOCAT_PEERPORT\,bind=$SOCAT_IP_LOCADDR\:27430\,reuseport; cat'

It's quite convoluted, because the 2nd part of the command has to be a program which gets the ancillary message information through environment variables exported by the first socat when it receives an UDP datagram (with SOCAT_IP_LOCADDR the important information). Here the script runs again socat to emit the reply, actually using a 2nd socket sharing the port with the 1st socket used in the first part of the command (hence the need of SO_REUSEPORT) instead of doing everything using the same socket. Then there are multiple layers of escaping needed for the outer shell, socat, the inner shell and the inner socat. A dedicated script doing the 2nd part would avoid some of the escaping. This construct doesn't (easily?) allow an use in interactive mode.

One should really use a dedicated tool and/or better suited language than shell, like python, to avoid this additional complexity and use a single socket for everything.

In the end, the client request works (below ncat is nmap's implementation of netcat, ie: OP's nc):

  $ (echo "TEST" && sleep 1) | ncat -u 127.0.0.100 27430
  ACK
  $ (echo "TEST" && sleep 1) | ncat -u 127.100.100.100 27430
  ACK

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related