How to Accurately Match and Filter Bridge Interface Traffic Using iptables PHYSOUT Rules


2 views

When working with Linux bridge interfaces in a firewall configuration, you'll encounter scenarios where standard iptables rules don't behave as expected. The core issue arises when trying to match packets based on their physical egress interface (PHYSOUT) in a bridged environment.

Consider this network topology:

    |          |         |
   eth0       eth1      eth2
    | == br0== |         |
          |              |
          |              |
         --- linux node ---

When implementing firewall rules, you might attempt to block traffic to eth0 while allowing it to eth1 using:

iptables -A FORWARD -o br0 --physdev-out eth0 -j DROP
iptables -A FORWARD -o br0 --physdev-out eth1 -j ACCEPT

However, you'll notice inconsistent behavior because:

  • The bridge's forwarding decision depends on MAC learning
  • Packets from non-bridge members (eth2) don't always populate PHYSOUT
  • Broadcast/multicast traffic complicates interface selection

Through testing, we identified three distinct forwarding patterns:

1. Intra-bridge traffic (eth1→eth0):
   IN=br0 OUT=br0 PHYSIN=eth1 PHYSOUT=eth0

2. External→bridge traffic (eth2→eth0):
   IN=eth2 OUT=br0 (PHYSOUT missing)

3. Broadcast traffic:
   IN=eth2 OUT=br0 (multiple PHYSOUTs possible)

To create consistent rules, we need multiple approaches:

Solution 1: MAC Address-Based Filtering

When you know destination MAC addresses:

# Get MAC address for eth0's network
ETH0_MAC="aa:bb:cc:dd:ee:ff"

iptables -A FORWARD -o br0 -m mac --mac-destination $ETH0_MAC -j DROP

Solution 2: Combined physdev and conntrack

For stateful filtering:

iptables -A FORWARD -o br0 -m conntrack --ctstate NEW \
         -m physdev --physdev-out eth0 -j DROP

Solution 3: ebtables for Bridge-Level Control

Sometimes better to handle at bridge level:

ebtables -A FORWARD --logical-out eth0 -j DROP
ebtables -A FORWARD --logical-out eth1 -j ACCEPT

For traffic that might flood multiple ports:

iptables -A FORWARD -o br0 -m physdev --physdev-is-bridged \
         -m pkttype --pkt-type unicast -j ACCEPT

Here's a working ruleset for our original scenario:

# Clear existing rules
iptables -F
ebtables -F

# Allow established connections
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Block HTTP to eth0 network
iptables -A FORWARD -o br0 -p tcp --dport 80 \
         -m physdev --physdev-out eth0 -j DROP

# Fallback for unlearned MACs
iptables -A FORWARD -o br0 -p tcp --dport 80 \
         -m pkttype --pkt-type unicast \
         -m physdev --physdev-is-bridged \
         -j NFLOG --nflog-prefix "Unknown-eth0-dest: "

# Allow all other traffic
iptables -P FORWARD ACCEPT

This configuration provides logging for uncertain cases while maintaining strict filtering where possible.


When working with bridged interfaces in Linux, iptables filtering becomes particularly challenging due to the way bridge forwarding works at Layer 2. The core issue arises from the bridge's learning mechanism and broadcast nature.

The --physdev match in iptables was specifically designed for bridging scenarios. However, its behavior depends heavily on whether the bridge has learned MAC addresses:

# Basic physdev rule format
-A FORWARD -o br0 --physdev-out eth0 -p tcp --dport 80 -j DROP
-A FORWARD -o br0 --physdev-out eth1 -p tcp --dport 80 -j ACCEPT

In our testing, we identified three distinct forwarding behaviors:

  1. Known MAC destination: Bridge forwards to specific port
  2. Unknown unicast: Floods to all ports except source
  3. Broadcast/multicast: Floods to all ports

Here's a robust approach combining multiple techniques:

# Mark packets entering through eth0
-A PREROUTING -i eth0 -j MARK --set-mark 1

# Mark packets entering through eth1
-A PREROUTING -i eth1 -j MARK --set-mark 2

# Then in FORWARD chain:
-A FORWARD -m mark --mark 1 -o br0 -j DROP
-A FORWARD -m mark --mark 2 -o br0 -j ACCEPT

For more precise bridge filtering, consider using ebtables:

ebtables -A FORWARD --logical-out br0 -o eth0 -j DROP
ebtables -A FORWARD --logical-out br0 -o eth1 -j ACCEPT

Here's a complete example for our specific port 80 case:

# Enable bridge-nf-call to make iptables see bridged traffic
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables

# Mark traffic by incoming interface
iptables -t mangle -A PREROUTING -i eth0 -j MARK --set-mark 0x1
iptables -t mangle -A PREROUTING -i eth1 -j MARK --set-mark 0x2

# Main filtering rules
iptables -A FORWARD -m mark --mark 0x1 -o br0 -p tcp --dport 80 -j DROP
iptables -A FORWARD -m mark --mark 0x2 -o br0 -p tcp --dport 80 -j ACCEPT

# Fallback for unmarked traffic (unknown MAC case)
iptables -A FORWARD -o eth0 -p tcp --dport 80 -j DROP
iptables -A FORWARD -o eth1 -p tcp --dport 80 -j ACCEPT

When implementing these solutions, remember that:

  • Bridge-nf adds processing overhead
  • Mark-based solutions are generally faster than physdev inspection
  • MAC learning state affects performance (known vs unknown destinations)