Could you explain the "weird" behaviour? I'm running netcat on Linux, as a UDP echo server:
ncat -4 --exec /bin/cat -u --listen 2000
Next, run client:
$ ncat -s 192.168.1.2 -u 127.0.0.1 2000
Machine has a real network adapter with the address: 192.168.1.2. After I typed something, the server just exited:
$ ncat -4 --exec /bin/cat -u --listen 2000 ; x=$?; echo $x
0
Why?
This is a ncat
limitation.
Let's use strace
to look into what ncat
is up to. Conveniently, strace
has -e
option to filter system calls, e.g. -e %net
for logging network-related syscalls. Let's start the server first:
$ strace -e %net ncat -4 --exec /bin/cat -u --listen 2000
socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
Note the bind
call. The socket is bound to 0.0.0.0, meaning that it will receive packets sent to 127.0.0.1:2000 as well as 10.0.2.15:2000 (my eth0
address), as well as any other address belonging to the machine.
Now let's start a client:
$ strace -e %net ncat -s 10.0.2.15 -u 127.0.0.1 2000
socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("10.0.2.15")}, 16) = 0
setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
At this point, the client is waiting for input.
Notice the connect
call. UDP is connectionless, so the call simply remembers the remote's address. It is convenient as send
will use the recorded address. Otherwise, it would be necessary to specify the remote address in every send
call. There's another implication which we'll see shortly.
Now let's type some input. Client:
sendto(3, "Hello, world!\n", 14, 0, NULL, 0) = 14
Server:
recvfrom(3, "Hello, world!\n", 131072, MSG_PEEK, {sa_family=AF_INET, sin_port=htons(35197), sin_addr=inet_addr("10.0.2.15")}, [128->16]) = 14
connect(3, {sa_family=AF_INET, sin_port=htons(35197), sin_addr=inet_addr("10.0.2.15")}, 16) = 0
recvfrom(3, "Hello, world!\n", 8192, 0, NULL, NULL) = 14
sendto(3, "Hello, world!\n", 14, 0, NULL, 0) = 14
recvfrom(3, 0x7fff8fcc9d70, 8192, 0, NULL, NULL) = -1 ECONNREFUSED (Connection refused)
The server sees a message from 10.0.2.15. The first recvfrom
call is done with MSG_PEEK
flag, which allows to peek at the message without actually consuming it. Then connect
(for convenience), another recvfrom
to consume the message, and sendto
to send a reply.
Now remember that the server socket is bound to 0.0.0.0. The client sent a message to 127.0.0.1:2000. The reply must include the server socket's address as the source. But the system can't use 0.0.0.0 as it doesn't generally make sense on the wire. Both 127.0.0.1 and 10.0.2.15 are possible, but ncat
doesn't tell explicitly which one to use. So the system ends up using 10.0.2.15 as the source.
Now back to the client. It sent a message to 127.0.0.1:2000, but the response came from 10.0.2.15:2000. Another effect of connect
on a UDP socket is that only datagrams originating from the specified address:port will be accepted. If the source doesn't match, the system responds with ICMP port unreachable. This is reported as ECONNREFUSED
error in the server.
Specify the intended source address in the server using IP_PKTINFO
/IPV6_PKTINFO
ancillary message.