Skip to content

Commit 7807d9f

Browse files
committed
feat: add PTF tests for basic_tunnel exercise
1 parent 0546752 commit 7807d9f

File tree

4 files changed

+233
-0
lines changed

4 files changed

+233
-0
lines changed

exercises/basic_tunnel/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
build/
2+
ptf.log
3+
ptf.pcap
4+
ss-log.txt

exercises/basic_tunnel/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
BMV2_SWITCH_EXE = simple_switch_grpc
33

44
include ../../utils/Makefile
5+
6+
test: dirs
7+
./runptf.sh
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/usr/bin/env python3
2+
3+
# SPDX-License-Identifier: Apache-2.0
4+
# Copyright 2026 Andrew Nguyen
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
import logging
20+
import os
21+
import sys
22+
23+
import ptf
24+
import ptf.testutils as tu
25+
from ptf.base_tests import BaseTest
26+
from scapy.all import IP, TCP, Ether, Packet, ShortField, bind_layers
27+
28+
TYPE_MYTUNNEL = 0x1212
29+
TYPE_IPV4 = 0x0800
30+
31+
32+
class MyTunnel(Packet):
33+
name = "MyTunnel"
34+
fields_desc = [ShortField("proto_id", TYPE_IPV4), ShortField("dst_id", 0)]
35+
36+
37+
bind_layers(Ether, MyTunnel, type=TYPE_MYTUNNEL)
38+
bind_layers(MyTunnel, IP, proto_id=TYPE_IPV4)
39+
40+
# Import p4runtime_lib from the tutorials repo utils directory
41+
sys.path.append(
42+
os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../utils/")
43+
)
44+
import p4runtime_lib.bmv2
45+
import p4runtime_lib.helper
46+
from p4runtime_lib.switch import ShutdownAllSwitchConnections
47+
48+
49+
logger = logging.getLogger(None)
50+
handler = logging.StreamHandler()
51+
handler.setLevel(logging.INFO)
52+
handler.setFormatter(
53+
logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
54+
)
55+
logger.addHandler(handler)
56+
57+
58+
class BasicTunnelTest(BaseTest):
59+
def setUp(self):
60+
self.dataplane = ptf.dataplane_instance
61+
self.dataplane.flush()
62+
63+
grpc_addr = tu.test_param_get("grpcaddr") or "localhost:9559"
64+
p4info_txt_fname = tu.test_param_get("p4info")
65+
p4prog_binary_fname = tu.test_param_get("config")
66+
67+
self.p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_txt_fname)
68+
self.sw = p4runtime_lib.bmv2.Bmv2SwitchConnection(
69+
name="s1",
70+
address=grpc_addr,
71+
device_id=0,
72+
proto_dump_file="logs/s1-p4runtime-requests.txt",
73+
)
74+
self.sw.MasterArbitrationUpdate()
75+
self.sw.SetForwardingPipelineConfig(
76+
p4info=self.p4info_helper.p4info, bmv2_json_file_path=p4prog_binary_fname
77+
)
78+
79+
def tearDown(self):
80+
ShutdownAllSwitchConnections()
81+
82+
def add_ipv4_lpm_entry(self, ipv4_addr_str, prefix_len, dst_mac_str, port):
83+
table_entry = self.p4info_helper.buildTableEntry(
84+
table_name="MyIngress.ipv4_lpm",
85+
match_fields={"hdr.ipv4.dstAddr": (ipv4_addr_str, prefix_len)},
86+
action_name="MyIngress.ipv4_forward",
87+
action_params={"dstAddr": dst_mac_str, "port": port},
88+
)
89+
self.sw.WriteTableEntry(table_entry)
90+
91+
def add_tunnel_entry(self, dst_id, port):
92+
table_entry = self.p4info_helper.buildTableEntry(
93+
table_name="MyIngress.myTunnel_exact",
94+
match_fields={"hdr.myTunnel.dst_id": dst_id},
95+
action_name="MyIngress.myTunnel_forward",
96+
action_params={"port": port},
97+
)
98+
self.sw.WriteTableEntry(table_entry)
99+
100+
101+
class Ipv4DropOnMissTest(BasicTunnelTest):
102+
def runTest(self):
103+
pkt = tu.simple_tcp_packet(
104+
eth_src="ee:cd:00:7e:70:00",
105+
eth_dst="ee:30:ca:9d:1e:00",
106+
ip_dst="10.0.1.1",
107+
ip_ttl=64,
108+
)
109+
tu.send_packet(self, 1, pkt)
110+
tu.verify_no_other_packets(self)
111+
112+
113+
class Ipv4ForwardTest(BasicTunnelTest):
114+
def runTest(self):
115+
in_dmac = "ee:30:ca:9d:1e:00"
116+
in_smac = "ee:cd:00:7e:70:00"
117+
ip_dst = "10.0.2.2"
118+
eg_port = 2
119+
out_dmac = "08:00:00:00:02:22"
120+
121+
self.add_ipv4_lpm_entry(ip_dst, 32, out_dmac, eg_port)
122+
123+
pkt = tu.simple_tcp_packet(
124+
eth_src=in_smac, eth_dst=in_dmac, ip_dst=ip_dst, ip_ttl=64
125+
)
126+
exp_pkt = tu.simple_tcp_packet(
127+
eth_src=in_dmac, eth_dst=out_dmac, ip_dst=ip_dst, ip_ttl=63
128+
)
129+
tu.send_packet(self, 1, pkt)
130+
tu.verify_packets(self, exp_pkt, [eg_port])
131+
132+
133+
class TunnelForwardTest(BasicTunnelTest):
134+
def runTest(self):
135+
in_pkt = (
136+
Ether(src="00:11:22:33:44:55", dst="ff:ff:ff:ff:ff:ff", type=TYPE_MYTUNNEL)
137+
/ MyTunnel(proto_id=TYPE_IPV4, dst_id=2)
138+
/ IP(src="10.0.1.1", dst="10.0.3.3", ttl=64)
139+
/ TCP(sport=12345, dport=1234)
140+
/ "tunnel-forward"
141+
)
142+
self.add_tunnel_entry(dst_id=2, port=3)
143+
tu.send_packet(self, 0, in_pkt)
144+
tu.verify_packets(self, in_pkt, [3])
145+
146+
147+
class TunnelDropOnMissTest(BasicTunnelTest):
148+
def runTest(self):
149+
in_pkt = (
150+
Ether(src="00:11:22:33:44:66", dst="ff:ff:ff:ff:ff:ff", type=TYPE_MYTUNNEL)
151+
/ MyTunnel(proto_id=TYPE_IPV4, dst_id=77)
152+
/ IP(src="10.0.1.1", dst="10.0.3.3", ttl=64)
153+
/ TCP(sport=12345, dport=1234)
154+
/ "tunnel-drop"
155+
)
156+
tu.send_packet(self, 0, in_pkt)
157+
tu.verify_no_other_packets(self)

