Reasons for observed behviour
The reason that iptables doesn't get the physical bridge information when the packet arrives from a non-bridged interface is that the packet has never been near the bridging mechanism, even though at this point we know we are sending it out on the bridge.
In the case where the packet did arrive over a bridge port, but it is an N>2 bridge, the problem is that the iptables PHYSDEV extention only provides for their being one value for "out", so it just doesn't bother telling us if there are two.
Solution
Use ebtables instead of iptables. The ebtables OUTPUT
chain will know which physical bridged interface it is sending packets out on.
In the scenario above, where you want to filter packets that are leaving via a specific bridged interface (eth0
), regardless of how it arrived into the system, add an ebtables rule along the following lines:
-A OUTPUT -o eth0 -j <target>
In a more complex scenario, where you want to filter packets arriving from a specific interface, and leaving via a bridged interface, it gets harder. Say we want to drop all traffic from eth2
(non-bridged) going to eth0
(bridged as part of br0
) we need to add this rule to iptables:
-A FORWARD -i eth2 -o br0 -j MARK --set-mark 1234
This will mark any packet that comes from eth2
and is bound for the bridge. Then we add this rule to ebtables:
-A OUTPUT -physdev-out eth0 --m mark --mark 1234 -j DROP
Which will DROP
any packet marked by iptables (as being from eth2
) that is egressing via the specific bridge port eth0
.
Acknowledgements
Thanks goes out to Pascal Hambourg over at the netfilter iptables mailing list for his help in coming up with this solution.
Best Answer
The
lo
interface is not an ethernet interface. It doesn't have a link layer address and can certainly not be part of a bridge. So there's no wayiifname lo
will ever match.The bridge layout is still organised similarly to the ip layout, but working at layer 2: ethernet frames that are switched (instead of routed) will enter the forward chain. Ethernet frames that are for the host or coming from the host won't traverse any chain (they weren't created below since they are useless in this use case).
Let's suppose the network is like this:
with
eth0
andeth1
enslaved tobridge0
.So with one unique bridge on the host, this "empty" nft ruleset is enough to isolate
slave
, while allowingmaster
unrestricted traffic with LAN andslave
, just by using a drop policy with the forward chain.It's certainly normal to expect ARP working both ways (else
slave
won't be able to do much at the IP level):Now if LAN is allowed to ping
slave
, but not the opposite:Note that using nftables at the bridge level is still somewhat limited (but cleaner) compared to iptables, because there's still no conntrack integration as of today. So there as far as I know no stateful firewalling available.So some usually easy tasks may seem awkward, eg: allow ssh from (possibly remote) IP
203.0.113.3
. It turns into: allow traffic both ways, except for the initial syn not allowed from slave to LAN:If there is more than one bridge on
master
, then the rules and/or default policies might have to be adapted.Stateful firewalling at the bridge layer
UPDATE: kernel 5.3 brought the module
nf_conntrack_bridge
to have conntrack available at the bridge layer, allowing stateful firewalling. Warning, interactions with olderbr_netfilter
, often loaded in Docker environments, might produce unexpected results.With such a kernel and a recent enough nftables, replacing the rules above by this ruleset below will allow ARP, and will allow incoming ping or incoming ssh only from IP address 203.0.113.3 to reach slave in a stateful manner: their replies are automatically allowed. slave is still barred from initiating any communication except ARP. The
setctzone
chain is completely optional: it allows to track separately the bridge conntrack entries from other conntrack entries (eg: generated at the IP routing layer) by assigning them a different conntrack zone id, to avoid clashing entries in very exotic setups.