17

I want to bridge the wifi interface with the ethernet interface on a Raspberry Pi so any device on the ethernet port becomes a member of the wifi network. Usually this is done by a bridge (OSI layer 2) but on wifi you need WDS (wireless distribution system) with 4addr to do it. Unfortunately WDS isn't supported by the wifi on board chip of a Raspberry Pi so we have to workaround it with proxy arp. Bridging Network Connections with Proxy ARP shows how to do it on a Debian system. But it uses old style networking that isn't supported out of the box by Raspbian.

How can I make proxy arp available on a Raspberry Pi with Raspbian?

Ingo
  • 40,606
  • 15
  • 76
  • 189

2 Answers2

33

Proxy arp is a way to build a pseudo bridge that is working on OSI layer 3 but it behaves like a real layer 2 bridge. So it is a good way to workaround the lack of WDS support on Raspberry Pi. There are mainly two methods to use it.

You can use a subnet overlapping with the main local network. This is only a configuration issue and does not need additional helper programs. This makes it more stable and less error prone. But it requires some more know how about subnets to configure it and to understand how it works. And it has some restrictions in using ip address ranges, but this doesn't matter in most cases. You will see.

There are also helper programs available that makes usage of ip address ranges more flexible but it is more sensible with correct configuration and stability. If ip address ranges doesn't matter I would prefer the subnetting method.

#############################################################################

PROXY ARP WITH SUBNETTING (recommended)

Example Setup

                           ┌─proxy arp─┐     UPLINK
                wired      V     ║     V      wifi             wan
    laptop <────────────> (eth0)RPi(wlan0) <~.~.~.~.> hotspot <---> INTERNET
         \                  ╱    ║     ╲
    (dhcp from   192.168.50.241  ║  (dhcp from hotspot)
       RPi)                      ║
                                 ║
subnet:   192.168.50.240/28      ║      192.168.50.0/24

As you can see, we have two subnets:

the main subnet (local WiFi network): 192.168.50.0/24
                        ip addresses: 192.168.50.1 to 192.168.50.254 = 254 ip addresses
                   broadcast address: 192.168.50.255

                    the wired subnet: 192.168.50.240/28
                        ip addresses: 192.168.50.241 to 152.168.50.254 = 14 ip addresses
                   broadcast address: 192.168.50.255

Have in mind that you cannot use the first network address and the last broadcast address of each subnet. For details on subnets look at Wikipedia - Subnetwork.

For proxy arp it is important that the smaller wired subnet is a subset of the main subnet and that it fits to the correct boundaries of possible subnets in the main subnet. I have set it to the end of the main subnet. With this example we can address 14 devices on the wired subnet. If you need more or less simply use a bigger or smaller wired subnet. To calculate the subnet I use this IP Calculator but there are some others. Use your own favorite one.

So we have this overlapping:

                0                                     |240       255
main subnet:   |N------------------------------------------------B|
wired subnet:                                         |N---------B|

Here you can see that a device on the main subnet can also broadcast for ip addresses 241 to 254. If you ensure - and that is important - that there are no devices on the main subnet having these addresses, the RasPi can use proxy arp to reply to the broadcast for an ip address instead of the device on the wired subnet, which isn't direct addressable from the main subnet. The RasPi works as proxy for the arp request. Please note that this has nothing to do with routing using ip addresses. It only works with mac addresses. That is also the reason why this only works on local area networks with one main subnet (broadcast domain).

Again, you must ensure that there are no devices on the main subnet using the ip addresses from the wired subnet. If using a DHCP server you must exclude this ip range from its address pool.

Now with this background information let's configure the RasPi. To simplify things I will use systemd-networkd.

Tested with
Raspberry Pi OS (32-bit) Lite 2020-05-27 on a Raspberry Pi 4B updated at 2020-08-19.
Updates done with sudo apt update && sudo apt full-upgrade && sudo reboot.

Switch over to systemd-networkd

Just follow to Use systemd-networkd for general networking. You can use section ♦ Quick Step. Then come back here.

Configure the WiFi client connection

Create this file for wpa_supplicant with your settings for country=, ssid= and psk=:

rpi ~$ sudo -Es    # if not already done
rpi ~# cat > /etc/wpa_supplicant/wpa_supplicant-wlan0.conf <<EOF
country=DE
ctrl_interface=DIR=/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="TestNet"
    psk="testingPassword"
}
EOF

rpi ~# chmod 600 /etc/wpa_supplicant/wpa_supplicant-wlan0.conf
rpi ~# systemctl disable wpa_supplicant.service
rpi ~# systemctl enable wpa_supplicant@wlan0.service
rpi ~# rfkill unblock 0

