Skip to content

setup() never binds the socket: provisioning packet leaves via the default route on multi-homed hosts #844

Description

@gabrielcolceriu

Summary

setup() creates a UDP socket and sends the provisioning payload without ever binding it to a local address:

https://github.com/mjg59/python-broadlink/blob/master/broadlink/__init__.py#L298

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(payload, (ip_address, DEFAULT_PORT))

On a host with more than one active interface, an unbound socket sending to 255.255.255.255 leaves via the default route, not via the interface attached to the device's BroadlinkProv AP. The call returns successfully, nothing is raised, and the device is simply never provisioned — a silent failure that is very hard to attribute.

This is inconsistent with discover() / xdiscover(), which already expose local_ip_address for exactly this reason.

How it shows up in practice

Provisioning a device from a laptop that is simultaneously on the device AP (Wi-Fi) and on a USB/Ethernet uplink is a completely ordinary setup — you need the second interface to keep working while the Wi-Fi is occupied by the AP. In that configuration setup() never reaches the device.

Diagnosed on macOS with Wi-Fi on the device AP (192.168.10.2) and an active USB tether holding the default route. setup() reported success; the device never associated. Binding the socket to the Wi-Fi address and sending to the subnet broadcast made it work on the first try.

Steps to reproduce

  1. Host with two active interfaces, default route on interface B.
  2. Put a Broadlink device in AP mode; connect interface A to BroadlinkProv (host gets 192.168.10.x).
  3. Call broadlink.setup(ssid, password, 3).
  4. Packet is transmitted on interface B. Device is never configured, no error raised.

Suggested fix

Accept an optional local address and bind, mirroring discover():

def setup(
    ssid: str,
    password: str,
    security_mode: int,
    ip_address: str = DEFAULT_BCAST_ADDR,
    local_ip_address: Optional[str] = None,
) -> None:
    ...
    if local_ip_address:
        sock.bind((local_ip_address, 0))
    sock.sendto(payload, (ip_address, DEFAULT_PORT))

Sending to the subnet broadcast in addition to 255.255.255.255 also helps on hosts that drop limited broadcasts.

Happy to open a PR if the approach looks right.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions