6

While troubleshooting the problem described here, I tried something really basic: I tried setting up a very basic netcat connection over UDP, and I noticed it's not working as I expect.

As with the TCP illustration below, I expected the UDP case to echo the same output on Window 1 as I type it in Window 2; instead, I get no output in Window 1 in the UDP case.

I'm running Debian Jessie on my laptop, and I have Wireshark running with the following capture filter:

udp and not (port 123 or port 5353 or port 1900)

In one terminal window, I began with a TCP test by running the following command:

$ nc -l 6900

In a second terminal window, I ran the following:

$ nc localhost 6900

In Window 2, I typed "one" and pressed Enter, then I typed "two" and pressed Enter, and finally I pressed Ctrl+D to exit.

After pressing Enter on "one", I saw "one" echoed in Window 1. After pressing Enter on "two", I saw "two" echoed in Window 1. When I pressed Ctrl+D, both netcat instances exited and I returned to the prompt.

This demonstrates I could get a TCP connection going without any problem. Next, I tried UDP.

Window 1:

$ nc -l -u 6900

Window 2:

$ nc -u localhost 6900

It's funny. I think I could only type "one", press Enter, then "two", and press Enter, and I automatically returned back to the prompt in Window 2.

On Window 1, I don't see any output.

I retried with -v in Window 1:

$ nc -v -l -u 6900
Listening on [0.0.0.0] (family 0, port 6900)

Funny when I retried with -v in Window 2:

$ nc -v -u localhost 6900
$

It's like netcat never ran; I returned immediately to the prompt.

Update: If I replace localhost with 127.0.0.1, there is progress.

With the "server" listening in Window 1:

$ nc -v -l -u 6900
one
two
three

I was able to get output echoed from Window 2 when typing "one", "two", and "three":

$ nc -u 127.0.0.1 6900
one
two
three
^C

The -v is still puzzling, because it's the same as above.

2 Answers 2

9

Using macOS, I come to a different conclusion. The key is IPv6. localhost resolves to both IPv6 and IPv4. However, the following command line will cause nc to listen on IPv4:

nc -l -u 6900

The result:

$ lsof -n -i:6900
COMMAND   PID  USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
nc      63923 fuzzy    3u  IPv4 0xad7f669b928a178b      0t0  UDP *:6900

Now when you use this to “connect”:

nc -u localhost 6900

... it actually connects to IPv6. When you use 127.0.0.1 it will not.

However, it does not connect, because UDP is connectionless. As such, there is no way of knowing whether the remote end of the connection really exists. As such, it cannot detect it should fall back to IPv4. Your messages will be sent, but there is nothing listening for those messages.

When sending, the following can be observed:

$ tcpdump -i lo0 udp port 6900
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo0, link-type NULL (BSD loopback), capture size 262144 bytes
00:46:37.543273 IP6 localhost.54473 > localhost.6900: UDP, length 6

With TCP, it will try to connect on IPv6, determine this isn’t working, and retry with IPv4:

$ tcpdump -i lo0 port 6900
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo0, link-type NULL (BSD loopback), capture size 262144 bytes
00:50:21.495107 IP6 localhost.64306 > localhost.6900: Flags [SEW], seq 1432891337, win 65535, options [mss 16324,nop,wscale 5,nop,nop,TS val 766376063 ecr 0,sackOK,eol], length 0
00:50:21.495136 IP6 localhost.6900 > localhost.64306: Flags [R.], seq 0, ack 1432891338, win 0, length 0
00:50:21.495231 IP localhost.64307 > localhost.6900: Flags [S], seq 307818799, win 65535, options [mss 16344,nop,wscale 5,nop,nop,TS val 766376063 ecr 0,sackOK,eol], length 0
00:50:21.495283 IP localhost.6900 > localhost.64307: Flags [S.], seq 4254625238, ack 307818800, win 65535, options [mss 16344,nop,wscale 5,nop,nop,TS val 766376063 ecr 766376063,sackOK,eol], length 0
00:50:21.495295 IP localhost.64307 > localhost.6900: Flags [.], ack 1, win 12759, options [nop,nop,TS val 766376063 ecr 766376063], length 0
00:50:21.495306 IP localhost.6900 > localhost.64307: Flags [.], ack 1, win 12759, options [nop,nop,TS val 766376063 ecr 766376063], length 0

You can force nc to use IPv4:

nc -4u localhost 6900
4

tl;dr

See conclusions at the very end.


Preamble

I can explain your results. I use Kubuntu, not Debian, still I believe the main points are common. To fully understand what happens it's good to analyze TCP case, then compare to UDP.

If you want to replicate my research, do not terminate nor kill nc unless I say so or it terminates by itself. Kill ncs when I say. It's very important. After reading the full answer you will understand the point of this.

