Skip to content

Commit e6d945b

Browse files
committed
test: new test, IPv6 version of lan-wan firewall
Signed-off-by: Joachim Wiberg <[email protected]>
1 parent c069308 commit e6d945b

File tree

9 files changed

+277
-2
lines changed

9 files changed

+277
-2
lines changed

test/case/infix_firewall/Readme.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ include::lan-wan/Readme.adoc[]
1212
<<<
1313

1414
include::wan-dmz-lan/Readme.adoc[]
15+
16+
<<<
17+
18+
include::ipv6-lan-wan/Readme.adoc[]

test/case/infix_firewall/all.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77

88
- name: WAN-DMZ-LAN Firewall with Port Forwarding
99
case: wan-dmz-lan/test.py
10+
11+
- name: IPv6 LAN-WAN Firewall
12+
case: ipv6-lan-wan/test.py
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test.adoc
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../lan-wan/lan-wan.svg
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== IPv6 LAN-WAN Firewall
2+
3+
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/infix_firewall/ipv6-lan-wan]
4+
5+
==== Description
6+
7+
IPv6 version of the typical home/office router scenario where the DUT acts as
8+
a gateway with LAN-to-WAN traffic forwarding and IPv6 prefix delegation.
9+
10+
image::lan-wan.svg[align=center, scaledwidth=50%]
11+
12+
- DUT/Gateway with IPv6 firewall and forwarding
13+
- Test host has two interfaces: a LAN-side and a WAN-side (Internet)
14+
- Test host's LAN interface acts as an IPv6 client behind the router
15+
- Test host's WAN interface acts as an IPv6 Internet server/destination
16+
- Demonstrates IPv6 policy-based forwarding between zones
17+
18+
==== Topology
19+
20+
image::topology.svg[IPv6 LAN-WAN Firewall topology, align=center, scaledwidth=75%]
21+
22+
==== Sequence
23+
24+
. Set up topology and attach to gateway
25+
. Configure gateway with firewall and forwarding
26+
. Test connectivity to gateway
27+
. Test LAN-to-WAN forwarding
28+
. Test WAN-to-LAN blocking
29+
. Verify LAN services accessibility
30+
31+
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env python3
2+
"""IPv6 LAN-WAN Firewall
3+
4+
IPv6 version of the typical home/office router scenario where the DUT acts as
5+
a gateway with LAN-to-WAN traffic forwarding and IPv6 prefix delegation.
6+
7+
image::lan-wan.svg[align=center, scaledwidth=50%]
8+
9+
- DUT/Gateway with IPv6 firewall and forwarding
10+
- Test host has two interfaces: a LAN-side and a WAN-side (Internet)
11+
- Test host's LAN interface acts as an IPv6 client behind the router
12+
- Test host's WAN interface acts as an IPv6 Internet server/destination
13+
- Demonstrates IPv6 policy-based forwarding between zones
14+
"""
15+
16+
import time
17+
import infamy
18+
from infamy.util import until
19+
20+
21+
with infamy.Test() as test:
22+
with test.step("Set up topology and attach to gateway"):
23+
env = infamy.Env()
24+
gateway = env.attach("gateway", "mgmt")
25+
_, lan_if = env.ltop.xlate("gateway", "lan")
26+
_, wan_if = env.ltop.xlate("gateway", "wan")
27+
_, mgmt_if = env.ltop.xlate("gateway", "mgmt")
28+
_, host_lan = env.ltop.xlate("host", "lan") # Host LAN-side interface
29+
_, host_wan = env.ltop.xlate("host", "wan") # Host WAN-side interface
30+
31+
LAN_NET = "fd01:db8:1::/64"
32+
LAN_ROUTER_IP = "fd01:db8:1::1" # Router's LAN interface
33+
LAN_CLIENT_IP = "fd01:db8:1::100" # Client on LAN side
34+
35+
WAN_NET = "2001:db8:2::/64" # RFC 3849 documentation prefix
36+
WAN_ROUTER_IP = "2001:db8:2::1" # Router's WAN interface
37+
WAN_SERVER_IP = "2001:db8:2::100" # Server on WAN side
38+
39+
with test.step("Configure gateway with firewall and forwarding"):
40+
gateway.put_config_dicts({
41+
"ietf-interfaces": {
42+
"interfaces": {
43+
"interface": [{
44+
"name": lan_if,
45+
"enabled": True,
46+
"ipv6": {
47+
"enabled": True,
48+
"forwarding": True,
49+
"address": [{
50+
"ip": LAN_ROUTER_IP,
51+
"prefix-length": 64
52+
}]
53+
}
54+
}, {
55+
"name": wan_if,
56+
"enabled": True,
57+
"ipv6": {
58+
"enabled": True,
59+
"forwarding": True,
60+
"address": [{
61+
"ip": WAN_ROUTER_IP,
62+
"prefix-length": 64
63+
}]
64+
}
65+
}]
66+
}
67+
},
68+
"infix-firewall": {
69+
"firewall": {
70+
"default": "wan",
71+
"logging": "all",
72+
"zone": [{
73+
"name": "lan",
74+
"description": "Internal LAN network - trusted",
75+
"action": "accept",
76+
"interface": [lan_if, mgmt_if],
77+
"service": ["ssh", "dhcpv6", "dns"]
78+
}, {
79+
"name": "wan",
80+
"description": "External WAN interface - untrusted",
81+
"action": "drop",
82+
"interface": [wan_if]
83+
}],
84+
"policy": [{
85+
"name": "lan-to-wan",
86+
"description": "Allow LAN to WAN traffic",
87+
"ingress": ["lan"],
88+
"egress": ["wan"],
89+
"action": "accept"
90+
}]
91+
}
92+
}
93+
})
94+
95+
# Wait for configuration to be activated
96+
infamy.Firewall.wait_for_operational(gateway, {
97+
"lan": {"action": "accept"},
98+
"wan": {"action": "drop"}
99+
})
100+
101+
# Verify firewall operational state
102+
data = gateway.get_data("/infix-firewall:firewall")
103+
fw = data["firewall"]
104+
zones = {z["name"]: z for z in fw["zone"]}
105+
106+
# Verify LAN zone
107+
lan_zone = zones["lan"]
108+
assert lan_zone["action"] == "accept"
109+
assert lan_if in lan_zone["interface"]
110+
111+
# Verify WAN zone
112+
wan_zone = zones["wan"]
113+
assert wan_zone["action"] == "drop"
114+
assert wan_if in wan_zone["interface"]
115+
116+
# Verify policy exists
117+
policies = {p["name"]: p for p in fw.get("policy", [])}
118+
assert "lan-to-wan" in policies
119+
policy = policies["lan-to-wan"]
120+
assert "lan" in policy["ingress"]
121+
assert "wan" in policy["egress"]
122+
assert policy["action"] == "accept"
123+
124+
with infamy.IsolatedMacVlan(host_lan) as lan_ns:
125+
lan_ns.addip(LAN_CLIENT_IP, prefix_length=64, proto="ipv6")
126+
lan_ns.addroute("default", LAN_ROUTER_IP, proto="ipv6")
127+
128+
with infamy.IsolatedMacVlan(host_wan) as wan_ns:
129+
wan_ns.addip(WAN_SERVER_IP, prefix_length=64, proto="ipv6")
130+
wan_ns.addroute("default", WAN_ROUTER_IP, proto="ipv6")
131+
132+
with test.step("Test connectivity to gateway"):
133+
lan_ns.must_reach(LAN_ROUTER_IP, timeout=5)
134+
wan_ns.must_not_reach(WAN_ROUTER_IP, timeout=5)
135+
136+
with test.step("Test LAN-to-WAN forwarding"):
137+
lan_ns.must_reach(WAN_SERVER_IP, timeout=10)
138+
139+
with test.step("Test WAN-to-LAN blocking"):
140+
wan_ns.must_not_reach(LAN_CLIENT_IP, timeout=5)
141+
142+
with test.step("Verify LAN services accessibility"):
143+
firewall_lan = infamy.Firewall(lan_ns, None)
144+
svc = [
145+
(22, "tcp", "ssh"),
146+
(53, "tcp", "dns")
147+
]
148+
149+
ok, ports = firewall_lan.verify_allowed(LAN_ROUTER_IP, svc)
150+
if not ok:
151+
print(f" ⚠ Some LAN services are filtered: {', '.join(ports)}")
152+
test.fail()
153+
154+
test.succeed()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
graph "lan-wan-v6" {
2+
layout = "neato";
3+
overlap = false;
4+
esep = "+80";
5+
6+
node [shape=record, fontname="DejaVu Sans Mono, Book"];
7+
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
8+
9+
host [
10+
label="host | { <mgmt> mgmt | <lan> lan | <wan> wan }",
11+
pos="1,1!",
12+
requires="controller"
13+
];
14+
15+
gateway [
16+
label="{ <mgmt> mgmt | <lan> lan | <wan> wan } | gateway",
17+
pos="3,1!",
18+
requires="infix",
19+
];
20+
21+
host:mgmt -- gateway:mgmt [requires="mgmt", color="lightgray"]
22+
host:lan -- gateway:lan [color=green, fontcolor=green, taillabel="fd01:db8:1::/64"]
23+
host:wan -- gateway:wan [color=red, fontcolor=red, taillabel="2001:db8:2::/64"]
24+
}
Lines changed: 53 additions & 0 deletions
Loading

test/infamy/portscanner.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,12 @@ def scan_port(self, host: str, port: int, protocol: str = "tcp",
6060
else:
6161
raise ValueError(f"Unsupported protocol: {protocol}")
6262

63-
cmd = f"nmap -n {proto} -Pn -p {port} --host-timeout={timeout} " \
64-
f"--min-rate=1000 --max-retries=1 --disable-arp-ping {host}"
63+
in6 = ""
64+
if ":" in host:
65+
in6 = "-6"
66+
67+
cmd = f"nmap -n {proto} {in6} -Pn -p {port} --host-timeout={timeout}" \
68+
f" --min-rate=1000 --max-retries=1 --disable-arp-ping {host}"
6569
result = self.netns.runsh(cmd)
6670

6771
# Parse nmap output to determine port state

0 commit comments

Comments
 (0)