test: new test, verify TTL on GRE/VXLAN tunnels

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-11-07 14:46:55 +01:00
parent eb1daf0ecd
commit e49f9adca4
10 changed files with 507 additions and 0 deletions
@@ -0,0 +1,6 @@
include::gre.adoc[]
<<<
include::vxlan.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
+1
View File
@@ -0,0 +1 @@
test.py
+269
View File
@@ -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()
@@ -0,0 +1,11 @@
---
- settings:
test-spec: <case>.adoc
- name: GRE Tunnel TTL verification
case: gre.py
opts: ["--tunnel", "gre"]
- name: VXLAN Tunnel TTL verification
case: vxlan.py
opts: ["--tunnel", "vxlan"]
@@ -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> mgmt1 | <west> west | <mgmt2> mgmt2 | <mgmt3> mgmt3 | <east> east | { PC }"
pos="10,0!",
requires="controller",
];
R1 [
label="R1 | <mgmt> mgmt | <west> west | <east> east",
pos="0, -5!",
requires="infix",
];
R2 [
label="<west> west | R2 | <mgmt> mgmt | <east> east",
pos="10, -5!",
requires="infix",
];
R3 [
label="<west> west | R3 | <mgmt> mgmt | <east> 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"]
}
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: tunnel&#45;ttl&#45;3dut Pages: 1 -->
<svg width="939pt" height="232pt"
viewBox="0.00 0.00 939.07 232.02" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 228.02)">
<title>tunnel&#45;ttl&#45;3dut</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-228.02 935.07,-228.02 935.07,4 -4,4"/>
<!-- PC -->
<g id="node1" class="node">
<title>PC</title>
<polygon fill="none" stroke="black" points="312.04,-187.52 312.04,-223.52 619.04,-223.52 619.04,-187.52 312.04,-187.52"/>
<text text-anchor="middle" x="341.04" y="-201.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="370.04,-187.52 370.04,-223.52 "/>
<text text-anchor="middle" x="395.04" y="-201.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">west</text>
<polyline fill="none" stroke="black" points="420.04,-187.52 420.04,-223.52 "/>
<text text-anchor="middle" x="449.04" y="-201.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
<polyline fill="none" stroke="black" points="478.04,-187.52 478.04,-223.52 "/>
<text text-anchor="middle" x="507.04" y="-201.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
<polyline fill="none" stroke="black" points="536.04,-187.52 536.04,-223.52 "/>
<text text-anchor="middle" x="561.04" y="-201.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">east</text>
<polyline fill="none" stroke="black" points="586.04,-187.52 586.04,-223.52 "/>
<text text-anchor="middle" x="602.54" y="-201.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
</g>
<!-- R1 -->
<g id="node2" class="node">
<title>R1</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 183,-36.5 183,-0.5 0,-0.5"/>
<text text-anchor="middle" x="16.5" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1</text>
<polyline fill="none" stroke="black" points="33,-0.5 33,-36.5 "/>
<text text-anchor="middle" x="58" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="83,-0.5 83,-36.5 "/>
<text text-anchor="middle" x="108" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">west</text>
<polyline fill="none" stroke="black" points="133,-0.5 133,-36.5 "/>
<text text-anchor="middle" x="158" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">east</text>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge1" class="edge">
<title>PC:mgmt1&#45;&#45;R1:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M311.54,-205.52C311.54,-205.52 57.5,-36.5 57.5,-36.5"/>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge4" class="edge">
<title>PC:west&#45;&#45;R1:west</title>
<path fill="none" stroke="black" stroke-width="2" d="M394.54,-187.52C394.54,-187.52 108.5,-36.5 108.5,-36.5"/>
<text text-anchor="middle" x="192.52" y="-115.81" font-family="DejaVu Serif, Book" font-size="14.00">192.168.10.0/24</text>
<text text-anchor="middle" x="128.48" y="-33.79" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
<text text-anchor="middle" x="374.56" y="-182.83" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
</g>
<!-- R2 -->
<g id="node3" class="node">
<title>R2</title>
<polygon fill="none" stroke="black" points="374.04,-0.5 374.04,-36.5 557.04,-36.5 557.04,-0.5 374.04,-0.5"/>
<text text-anchor="middle" x="399.04" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">west</text>
<polyline fill="none" stroke="black" points="424.04,-0.5 424.04,-36.5 "/>
<text text-anchor="middle" x="440.54" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2</text>
<polyline fill="none" stroke="black" points="457.04,-0.5 457.04,-36.5 "/>
<text text-anchor="middle" x="482.04" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="507.04,-0.5 507.04,-36.5 "/>
<text text-anchor="middle" x="532.04" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">east</text>
</g>
<!-- PC&#45;&#45;R2 -->
<g id="edge2" class="edge">
<title>PC:mgmt2&#45;&#45;R2:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M448.54,-187.52C448.54,-187.52 482.54,-36.5 482.54,-36.5"/>
</g>
<!-- R3 -->
<g id="node4" class="node">
<title>R3</title>
<polygon fill="none" stroke="black" points="748.07,-0.5 748.07,-36.5 931.07,-36.5 931.07,-0.5 748.07,-0.5"/>
<text text-anchor="middle" x="773.07" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">west</text>
<polyline fill="none" stroke="black" points="798.07,-0.5 798.07,-36.5 "/>
<text text-anchor="middle" x="814.57" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">R3</text>
<polyline fill="none" stroke="black" points="831.07,-0.5 831.07,-36.5 "/>
<text text-anchor="middle" x="856.07" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="881.07,-0.5 881.07,-36.5 "/>
<text text-anchor="middle" x="906.07" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">east</text>
</g>
<!-- PC&#45;&#45;R3 -->
<g id="edge3" class="edge">
<title>PC:mgmt3&#45;&#45;R3:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M507.54,-187.52C507.54,-187.52 856.57,-36.5 856.57,-36.5"/>
</g>
<!-- R1&#45;&#45;R2 -->
<g id="edge5" class="edge">
<title>R1:east&#45;&#45;R2:west</title>
<path fill="none" stroke="blue" stroke-width="2" d="M183.5,-18.5C183.5,-18.5 373.54,-18.5 373.54,-18.5"/>
<text text-anchor="middle" x="308.02" y="-7.3" font-family="DejaVu Serif, Book" font-size="14.00">192.168.50.0/24</text>
<text text-anchor="middle" x="355.41" y="-23.25" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
<text text-anchor="middle" x="201.63" y="-6.35" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
</g>
<!-- R2&#45;&#45;R3 -->
<g id="edge6" class="edge">
<title>R2:east&#45;&#45;R3:west</title>
<path fill="none" stroke="blue" stroke-width="2" d="M557.54,-18.5C557.54,-18.5 747.57,-18.5 747.57,-18.5"/>
<text text-anchor="middle" x="682.06" y="-7.3" font-family="DejaVu Serif, Book" font-size="14.00">192.168.60.0/24</text>
<text text-anchor="middle" x="729.45" y="-23.25" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
<text text-anchor="middle" x="575.66" y="-6.35" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
</g>
<!-- R3&#45;&#45;PC -->
<g id="edge7" class="edge">
<title>R3:east&#45;&#45;PC:east</title>
<path fill="none" stroke="black" stroke-width="2" d="M906.57,-36.5C906.57,-36.5 561.54,-187.52 561.54,-187.52"/>
<text text-anchor="middle" x="675.06" y="-115.81" font-family="DejaVu Serif, Book" font-size="14.00">192.168.70.0/24</text>
<text text-anchor="middle" x="574.75" y="-168.81" font-family="DejaVu Serif, Book" font-size="14.00">.1</text>
<text text-anchor="middle" x="893.36" y="-47.81" font-family="DejaVu Serif, Book" font-size="14.00">.2</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

@@ -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
+1
View File
@@ -0,0 +1 @@
test.py
+3
View File
@@ -4,3 +4,6 @@
- name: Tunnel interface bridged with physical
suite: tunnel_bridged/test.yaml
- name: Tunnel TTL verification
suite: tunnel_ttl/test.yaml