Skip to content

Commit cb23e30

Browse files
committed
packet: use bpf_csum_diff to patch ICMP/ICMPv6 checksum
Instead of a handmade solution to compute a new checksum, use the standard bpf_csum_diff function. However, leave the handmade solution as a comment in code, as it better illustrates how the checksum actually changes. Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
1 parent 860e2c4 commit cb23e30

2 files changed

Lines changed: 53 additions & 12 deletions

File tree

packet-solutions/xdp_prog_kern.c

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,27 @@ int xdp_vlan_swap_func(struct xdp_md *ctx)
5757
return XDP_PASS;
5858
}
5959

60-
static __always_inline __u16 csum16_add(__u16 csum, __u16 addend)
60+
static __always_inline __u16 csum_fold_helper(__u32 csum)
6161
{
62-
csum += addend;
63-
return csum + (csum < addend);
62+
return ~((csum & 0xffff) + (csum >> 16));
63+
}
64+
65+
/*
66+
* The icmp_checksum_diff function takes pointers to old and new structures and
67+
* the old checksum and returns the new checksum. It uses the bpf_csum_diff
68+
* helper to compute the checksum difference. Note that the sizes passed to the
69+
* bpf_csum_diff helper should be multiples of 4, as it operates on 32-bit
70+
* words.
71+
*/
72+
static __always_inline __u16 icmp_checksum_diff(
73+
__u16 seed,
74+
struct icmphdr_common *icmphdr_new,
75+
struct icmphdr_common *icmphdr_old)
76+
{
77+
__u32 csum, size = sizeof(struct icmphdr_common);
78+
79+
csum = bpf_csum_diff(icmphdr_old, size, icmphdr_new, size, seed);
80+
return csum_fold_helper(csum);
6481
}
6582

6683
/* Solution to packet03/assignment-1 */
@@ -76,8 +93,9 @@ int xdp_icmp_echo_func(struct xdp_md *ctx)
7693
int icmp_type;
7794
struct iphdr *iphdr;
7895
struct ipv6hdr *ipv6hdr;
79-
__u16 echo_reply, m0, m1;
96+
__u16 echo_reply, old_csum;
8097
struct icmphdr_common *icmphdr;
98+
struct icmphdr_common icmphdr_old;
8199
__u32 action = XDP_PASS;
82100

83101
/* These keep track of the next header type and iterator pointer */
@@ -119,11 +137,34 @@ int xdp_icmp_echo_func(struct xdp_md *ctx)
119137
/* Swap Ethernet source and destination */
120138
swap_src_dst_mac(eth);
121139

122-
/* Patch the packet and update the checksum */
123-
m0 = * (__u16 *) icmphdr;
140+
141+
/* Patch the packet and update the checksum.*/
142+
old_csum = icmphdr->cksum;
143+
icmphdr->cksum = 0;
144+
icmphdr_old = *icmphdr;
124145
icmphdr->type = echo_reply;
125-
m1 = * (__u16 *) icmphdr;
126-
icmphdr->checksum = ~(csum16_add(csum16_add(~icmphdr->checksum, ~m0), m1));
146+
icmphdr->cksum = icmp_checksum_diff(~old_csum, icmphdr, &icmphdr_old);
147+
148+
/* Another, less generic, but a bit more efficient way to update the
149+
* checksum is listed below. As only one 16-bit word changed, the sum
150+
* can be patched using this formula: sum' = ~(~sum + ~m0 + m1), where
151+
* sum' is a new sum, sum is an old sum, m0 and m1 are the old and new
152+
* 16-bit words, correspondingly. In the formula above the + operation
153+
* is defined as the following function:
154+
*
155+
* static __always_inline __u16 csum16_add(__u16 csum, __u16 addend)
156+
* {
157+
* csum += addend;
158+
* return csum + (csum < addend);
159+
* }
160+
*
161+
* So an alternative code to update the checksum might look like this:
162+
*
163+
* __u16 m0 = * (__u16 *) icmphdr;
164+
* icmphdr->type = echo_reply;
165+
* __u16 m1 = * (__u16 *) icmphdr;
166+
* icmphdr->checksum = ~(csum16_add(csum16_add(~icmphdr->checksum, ~m0), m1));
167+
*/
127168

128169
action = XDP_TX;
129170

packet03-redirecting/README.org

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ Use the =XDP_TX= functionality to implement an ICMP/ICMPv6 echo server.
8181
Namely, swap destination and source MAC addresses, then swap IP/IPv6 addresses,
8282
and, finally, replace the ICMP/ICMPv6 =Type= field with an appropriate value.
8383

84-
Note that changing the =Type= field will affect the ICMP checksum. However,
85-
as only a small part of the packet is changing, the Incremental Internet
86-
Checksum (RFC 1624) can be used. Hint: see the =csum_replace2= function inside
87-
the Linux kernel, or use the =bpf_csum_diff= kernel helper.
84+
Note that changing the =Type= field will affect the ICMP checksum. However, as
85+
only a small part of the packet is changing, the Incremental Internet Checksum
86+
(RFC 1624) can be used, so use the =bpf_csum_diff= helper to update the
87+
checksum.
8888

8989
To test the echo server create a new environment with both address families
9090
supported and load the XDP program:

0 commit comments

Comments
 (0)