@@ -469,13 +469,14 @@ static __always_inline int
469469snat_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+
535571static __always_inline bool
536572snat_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,
10051041static __always_inline __maybe_unused int
10061042snat_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,
11691227rev_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) */
12001260static __always_inline __maybe_unused
0 commit comments