Files
infix/test/case/firewall/lan-wan/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

177 lines
6.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""LAN-WAN Firewall with Masquerading
Typical home/office router scenario where the DUT acts as a gateway with
LAN-to-WAN traffic forwarding and masquerading (SNAT).
image::lan-wan.svg[align=center, scaledwidth=50%]
- DUT/Gateway with firewall and NAT
- Test host has two interfaces: a LAN-side and a WAN-side (Internet)
- Test host's LAN interface acts as a client behind the router
- Test host's WAN interface acts as an Internet server/destination
"""
import time
import infamy
from infamy.util import until
with infamy.Test() as test:
with test.step("Set up topology and attach to gateway"):
env = infamy.Env()
gateway = env.attach("gateway", "mgmt")
_, lan_if = env.ltop.xlate("gateway", "lan")
_, wan_if = env.ltop.xlate("gateway", "wan")
_, mgmt_if = env.ltop.xlate("gateway", "mgmt")
_, host_lan = env.ltop.xlate("host", "lan") # Host LAN-side interface
_, host_wan = env.ltop.xlate("host", "wan") # Host WAN-side interface
LAN_NET = "192.168.1.0/24"
LAN_ROUTER_IP = "192.168.1.1" # Router's LAN interface
LAN_CLIENT_IP = "192.168.1.100" # Client on LAN side
WAN_NET = "203.0.113.0/24" # RFC 5737 test network
WAN_ROUTER_IP = "203.0.113.1" # Router's WAN interface
WAN_SERVER_IP = "203.0.113.100" # Server on WAN side
with test.step("Configure gateway with firewall and SNAT"):
gateway.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [
{
"name": lan_if,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": LAN_ROUTER_IP,
"prefix-length": 24
}]
}
},
{
"name": wan_if,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": WAN_ROUTER_IP,
"prefix-length": 24
}]
}
}
]
}
})
gateway.put_config_dict("infix-firewall", {
"firewall": {
"default": "wan",
"logging": "all",
"zone": [
{
"name": "lan",
"description": "Internal LAN network - trusted",
"action": "accept",
"interface": [lan_if, mgmt_if],
"service": ["ssh", "dhcp", "dns"]
}, {
"name": "wan",
"description": "External WAN interface - untrusted",
"action": "drop",
"interface": [wan_if]
}
],
"policy": [
{
"name": "lan-to-wan",
"description": "Allow LAN to WAN traffic with SNAT",
"ingress": ["lan"],
"egress": ["wan"],
"action": "accept",
"masquerade": True
}
]
}
})
# Wait for configuration to be activated
infamy.Firewall.wait_for_operational(gateway, {
"lan": {"action": "accept"},
"wan": {"action": "drop"}
})
# Verify firewall operational state
data = gateway.get_data("/infix-firewall:firewall")
fw = data["firewall"]
zones = {z["name"]: z for z in fw["zone"]}
# Verify LAN zone
lan_zone = zones["lan"]
assert lan_zone["action"] == "accept"
assert lan_if in lan_zone["interface"]
# Verify WAN zone
wan_zone = zones["wan"]
assert wan_zone["action"] == "drop"
assert wan_if in wan_zone["interface"]
# Verify policy
policies = {p["name"]: p for p in fw["policy"]}
lan_wan_policy = policies["lan-to-wan"]
assert lan_wan_policy["ingress"] == ["lan"]
assert lan_wan_policy["egress"] == ["wan"]
assert lan_wan_policy["action"] == "accept"
assert lan_wan_policy["masquerade"] is True
with infamy.IsolatedMacVlan(host_lan) as lan_client:
lan_client.addip(LAN_CLIENT_IP)
lan_client.addroute("0.0.0.0", LAN_ROUTER_IP, prefix_length="0")
with infamy.IsolatedMacVlan(host_wan) as wan_server:
wan_server.addip(WAN_SERVER_IP)
with test.step("Verify LAN access to router"):
lan_client.must_reach(LAN_ROUTER_IP, timeout=3)
with test.step("Verify LAN services accessibility"):
firewall = infamy.Firewall(lan_client, None)
svc = [
(22, "tcp", "ssh"),
(53, "udp", "dns"),
(67, "udp", "dhcp"),
]
ok, ports = firewall.verify_allowed(LAN_ROUTER_IP, svc)
if not ok:
print(f" ⚠ Some LAN services are filtered: {', '.join(ports)}")
test.fail()
with test.step("Verify WAN access to router is blocked"):
wan_server.must_not_reach(WAN_ROUTER_IP, timeout=3)
with test.step("Verify WAN blocks all well-known ports"):
firewall = infamy.Firewall(wan_server, None)
ok, ports, _ = firewall.verify_blocked(WAN_ROUTER_IP)
if not ok:
print(f" ⚠ Some ports are unexpectedly open from WAN: {', '.join(ports)}")
test.fail()
with test.step("Verify LAN-to-WAN connectivity (outbound)"):
lan_client.must_reach(WAN_SERVER_IP, timeout=3)
with test.step("Verify LAN-to-WAN masquerading"):
firewall = infamy.Firewall(lan_client, wan_server)
ok, info = firewall.verify_snat(WAN_SERVER_IP, WAN_ROUTER_IP)
if not ok:
print(f" ⚠ {info}")
test.fail()
with test.step("Verify WAN-to-LAN blocking (inbound)"):
wan_server.must_not_reach(LAN_CLIENT_IP, timeout=3)
test.succeed()