Skip to contentSkip to conversation

2.6 UDP recvmsg vs POSIX

PATCH In review Olaf Kirch 3 participants 8 messages Feb 17, 2004 – Feb 24, 2004

Subsystems (3)
lore
Share & tools
lore

Olaf Kirch

author
#0 lore raw

PATCH 1/1 2.6 UDP recvmsg vs POSIX

Hi,

I'm currently investigating a problem in udp recvmsg. In short, a test program selects on the socket, gets woken up as a packet arrives, but recvmsg return EAGAIN because the UDP checksum was wrong.

This broke one real-life application, and reportedly this violates POSIX, as operations on a blocking socket must never return EAGAIN.

The quick and dirty fix would be to always do the checksum in udp_queue_rcv_skb, but I assume this "checksum-outside-the-bottom-half" approach is there because it improves performance, so I've tried to come up with a different solution.

Please find attached a patch that tries to pluck bad udp packets from the receive queue as part of poll/select handling, so that we never mark a udp socket as POLLIN when there are only bad packets in the queue. This is admittedly not very elegant, and it may have side effects I'm not seeing yet. But it seems to fix things for me here, without slowing the bottom half handlers down.

The second half of the patch deals with bad checksums in recvmsg. If an application calls recvmsg on a blocking socket, and we find a packet with a bad checksum (this may happen if the application didn't select first), we start over at the top of the function, so that we block in skb_datagram_receive rather than returning EAGAIN.

Any comments welcome.

Olaf

Signature · 3 lines
Olaf Kirch     |  Stop wasting entropy - start using predictable
[email protected]   |  tempfile names today!
---------------+ 
include/net/udp.h renamed from include/net/udp.h.udpfix +4 -0
@@ -74,6 +74,10 @@
 extern int	udp_ioctl(struct sock *sk, int cmd, unsigned long arg);
 extern int	udp_disconnect(struct sock *sk, int flags);
 
+extern unsigned int udp_poll(struct file *file,
+			    struct socket *sock,
+			    struct poll_table_struct *wait);
+
 DECLARE_SNMP_STAT(struct udp_mib, udp_statistics);
 #define UDP_INC_STATS(field)		SNMP_INC_STATS(udp_statistics, field)
 #define UDP_INC_STATS_BH(field)		SNMP_INC_STATS_BH(udp_statistics, field)
net/ipv4/af_inet.c renamed from net/ipv4/af_inet.c.udpfix +1 -1
@@ -912,7 +912,7 @@
 	.socketpair =	sock_no_socketpair,
 	.accept =	sock_no_accept,
 	.getname =	inet_getname,
-	.poll =		datagram_poll,
+	.poll =		udp_poll,
 	.ioctl =	inet_ioctl,
 	.listen =	sock_no_listen,
 	.shutdown =	inet_shutdown,
net/ipv4/udp.c renamed from net/ipv4/udp.c.udpfix +60 -1

79 lines not on this page · raw message

#1 lore raw
Quoted 1 line · On Tue, 17 Feb 2004 13:17:26 +0100 Olaf Kirch <[email protected]> wrote:

Any comments welcome.

Looks good to me.

One small optimization would be to quickly check the receive queue without taking the receive queue lock. This would avoid the additional lock in many cases.

-Andi

#2 lore raw
In reply to Olaf Kirch
Quoted 3 lines · On Tue, 17 Feb 2004 13:17:26 +0100 Olaf Kirch <[email protected]> wrote:

I'm currently investigating a problem in udp recvmsg. In short, a test program selects on the socket, gets woken up as a packet arrives, but recvmsg return EAGAIN because the UDP checksum was wrong.

Thanks Olaf, I'm not ignoring your patch I just didn't get to it today.

#3 lore raw
Quoted 8 lines · On Tue, 17 Feb 2004 22:17:05 -0800 "David S. Miller" <[email protected]> wrote:
Quoted 3 lines · On Tue, 17 Feb 2004 13:17:26 +0100 Olaf Kirch <[email protected]> wrote:

I'm currently investigating a problem in udp recvmsg. In short, a test program selects on the socket, gets woken up as a packet arrives, but recvmsg return EAGAIN because the UDP checksum was wrong.

Thanks Olaf, I'm not ignoring your patch I just didn't get to it today.

BTW I think TCP could hit the same problem. But it did already csum-copy in user context in 2.4 and nobody reported it.

-Andi

#4 lore raw
In reply to Olaf Kirch
Quoted 6 lines · On Tue, 17 Feb 2004 13:17:26 +0100 Olaf Kirch <[email protected]> wrote:

I'm currently investigating a problem in udp recvmsg. In short, a test program selects on the socket, gets woken up as a packet arrives, but recvmsg return EAGAIN because the UDP checksum was wrong.

This broke one real-life application, and reportedly this violates POSIX, as operations on a blocking socket must never return EAGAIN.

Well, first things first, using blocking sockets with select/poll is kind of stupid programming.

Nonetheless, udp_recvmsg() can't return -EAGAIN on a blocking socket.

Andi, TCP does not have this issue, it simply goes back to sleep waiting for more data to arrive if the prequeue delayed checksumming fails.

I think we should fix it like the following, which is basically the last part of Olaf's original patch in this thread. Just wait for another packet if a blocking socket and checksum fails, else if non-blocking -EAGAIN is OK.

To be honest, I believe this was Alexey's original intention when he wrote this code.

===== net/ipv4/udp.c 1.56 vs edited =====

net/ipv4/udp.c +4 -1
@@ -787,6 +787,7 @@
 	if (flags & MSG_ERRQUEUE)
 		return ip_recv_error(sk, msg, len);
 
+try_again:
 	skb = skb_recv_datagram(sk, flags, noblock, &err);
 	if (!skb)
 		goto out;
@@ -852,7 +853,9 @@
 
 	skb_free_datagram(sk, skb);
 
-	return -EAGAIN;	
+	if (noblock)
+		return -EAGAIN;	
+	goto try_again;
 }
 
 int udp_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
