Content-Type: text/plain

At some point during the development of lighthouse, I bought a network repeater to extend the range of my home wireless network. It was soon after setting it up that I noticed it was doing something unexpected with ARP packets.

None of the devices sitting behind the repeater responded to ARP requests. Or so it seemed. Some wiresharking later and it was clear that ARP responses from devices behind the repeater were all claiming to be from the same MAC address; that is, the MAC address of the repeater itself.

It turns out this is simply how wireless repeaters work when they don't implement WDS or a mesh protocol like EasyMesh, due to addressing limitations of 802.11 frames.

In this limited mode, only 3 of the 4 available address fields are used. Their meaning depends on which direction the traffic is going. If we consider the case of a packet travelling from a wireless client (sitting behind the wireless repeater) to the Internet (via a physical router), then the 3 addresses correspond to the receiver (the AP, likely your router), the transmitter (the client NIC, your computer), and the final destination (router or gateway).

For a client sitting behind a wireless repeater, there's no way for a packet to get to the router unless destined originally for the repeater and then passed on (with rewritten frame headers) as though it came from the repeater itself.

This is known as Proxy-ARP: the repeater answers ARP requests with its own MAC address for every device on its side of the network, ensuring that all subsequent unicast packets are sent directly to it. On receiving such a unicast packet, it rewrites the header again before handing the frame to the device.

That was a highly simplified (perhaps oversimplified) explanation. For much more detail, check out this detailed overview of 802.11 frames.

The solution was quite simple in the end: we just needed to join the packet-rewriting party, at least in spirit. The only reasonable way I figured to achieve this was by statically mapping the IPs of the devices behind the repeater to their MAC addresses, then using that as a lookup when handling ARP responses.

if repeaters, ok := netutil.ParseMACList(settingR.SourceMACAddresses()); ok {
    if slices.Contains(repeaters, macAddressStr) {
        mappings := mappingR.Map()
        if value, ok := mappings[ipAddressStr]; ok {
            macAddressStr = value
        }
    }
}

As you can see in the snippet above, if the ARP response comes from a repeater, use the IP address to look up and swap out the MAC address for the mapped one.

Having to maintain this mapping is unfortunate but, in my case at least, an acceptable tradeoff. The number of devices I have sitting behind the repeater is small (4) and I already use static IP addresses across the network, which rarely change.

An interesting note about the original commit is that the UI and CRUD logic takes up almost all of the changes, but the "real work" ends up being a mere 5 sloc.