We have to enable promiscuous mode on wlan0 to see broadcasts on both subnets. Edit the wpa_supplicant.service with:

rpi ~# sudo systemctl edit wpa_supplicant@wlan0.service

In the empty editor insert these statements, save them and quit the editor:

[Service]
ExecStartPre=/sbin/ip link set %i promisc on
ExecStopPost=/sbin/ip link set %i promisc off

Configure interfaces

Create these two files.

rpi ~# cat > /etc/systemd/network/04-wired.network <<EOF
[Match]
Name=e*

[Network]
# Have attention to the bit mask at the end of the address
Address=192.168.50.241/28
# or just the smallest possible subnet with 2 ip addresses
#Address=192.168.50.253/30
# or the half of the main subnet with 126 ip addresses
#Address=192.168.50.129/25
DHCPServer=yes

[DHCPServer]
DNS=84.200.69.80 1.1.1.1
EOF

rpi ~# cat > /etc/systemd/network/08-wifi.network <<EOF
[Match]
Name=wl*

[Network]
DHCP=yes
IPForward=ipv4
IPv4ProxyARP=yes
EOF

Reboot.
That's it.

#############################################################################

PROXY ARP WITH HELPER PROGRAMS

In general I followed the tutorial (2). Have a look at it for the background.

Example Setup

                    ┌─proxy arp─┐     UPLINK
          wired     V           V      wifi             wan
laptop <─────────> (eth0)RPi(wlan0) <~.~.~.~.> hotspot <---> INTERNET
     \                           \
  (dhcp from hotspot)         (dhcp from hotspot)

I will present two setups. One that is only static but simpler. It is configured one time on startup and does not respect changes on its interfaces. You have to reboot then. It is good for static use cases, for example to connect a printer. The other setup is a bit more complex but it will monitor connection changes on its interfaces. It is usable on mobile RasPis that will connect to different WiFi networks and wired connections on the fly.

Tested with
Raspbian Buster Lite 2019-09-26 on a Raspberry Pi 4B updated at 2020-02-05. Updates done with sudo apt update && sudo apt full-upgrade && sudo reboot.
Here you can find the last tested revision for previous Raspbian versions.


♦ Static configuration of proxy arp

First do ♦ General Setup (look at the end).

Then follow this setup. parprouted runs as a daemon but has no systemd unit installed. So we will make it and enable also promiscous mode:

rpi ~# systemctl edit --full --force parprouted.service

In the empty editor insert these statements, save them and quit the editor:

[Unit]
Description=proxy arp routing service
Documentation=https://raspberrypi.stackexchange.com/q/88954/79866

[Service]
Type=forking
PIDFile=/run/parprouted.pid
# Restart until wlan0 gained carrier
Restart=on-failure
RestartSec=5
TimeoutStartSec=30
ExecStartPre=/lib/systemd/systemd-networkd-wait-online --interface=wlan0 --timeout=6 --quiet
ExecStartPre=/bin/echo 'systemd-networkd-wait-online: wlan0 is online'
# clone the dhcp-allocated IP to eth0 so dhcp-helper will relay for the correct subnet
ExecStartPre=/bin/bash -c '/sbin/ip addr add $(/sbin/ip -4 -br addr show wlan0 | /bin/grep -Po "\\d+\\.\\d+\\.\\d+\\.\\d+")/32 dev eth0'
ExecStartPre=/sbin/ip link set dev eth0 up
ExecStartPre=/sbin/ip link set wlan0 promisc on

#         v minus sign
ExecStart=-/usr/sbin/parprouted eth0 wlan0

ExecStopPost=/sbin/ip link set wlan0 promisc off
ExecStopPost=/sbin/ip link set dev eth0 down
ExecStopPost=/bin/bash -c '/sbin/ip addr del $(/sbin/ip -4 -br addr show eth0 | /bin/grep -Po "\\d+\\.\\d+\\.\\d+\\.\\d+")/32 dev eth0'

[Install]
WantedBy=wpa_supplicant@wlan0.service

Enable the service:

rpi ~# systemctl enable parprouted.service

Reboot.
That's it.


♦ Dynamic configuration of proxy arp with monitoring interfaces

Because we will monitor the state of the inerfaces wlan0 and eth0 we have to use a program that will report changes. For this I have made the ifplug.service.

So first Make ifplugd available again since Raspbian Stretch.

If you have tested the ifplug.service and it works,

then do ♦ General Setup.

After that configure the eth0 interface with this file:

