Skip to content

USB-NCM DHCP-Server shared with WiFi-AP DHCP-Server -> local link IP on AP #19685

Description

@SciLor

Port, board and/or hardware

pico2w

MicroPython version

v1.29.0

Reproduction

Build with USB-NCM enabled and run this on a Pico W/Pico 2 W:

import network

# Brings up the SoftAP - own DHCP server on 192.168.4.0/24.
ap = network.WLAN(network.AP_IF)
ap.config(ssid="repro-ap", password="testtest", security=network.WLAN.SEC_WPA_WPA2)
ap.active(True)
print("AP up:", ap.ifconfig())  # ('192.168.4.1', '255.255.255.0', ...)

# Brings up USB-NCM - its own separate DHCP server on a MAC-derived
# 169.254.x.0/16 pool, active unconditionally regardless of WiFi mode.
nic = network.USBD_NCM()
nic.active(True)
print("USB-NCM up:", nic.ifconfig())  # ('169.254.X.1', '255.255.0.0', ...)

Then connect any WiFi client (phone/laptop) to repro-ap. Expected: the client gets a 192.168.4.x lease. Actual: the client gets a 169.254.X.y lease (confirm via the client's own reported gateway/DNS, which will read back as the USB-NCM interface's own 169.254.X.1 address) and cannot reach 192.168.4.1.

Expected behaviour

Get an IP-Address in the 192.168.0.0/16 range

Observed behaviour

Getting a local link 169.254 IP via the WiFi AP DHCP, which is identical to the one on the USB-NCM

Additional Information

Suggested fix

Scope each DHCP server's UDP PCB to its own interface with the existing udp_bind_netif() API, so lwIP's first-line netif check in udp_input_local_match() (if (pcb->netif_idx != NETIF_NO_INDEX && pcb->netif_idx != netif_get_index(current_input_netif)) return 0;) rejects it outright for any broadcast arriving on a different interface:

Add a struct netif *netif parameter to dhcp_server_init() (shared/netutils/dhcpserver.h/.c).
In dhcp_server_init(), after dhcp_socket_bind()'s udp_bind() call, add udp_bind_netif(d->udp, netif);.
Update both call sites to pass their own netif:
extmod/network_cyw43.c's AP-mode cyw43_cb_tcpip_init() path (in lib/cyw43-driver/src/cyw43_lwip.c) — pass n (the netif it just created).
extmod/network_usbd_ncm.c's ncm_init() — pass &ncm_obj.netif.
This keeps both DHCP servers fully functional and correctly isolated to their own interface, rather than requiring either one to be disabled to avoid the collision.

Code of Conduct

Yes, I agree

