Skip to content

Commit b0fc6d7

Browse files
committed
bpf/test/bpf_nat_icmp: introduce comprehensive icmp revsnat tests.
We already have such tests in bpf_nat_icmp6.h which is used to test the revSNAT icmp PTB message scenario against different configurations scenarios. This adds a similar tests but instead of manually constructing packets uses the scapy packet generator to generate correct packets. Notably, this will also add coverage for the code path handling csum adjustments correctly. We use a similar pattern to bpf_nat_icmp6.h where we implement a re-usable test suite in a header file and use that in several test artifacts to test diferent configurations. In future work we will de-duplicate the icmp6 and icmp tests object files. Signed-off-by: Tom Hadlaw <tom.hadlaw@isovalent.com>
1 parent f33efa8 commit b0fc6d7

4 files changed

Lines changed: 271 additions & 1 deletion

File tree

bpf/tests/bpf_nat_icmp.h

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
2+
/* Copyright Authors of Cilium */
3+
4+
#pragma once
5+
6+
#include <bpf/ctx/skb.h>
7+
#include <bpf/api.h>
8+
#include "common.h"
9+
#include "pktgen.h"
10+
11+
#define ENABLE_IPV4 1
12+
#define ENABLE_NODEPORT 1
13+
#define ENABLE_MASQUERADE_IPV4 1
14+
15+
#define EXT_IP v4_ext_one
16+
#define NODE_IP v4_node_one
17+
#define POD_IP v4_pod_one
18+
19+
#define POD_SEC_IDENTITY 112233
20+
21+
#include "lib/bpf_host.h"
22+
23+
#include <bpf/config/node.h>
24+
25+
#define DEBUG
26+
27+
#include <lib/dbg.h>
28+
#include <lib/eps.h>
29+
#include <lib/nat.h>
30+
#include <lib/time.h>
31+
32+
ASSIGN_CONFIG(union v4addr, nat_ipv4_masquerade, { .be32 = NODE_IP })
33+
34+
#include "nodeport_defaults.h"
35+
#include "bpf_nat_tuples.h"
36+
#include "scapy.h"
37+
38+
#include "lib/endpoint.h"
39+
40+
const __u8 icmp4_err_revnat_egress_tcp[] = {
41+
SCAPY_BUF_BYTES(icmp4_err_revnat_egress_tcp)
42+
};
43+
const __u8 icmp4_err_revnat_egress_post_tcp[] = {
44+
SCAPY_BUF_BYTES(icmp4_err_revnat_egress_post_tcp)
45+
};
46+
const __u8 icmp4_err_revnat_full_tcp[] = {
47+
SCAPY_BUF_BYTES(icmp4_err_revnat_full_tcp)
48+
};
49+
const __u8 icmp4_err_revnat_full_tcp_after[] = {
50+
SCAPY_BUF_BYTES(icmp4_err_revnat_full_tcp_after)
51+
};
52+
const __u8 icmp4_err_revnat_min_tcp[] = {
53+
SCAPY_BUF_BYTES(icmp4_err_revnat_min_tcp)
54+
};
55+
const __u8 icmp4_err_revnat_min_tcp_after[] = {
56+
SCAPY_BUF_BYTES(icmp4_err_revnat_min_tcp_after)
57+
};
58+
59+
/*
60+
* Push an egressing pod->external TCP packet through to-netdev and let the
61+
* datapath set up nat mappings due to node ip masquerading.
62+
* Exploit the fact that tests are run in alphabetical order to ensure this
63+
* happens before we check the icmp pmtud revSNAT mappings.
64+
*/
65+
PKTGEN(PROG_TYPE, "00_snat_v4_tcp_egress")
66+
int snat_v4_egress_pktgen(struct __ctx_buff *ctx)
67+
{
68+
struct pktgen builder;
69+
70+
pktgen__init(&builder, ctx);
71+
scapy_push_data(&builder,
72+
icmp4_err_revnat_egress_tcp,
73+
sizeof(icmp4_err_revnat_egress_tcp));
74+
pktgen__finish(&builder);
75+
return TEST_PASS;
76+
}
77+
78+
SETUP(PROG_TYPE, "00_snat_v4_tcp_egress")
79+
int snat_v4_egress_setup(struct __ctx_buff *ctx)
80+
{
81+
endpoint_v4_add_entry(POD_IP, 0, 0, 0, POD_SEC_IDENTITY,
82+
0, (__u8 *)mac_one, (__u8 *)mac_one);
83+
84+
return netdev_send_packet(ctx);
85+
}
86+
87+
/*
88+
* Add a check prior to moving onto actual icmp pmtu icmp testing
89+
* to quickly fail if priror steps did not setup expected nat state
90+
*/
91+
CHECK(PROG_TYPE, "00_snat_v4_tcp_egress")
92+
int snat_v4_egress_check(const struct __ctx_buff *ctx)
93+
{
94+
test_init();
95+
96+
ASSERT_CTX_BUF_OFF("snat_v4_egress", "Ether", ctx, sizeof(__u32),
97+
icmp4_err_revnat_egress_post_tcp,
98+
sizeof(icmp4_err_revnat_egress_post_tcp));
99+
100+
struct ipv4_ct_tuple tuple = {
101+
.daddr = NODE_IP,
102+
.saddr = EXT_IP,
103+
.dport = tcp_src_two,
104+
.sport = tcp_dst_one,
105+
.nexthdr = IPPROTO_TCP,
106+
.flags = NAT_DIR_INGRESS,
107+
};
108+
struct ipv4_nat_entry *entry = map_lookup_elem(&cilium_snat_v4_external,
109+
&tuple);
110+
111+
if (!entry)
112+
test_fatal("no revSNAT entry created by to-netdev");
113+
if (entry->to_daddr != POD_IP)
114+
test_fatal("revSNAT entry to_daddr is not the pod IP");
115+
if (entry->to_dport != tcp_src_two)
116+
test_fatal("revSNAT entry to_dport is not the pod source port");
117+
118+
test_finish();
119+
}
120+
121+
/*
122+
* Full inner TCP header + data payload variant.
123+
*/
124+
PKTGEN(PROG_TYPE, "snat_v4_tcp_pmtu")
125+
int snat_v4_pmtu_pktgen(struct __ctx_buff *ctx)
126+
{
127+
struct pktgen builder;
128+
129+
pktgen__init(&builder, ctx);
130+
scapy_push_data(&builder,
131+
icmp4_err_revnat_full_tcp,
132+
sizeof(icmp4_err_revnat_full_tcp));
133+
pktgen__finish(&builder);
134+
return TEST_PASS;
135+
}
136+
137+
SETUP(PROG_TYPE, "snat_v4_tcp_pmtu")
138+
int snat_v4_pmtu_setup(struct __ctx_buff *ctx)
139+
{
140+
return netdev_receive_packet(ctx);
141+
}
142+
143+
CHECK(PROG_TYPE, "snat_v4_tcp_pmtu")
144+
int snat_v4_pmtu_check(const struct __ctx_buff *ctx)
145+
{
146+
test_init();
147+
ASSERT_CTX_BUF_OFF("snat_v4_tcp_pmtu", "Ether", ctx, sizeof(__u32),
148+
icmp4_err_revnat_full_tcp_after,
149+
sizeof(icmp4_err_revnat_full_tcp_after));
150+
test_finish();
151+
152+
return 0;
153+
}
154+
155+
/*
156+
* Minimal inner TCP (8 bytes: sport + dport + seq) variant.
157+
* Tests that revSNAT handles the RFC 792 minimum embedded header correctly.
158+
*/
159+
PKTGEN(PROG_TYPE, "snat_v4_tcp_pmtu_min_hdr")
160+
int snat_v4_pmtu_min_hdr_pktgen(struct __ctx_buff *ctx)
161+
{
162+
struct pktgen builder;
163+
164+
pktgen__init(&builder, ctx);
165+
scapy_push_data(&builder,
166+
icmp4_err_revnat_min_tcp,
167+
sizeof(icmp4_err_revnat_min_tcp));
168+
pktgen__finish(&builder);
169+
return TEST_PASS;
170+
}
171+
172+
SETUP(PROG_TYPE, "snat_v4_tcp_pmtu_min_hdr")
173+
int snat_v4_pmtu_min_hdr_setup(struct __ctx_buff *ctx)
174+
{
175+
return netdev_receive_packet(ctx);
176+
}
177+
178+
CHECK(PROG_TYPE, "snat_v4_tcp_pmtu_min_hdr")
179+
int snat_v4_pmtu_min_hdr_check(const struct __ctx_buff *ctx)
180+
{
181+
test_init();
182+
ASSERT_CTX_BUF_OFF("snat_v4_tcp_pmtu_min_hdr", "Ether", ctx, sizeof(__u32),
183+
icmp4_err_revnat_min_tcp_after,
184+
sizeof(icmp4_err_revnat_min_tcp_after));
185+
test_finish();
186+
187+
return 0;
188+
}

