Summary
Containers using isolated networks (set container network) are completely unreachable when Zone-Based Firewall (ZBF) and firewall global-options state-policy are configured together. ARP between the container and its bridge gateway is silently dropped, breaking all connectivity.
Steps to Reproduce
- Configure Zone-Based Firewall with at least two zones
- Configure a global state policy:
set firewall global-options state-policy established action ‘accept’
set firewall global-options state-policy invalid action ‘drop’
set firewall global-options state-policy related action ‘accept’ - Configure a container with an isolated network:
set container network npm prefix ‘172.20.0.0/24’
set container name npm image ‘docker.io/jc21/nginx-proxy-manager’
set container name npm network npm address ‘172.20.0.2’ - Commit and attempt to reach the container:
ping 172.20.0.2 # 100% packet loss
Expected Behavior
Container is reachable on its network address. ARP resolves the bridge gateway.
Actual Behavior
Container is completely unreachable. ARP requests from the container never receive a reply. The container cannot resolve its gateway MAC address.
Root Cause
VyOS 1.5’s ZBF implementation generates a VYOS_STATE_POLICY chain in table bridge vyos_filter and loads the nf_conntrack_bridge kernel module.
On VyOS 1.5 with ZBF, the bridge VYOS_STATE_POLICY hooks globally across every bridge on the system, including bridges dynamically created by the container runtime (e.g., pod-npm). ARP packets (EtherType 0x0806) traversing the container bridge are evaluated by nf_conntrack_bridge, marked as ct state invalid since ARP is not a trackable Layer 3 protocol, and dropped before the kernel ARP stack can generate a reply.
Evidence
VyOS 2026.03 with ZBF + global-options state-policy
$ lsmod | grep conntrack_bridge
nf_conntrack_bridge 12288 3
On VyOS 2026.03 with ZBF, VYOS_STATE_POLICY incorrectly appears in the bridge filter:
table bridge vyos_filter
chain VYOS_INPUT_filter {
type filter hook input priority filter; policy accept;
jump VYOS_STATE_POLICY # applies to ALL bridges globally
}
chain VYOS_STATE_POLICY {
ct state established accept
ct state invalid drop # drops ARP from container bridges
ct state related accept
}
ARP requests leave the container and arrive at the host-side veth but receive no reply:
$ tcpdump -i veth0 arp
02:1f:f4:05:ce:00 > ff:ff:ff:ff:ff:ff, ARP Request who-has 172.20.0.1 tell 172.20.0.2
02:1f:f4:05:ce:00 > ff:ff:ff:ff:ff:ff, ARP Request who-has 172.20.0.1 tell 172.20.0.2
# No reply bridge drops the request before kernel ARP stack can respond
Proposed Fix
The bridge VYOS_STATE_POLICY generated by VyOS 1.5’s ZBF implementation should be scoped to bridges that are members of a configured firewall zone, not applied as a global hook to every bridge. Container runtime bridges are created dynamically and should not be subject to the zone-based stateful filter.
Alternatively, an ARP exemption should be inserted before the ct state invalid drop in the bridge filter:
chain VYOS_STATE_POLICY {
ether type arp accept # exempt ARP from conntrack evaluation
ct state established accept
ct state invalid drop
ct state related accept
}
Or maybe
chain VYOS_STATE_POLICY {
ether type != { ip, ip6 } return # skip state check for non-IP traffic entirely
ct state established accept
ct state invalid drop
ct state related accept
}
Testing Proposed Fix Results
Ningx proxy manager container:
vyos@vyos# curl http://localhost:81
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Nginx Proxy Manager</title>
<meta name="description" content="In The Office Planner" />
<link rel="preload" href="/images/logo-no-text.svg" as="image" type="image/svg+xml" fetchPriority="high">
<link
rel="apple-touch-icon"
sizes="180x180"
href="/images/favicon/apple-touch-icon.png" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/images/favicon/favicon-32x32.png" />
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/images/favicon/favicon-16x16.png" />
<link rel="manifest" href="/images/favicon/site.webmanifest" />
<link
rel="mask-icon"
href="/images/favicon/safari-pinned-tab.svg"
color="#5bbad5" />
<link rel="shortcut icon" href="/images/favicon/favicon.ico" />
<meta name="msapplication-TileColor" content="#4a4a4a" />
<meta
name="msapplication-config"
content="/images/favicon/browserconfig.xml" />
<meta name="theme-color" content="#ffffff" />
<script type="module" crossorigin src="/assets/index-BwxhCj6Q.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-PuE7CNBN.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script>
if (global === undefined) {
var global = window;
}
</script>
</body>
</html>
[edit]