Activity

  1. SciLor commented on Sep 9, 2026

    @SciLor
    Author

    Possible patches (I currently use myself)
    cyw32-driver

    From 982b0ece8661cb2c9029121e23e81066bbbab331 Mon Sep 17 00:00:00 2001
    From: SciLor <[email protected]>
    Date: Sat, 5 Sep 2026 23:45:01 +0000
    Subject: [PATCH] usb-ncm dhcp fix: pass netif pointer to dhcp_server_init
    
    Pass the AP interface's struct netif pointer to dhcp_server_init() to
    allow binding and pinning the DHCP socket to the specific netif.
    ---
     src/cyw43_lwip.c | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/src/cyw43_lwip.c b/src/cyw43_lwip.c
    index 1bb5c6b..788169b 100644
    --- a/src/cyw43_lwip.c
    +++ b/src/cyw43_lwip.c
    @@ -235,7 +235,7 @@ void cyw43_cb_tcpip_init(cyw43_t *self, int itf) {
             #endif
         } else {
             #if CYW43_NETUTILS
    -        dhcp_server_init(&self->dhcp_server, &ipconfig[0], &ipconfig[1]);
    +        dhcp_server_init(&self->dhcp_server, &ipconfig[0], &ipconfig[1], n);
             #endif
         }
     
    -- 
    2.43.0
    
    

    Micropython

    From 920995b18a59e3af594274e980ad0d67fa76d26b Mon Sep 17 00:00:00 2001
    From: SciLor <[email protected]>
    Date: Sat, 5 Sep 2026 23:44:07 +0000
    Subject: [PATCH] extmod/dhcp: Update dhcp_server_init to accept netif
     parameter for improved binding
    
    Bind the DHCP server socket to the specific server IP and netif rather
    than 0.0.0.0 (INADDR_ANY). This allows multiple DHCP servers (such as
    WiFi AP and USBD_NCM) to operate concurrently without UDP port 67
    collisions.
    ---
     extmod/network_usbd_ncm.c    |  2 +-
     shared/netutils/dhcpserver.c | 25 +++++++++++++++++++------
     shared/netutils/dhcpserver.h |  4 +++-
     3 files changed, 23 insertions(+), 8 deletions(-)
    
    diff --git a/extmod/network_usbd_ncm.c b/extmod/network_usbd_ncm.c
    index d6ae152a8..ab1e5b402 100644
    --- a/extmod/network_usbd_ncm.c
    +++ b/extmod/network_usbd_ncm.c
    @@ -201,7 +201,7 @@ static void ncm_init(void) {
         #endif
     
         #if MICROPY_PY_NETWORK_USBD_NCM_DHCP_SERVER
    -    dhcp_server_init(&ncm_obj.dhcp_server, &ncm_obj.ipaddr, &ncm_obj.netmask);
    +    dhcp_server_init(&ncm_obj.dhcp_server, &ncm_obj.ipaddr, &ncm_obj.netmask, netif);
         // Don't advertise a default gateway -- this is a point-to-point USB link
         // and advertising a router causes the host to route all traffic over USB.
         ncm_obj.dhcp_server.send_router = false;
    diff --git a/shared/netutils/dhcpserver.c b/shared/netutils/dhcpserver.c
    index c54dad357..95680312f 100644
    --- a/shared/netutils/dhcpserver.c
    +++ b/shared/netutils/dhcpserver.c
    @@ -109,11 +109,9 @@ static void dhcp_socket_free(struct udp_pcb **udp) {
         }
     }
     
    -static int dhcp_socket_bind(struct udp_pcb **udp, uint32_t ip, uint16_t port) {
    -    ip_addr_t addr;
    -    IP_ADDR4(&addr, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff);
    +static int dhcp_socket_bind(struct udp_pcb **udp, ip_addr_t *ip, uint16_t port) {
         // TODO convert lwIP errors to errno
    -    return udp_bind(*udp, &addr, port);
    +    return udp_bind(*udp, ip, port);
     }
     
     static int dhcp_socket_sendto(struct udp_pcb **udp, struct netif *netif, const void *buf, size_t len, uint32_t ip, uint16_t port) {
    @@ -293,7 +291,7 @@ ignore_request:
         pbuf_free(p);
     }
     
    -void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) {
    +void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm, struct netif *netif) {
         ip_addr_copy(d->ip, *ip);
         ip_addr_copy(d->nm, *nm);
         memset(d->lease, 0, sizeof(d->lease));
    @@ -301,7 +299,22 @@ void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) {
         if (dhcp_socket_new_dgram(&d->udp, d, dhcp_server_process) != 0) {
             return;
         }
    -    dhcp_socket_bind(&d->udp, 0, PORT_DHCP_SERVER);
    +    // Bind to this server's own specific address, not the wildcard
    +    // 0.0.0.0 the old `dhcp_socket_bind(&d->udp, 0, ...)` used - and pin
    +    // the socket to its own netif. Without both of these, a second
    +    // dhcp_server_init() call for a different interface (e.g. a WiFi AP
    +    // and USBD_NCM both active at once) silently fails to bind at all:
    +    // lwIP's udp_bind() rejects a second PCB on the same port whenever
    +    // either side of the conflict is the wildcard address, so only the
    +    // first-initialized server ever actually receives anything, and every
    +    // client on every interface ends up served from that one server's
    +    // pool regardless of which physical link it actually asked on - see
    +    // the "AP-mode DHCP server hands out addresses from another active
    +    // interface's DHCP server" issue this fixes.
    +    dhcp_socket_bind(&d->udp, &d->ip, PORT_DHCP_SERVER);
    +    if (netif != NULL) {
    +        udp_bind_netif(d->udp, netif);
    +    }
     }
     
     void dhcp_server_deinit(dhcp_server_t *d) {
    diff --git a/shared/netutils/dhcpserver.h b/shared/netutils/dhcpserver.h
    index 24224d6aa..f06794c6c 100644
    --- a/shared/netutils/dhcpserver.h
    +++ b/shared/netutils/dhcpserver.h
    @@ -28,6 +28,8 @@
     
     #include "lwip/ip_addr.h"
     
    +struct netif;
    +
     #define DHCPS_BASE_IP (16)
     #define DHCPS_MAX_IP (8)
     
    @@ -44,7 +46,7 @@ typedef struct _dhcp_server_t {
         bool send_router; // advertise server IP as default gateway
     } dhcp_server_t;
     
    -void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm);
    +void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm, struct netif *netif);
     void dhcp_server_deinit(dhcp_server_t *d);
     
     #endif // MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H
    -- 
    2.43.0
    
    
  2. added this to the release-1.30 milestone on Sep 9, 2026
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

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions