udp: Remove partial csum code in RX.

UDP-Lite supports the partial checksum and the coverage is
stored in the position of the length field of struct udphdr.

In RX paths, udp4_csum_init() / udp6_csum_init() save the value
in UDP_SKB_CB(skb)->cscov and set UDP_SKB_CB(skb)->partial_cov
to 1 if the coverage is not full.

The subsequent processing diverges depending on the value,
but such paths are now dead.

Also, these functions have some code guarded for UDP:

  * udp_unicast_rcv_skb / udp6_unicast_rcv_skb
  * __udp4_lib_rcv() and __udp6_lib_rcv().

Let's remove the partial csum code and the unnecessary
guard for UDP-Lite in RX.

Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Link: https://patch.msgid.link/20260311052020.1213705-8-kuniyu@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Kuniyuki Iwashima 2026-03-11 05:19:54 +00:00 committed by Jakub Kicinski
parent b972cb5d39
commit c2539d4f2d
4 changed files with 39 additions and 179 deletions

View File

@ -32,11 +32,9 @@
#include <linux/math.h>
/**
* struct udp_skb_cb - UDP(-Lite) private variables
* struct udp_skb_cb - UDP private variables
*
* @header: private variables used by IPv4/IPv6
* @cscov: checksum coverage length (UDP-Lite only)
* @partial_cov: if set indicates partial csum coverage
*/
struct udp_skb_cb {
union {
@ -45,8 +43,6 @@ struct udp_skb_cb {
struct inet6_skb_parm h6;
#endif
} header;
__u16 cscov;
__u8 partial_cov;
};
#define UDP_SKB_CB(__skb) ((struct udp_skb_cb *)((__skb)->cb))
@ -216,13 +212,11 @@ extern int sysctl_udp_wmem_min;
struct sk_buff;
/*
* Generic checksumming routines for UDP(-Lite) v4 and v6
* Generic checksumming routines for UDP v4 and v6
*/
static inline __sum16 __udp_lib_checksum_complete(struct sk_buff *skb)
{
return (UDP_SKB_CB(skb)->cscov == skb->len ?
__skb_checksum_complete(skb) :
__skb_checksum_complete_head(skb, UDP_SKB_CB(skb)->cscov));
return __skb_checksum_complete(skb);
}
static inline int udp_lib_checksum_complete(struct sk_buff *skb)
@ -273,7 +267,6 @@ static inline void udp_csum_pull_header(struct sk_buff *skb)
skb->csum = csum_partial(skb->data, sizeof(struct udphdr),
skb->csum);
skb_pull_rcsum(skb, sizeof(struct udphdr));
UDP_SKB_CB(skb)->cscov -= sizeof(struct udphdr);
}
typedef struct sock *(*udp_lookup_t)(const struct sk_buff *skb, __be16 sport,
@ -641,9 +634,6 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
static inline void udp_post_segment_fix_csum(struct sk_buff *skb)
{
/* UDP-lite can't land here - no GRO */
WARN_ON_ONCE(UDP_SKB_CB(skb)->partial_cov);
/* UDP packets generated with UDP_SEGMENT and traversing:
*
* UDP tunnel(xmit) -> veth (segmentation) -> veth (gro) -> UDP tunnel (rx)
@ -657,7 +647,6 @@ static inline void udp_post_segment_fix_csum(struct sk_buff *skb)
* a valid csum after the segmentation.
* Additionally fixup the UDP CB.
*/
UDP_SKB_CB(skb)->cscov = skb->len;
if (skb->ip_summed == CHECKSUM_NONE && !skb->csum_valid)
skb->csum_valid = 1;
}

View File

@ -25,40 +25,6 @@ static __inline__ int udplite_getfrag(void *from, char *to, int offset,
/*
* Checksumming routines
*/
static inline int udplite_checksum_init(struct sk_buff *skb, struct udphdr *uh)
{
u16 cscov;
/* In UDPv4 a zero checksum means that the transmitter generated no
* checksum. UDP-Lite (like IPv6) mandates checksums, hence packets
* with a zero checksum field are illegal. */
if (uh->check == 0) {
net_dbg_ratelimited("UDPLite: zeroed checksum field\n");
return 1;
}
cscov = ntohs(uh->len);
if (cscov == 0) /* Indicates that full coverage is required. */
;
else if (cscov < 8 || cscov > skb->len) {
/*
* Coverage length violates RFC 3828: log and discard silently.
*/
net_dbg_ratelimited("UDPLite: bad csum coverage %d/%d\n",
cscov, skb->len);
return 1;
} else if (cscov < skb->len) {
UDP_SKB_CB(skb)->partial_cov = 1;
UDP_SKB_CB(skb)->cscov = cscov;
if (skb->ip_summed == CHECKSUM_COMPLETE)
skb->ip_summed = CHECKSUM_NONE;
skb->csum_valid = 0;
}
return 0;
}
/* Fast-path computation of checksum. Socket may not be locked. */
static inline __wsum udplite_csum(struct sk_buff *skb)

View File

@ -2072,14 +2072,13 @@ EXPORT_IPV6_MOD(udp_read_skb);
INDIRECT_CALLABLE_SCOPE
int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
{
struct inet_sock *inet = inet_sk(sk);
DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name);
struct sk_buff *skb;
unsigned int ulen, copied;
int off, err, peeking = flags & MSG_PEEK;
int is_udplite = IS_UDPLITE(sk);
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
bool checksum_valid = false;
unsigned int ulen, copied;
struct sk_buff *skb;
if (flags & MSG_ERRQUEUE)
return ip_recv_error(sk, msg, len);
@ -2097,14 +2096,10 @@ int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
else if (copied < ulen)
msg->msg_flags |= MSG_TRUNC;
/*
* If checksum is needed at all, try to do it while copying the
* data. If the data is truncated, or if we only want a partial
* coverage checksum (UDP-Lite), do it before the copy.
/* If checksum is needed at all, try to do it while copying the
* data. If the data is truncated, do it before the copy.
*/
if (copied < ulen || peeking ||
(is_udplite && UDP_SKB_CB(skb)->partial_cov)) {
if (copied < ulen || peeking) {
checksum_valid = udp_skb_csum_unnecessary(skb) ||
!__udp_lib_checksum_complete(skb);
if (!checksum_valid)
@ -2444,42 +2439,6 @@ static int udp_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
/* FALLTHROUGH -- it's a UDP Packet */
}
/*
* UDP-Lite specific tests, ignored on UDP sockets
*/
if (unlikely(udp_test_bit(UDPLITE_RECV_CC, sk) &&
UDP_SKB_CB(skb)->partial_cov)) {
u16 pcrlen = READ_ONCE(up->pcrlen);
/*
* MIB statistics other than incrementing the error count are
* disabled for the following two types of errors: these depend
* on the application settings, not on the functioning of the
* protocol stack as such.
*
* RFC 3828 here recommends (sec 3.3): "There should also be a
* way ... to ... at least let the receiving application block
* delivery of packets with coverage values less than a value
* provided by the application."
*/
if (pcrlen == 0) { /* full coverage was set */
net_dbg_ratelimited("UDPLite: partial coverage %d while full coverage %d requested\n",
UDP_SKB_CB(skb)->cscov, skb->len);
goto drop;
}
/* The next case involves violating the min. coverage requested
* by the receiver. This is subtle: if receiver wants x and x is
* greater than the buffersize/MTU then receiver will complain
* that it wants x while sender emits packets of smaller size y.
* Therefore the above ...()->partial_cov statement is essential.
*/
if (UDP_SKB_CB(skb)->cscov < pcrlen) {
net_dbg_ratelimited("UDPLite: coverage %d too small, need min %d\n",
UDP_SKB_CB(skb)->cscov, pcrlen);
goto drop;
}
}
prefetch(&sk->sk_rmem_alloc);
if (rcu_access_pointer(sk->sk_filter) &&
udp_lib_checksum_complete(skb))
@ -2613,29 +2572,14 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
* Otherwise, csum completion requires checksumming packet body,
* including udp header and folding it to skb->csum.
*/
static inline int udp4_csum_init(struct sk_buff *skb, struct udphdr *uh,
int proto)
static inline int udp4_csum_init(struct sk_buff *skb, struct udphdr *uh)
{
int err;
UDP_SKB_CB(skb)->partial_cov = 0;
UDP_SKB_CB(skb)->cscov = skb->len;
if (proto == IPPROTO_UDPLITE) {
err = udplite_checksum_init(skb, uh);
if (err)
return err;
if (UDP_SKB_CB(skb)->partial_cov) {
skb->csum = inet_compute_pseudo(skb, proto);
return 0;
}
}
/* Note, we are only interested in != 0 or == 0, thus the
* force to int.
*/
err = (__force int)skb_checksum_init_zero_check(skb, proto, uh->check,
err = (__force int)skb_checksum_init_zero_check(skb, IPPROTO_UDP, uh->check,
inet_compute_pseudo);
if (err)
return err;
@ -2663,7 +2607,7 @@ static int udp_unicast_rcv_skb(struct sock *sk, struct sk_buff *skb,
{
int ret;
if (inet_get_convert_csum(sk) && uh->check && !IS_UDPLITE(sk))
if (inet_get_convert_csum(sk) && uh->check)
skb_checksum_try_convert(skb, IPPROTO_UDP, inet_compute_pseudo);
ret = udp_queue_rcv_skb(sk, skb);
@ -2708,14 +2652,17 @@ static int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
if (ulen > skb->len)
goto short_packet;
if (proto == IPPROTO_UDP) {
/* UDP validates ulen. */
if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))
if (ulen < sizeof(*uh))
goto short_packet;
if (ulen < skb->len) {
if (pskb_trim_rcsum(skb, ulen))
goto short_packet;
uh = udp_hdr(skb);
}
if (udp4_csum_init(skb, uh, proto))
if (udp4_csum_init(skb, uh))
goto csum_error;
sk = inet_steal_sock(net, skb, sizeof(struct udphdr), saddr, uh->source, daddr, uh->dest,

View File

@ -469,15 +469,13 @@ INDIRECT_CALLABLE_SCOPE
int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
int flags)
{
int off, is_udp4, err, peeking = flags & MSG_PEEK;
struct ipv6_pinfo *np = inet6_sk(sk);
struct inet_sock *inet = inet_sk(sk);
struct sk_buff *skb;
unsigned int ulen, copied;
int off, err, peeking = flags & MSG_PEEK;
int is_udplite = IS_UDPLITE(sk);
struct udp_mib __percpu *mib;
bool checksum_valid = false;
int is_udp4;
unsigned int ulen, copied;
struct sk_buff *skb;
if (flags & MSG_ERRQUEUE)
return ipv6_recv_error(sk, msg, len);
@ -501,14 +499,10 @@ int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
is_udp4 = (skb->protocol == htons(ETH_P_IP));
mib = __UDPX_MIB(sk, is_udp4);
/*
* If checksum is needed at all, try to do it while copying the
* data. If the data is truncated, or if we only want a partial
* coverage checksum (UDP-Lite), do it before the copy.
/* If checksum is needed at all, try to do it while copying the
* data. If the data is truncated, do it before the copy.
*/
if (copied < ulen || peeking ||
(is_udplite && UDP_SKB_CB(skb)->partial_cov)) {
if (copied < ulen || peeking) {
checksum_valid = udp_skb_csum_unnecessary(skb) ||
!__udp_lib_checksum_complete(skb);
if (!checksum_valid)
@ -870,25 +864,6 @@ static int udpv6_queue_rcv_one_skb(struct sock *sk, struct sk_buff *skb)
/* FALLTHROUGH -- it's a UDP Packet */
}
/*
* UDP-Lite specific tests, ignored on UDP sockets (see net/ipv4/udp.c).
*/
if (unlikely(udp_test_bit(UDPLITE_RECV_CC, sk) &&
UDP_SKB_CB(skb)->partial_cov)) {
u16 pcrlen = READ_ONCE(up->pcrlen);
if (pcrlen == 0) { /* full coverage was set */
net_dbg_ratelimited("UDPLITE6: partial coverage %d while full coverage %d requested\n",
UDP_SKB_CB(skb)->cscov, skb->len);
goto drop;
}
if (UDP_SKB_CB(skb)->cscov < pcrlen) {
net_dbg_ratelimited("UDPLITE6: coverage %d too small, need min %d\n",
UDP_SKB_CB(skb)->cscov, pcrlen);
goto drop;
}
}
prefetch(&sk->sk_rmem_alloc);
if (rcu_access_pointer(sk->sk_filter) &&
udp_lib_checksum_complete(skb))
@ -1053,7 +1028,7 @@ static int udp6_unicast_rcv_skb(struct sock *sk, struct sk_buff *skb,
{
int ret;
if (inet_get_convert_csum(sk) && uh->check && !IS_UDPLITE(sk))
if (inet_get_convert_csum(sk) && uh->check)
skb_checksum_try_convert(skb, IPPROTO_UDP, ip6_compute_pseudo);
ret = udpv6_queue_rcv_skb(sk, skb);
@ -1064,24 +1039,10 @@ static int udp6_unicast_rcv_skb(struct sock *sk, struct sk_buff *skb,
return 0;
}
static int udp6_csum_init(struct sk_buff *skb, struct udphdr *uh, int proto)
static int udp6_csum_init(struct sk_buff *skb, struct udphdr *uh)
{
int err;
UDP_SKB_CB(skb)->partial_cov = 0;
UDP_SKB_CB(skb)->cscov = skb->len;
if (proto == IPPROTO_UDPLITE) {
err = udplite_checksum_init(skb, uh);
if (err)
return err;
if (UDP_SKB_CB(skb)->partial_cov) {
skb->csum = ip6_compute_pseudo(skb, proto);
return 0;
}
}
/* To support RFC 6936 (allow zero checksum in UDP/IPV6 for tunnels)
* we accept a checksum of zero here. When we find the socket
* for the UDP packet we'll check if that socket allows zero checksum
@ -1090,7 +1051,7 @@ static int udp6_csum_init(struct sk_buff *skb, struct udphdr *uh, int proto)
* Note, we are only interested in != 0 or == 0, thus the
* force to int.
*/
err = (__force int)skb_checksum_init_zero_check(skb, proto, uh->check,
err = (__force int)skb_checksum_init_zero_check(skb, IPPROTO_UDP, uh->check,
ip6_compute_pseudo);
if (err)
return err;
@ -1132,26 +1093,23 @@ static int __udp6_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
if (ulen > skb->len)
goto short_packet;
if (proto == IPPROTO_UDP) {
/* UDP validates ulen. */
/* Check for jumbo payload */
if (ulen == 0)
ulen = skb->len;
/* Check for jumbo payload */
if (ulen == 0)
ulen = skb->len;
if (ulen < sizeof(*uh))
goto short_packet;
if (ulen < sizeof(*uh))
if (ulen < skb->len) {
if (pskb_trim_rcsum(skb, ulen))
goto short_packet;
if (ulen < skb->len) {
if (pskb_trim_rcsum(skb, ulen))
goto short_packet;
saddr = &ipv6_hdr(skb)->saddr;
daddr = &ipv6_hdr(skb)->daddr;
uh = udp_hdr(skb);
}
saddr = &ipv6_hdr(skb)->saddr;
daddr = &ipv6_hdr(skb)->daddr;
uh = udp_hdr(skb);
}
if (udp6_csum_init(skb, uh, proto))
if (udp6_csum_init(skb, uh))
goto csum_error;
/* Check if the socket is already available, e.g. due to early demux */