bpf/tests/scapy/icmp_err_revnat_pkt_defs.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,76 @@
55

66
from pkt_defs_common import *
77

8+
# Shared header layers for the egress TCP flow, pre- and post-masquerade.
9+
_ip_hdr_egress = IP(src=v4_pod_one, dst=v4_ext_one, flags="DF")
10+
_tcp_hdr_egress = TCP(sport=tcp_src_two, dport=tcp_dst_one, seq=tcp_default_seq)
11+
12+
# Pod -> external host as goes into to-netdev host device (pre-masquerading).
13+
# pod_ip:33440 -> ext_ip:22331
14+
# The intention is that this should setup the correct nat entries for
15+
# testing revSNAT.
16+
# We use tcp_src_two=33440 which is above the nodeport minimum default value
17+
# meaning that, assuming a empty nat map, the SNAT mapping can reuse the same
18+
# port.
19+
icmp4_err_revnat_egress_tcp = (
20+
Ether(src=mac_one, dst=mac_two) /
21+
_ip_hdr_egress /
22+
_tcp_hdr_egress /
23+
Raw(default_data)
24+
)
25+
26+
# Post-masquerade: saddr rewritten to node IP. Ports are unchanged because
27+
# tcp_src_two (33440) > NODEPORT_PORT_MIN_NAT so the same source port is reused.
28+
_ip_hdr_egress_post = IP(src=v4_node_one, dst=v4_ext_one, flags="DF")
29+
_tcp_hdr_egress_post = TCP(sport=tcp_src_two, dport=tcp_dst_one, seq=tcp_default_seq)
30+
icmp4_err_revnat_egress_post_tcp = (
31+
Ether(src=mac_one, dst=mac_two) /
32+
_ip_hdr_egress_post /
33+
_tcp_hdr_egress_post /
34+
Raw(default_data)
35+
)
36+
37+
# Full inner TCP header + complete data payload
38+
icmp4_err_revnat_full_tcp = (
39+
Ether(src=mac_one, dst=mac_two) /
40+
IP(src=v4_ext_one, dst=v4_node_one) /
41+
ICMP(type=3, code=4, nexthopmtu=1500) /
42+
IPerror(**_ip_hdr_egress_post.fields) /
43+
TCPerror(**_tcp_hdr_egress_post.fields) /
44+
Raw(default_data)
45+
)
46+
47+
# After revSNAT: outer daddr -> pod_ip, inner saddr -> pod_ip. The ports are
48+
# unchanged because the SNAT preserved the source port (see above).
49+
icmp4_err_revnat_full_tcp_after = (
50+
Ether(src=mac_one, dst=mac_two) /
51+
IP(src=v4_ext_one, dst=v4_pod_one) /
52+
ICMP(type=3, code=4, nexthopmtu=1500) /
53+
IPerror(**_ip_hdr_egress.fields) /
54+
TCPerror(**_tcp_hdr_egress.fields) /
55+
Raw(default_data)
56+
)
57+
58+
# Inner TCP truncated to first 8 bytes: sport + dport + seq (RFC 792 minimum)
59+
_tcp_hdr_min = bytes(TCP(sport=tcp_src_two, dport=tcp_dst_one, seq=tcp_default_seq))[:8]
60+
icmp4_err_revnat_min_tcp = (
61+
Ether(src=mac_one, dst=mac_two) /
62+
IP(src=v4_ext_one, dst=v4_node_one) /
63+
ICMP(type=3, code=4, nexthopmtu=1500) /
64+
IPerror(src=v4_node_one, dst=v4_ext_one, flags="DF", proto=6) /
65+
Raw(_tcp_hdr_min)
66+
)
67+
68+
# After revSNAT (min TCP): outer daddr -> pod_ip, inner saddr -> pod_ip
69+
_tcp_hdr_min_after = bytes(TCP(sport=tcp_src_two, dport=tcp_dst_one, seq=tcp_default_seq))[:8]
70+
icmp4_err_revnat_min_tcp_after = (
71+
Ether(src=mac_one, dst=mac_two) /
72+
IP(src=v4_ext_one, dst=v4_pod_one) /
73+
ICMP(type=3, code=4, nexthopmtu=1500) /
74+
IPerror(src=v4_pod_one, dst=v4_ext_one, flags="DF", proto=6) /
75+
Raw(_tcp_hdr_min_after)
76+
)
77+
878
# outer IPv4 (pod_two -> pod_one), ICMP Destination Unreachable / Fragmentation Needed,
979
# embedded original IPv4 + TCP with SNAT'd port
1080
icmp4_err_frag_needed_for_revnat = (
@@ -15,7 +85,6 @@
1585
TCP(sport=32768, dport=80) # NODEPORT_PORT_MIN_NAT (SNAT'd port)
1686
)
1787

18-
1988
# After rev-NAT: pod_two -> node_one, with original port restored
2089
icmp4_err_frag_needed_after_revnat = (
2190
Ether(src=mac_one, dst=mac_two) /

bpf/tests/tc_icmp_snat_hostfw.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2+
/* Copyright Authors of Cilium */
3+
4+
#define ENABLE_HOST_FIREWALL
5+
6+
#include "bpf_nat_icmp6.h"

bpf/tests/tc_icmp_snat_tunnel.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2+
/* Copyright Authors of Cilium */
3+
4+
#define ENABLE_DSR 1
5+
#define ENCAP_IFINDEX 1
6+
7+
#include "bpf_nat_icmp.h"

0 commit comments

Comments
 (0)