diff --git a/test/case/ietf_interfaces/tunnel_ttl/Readme.adoc b/test/case/ietf_interfaces/tunnel_ttl/Readme.adoc new file mode 100644 index 00000000..062cb018 --- /dev/null +++ b/test/case/ietf_interfaces/tunnel_ttl/Readme.adoc @@ -0,0 +1,6 @@ +include::gre.adoc[] + +<<< + +include::vxlan.adoc[] + diff --git a/test/case/ietf_interfaces/tunnel_ttl/gre.adoc b/test/case/ietf_interfaces/tunnel_ttl/gre.adoc new file mode 100644 index 00000000..6b42cc0a --- /dev/null +++ b/test/case/ietf_interfaces/tunnel_ttl/gre.adoc @@ -0,0 +1,32 @@ +=== GRE Tunnel TTL verification + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ietf_interfaces/tunnel_ttl] + +==== Description + +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 ==' + +==== Topology + +image::topology.svg[GRE Tunnel TTL verification topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUTs +. Configure R1 with gre tunnel to R3 +. Configure R2 as intermediate router (underlay forwarding) +. Configure R3 with gre tunnel to R1 +. Send ping from PC:west to PC:east with low TTL +. Verify packets arrived at PC:east + + diff --git a/test/case/ietf_interfaces/tunnel_ttl/gre.py b/test/case/ietf_interfaces/tunnel_ttl/gre.py new file mode 120000 index 00000000..94656643 --- /dev/null +++ b/test/case/ietf_interfaces/tunnel_ttl/gre.py @@ -0,0 +1 @@ +test.py \ No newline at end of file diff --git a/test/case/ietf_interfaces/tunnel_ttl/test.py b/test/case/ietf_interfaces/tunnel_ttl/test.py new file mode 100755 index 00000000..72c4f9a4 --- /dev/null +++ b/test/case/ietf_interfaces/tunnel_ttl/test.py @@ -0,0 +1,269 @@ +#!/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 3 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 3 -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() diff --git a/test/case/ietf_interfaces/tunnel_ttl/test.yaml b/test/case/ietf_interfaces/tunnel_ttl/test.yaml new file mode 100644 index 00000000..4b38e5d8 --- /dev/null +++ b/test/case/ietf_interfaces/tunnel_ttl/test.yaml @@ -0,0 +1,11 @@ +--- +- settings: + test-spec: .adoc + +- name: GRE Tunnel TTL verification + case: gre.py + opts: ["--tunnel", "gre"] + +- name: VXLAN Tunnel TTL verification + case: vxlan.py + opts: ["--tunnel", "vxlan"] diff --git a/test/case/ietf_interfaces/tunnel_ttl/topology.dot b/test/case/ietf_interfaces/tunnel_ttl/topology.dot new file mode 100644 index 00000000..8bcc8b61 --- /dev/null +++ b/test/case/ietf_interfaces/tunnel_ttl/topology.dot @@ -0,0 +1,41 @@ +graph "tunnel-ttl-3dut" { + layout="neato"; + overlap="false"; + esep="+60"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + PC [ + label=" mgmt1 | west | mgmt2 | mgmt3 | east | { PC }" + pos="10,0!", + requires="controller", + ]; + + R1 [ + label="R1 | mgmt | west | east", + pos="0, -5!", + requires="infix", + ]; + + R2 [ + label=" west | R2 | mgmt | east", + pos="10, -5!", + requires="infix", + ]; + + R3 [ + label=" west | R3 | mgmt | east", + pos="20, -5!", + requires="infix", + ]; + + PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"] + PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"] + PC:mgmt3 -- R3:mgmt [requires="mgmt", color="lightgray"] + + PC:west -- R1:west [headlabel=".1", label="192.168.10.0/24", taillabel=".2", labeldistance=2, fontcolor="black", color="black"] + R1:east -- R2:west [headlabel=".1", label="192.168.50.0/24", taillabel=".2", labeldistance=2, fontcolor="black", color="blue"] + R2:east -- R3:west [headlabel=".1", label="192.168.60.0/24", taillabel=".2", labeldistance=2, fontcolor="black", color="blue"] + R3:east -- PC:east [headlabel=".1", label="192.168.70.0/24", taillabel=".2", labeldistance=2, fontcolor="black", color="black"] +} diff --git a/test/case/ietf_interfaces/tunnel_ttl/topology.svg b/test/case/ietf_interfaces/tunnel_ttl/topology.svg new file mode 100644 index 00000000..5ea25e14 --- /dev/null +++ b/test/case/ietf_interfaces/tunnel_ttl/topology.svg @@ -0,0 +1,111 @@ + + + + + + +tunnel-ttl-3dut + + + +PC + +mgmt1 + +west + +mgmt2 + +mgmt3 + +east + +PC + + + +R1 + +R1 + +mgmt + +west + +east + + + +PC:mgmt1--R1:mgmt + + + + +PC:west--R1:west + +192.168.10.0/24 +.1 +.2 + + + +R2 + +west + +R2 + +mgmt + +east + + + +PC:mgmt2--R2:mgmt + + + + +R3 + +west + +R3 + +mgmt + +east + + + +PC:mgmt3--R3:mgmt + + + + +R1:east--R2:west + +192.168.50.0/24 +.1 +.2 + + + +R2:east--R3:west + +192.168.60.0/24 +.1 +.2 + + + +R3:east--PC:east + +192.168.70.0/24 +.1 +.2 + + + diff --git a/test/case/ietf_interfaces/tunnel_ttl/vxlan.adoc b/test/case/ietf_interfaces/tunnel_ttl/vxlan.adoc new file mode 100644 index 00000000..5e89c57e --- /dev/null +++ b/test/case/ietf_interfaces/tunnel_ttl/vxlan.adoc @@ -0,0 +1,32 @@ +=== VXLAN Tunnel TTL verification + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ietf_interfaces/tunnel_ttl] + +==== Description + +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 ==' + +==== Topology + +image::topology.svg[VXLAN Tunnel TTL verification topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUTs +. Configure R1 with vxlan tunnel to R3 +. Configure R2 as intermediate router (underlay forwarding) +. Configure R3 with vxlan tunnel to R1 +. Send ping from PC:west to PC:east with low TTL +. Verify packets arrived at PC:east + + diff --git a/test/case/ietf_interfaces/tunnel_ttl/vxlan.py b/test/case/ietf_interfaces/tunnel_ttl/vxlan.py new file mode 120000 index 00000000..94656643 --- /dev/null +++ b/test/case/ietf_interfaces/tunnel_ttl/vxlan.py @@ -0,0 +1 @@ +test.py \ No newline at end of file diff --git a/test/case/ietf_interfaces/tunnels.yaml b/test/case/ietf_interfaces/tunnels.yaml index dd0b8242..5886bc19 100644 --- a/test/case/ietf_interfaces/tunnels.yaml +++ b/test/case/ietf_interfaces/tunnels.yaml @@ -4,3 +4,6 @@ - name: Tunnel interface bridged with physical suite: tunnel_bridged/test.yaml + +- name: Tunnel TTL verification + suite: tunnel_ttl/test.yaml