This analysis requires multiple terminals (or tmux, or screen).


TCP case

At first get rid of all previous nc processes.

killall nc

Run a listening process:

nc -l 6900

Examine the output of lsof -i :6900. Your nc is listed as a listening process there.

Run a connecting process:

nc localhost 6900

Examine the output of lsof -i :6900. The two ncs are the ends of the established connection.

Because your first nc is not listening any more, you can run a second listening process:

nc -l 6900

and a second connecting process:

nc localhost 6900

Type something in every console where nc runs (don't forget to hit Enter every time). You will notice there are two separate connections. You can establish a third one if you wish.

Check lsof -i :6900 again. Although the two (formerly) listening ncs use the same port 6900, the two connections don't mix because ncs on the other ends use different ports. When a packet comes to the 6900 port, the kernel checks this other port and decides which of the (formerly) listening ncs should receive it.

You can run additional (unpaired) connecting nc:

nc localhost 6900 || echo fail

and it will fail immediately.

You still have two separate connections established. Terminate one nc of every pair with Ctrl + C or Ctrl + D and you will notice the corresponding ends will terminate also. This is because TCP is connection-oriented. When a connection is gracefully terminated from any of its ends, the other end is notified so it can react accordingly – in this case nc just exits.


UDP case

Important:

killall nc

Run the following commands in sequence, each in a separate console. Also check lsof -i :6900 after each one to see what happens. The commands are:

nc -ul 6900
nc -u localhost 6900

Type something in the first ("listening") nc (hit Enter); check lsof -i :6900; type something in the second nc (hit Enter); check lsof -i :6900 again and note the change.

Then prepare another pair (in separate consoles):

nc -ul 6900
nc -u localhost 6900

Pass some strings back and forth to see if it works. Terminate one nc with Ctrl + C (Ctrl + D won't work) and notice the other nc (on the corresponding end) still runs. The other side of the "connection" doesn't know when the "connection" is "terminated". As you could see with lsof it doesn't know the "connection" is "established" until the data starts flowing.

I quote some words here because UDP is connectionless and this is the main difference.


What can get complicated?

So far everything should have worked. It's time to start explaining your previous results when things didn't work.

Important:

killall nc

Prepare a "listening" nc:

nc -ul 6900

Check lsof -i :6900. The output will be like:

… UDP *:6900

Prepare a "connecting" nc:

nc -u localhost 6900

Check lsof -i :6900. Example output (your 54766 may vary):

… UDP *:6900
… UDP localhost:54766->localhost:6900

Pass some data from the second nc to the first. Check lsof -i :6900:

… UDP localhost:6900->localhost:54766
… UDP localhost:54766->localhost:6900

Terminate the second nc with Ctrl + C. Again lsof -i :6900:

… UDP localhost:6900->localhost:54766

This means the first nc "talks" only to the port 54766 on the other end of the "connection". When you try to connect with a third nc:

nc -u localhost 6900

It would most probably choose a different random port on its side. Try it. You will be able to "pass" (about) two lines, just like you did in your question, before this nc terminates.

(Note: this nc terminates due to ICMP packet, see this; you can capture the packet with wireshark like I did.)

However you can force the proper port (change 54766 to fit your lsof output):

nc -up 54766 localhost 6900

and this fourth nc will be able to pass data! The first nc won't see the difference. It will be as if the second nc was never terminated.

Terminate the fourth nc before you proceed. Leave the first one running.


The -v option

The last mystery is: why did nc -v -u localhost 6900 exit immediately?

In the above example we had four ncs:

  1. the first – "listening" one;
  2. the second – successfully connected, then terminated;
  3. the third – unable to connect due to wrong local port;
  4. the fourth – able to connect thanks to right (forced) local port.

In my Kubuntu nc -uv …, if it's the second one, transmits few X characters, probably to probe the connection. For this reason, if it's the third one, it doesn't need (about) two lines of external input to fail, it fails immediately. Having the first nc still running, try:

nc -uv localhost 6900

It should fail. Again, you can force the right port and it will work just like the fourth nc in the above example.

nc -uvp 54766 localhost 6900

When I invoke this, I can see X-s printed in the console of the first nc.


Cleaning

killall nc

Conclusions

It looks to me you had (the "listening") nc which was already associated with a certain port on the other side of the "connection", most certainly because of another nc (or something else) which had successfully transmitted at least one packet. Your other ncs tried to use other ports on their side and couldn't communicate with this first one.

If I replace localhost with 127.0.0.1, there is progress

Irrelevant. I guess in this case you just started clean, like we did after killall nc.

1
  • This answer provides excellent detail and I was able to follow it along all the way; however, I needed to amend many of the nc commands to additionally specify -4 per Daniel's answer.
    – jia103
    Aug 5, 2017 at 23:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.