Skip to content

Commit e27cc7d

Browse files
yushoyamaguchijulianwiedmann
authored andcommitted
bpf-test of SNAT RevNAT using scapy
This commit adds a new test case of SNAT RevNAT using scapy. By this test case, we could also validate checksum of outputed packet. Signed-off-by: Yusho Yamaguchi <ysh.824@outlook.jp>
1 parent aadc4ff commit e27cc7d

3 files changed

Lines changed: 169 additions & 0 deletions

File tree

bpf/tests/icmp_error_revnat.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2+
/* Copyright Authors of Cilium */
3+
4+
#include <bpf/ctx/skb.h>
5+
#include <bpf/api.h>
6+
#include "common.h"
7+
#include "pktgen.h"
8+
9+
#define ENABLE_IPV4
10+
#define ENABLE_NODEPORT
11+
#include <bpf/config/node.h>
12+
13+
#define DEBUG
14+
15+
#include <lib/dbg.h>
16+
#include <lib/eps.h>
17+
#include <lib/nat.h>
18+
#include <lib/time.h>
19+
20+
#include "bpf_nat_tuples.h"
21+
#include "scapy.h"
22+
23+
/* IP addresses mapping to Scapy definitions (in host byte order):
24+
* v4_node_one = "10.0.10.1" -> IP_ENDPOINT (node/endpoint)
25+
* v4_pod_one = "192.168.0.1" -> IP_HOST (pod being SNATed)
26+
* v4_pod_two = "192.168.0.2" -> IP_ROUTER (pod sending ICMP error)
27+
*/
28+
#define IP_ENDPOINT ((10 << 24) | (0 << 16) | (10 << 8) | 1) /* 10.0.10.1 */
29+
#define IP_HOST ((192 << 24) | (168 << 16) | (0 << 8) | 1) /* 192.168.0.1 */
30+
#define IP_ROUTER ((192 << 24) | (168 << 16) | (0 << 8) | 2) /* 192.168.0.2 */
31+
#define IP_WORLD IP_ROUTER /* same as router for this test */
32+
33+
/* Test snat_v4_rev_nat() with ICMP error containing embedded TCP packet
34+
*
35+
* Flow:
36+
* 1. Simulate an outgoing connection: endpoint (10.0.10.1:3030) -> pod (192.168.0.2:80)
37+
* This gets SNATed to: pod (192.168.0.1:NODEPORT_PORT_MIN_NAT) -> pod (192.168.0.2:80)
38+
* 2. Pod sends back ICMP Frag Needed error about the SNATed packet
39+
* 3. snat_v4_rev_nat() should reverse the NAT in both outer and inner (embedded) packets
40+
* 4. Result: ICMP error should be addressed to endpoint (10.0.10.1)
41+
* with embedded packet showing original src (10.0.10.1:3030)
42+
*/
43+
PKTGEN("tc", "nat4_icmp_error_tcp_snat_revnat")
44+
int nat4_icmp_error_tcp_snat_revnat_pktgen(struct __ctx_buff *ctx)
45+
{
46+
struct pktgen builder;
47+
48+
pktgen__init(&builder, ctx);
49+
50+
/* Use Scapy-generated ICMP error packet */
51+
BUF_DECL(ICMP4_ERR_FRAG_NEEDED_FOR_REVNAT, icmp4_err_frag_needed_for_revnat);
52+
BUILDER_PUSH_BUF(builder, ICMP4_ERR_FRAG_NEEDED_FOR_REVNAT);
53+
54+
pktgen__finish(&builder);
55+
return 0;
56+
}
57+
58+
SETUP("tc", "nat4_icmp_error_tcp_snat_revnat")
59+
int nat4_icmp_error_tcp_snat_revnat_setup(struct __ctx_buff *ctx)
60+
{
61+
/* Set up NAT mapping to simulate prior outgoing connection.
62+
* Original tuple: endpoint -> pod
63+
*/
64+
struct ipv4_ct_tuple tuple = {
65+
.nexthdr = IPPROTO_TCP,
66+
.saddr = bpf_htonl(IP_ENDPOINT), /* 10.0.10.1 */
67+
.daddr = bpf_htonl(IP_WORLD), /* 192.168.0.2 */
68+
.sport = bpf_htons(3030),
69+
.dport = bpf_htons(80),
70+
.flags = 0,
71+
};
72+
73+
/* NAT target: translate to pod IP */
74+
struct ipv4_nat_target target = {
75+
.addr = bpf_htonl(IP_HOST), /* 192.168.0.1 */
76+
.min_port = NODEPORT_PORT_MIN_NAT,
77+
.max_port = NODEPORT_PORT_MIN_NAT,
78+
};
79+
80+
struct ipv4_nat_entry state;
81+
struct trace_ctx trace;
82+
void *map;
83+
int ret;
84+
85+
/* Get SNAT map */
86+
map = get_cluster_snat_map_v4(target.cluster_id);
87+
if (!map)
88+
return TEST_ERROR;
89+
90+
/* Create NAT mapping */
91+
ret = snat_v4_new_mapping(ctx, map, &tuple, &state, &target,
92+
false, NULL);
93+
if (ret != 0)
94+
return TEST_ERROR;
95+
96+
/* Now call snat_v4_rev_nat() - this is the function under test.
97+
* It should:
98+
* 1. Reverse NAT the outer IP dst: pod -> endpoint
99+
* 2. Reverse NAT the embedded IP src: pod -> endpoint
100+
* 3. Restore the embedded TCP sport: NODEPORT_PORT_MIN_NAT -> 3030
101+
*/
102+
ret = snat_v4_rev_nat(ctx, &target, &trace, NULL);
103+
if (ret != 0)
104+
return TEST_ERROR;
105+
106+
return TEST_PASS;
107+
}
108+
109+
CHECK("tc", "nat4_icmp_error_tcp_snat_revnat")
110+
int nat4_icmp_error_tcp_snat_revnat_check(const struct __ctx_buff *ctx)
111+
{
112+
void *data;
113+
void *data_end;
114+
__u32 *status_code;
115+
116+
test_init();
117+
118+
data = (void *)(long)ctx->data;
119+
data_end = (void *)(long)ctx->data_end;
120+
121+
/* First 4 bytes contain the return code from SETUP */
122+
if (data + sizeof(*status_code) > data_end)
123+
test_fatal("status code out of bounds");
124+
125+
status_code = data;
126+
127+
/* Verify SETUP succeeded */
128+
if (*status_code != TEST_PASS)
129+
test_fatal("SETUP failed with status code: %d", *status_code);
130+
131+
/* Compare the packet with expected output after rev-NAT.
132+
* Note: offset sizeof(__u32) to skip the return code prepended by framework.
133+
*/
134+
BUF_DECL(ICMP4_ERR_FRAG_NEEDED_AFTER_REVNAT, icmp4_err_frag_needed_after_revnat);
135+
ASSERT_CTX_BUF_OFF("icmp4_revnat_ok", "Ether", ctx, sizeof(__u32),
136+
ICMP4_ERR_FRAG_NEEDED_AFTER_REVNAT,
137+
sizeof(BUF(ICMP4_ERR_FRAG_NEEDED_AFTER_REVNAT)));
138+
139+
test_finish();
140+
}
141+
142+
BPF_LICENSE("Dual BSD/GPL");
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright Authors of Cilium
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from scapy.all import *
5+
6+
from pkt_defs_common import *
7+
8+
# outer IPv4 (pod_two -> pod_one), ICMP Destination Unreachable / Fragmentation Needed,
9+
# embedded original IPv4 + TCP with SNAT'd port
10+
icmp4_err_frag_needed_for_revnat = (
11+
Ether(src=mac_one, dst=mac_two) /
12+
IP(src=v4_pod_two, dst=v4_pod_one) /
13+
ICMP(type=3, code=4, nexthopmtu=1500) /
14+
IP(src=v4_pod_one, dst=v4_pod_two, flags="DF") /
15+
TCP(sport=32768, dport=80) # NODEPORT_PORT_MIN_NAT (SNAT'd port)
16+
)
17+
18+
19+
# After rev-NAT: pod_two -> node_one, with original port restored
20+
icmp4_err_frag_needed_after_revnat = (
21+
Ether(src=mac_one, dst=mac_two) /
22+
IP(src=v4_pod_two, dst=v4_node_one) /
23+
ICMP(type=3, code=4, nexthopmtu=1500) /
24+
IP(src=v4_node_one, dst=v4_pod_two, flags="DF") /
25+
TCP(sport=3030, dport=80) # original port restored
26+
)

bpf/tests/scapy/pkt_defs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
from tc_l2_announce6_pkt_defs import *
1313
from wg_from_netdev_pkt_defs import *
1414
from tc_wireguard_from_overlay_pkt_defs import *
15+
from icmp_err_revnat_pkt_defs import *

0 commit comments

Comments
 (0)