author
#5 lore raw
Quoted 2 lines · David S. Miller

Well, first things first, using blocking sockets with select/poll is kind of stupid programming.

But that's the way many real world applications do it, including for instance the glibc sunrpc code.

Quoted 3 lines

I think we should fix it like the following, which is basically the last part of Olaf's original patch in this thread. Just wait for another packet if a blocking socket and checksum fails, else if non-blocking -EAGAIN is OK.

But that breaks poll/recvmsg on a blocking socket, because poll with assert POLLIN, but the recvmsg call with block.

I agree that plucking packets off the queue inside poll() is ugly. The alternative would be to just walk the queue to see if there is at least one packet with valid checksum, and clear POLLIN if there isn't, but otherwise leave the queue untouched. This would work in all except the most extreme scenarios where your recv queue is filled to the limit with bad packets, and any new packets with potentially good checksums get dropped. This would make it easy to DoS any UDP based service by spraying it with bad udp packets.

Olaf

Signature · 3 lines
Olaf Kirch     |  Stop wasting entropy - start using predictable
[email protected]   |  tempfile names today!
---------------+ 
#6 lore raw
Quoted 2 lines · On Mon, 23 Feb 2004 10:42:10 +0100 Olaf Kirch <[email protected]> wrote:

But that breaks poll/recvmsg on a blocking socket, because poll with assert POLLIN, but the recvmsg call with block.

There is nothing wrong with that, I even pinged Linus about this and he agreed with me.

What stops another thread from pulling a packet from the receive queue and thus emptying it? Nothing. POLLIN does not guarentee a read will give you data right now, it never has and it never will.

Therefore, the only bug was returning -EAGAIN unconditionally and that's what is fixed by the correct half of your changes.

TCP does the same thing Olaf, and there is no way I'm adding the gross poll hacks there :-)

author
#7 lore raw
Quoted 5 lines · David S. Miller

Therefore, the only bug was returning -EAGAIN unconditionally and that's what is fixed by the correct half of your changes.

TCP does the same thing Olaf, and there is no way I'm adding the gross poll hacks there :-)

Okay, I can live with that. I will push this info to our posix guys and let them chew on it.

UDPv6 needs the exact same fix BTW.

Cheers,
Olaf

Signature · 3 lines
Olaf Kirch     |  Stop wasting entropy - start using predictable
[email protected]   |  tempfile names today!
---------------+