rpi ~# cat > /etc/systemd/network/04-eth.network <<EOF
[Match]
Name=eth0
EOF

As action file for the ifplug.service use this /etc/ifplugs/ifplugs.action script:

#!/bin/bash
# redirect all output into a logfile for debug
#exec 1>> /tmp/ifplug-debug.log 2>&1

INTERFACE="$1"
EVENT="$2"
IPADDR=''

get_ipaddr () {
    if [ "$EVENT" = "down" ]; then
        IPADDR=$(/sbin/ip -4 -br addr show $INTERFACE | /bin/grep -Po "\d+\.\d+\.\d+\.\d+")
        return 0
    fi
    # check 10 times with 1 sec delay if ip address is available
    for ((i=10; i>0; i--)); do
        IPADDR=$(/sbin/ip -4 -br addr show $INTERFACE | /bin/grep -Po "\d+\.\d+\.\d+\.\d+")
        [ $? -eq 0 ] && break
        /bin/sleep 1
    done
}

case "$INTERFACE" in
eth0)
    case "$EVENT" in
    up)
        # clone ip address from wlan0 and start parprouted
        IPADDR=$(/sbin/ip -4 -br addr show wlan0 | /bin/grep -Po "\d+\.\d+\.\d+\.\d+")
        if [ -n "$IPADDR" ]; then
            /sbin/ip addr add $IPADDR/32 dev eth0
            /usr/sbin/parprouted eth0 wlan0
        fi
        ;;
    down)
        # stop parprouted
        /usr/bin/killall -q parprouted
        /sbin/ip -4 addr flush dev eth0
        ;;
    *)
        >&2 echo empty or undefined event for "$INTERFACE": \""$EVENT"\"
        exit 1
        ;;
    esac
    ;;

wlan0)
    case "$EVENT" in
    up)
        # clone ip address from wlan0 and start parprouted
        get_ipaddr
        if [ -n "$IPADDR" ]; then
            /sbin/ip addr add $IPADDR/32 dev eth0
            /sbin/ip link set wlan0 promisc on
            /usr/sbin/parprouted eth0 wlan0
        fi
        ;;
    down)
        # stop parprouted
        /usr/bin/killall -q parprouted
        /sbin/ip link set wlan0 promisc off
        /sbin/ip -4 addr flush dev eth0
        ;;
    *)
        >&2 echo empty or undefined event for "$INTERFACE": \""$EVENT"\"
        exit 1
        ;;
    esac
    ;;

*)
    >&2 echo empty or unknown interface: \""$INTERFACE"\"
    exit 1
    ;;
esac

Reboot.
That's it.

You can follow actions with:

rpi ~$ journalctl --follow | grep "ifplugd\|parprouted"

♦ General Setup

Install helpers and setup systemd-networkd

I will use systemd-networkd for reasons so first we have to switch over to it. For detailed information look at (1). Here only in short. Execute these commands:

# install helpers
rpi ~$ sudo -Es
rpi ~# apt install parprouted dhcp-helper
rpi ~# systemctl stop dhcp-helper
rpi ~# systemctl enable dhcp-helper

# deinstall classic networking
rpi ~# apt --autoremove purge ifupdown dhcpcd5 isc-dhcp-client isc-dhcp-common
rpi ~# rm -r /etc/network /etc/dhcp

# setup systemd-resolved
rpi ~# apt --autoremove purge avahi-daemon
rpi ~# apt install libnss-resolve
rpi ~# systemctl enable systemd-resolved.service
rpi ~# ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

# enable systemd-networkd
rpi ~# systemctl enable systemd-networkd.service

Configure wpa_supplicant

To configure wpa_supplicant create this file with your settings for country=, ssid= and psk=. You can just copy and paste this in one block to your command line beginning with cat and including EOF (delimiter EOF will not get part of the file):

rpi ~# cat > /etc/wpa_supplicant/wpa_supplicant-wlan0.conf <<EOF
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=DE

network={
    ssid="TestNet"
    key_mgmt=WPA-PSK
    proto=RSN WPA
    psk="verySecretPassword"
}
EOF

rpi ~# chmod 600 /etc/wpa_supplicant/wpa_supplicant-wlan0.conf
rpi ~# systemctl disable wpa_supplicant.service
rpi ~# systemctl enable wpa_supplicant@wlan0.service

To configure the wlan0 interface create this file:

rpi ~# cat > /etc/systemd/network/08-wlan0.network <<EOF
[Match]
Name=wlan0
[Network]
IPForward=yes
DHCP=yes
# for a static ip address comment DHCP=yes
# and uncomment next 3 lines with your settings
#Address=192.168.50.2/24
#Gateway=192.168.50.1
#DNS=84.200.69.80 1.1.1.1
EOF

