Files
infix/test/case/interfaces/tunnel_ttl/test.py
T
Joachim Wiberg 1bbd80d8c7 test: drop leading ietf/infix prefix from directories
Let's drop the leading IETF or Infix prefixes from tests.  Initially the
idea was to mimnic the YANG models, but it's difficult to navigate and
does not provide any real benefit to developers or end-users.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-11-20 20:23:23 +01:00

270 lines
9.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Tunnel TTL Verification
Verify that GRE and VXLAN tunnels use a fixed TTL (default 64) for
encapsulated frames instead of inheriting the TTL from inner packets.
Critical for protocols like OSPF that use TTL=1 for their packets.
The test setup creates a tunnel between R1 and R3 so that injecting
a frame with TTL=3 from PC:west, routing it through the tunnel, it
would still reach PC:east. (Had it been routed via R2 it would be too
many hops and the TTL would reach zero before the last routing step.)
PC:west -- R1 -- R2 -- R3 -- PC:east
`== Tunnel =='
"""
import infamy
class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.add_argument("--tunnel")
# IP address plan
# PC:west subnet
PC_WEST_R1 = "192.168.10.1"
PC_WEST_HOST = "192.168.10.2"
PC_WEST_NET = "192.168.10.0/24"
# R1-R2 underlay subnet
R1_R2_R1 = "192.168.50.1"
R1_R2_R2 = "192.168.50.2"
R1_R2_NET = "192.168.50.0/24"
# R2-R3 underlay subnet
R2_R3_R2 = "192.168.60.1"
R2_R3_R3 = "192.168.60.2"
R2_R3_NET = "192.168.60.0/24"
# PC:east subnet
PC_EAST_R3 = "192.168.70.1"
PC_EAST_HOST = "192.168.70.2"
PC_EAST_NET = "192.168.70.0/24"
# Tunnel subnet
TUNNEL_R1 = "10.255.0.1"
TUNNEL_R3 = "10.255.0.2"
TUNNEL_NET = "10.255.0.0/30"
# Prefix lengths
PREFIX_24 = 24
PREFIX_30 = 30
# Test TTL value
TEST_TTL = 3
# VXLAN VNI
VNI = 10
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
arg = ArgumentParser()
env = infamy.Env(args=arg)
tunnel = env.args.tunnel
r1 = env.attach("R1", "mgmt")
r2 = env.attach("R2", "mgmt")
r3 = env.attach("R3", "mgmt")
with test.step(f"Configure R1 with {tunnel} tunnel to R3"):
# R1: Entry point, west facing PC, east facing R2, tunnel to R3
# Build tunnel container configs
container_r1 = {
"local": R1_R2_R1,
"remote": R2_R3_R3
}
container_r3 = {
"local": R2_R3_R3,
"remote": R1_R2_R1
}
CONTAINER_TYPE = tunnel
if tunnel == "vxlan":
container_r1["vni"] = VNI
container_r3["vni"] = VNI
r1.put_config_dicts({"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": r1["west"],
"ipv4": {
"address": [{
"ip": PC_WEST_R1,
"prefix-length": PREFIX_24
}],
"forwarding": True
}
}, {
"name": r1["east"],
"ipv4": {
"address": [{
"ip": R1_R2_R1,
"prefix-length": PREFIX_24
}],
"forwarding": True
}
}, {
"name": f"{tunnel}0",
"type": f"infix-if-type:{tunnel}",
"ipv4": {
"address": [{
"ip": TUNNEL_R1,
"prefix-length": PREFIX_30
}],
"forwarding": True
},
CONTAINER_TYPE: container_r1
}]
}
}, "ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:static",
"name": "default",
"static-routes": {
"ipv4": {
"route": [{
"destination-prefix": R2_R3_NET,
"next-hop": {
"next-hop-address": R1_R2_R2
}
}, {
"destination-prefix": PC_EAST_NET,
"next-hop": {
"next-hop-address": TUNNEL_R3
}
}]
}
}
}]
}
}
}})
with test.step("Configure R2 as intermediate router (underlay forwarding)"):
# R2: Intermediate router, just forwards packets between west and east
r2.put_config_dicts({"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": r2["west"],
"ipv4": {
"address": [{
"ip": R1_R2_R2,
"prefix-length": PREFIX_24
}],
"forwarding": True
}
}, {
"name": r2["east"],
"ipv4": {
"address": [{
"ip": R2_R3_R2,
"prefix-length": PREFIX_24
}],
"forwarding": True
}
}]
}
}})
with test.step(f"Configure R3 with {tunnel} tunnel to R1"):
# R3: Exit point, west facing R2, east facing PC, tunnel to R1
r3.put_config_dicts({"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": r3["west"],
"ipv4": {
"address": [{
"ip": R2_R3_R3,
"prefix-length": PREFIX_24
}],
"forwarding": True
}
}, {
"name": r3["east"],
"ipv4": {
"address": [{
"ip": PC_EAST_R3,
"prefix-length": PREFIX_24
}],
"forwarding": True
}
}, {
"name": f"{tunnel}0",
"type": f"infix-if-type:{tunnel}",
"ipv4": {
"address": [{
"ip": TUNNEL_R3,
"prefix-length": PREFIX_30
}],
"forwarding": True
},
CONTAINER_TYPE: container_r3
}]
}
}, "ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:static",
"name": "default",
"static-routes": {
"ipv4": {
"route": [{
"destination-prefix": R1_R2_NET,
"next-hop": {
"next-hop-address": R2_R3_R2
}
}, {
"destination-prefix": PC_WEST_NET,
"next-hop": {
"next-hop-address": TUNNEL_R1
}
}]
}
}
}]
}
}
}})
with test.step("Send ping from PC:west to PC:east with low TTL"):
_, pc_east = env.ltop.xlate("PC", "east")
with infamy.IsolatedMacVlan(pc_east) as east_ns:
east_ns.addip(PC_EAST_HOST)
east_ns.addroute("default", PC_EAST_R3)
pcap = east_ns.pcap("icmp")
with pcap:
_, pc_west = env.ltop.xlate("PC", "west")
with infamy.IsolatedMacVlan(pc_west) as west_ns:
west_ns.addip(PC_WEST_HOST)
west_ns.addroute("default", PC_WEST_R1)
# Send 10 pings with TTL=3, TTL is decremented before each
# router hop. So at PC:west (TTL=3) -> R1 routed to GRE
# tunnel (TTL=2) -> frame egresses tunnel -> R3 where it
# is routed to PC:east (TTL=1).
#
# If outer TTL was inherited (TTL=2), packet would be
# dropped at R3.
west_ns.runsh(f"ping -c 10 -t {TEST_TTL} {PC_EAST_HOST}")
with test.step("Verify packets arrived at PC:east"):
packets = pcap.tcpdump()
print("Captured packets on PC:east:")
print(packets)
pings = [line for line in packets.splitlines()
if f"{PC_WEST_HOST} > {PC_EAST_HOST}: ICMP echo request" in line]
assert len(pings) >= 1, f"Expected at least 1 ping, got {len(pings)}."
test.succeed()