Skip to content

Commit aadc4ff

Browse files
yushoyamaguchijulianwiedmann
authored andcommitted
ICMP csum handling in SNAT RevNAT
When a LB node forwards packets to a backend-pod on another node, the packets are SNATed. The corresponding reverse NAT (RevNAT) is then applied to the reply packets. Checksum recaluculation is required when RevNAT. When the reply packet is an ICMP error packet, the checksum in the ICMP header must be recalculated to reflect the changes of the inner packet header. https://datatracker.ietf.org/doc/html/rfc5508#section-4.1 Since that logic was missing, it has been added in this commit. Fixes: cilium#40827 Signed-off-by: Yusho Yamaguchi <ysh.824@outlook.jp>
1 parent dd58308 commit aadc4ff

1 file changed

Lines changed: 69 additions & 9 deletions

File tree

bpf/lib/nat.h

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,14 @@ static __always_inline int
469469
snat_v4_rewrite_headers(struct __ctx_buff *ctx, __u8 nexthdr, int l3_off,
470470
bool has_l4_header, int l4_off,
471471
__be32 old_addr, __be32 new_addr, __u16 addr_off,
472-
__be16 old_port, __be16 new_port, __u16 port_off)
472+
__be16 old_port, __be16 new_port, __u16 port_off,
473+
__wsum l4_csum_diff_from_inner)
473474
{
474475
__wsum sum;
475476
int err;
476477

477478
/* No change needed: */
478-
if (old_addr == new_addr && old_port == new_port)
479+
if (old_addr == new_addr && old_port == new_port && !l4_csum_diff_from_inner)
479480
return 0;
480481

481482
sum = csum_diff(&old_addr, 4, &new_addr, 4, 0);
@@ -527,11 +528,46 @@ snat_v4_rewrite_headers(struct __ctx_buff *ctx, __u8 nexthdr, int l3_off,
527528
if (csum.offset &&
528529
csum_l4_replace(ctx, l4_off, &csum, 0, sum, flags) < 0)
529530
return DROP_CSUM_L4;
531+
532+
/* Apply additional L4 checksum diff if provided (for ICMP error messages). */
533+
if (l4_csum_diff_from_inner && !csum.offset) {
534+
csum.offset = offsetof(struct icmphdr, checksum);
535+
if (csum_l4_replace(ctx, l4_off, &csum, 0, l4_csum_diff_from_inner, 0) < 0)
536+
return DROP_CSUM_L4;
537+
}
530538
}
531539

532540
return 0;
533541
}
534542

543+
static __always_inline void
544+
snat_v4_calc_icmp_error_csum_diff(__be32 old_addr, __be32 new_addr,
545+
__be16 old_port, __be16 new_port,
546+
bool inner_has_l4_csum, __wsum *diff_for_csum)
547+
{
548+
__be32 old_port32 = (__be32)old_port;
549+
__be32 new_port32 = (__be32)new_port;
550+
551+
*diff_for_csum = 0;
552+
553+
if (inner_has_l4_csum) {
554+
/* Calculate diff value for checksum.
555+
* Reflect the change in the inner L4 checksum caused by the pseudo-address update
556+
* into diff_for_csum.
557+
* All the other changes in inner packet cancel each other out.
558+
*/
559+
if (old_addr != new_addr)
560+
*diff_for_csum = csum_diff(&new_addr, 4, &old_addr, 4, 0);
561+
} else {
562+
/* Calculate diff value for checksum.
563+
* If the inner L4 header does not include the L4 checksum,
564+
* only the port is modified within the inner L4 header.
565+
*/
566+
if (old_port != new_port)
567+
*diff_for_csum = csum_diff(&old_port32, 4, &new_port32, 4, 0);
568+
}
569+
}
570+
535571
static __always_inline bool
536572
snat_v4_nat_can_skip(const struct ipv4_nat_target *target,
537573
const struct ipv4_ct_tuple *tuple)
@@ -862,7 +898,7 @@ snat_v4_nat_handle_icmp_error(struct __ctx_buff *ctx, __u64 off,
862898
*/
863899
ret = snat_v4_rewrite_headers(ctx, tuple.nexthdr, inner_l3_off, true, icmpoff,
864900
tuple.saddr, (*state)->to_saddr, IPV4_DADDR_OFF,
865-
tuple.sport, (*state)->to_sport, port_off);
901+
tuple.sport, (*state)->to_sport, port_off, 0);
866902
/* Failing to update the inner L4 checksum is not fatal if the header
867903
* is incomplete.
868904
*/
@@ -897,7 +933,7 @@ __snat_v4_nat(struct __ctx_buff *ctx, struct ipv4_ct_tuple *tuple,
897933
ret = snat_v4_rewrite_headers(ctx, tuple->nexthdr, ETH_HLEN,
898934
ipfrag_has_l4_header(fraginfo), l4_off,
899935
tuple->saddr, state->to_saddr, IPV4_SADDR_OFF,
900-
tuple->sport, to_sport, port_off);
936+
tuple->sport, to_sport, port_off, 0);
901937

