Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 

Repository files navigation

WiFi Bridge

I'm using this on a raspberry pi 3 running Ubuntu server to bridge an Ethernet-only OpenSprinkler 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.

Install

sudo apt install parprouted dhcp-helper avahi-daemon net-tools networkd-dispatcher arp-scan iw

/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

/usr/local/sbin/wifi-bridge-setup.sh:

#!/bin/sh
# Mirror wlan0's current address onto eth0 as a /32 for parprouted.
set -e

addr=$(/sbin/ip -o -f inet addr show wlan0 | awk '{print $4}' | cut -d/ -f1 | head -n1)
[ -n "$addr" ] || exit 1

/usr/sbin/iw dev wlan0 set power_save off || true # Eat errors instead of failing the entire setup
/sbin/ip link set wlan0 promisc on
/sbin/ip link set eth0 up
# flush first: a changed wlan0 lease must not leave a stale /32 behind
/sbin/ip -4 addr flush dev eth0
/sbin/ip addr add "$addr/32" dev eth0

/etc/systemd/system/wifi-bridge.service:

[Unit]
Description=parprouted proxy-ARP bridge (eth0 <-> wlan0)
After=network.target

[Service]
Type=forking
ExecStartPre=/usr/local/sbin/wifi-bridge-setup.sh
ExecStart=/usr/sbin/parprouted eth0 wlan0
ExecStopPost=-/sbin/ip -4 addr flush dev eth0
Restart=always
RestartSec=5

/etc/networkd-dispatcher/routable.d/50-wifi-bridge:

#!/bin/sh

[ "$IFACE" = "wlan0" ] || exit 0
/usr/bin/systemctl restart wifi-bridge.service

/etc/networkd-dispatcher/off.d/50-wifi-bridge:

#!/bin/sh

[ "$IFACE" = "wlan0" ] || exit 0
/usr/bin/systemctl stop wifi-bridge.service

/usr/local/sbin/wifi-bridge-prime.sh:

#!/bin/sh
# Discover whatever the OpenSprinkler leased and keep its eth0 neighbor entry fresh.
# Also clears bogus wlan0 neighbor entries for eth0-side hosts, which make
# parprouted thrash (create arptab entry -> want_route 0 -> delete, forever).
# 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

self=$(cat /sys/class/net/eth0/address)
hosts=$(/usr/sbin/arp-scan --interface=eth0 --retry=2 "$subnet" 2>&1 \
  | awk -v self="$self" \
      'tolower($2) != self && /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[ \t]/ {print $1, $2}')

# The real work: refresh every run, silently. Clearing a stray wlan0 entry is
# rare enough to log as an event.
echo "$hosts" | while read ip mac; do
  [ -n "$ip" ] || continue
  /sbin/ip neigh replace "$ip" lladdr "$mac" dev eth0 nud reachable
  if /sbin/ip neigh show to "$ip" dev wlan0 2>/dev/null | grep -q .; then
    echo "clearing bogus wlan0 neighbor entry for $ip"
    /sbin/ip neigh del "$ip" dev wlan0 2>/dev/null
  fi
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"
fi

Make 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
sudo chmod +x /usr/local/sbin/wifi-bridge-setup.sh

Add Symlink:

sudo mkdir -p /etc/networkd-dispatcher/no-carrier.d
sudo ln -s /etc/networkd-dispatcher/off.d/50-wifi-bridge /etc/networkd-dispatcher/no-carrier.d/50-wifi-bridge

/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.target

Edit /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

Edit /etc/systemd/journald.conf:

SystemMaxUse=50M

Enable services:

sudo systemctl enable dhcp-helper
sudo systemctl enable wifi-bridge-prime.timer

Note: wifi-bridge.service is not enabled as it's managed by the routable hooks.

Reboot!

Troubleshooting

ip neigh show dev eth0
ip route | grep eth0
systemctl status wifi-bridge
journalctl -u wifi-bridge-prime.service
journalctl -u wifi-bridge.service

References

Inspired by: https://gist.github.com/Jiab77/76000284f8200da5019a232854421564

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors