Difference between /dev/udp and netcat

I have a syslog server listening on localhost:514 as UDP and would like to write messages to it on that port. (Using Ubuntu 14.04)

If I run either of these commands from bash it prints the date every 2 seconds to syslog

# Using netcat
while true; do sleep 2; date; done | nc -u localhost 514

# Using /dev/udp
while true; do sleep 2; date; done > /dev/udp/localhost/514

Now just to test things I kill the syslog server then start it up a few seconds later.

While the syslog process is dead, the /dev/udp command every 2 seconds prints an error message to the console, so it recognizes that there is no localhost:514 for it to write to.

date: write error: Connection refused

Once syslog is back, these connection refused messages stop and it resumes writing the date to syslog. This is as expected.

But the netcat command doesn't do this. While the syslog process is dead, it doesn't print any output to the console. And when syslog is back, it doesn't continue writing the date to syslog.

Why won't netcat continue writing to localhost:514 when syslog is restarted? How can I get netcat to behave the way /dev/udp does in this example?


Solution 1:

This appears to be a bug in nc. The nc command uses the poll system call to wait until input is received from either stdin or the socket.

When a UDP packet has been send to a closed UDP port on the receiving end, an error message is send back. The poll call will return this status to the nc command, but nc does not actually process the error. Instead nc goes back to call the poll system call again, which returns immediately due to the error still being queued on the socket.

This could have been an infinite loop, where nc would consume all the CPU time it could get doing nothing useful.

However it lasts only until the next message is received on stdin. At this point poll returns both statuses to nc, which process the data from stdin. Now nc will try to write the data from stdin to the socket. The attempt to write to the socket will deliver the queued error to nc. The write error message will cause nc to terminate.

This leaves you with a broken pipe (i.e. a pipe with a writer but no reader). Any attempt to write to the pipe will trigger a SIGPIPE signal and if the process isn't killed by the SIGPIPE an error from the write call.

date isn't handling SIGPIPE, so date is killed. So the loop keeps going, but each time date is killed when attempting to write to the pipe where the reader is long gone.

What can be done

Though nc looping on the poll system call without handing the error is definitely a bug, fixing that bug would not necessarily be enough to cover your need. Simply terminating once the error is returned from poll might be considered the correct bugfix for nc. One could imagine an additional feature, where nc could be told to keep going after an error if given a specific flag.

But you could work around the problem by simply restarting nc in a loop like this:

while sleep 1 ; do date ; done | while true ; do nc -u localhost 514 ; done

The bug causing periodic excessive CPU usage in nc, would still be there. Additionally the combination of that bug and the workaround can cause the first message after the receiving port has been reopened to get lost.