I'm using this on a raspberry pi 3 running Ubuntu server to bridge an Ethernet-only OpenSprinker to my WiFi network.
This is a proxy-ARP pseudo-bridge (parprouted + dhcp-helper), not an L2 bridge. The pi routes between eth0 and wlan0.
sudo apt install parprouted dhcp-helper avahi-daemon net-tools networkd-dispatcher arp-scan/etc/netplan/60-wifi-bridge.yaml:
network:
version: 2
ethernets:
renderer: networkd
eth0:
optional: true
dhcp4: false/etc/sysctl.d/local.conf:
net.ipv4.ip_forward=1
# best-effort: let eth0 learn the OpenSprinkler from its gratuitous ARP after DHCP.
# not sufficient on its own — see the priming timer below.
net.ipv4.conf.eth0.arp_accept=1
/etc/networkd-dispatcher/routable.d/50-wifi-bridge:
#!/bin/sh
if [ "$IFACE" = "wlan0" ]; then
/sbin/ip link set wlan0 promisc on
/sbin/ip addr add $(/sbin/ip addr show wlan0 | perl -wne 'm|^\s+inet (.*)/| && print $1')/32 dev eth0
/usr/sbin/parprouted eth0 wlan0
fi/etc/networkd-dispatcher/off.d/50-wifi-bridge:
#!/bin/sh
if [ "$IFACE" = "wlan0" ]; then
/usr/bin/killall /usr/sbin/parprouted
/sbin/ip link set eth0 down
fi/usr/local/sbin/wifi-bridge-prime.sh:
#!/bin/sh
# Discover whatever the OpenSprinkler leased and keep its eth0 neighbor entry fresh.
# Logs only on change so the journal reads as events, not a 15s heartbeat.
state=/run/wifi-bridge-prime.state
subnet=$(/sbin/ip -o -f inet route show dev wlan0 scope link | awk '{print $1; exit}')
if [ -z "$subnet" ]; then
echo "wlan0 has no connected subnet yet; skipping this cycle"
exit 0
fi
hosts=$(/usr/sbin/arp-scan --interface=eth0 --retry=2 "$subnet" 2>&1 \
| awk '/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[ \t]/ {print $1, $2}')
# The real work: refresh every run, silently.
echo "$hosts" | while read ip mac; do
[ -n "$ip" ] && /sbin/ip neigh replace "$ip" lladdr "$mac" dev eth0 nud reachable
done
# The logging: only when what we found differs from last run.
if [ "$hosts" != "$(cat "$state" 2>/dev/null)" ]; then
if [ -z "$hosts" ]; then
echo "no hosts found on eth0 (subnet $subnet)"
else
echo "discovered on eth0: $hosts"
fi
printf '%s\n' "$hosts" > "$state"
fiMake the hook scripts executable.
sudo chmod +x /etc/networkd-dispatcher/routable.d/50-wifi-bridge
sudo chmod +x /etc/networkd-dispatcher/off.d/50-wifi-bridge
sudo chmod +x /usr/local/sbin/wifi-bridge-prime.sh/etc/systemd/system/wifi-bridge-prime.service:
[Unit]
Description=Prime parprouted neighbor table for OpenSprinkler
After=network-online.target
[Service]
Type=oneshot
ExecStart=-/usr/local/sbin/wifi-bridge-prime.sh/etc/systemd/system/wifi-bridge-prime.timer:
[Unit]
Description=Periodically prime parprouted for OpenSprinkler
[Timer]
OnBootSec=30
OnUnitActiveSec=15
[Install]
WantedBy=timers.targetEdit /etc/default/dhcp-helper:
# relay dhcp requests as broadcast to wlan0
DHCPHELPER_OPTS="-b wlan0"Edit /etc/avahi/avahi-daemon.conf:
[reflector]
enable-reflector=yes
Enable services:
sudo systemctl enable dhcp-helper
sudo systemctl enable wifi-bridge-prime.timerReboot!
ip neigh show dev eth0
ip route | grep eth0
journalctl -u wifi-bridge-prime.service- Main documentation: https://wiki.debian.org/BridgeNetworkConnectionsProxyArp
- Netplan yaml reference: https://netplan.readthedocs.io/en/stable/netplan-yaml/
- Netplan FAQ for post-up/post-down scripts: https://netplan.io/faq
- networkd-dispatcher docs: https://gitlab.com/craftyguy/networkd-dispatcher/-/tree/master
- WiFi promiscuous mode: PiSCSI/piscsi#1387
Inspired by: https://gist.github.com/Jiab77/76000284f8200da5019a232854421564