Skip to content

Commit 10154db

Browse files
jsitnickikuba-moo
authored andcommitted
udp: Allow GSO transmit from devices with no checksum offload
Today sending a UDP GSO packet from a TUN device results in an EIO error: import fcntl, os, struct from socket import * TUNSETIFF = 0x400454CA IFF_TUN = 0x0001 IFF_NO_PI = 0x1000 UDP_SEGMENT = 103 tun_fd = os.open("/dev/net/tun", os.O_RDWR) ifr = struct.pack("16sH", b"tun0", IFF_TUN | IFF_NO_PI) fcntl.ioctl(tun_fd, TUNSETIFF, ifr) os.system("ip addr add 192.0.2.1/24 dev tun0") os.system("ip link set dev tun0 up") s = socket(AF_INET, SOCK_DGRAM) s.setsockopt(SOL_UDP, UDP_SEGMENT, 1200) s.sendto(b"x" * 3000, ("192.0.2.2", 9)) # EIO This is due to a check in the udp stack if the egress device offers checksum offload. While TUN/TAP devices, by default, don't advertise this capability because it requires support from the TUN/TAP reader. However, the GSO stack has a software fallback for checksum calculation, which we can use. This way we don't force UDP_SEGMENT users to handle the EIO error and implement a segmentation fallback. Lift the restriction so that UDP_SEGMENT can be used with any egress device. We also need to adjust the UDP GSO code to match the GSO stack expectation about ip_summed field, as set in commit 8d63bee ("net: avoid skb_warn_bad_offload false positives on UFO"). Otherwise we will hit the bad offload check. Users should, however, expect a potential performance impact when batch-sending packets with UDP_SEGMENT without checksum offload on the egress device. In such case the packet payload is read twice: first during the sendmsg syscall when copying data from user memory, and then in the GSO stack for checksum computation. This double memory read can be less efficient than a regular sendmsg where the checksum is calculated during the initial data copy from user memory. Signed-off-by: Jakub Sitnicki <[email protected]> Reviewed-by: Willem de Bruijn <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 748e3bb commit 10154db

File tree

3 files changed

+10
-4
lines changed

3 files changed

+10
-4
lines changed

net/ipv4/udp.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -938,8 +938,7 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
938938
kfree_skb(skb);
939939
return -EINVAL;
940940
}
941-
if (skb->ip_summed != CHECKSUM_PARTIAL || is_udplite ||
942-
dst_xfrm(skb_dst(skb))) {
941+
if (is_udplite || dst_xfrm(skb_dst(skb))) {
943942
kfree_skb(skb);
944943
return -EIO;
945944
}

net/ipv4/udp_offload.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,14 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
357357
else
358358
uh->check = gso_make_checksum(seg, ~check) ? : CSUM_MANGLED_0;
359359

360+
/* On the TX path, CHECKSUM_NONE and CHECKSUM_UNNECESSARY have the same
361+
* meaning. However, check for bad offloads in the GSO stack expects the
362+
* latter, if the checksum was calculated in software. To vouch for the
363+
* segment skbs we actually need to set it on the gso_skb.
364+
*/
365+
if (gso_skb->ip_summed == CHECKSUM_NONE)
366+
gso_skb->ip_summed = CHECKSUM_UNNECESSARY;
367+
360368
/* update refcount for the packet */
361369
if (copy_dtor) {
362370
int delta = sum_truesize - gso_skb->truesize;

net/ipv6/udp.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,8 +1257,7 @@ static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6,
12571257
kfree_skb(skb);
12581258
return -EINVAL;
12591259
}
1260-
if (skb->ip_summed != CHECKSUM_PARTIAL || is_udplite ||
1261-
dst_xfrm(skb_dst(skb))) {
1260+
if (is_udplite || dst_xfrm(skb_dst(skb))) {
12621261
kfree_skb(skb);
12631262
return -EIO;
12641263
}

0 commit comments

Comments
 (0)