|
1 | 1 | /* Copyright (c) Etienne Le Cousin 2024-03-10. */ |
2 | 2 |
|
3 | 3 | #include "WiFiPrivate.h" |
| 4 | +#include <lwip/dhcp.h> // dhcp_supplied_address, dhcp_release_and_stop |
| 5 | +#include <lwip/tcpip.h> // tcpip_callback |
| 6 | + |
| 7 | +static void wifiEventIpReceived(struct netif *nif); |
| 8 | + |
| 9 | +// --------------------------------------------------------------------------- |
| 10 | +// Async callback: scheduled via tcpip_callback() from within wifiEventIpReceived |
| 11 | +// when DHCP completes despite our best-effort stop in wifiEventStaConnected(). |
| 12 | +// |
| 13 | +// Runs in the LwIP tcpip_thread AFTER dhcp_recv() has fully returned, so it |
| 14 | +// is safe to call dhcp_release_and_stop() directly here (no use-after-free). |
| 15 | +// We must NOT use netifapi_dhcp_release_and_stop() here — that function posts |
| 16 | +// a message to the tcpip_thread mailbox and waits for completion, which would |
| 17 | +// deadlock since we ARE the tcpip_thread. |
| 18 | +// --------------------------------------------------------------------------- |
| 19 | +static void wifiApplyStaticIpCallback(void *arg) { |
| 20 | + struct netif *nif = (struct netif *)arg; |
| 21 | + if (!pWiFi || !nif) |
| 22 | + return; |
| 23 | + WiFiNetworkInfo &info = pDATA->sta; |
| 24 | + if (!info.localIP) |
| 25 | + return; |
| 26 | + |
| 27 | + // Stop DHCP — safe here, we're outside dhcp_recv() |
| 28 | + dhcp_release_and_stop(nif); |
| 29 | + |
| 30 | + // Apply the configured static address |
| 31 | + ip4_addr_t ipaddr, netmask, gw; |
| 32 | + ipaddr.addr = info.localIP; |
| 33 | + netmask.addr = info.subnet; |
| 34 | + gw.addr = info.gateway; |
| 35 | + netif_set_addr(nif, &ipaddr, &netmask, &gw); |
| 36 | + // netif_set_addr triggers the netif status callback again, but since DHCP |
| 37 | + // is now stopped dhcp_supplied_address() returns false, so g_get_ip_cb is |
| 38 | + // NOT re-entered — no infinite recursion. |
| 39 | + |
| 40 | + // Restore configured DNS servers — DHCP may have overwritten them via option 6 |
| 41 | + // before we could stop it (reconnect/roam fallback path only). |
| 42 | + ip4_addr_t d1, d2; |
| 43 | + d1.addr = info.dns1; |
| 44 | + d2.addr = info.dns2; |
| 45 | + if (d1.addr) |
| 46 | + dns_setserver(0, &d1); |
| 47 | + if (d2.addr) |
| 48 | + dns_setserver(1, &d2); |
| 49 | + |
| 50 | + // Emit GOT_IP with the correct static address |
| 51 | + wifiEventIpReceived(nif); |
| 52 | +} |
| 53 | + |
| 54 | +// --------------------------------------------------------------------------- |
4 | 55 |
|
5 | 56 | void wifiEventSendArduino(EventId event) { |
6 | 57 | EventInfo eventInfo; |
@@ -28,6 +79,28 @@ static void wifiEventStaConnected(void *arg) { |
28 | 79 | memcpy(eventInfo.wifi_sta_connected.bssid, pWiFi->BSSID(), 6); |
29 | 80 |
|
30 | 81 | pWiFi->postEvent(ARDUINO_EVENT_WIFI_STA_CONNECTED, eventInfo); |
| 82 | + |
| 83 | + // If static IP is configured, apply it now and stop DHCP before the SDK |
| 84 | + // starts it. wifi_sta_connect() always starts DHCP internally; we have no |
| 85 | + // way to pass dhcp_mode to it (unlike BK72XX's bk_wlan_start_sta_adv_fix). |
| 86 | + // |
| 87 | + // IMPORTANT: stop DHCP *before* calling netif_set_addr. If DHCP is already |
| 88 | + // BOUND when this event fires, netif_set_addr would trigger the netif status |
| 89 | + // callback with dhcp_supplied_address==true, which would schedule an |
| 90 | + // unnecessary wifiApplyStaticIpCallback. Stopping DHCP first ensures the |
| 91 | + // status callback sees dhcp_supplied_address==false and stays silent. |
| 92 | + WiFiNetworkInfo &info = pDATA->sta; |
| 93 | + if (info.localIP) { |
| 94 | + struct netif *ifs = netdev_get_netif(NETIF_IDX_STA); |
| 95 | + ip4_addr_t ipaddr, netmask, gw; |
| 96 | + ipaddr.addr = info.localIP; |
| 97 | + netmask.addr = info.subnet; |
| 98 | + gw.addr = info.gateway; |
| 99 | + netifapi_dhcp_release_and_stop(ifs); // Stop first — safe (wifi_manager task) |
| 100 | + netif_set_addr(ifs, &ipaddr, &netmask, &gw); |
| 101 | + // Emit GOT_IP manually — DHCP is stopped so the netdev callback won't fire |
| 102 | + wifiEventIpReceived(ifs); |
| 103 | + } |
31 | 104 | } |
32 | 105 |
|
33 | 106 | static void wifiEventStaDisconnected(void *arg) { |
@@ -100,6 +173,33 @@ static void wifiEventIpReceived(struct netif *nif) { |
100 | 173 | if (!pWiFi || !nif) |
101 | 174 | return; // failsafe |
102 | 175 |
|
| 176 | + // Fallback: DHCP completed and assigned an address despite our attempt to |
| 177 | + // stop it in wifiEventStaConnected(). This can happen when: |
| 178 | + // (a) WIFI_MGR_EVENT_STA_CONNECTED fires before the SDK starts DHCP, so |
| 179 | + // netifapi_dhcp_release_and_stop() was a NOP and DHCP ran afterward; or |
| 180 | + // (b) a reconnect/roam caused the SDK to restart DHCP without triggering |
| 181 | + // WIFI_MGR_EVENT_STA_CONNECTED again (e.g. SDK-internal auto-reconnect). |
| 182 | + // |
| 183 | + // In this case we are being called from sta_netif_status_changed_cb(), which |
| 184 | + // is the LwIP netif status callback invoked from within dhcp_recv() in the |
| 185 | + // tcpip_thread. Two important constraints apply: |
| 186 | + // - Cannot call dhcp_release_and_stop() directly: dhcp_recv() hasn't |
| 187 | + // returned yet, freeing the dhcp struct now would be a use-after-free. |
| 188 | + // - Cannot call netifapi_dhcp_release_and_stop(): it posts a message to |
| 189 | + // the tcpip_thread mailbox and waits for a reply — deadlock because we |
| 190 | + // ARE the tcpip_thread. |
| 191 | + // |
| 192 | + // Solution: schedule wifiApplyStaticIpCallback via tcpip_callback(). It will |
| 193 | + // run in the tcpip_thread after the current message (dhcp_recv) has returned, |
| 194 | + // so neither constraint applies there. |
| 195 | + WiFiNetworkInfo &info = pDATA->sta; |
| 196 | + if (info.localIP && dhcp_supplied_address(nif)) { |
| 197 | + tcpip_callback(wifiApplyStaticIpCallback, nif); |
| 198 | + // Do not emit GOT_IP here with the wrong DHCP address. |
| 199 | + // wifiApplyStaticIpCallback will emit it with the static address. |
| 200 | + return; |
| 201 | + } |
| 202 | + |
103 | 203 | eventInfo.got_ip.if_index = 0; |
104 | 204 | eventInfo.got_ip.ip_changed = true; |
105 | 205 | eventInfo.got_ip.ip_info.ip.addr = nif->ip_addr.addr; |
|
0 commit comments