mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-29 12:13:01 +02:00
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>
177 lines
6.5 KiB
Python
Executable File
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()
|