902938
if (update_tuple) {
903939
tuple->saddr = state->to_saddr;
@@ -1005,14 +1041,16 @@ snat_v4_nat(struct __ctx_buff *ctx, struct ipv4_ct_tuple *tuple,
10051041
static __always_inline __maybe_unused int
10061042
snat_v4_rev_nat_handle_icmp_error(struct __ctx_buff *ctx,
10071043
__u64 inner_l3_off,
1008-
struct ipv4_nat_entry **state)
1044+
struct ipv4_nat_entry **state,
1045+
__wsum *outer_csum_diff)
10091046
{
10101047
struct ipv4_ct_tuple tuple = {};
10111048
struct iphdr iphdr;
10121049
__u16 port_off;
10131050
__u32 icmpoff;
10141051
__u8 type;
10151052
bool icmp_has_inner_l4_csum = true;
1053+
bool is_inner_l4_csum_enabled = true;
10161054
int ret;
10171055
__u32 total_inner_len = (__u32)(ctx_full_len(ctx) - inner_l3_off);
10181056

@@ -1080,10 +1118,29 @@ snat_v4_rev_nat_handle_icmp_error(struct __ctx_buff *ctx,
10801118
total_inner_len < iphdr.ihl + TCP_CSUM_OFF + sizeof(__u16))
10811119
icmp_has_inner_l4_csum = false;
10821120

1121+
/* For UDP, a checksum value of zero means that no checksum */
1122+
if (tuple.nexthdr == IPPROTO_UDP) {
1123+
__be16 l4_csum_be = 0;
1124+
1125+
if (ctx_load_bytes(ctx, icmpoff + offsetof(struct udphdr, check),
1126+
&l4_csum_be, sizeof(l4_csum_be)) < 0)
1127+
return DROP_INVALID;
1128+
if (l4_csum_be == 0)
1129+
is_inner_l4_csum_enabled = false;
1130+
}
1131+
1132+
/* Calculate the diff for the outer ICMP checksum. */
1133+
snat_v4_calc_icmp_error_csum_diff(tuple.daddr, (*state)->to_daddr,
1134+
tuple.dport, (*state)->to_dport,
1135+
icmp_has_inner_l4_csum &&
1136+
is_inner_l4_csum_enabled,
1137+
outer_csum_diff);
1138+
10831139
/* The embedded packet was SNATed on egress. Reverse it again: */
1084-
ret = snat_v4_rewrite_headers(ctx, tuple.nexthdr, (int)inner_l3_off, true, icmpoff,
1140+
ret = snat_v4_rewrite_headers(ctx, tuple.nexthdr, (int)inner_l3_off,
1141+
true, icmpoff,
10851142
tuple.daddr, (*state)->to_daddr, IPV4_SADDR_OFF,
1086-
tuple.dport, (*state)->to_dport, port_off);
1143+
tuple.dport, (*state)->to_dport, port_off, 0);
10871144
/* Failing to update the inner L4 checksum is not fatal if the header
10881145
* is incomplete.
10891146
*/
@@ -1105,6 +1162,7 @@ snat_v4_rev_nat(struct __ctx_buff *ctx, const struct ipv4_nat_target *target,
11051162
__u64 off, inner_l3_off;
11061163
__be16 to_dport = 0;
11071164
__u16 port_off = 0;
1165+
__wsum outer_csum_diff = 0;
11081166
int ret;
11091167

11101168
build_bug_on(sizeof(struct ipv4_nat_entry) > 64);
@@ -1169,7 +1227,8 @@ snat_v4_rev_nat(struct __ctx_buff *ctx, const struct ipv4_nat_target *target,
11691227
rev_nat_icmp_v4:
11701228
inner_l3_off = off + sizeof(struct icmphdr);
11711229

1172-
ret = snat_v4_rev_nat_handle_icmp_error(ctx, inner_l3_off, &state);
1230+
ret = snat_v4_rev_nat_handle_icmp_error(ctx, inner_l3_off, &state,
1231+
&outer_csum_diff);
11731232
if (IS_ERR(ret))
11741233
return ret;
11751234

@@ -1194,7 +1253,8 @@ snat_v4_rev_nat(struct __ctx_buff *ctx, const struct ipv4_nat_target *target,
11941253
return snat_v4_rewrite_headers(ctx, tuple.nexthdr, ETH_HLEN,
11951254
ipfrag_has_l4_header(fraginfo), (int)off,
11961255
tuple.daddr, state->to_daddr, IPV4_DADDR_OFF,
1197-
tuple.dport, to_dport, port_off);
1256+
tuple.dport, to_dport, port_off,
1257+
outer_csum_diff);
11981258
}
11991259
#else /* defined(ENABLE_IPV4) && defined(ENABLE_NODEPORT) */
12001260
static __always_inline __maybe_unused

0 commit comments

Comments
 (0)