Setup helpers

We have installed dhcp-helper, a proxy to get ip addresses from the wifi network, and parprouted that manages proxy arp.

Enable DHCP relay in /etc/default/dhcp-helper:

# relay dhcp requests as broadcast to wlan0
DHCPHELPER_OPTS="-b wlan0"

End of General Setup. Go back.


references:
[1] Howto migrate from networking to systemd-networkd with dynamic failover
[2] Bridging Network Connections with Proxy ARP
[3] ProxyARP Subnetting HOWTO

Ingo
  • 40,606
  • 15
  • 76
  • 189
  • I have followed your tutorial, but I get this error in syslog when inserting a network cable "parprouted[426]: error: ioctl SIOCGIFADDR for eth0: Cannot assign requested address". What can be wrong? – The87Boy Sep 11 '18 at 08:40
  • @The87Boy I guess it is the first error message on startup I have pointed to in the last sentence of my answer. If you look with `journalctl -b -e` you should also find the message "*systemd[1]: **Started** proxy arp routing service*" after your error message. Distinguish it from **Starting ...**. Your error message should not occur more than one or two times. – Ingo Sep 11 '18 at 09:08
  • It starts occuring, when I insert the network cable and continues, until I remove it again – The87Boy Sep 11 '18 at 09:32
  • Ah, ok. Haven't tested autoplug. I will look at it. Just a moment please. Try to use it first with cable plugged in. – Ingo Sep 11 '18 at 10:03
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/83011/discussion-between-ingo-and-the87boy). – Ingo Sep 11 '18 at 13:53
  • +1 for all of your hard work, and I have a couple of questions if I may: 1. I've found this (wifi-Enet) bridging useful when I've needed to network a piece of equipment that had Enet, but not WiFi, and no Enet jacks handy - is that your intended purpose? Wrt your comments re *promiscuous mode*: Do you think this shortcoming is due to the fact that RPI's Enet interface is partially implemented with a "re-purposed" USB hardware rather than Ethernet hardware? Finally: This answer would seem to be a great idea as a startup script for *systemd* - thoughts on that? – Seamus Sep 14 '18 at 12:17
  • @Ingo, Thanks for the toturials, it is very useful. Though I have one more question, is there a way to use parprouted.service work with connmanctl? Currently I use connman to manage wifi links and have not a way to use network-manager or networking service. – esky-sh service May 13 '19 at 12:25
  • @esky-shservice I haven't used *connman* so far so I don't know how it can be made workable with *parprouted*. The answer is made for a static configuration. If one of the connections changes, e.g. on a mobile device, then you have to reboot the RasPi. Im just working on a more dynamic setup using a modified old **ifplugd** package: [Make ifplugd available again since Raspbian Stretch](https://raspberrypi.stackexchange.com/a/96605/79866). I will update the answer in some days. – Ingo May 13 '19 at 15:02
  • @Ingo Incredible! Very thorough and detailed -- many thanks for taking your time in writing it. I don't know how I would have managed to make `dchp-helper` work without your hints. ( I was using a configuration loosely based on method 1. ) @skerit : for me it helped (a) to make sure `ip addr add` failure stops the unit ( so that it will get restarted ) and (b) to add configured network as a dependency -- e.g. Wants/After `network-online.target` ; I shall add that in my case a manual unit restart seemed to work, so it clearly looked like a timing issue – ジョージ Aug 17 '19 at 13:56
  • @Ingo to add, I was normally able to bridge an ethernet and a wireless device together in a "proper" bridge ( one can e.g. directly use `bridge-utils` ) -- although in my tests it was the other way around: `world <-> eth0 <-> wlan0` ; however, for a wlan/wlan connection this looks as the best approximation to bridging one can have -- at least to my knowledge. – ジョージ Aug 17 '19 at 14:02
  • @ジョージ What I have verified is that you cannot add an interface (wlan0) for a wifi client connection (uplink) to a bridge even if you use `bridge-utils`. I got error message `Operation not permitted`; details at [Raspberry Pi WiFi to Ethernet Bridge for a server?](https://raspberrypi.stackexchange.com/a/81518/79866). Bridging an interface from an access point is possible if you mean that. – Ingo Aug 17 '19 at 14:55
  • I managed to use this method with libvirt/kvm. I came across something useful. dnsmasq can replace dhcp-helper. dnsmasq doesn't require exclusive access to the DHCP ports (it has a configuration option "bind-dynamic") and it can act as a DHCP relay. When you configure dnsmasq as a relay, you need to use the IP address of the WLAN interface and give the IP address of the DHCP server for your network. – Bonaparte Apr 28 '21 at 11:17
  • @Bonaparte Very interesting to use `dnsmasq` instead of `dhcp-helper`. How exactly do you managed it? Can you give a configuration example, maybe with a new answer? – Ingo Jun 21 '21 at 08:22
  • Thanks for this detailed guide! I found that for me, the command order was to first start `parprouted` and then add the ip using `ip addr add`, while `dhcp-helper` was running already. I'm running pi-hole so couldn't switch to `systemd-networkd` either. If you have pi-hole set up on the same RPi, I don't think it's possible to use that as the DNS server for the connected computer. My guess is that that's because all requests go through the RPi to the router. – Afzal N Jul 10 '21 at 00:13
  • @AfzalN You do not need to use `systemd-networkd`. Proxy arp is used since ages of years long before `systemd-networkd` was available. – Ingo Jul 10 '21 at 08:21
  • For sure. I'm just saying that maybe because of that difference, I needed the reverse order of commands maybe? Still had to use static IP on the client though, so I guess dhcp-helper didn't really do anything for me. – Afzal N Jul 11 '21 at 01:09
  • @AfzalN If possible use recommended subneting. There you are free with static ip addresses within its subnets and you don't need to use `systemd-networkd` to configure it. And I think you can run pi-hole in any of the subnets (haven't used pi-hole so far). proxy-arp is completely transparent and it doesn't see it. – Ingo Jul 11 '21 at 08:54
3

The above answer is great and helped me to save a lot of time with my setup, but I chose to make a few changes I thought I'll share.

The setup is this: I have a Denon amplituner that has an Ethernet jack, but no wifi; it also doesn't have Spotify. Since it accepts HDMI input, I chose to use Volumio on a Raspberry Pi 1B - audio via HDMI, Raspberry gets WiFi uplink, and Denon is connected to RPi Ethernet so I can control it from Android app.

My Denon is using static IP, so dhcp-helper is not needed, but if you use dynamic IP then just install it as described above. Also, due to appliance-like nature of Volumio I did not want to move from networking to systemd-networkd (actually I tried, made a lot of mess and had to re-burn Volumio). Also, wpa_supplicant service is not running. parprouted.service file is updated to reflect this.

What is needed:

  1. After burning Volumio image to SD card, connect Raspberry Ethernet jack to your router for initial network configuration - this is needed since it's also updating some Volumio config files, you'll just lose time if you start to configure interfaces from CLI.

  2. Update line net.ipv4.ip_forward in /etc/sysctl.conf to net.ipv4.ip_forward=1.

  3. Install parprouted: sudo apt install parprouted.

  4. Create systemd unit file - unfortunately, on current (September 2019) Volumio image systemctl edit doesn't work. Instead, run sudo touch /etc/systemd/system/parprouted.service and then use your editor of choice to put this inside:

parprouted.service

[Unit]
Description=proxy arp routing service
Documentation=https://raspberrypi.stackexchange.com/q/88954/79866

[Service]
Type=forking
# Restart until wlan0 gained carrier
Restart=on-failure
RestartSec=5
TimeoutStartSec=30
ExecStartPre=/usr/bin/perl -e 'sleep 1 until -e "/sys/class/net/wlan0"'
ExecStartPre=/bin/echo 'parprouted: wlan0 is online'
# clone the dhcp-allocated IP to eth0 so dhcp-helper will relay for the correct subnet
ExecStartPre=/bin/bash -c '/sbin/ip addr add $(/sbin/ip -4 addr show wlan0 | /bin/grep -Po "\\d+\\.\\d+\\.\\d+\\.\\d+\/")32 dev eth0'
ExecStartPre=/sbin/ip link set dev eth0 up
ExecStartPre=/sbin/ip link set wlan0 promisc on

#         v minus sign
ExecStart=-/usr/sbin/parprouted eth0 wlan0

ExecStopPost=/sbin/ip link set wlan0 promisc off
ExecStopPost=/sbin/ip link set dev eth0 down
ExecStopPost=/bin/bash -c '/sbin/ip addr del $(/sbin/ip -4 addr show eth0 | /bin/grep -c1 -Po "\\d+\\.\\d+\\.\\d+\\.\\d+")/32 dev eth0'

[Install]
WantedBy=multi-user.target
  1. Finish by running sudo systemctl enable parprouted.
StanTastic
  • 131
  • 3