exercises/basic_tunnel/runptf.sh

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/bin/bash
2+
# Run PTF tests for the basic tunnel exercise.
3+
# Tests run against the solution P4 program.
4+
5+
set -e
6+
BINDIR=$(realpath ../../bin)
7+
8+
sudo "${BINDIR}"/veth_setup.sh
9+
10+
set -x
11+
12+
# ---- compile ----
13+
mkdir -p build logs
14+
p4c --target bmv2 \
15+
--arch v1model \
16+
--p4runtime-files build/basic_tunnel.p4info.txtpb \
17+
-o build \
18+
solution/basic_tunnel.p4
19+
20+
/bin/rm -f ss-log.txt
21+
22+
# ---- start switch ----
23+
sudo simple_switch_grpc \
24+
--log-file ss-log \
25+
--log-flush \
26+
--dump-packet-data 10000 \
27+
-i 0@veth0 \
28+
-i 1@veth2 \
29+
-i 2@veth4 \
30+
-i 3@veth6 \
31+
-i 4@veth8 \
32+
-i 5@veth10 \
33+
-i 6@veth12 \
34+
-i 7@veth14 \
35+
--no-p4 &
36+
37+
echo ""
38+
echo "Started simple_switch_grpc. Waiting 2 seconds before starting PTF test..."
39+
sleep 2
40+
41+
# ---- run tests ----
42+
sudo ${P4_EXTRA_SUDO_OPTS} `which ptf` \
43+
-i 0@veth1 \
44+
-i 1@veth3 \
45+
-i 2@veth5 \
46+
-i 3@veth7 \
47+
-i 4@veth9 \
48+
-i 5@veth11 \
49+
-i 6@veth13 \
50+
-i 7@veth15 \
51+
--test-params="grpcaddr='localhost:9559';p4info='build/basic_tunnel.p4info.txtpb';config='build/basic_tunnel.json'" \
52+
--test-dir ptf
53+
54+
echo ""
55+
echo "PTF test finished. Waiting 2 seconds before killing simple_switch_grpc..."
56+
sleep 2
57+
58+
# ---- cleanup ----
59+
sudo pkill --signal 9 --list-name simple_switch
60+
61+
62+
echo ""
63+
echo "Cleaning up veth interfaces..."
64+
sudo "${BINDIR}"/veth_teardown.sh
65+
66+
echo ""
67+
echo "Verifying no simple_switch_grpc processes remain..."
68+
sleep 2
69+
ps axguwww | grep simple_switch

0 commit comments

Comments
 (0)