From ba8aad3d1dbc25a397c9b23b1428018c5a7be505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Tue, 4 Jul 2023 15:36:37 +0200 Subject: [PATCH] esp32/modnetwork: Add support for SO_BINDTODEVICE socket option. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This implements support for SO_BINDTODEVICE, which allows telling a socket to use a specific interface instead of lwIP automatically selecting one. This allows devices that have multiple connections (for example cellular over PPP in addition to WLAN) to explicitly choose which data is send over which connection, which may have different reliability and or (mobile data) costs associated with using them. The used lwIP network stack already has support for this, so all that was needed was to expose this functionality in MicroPython. This commit exposes a new constant SO_BINDTODEVICE which can be set as an socket option. As a value it expects the name of the interface to bind to. These names can be retrieved using `.config('ifname')` implemented on each interface type (including adding in this commit a `.config()` method to PPP, which it didn't have before), which returns a string with the interface name: >>> import machine >>> import network >>> network.WLAN(network.AP_IF).config('ifname') 'lo0' >>> wlan = network.WLAN(network.AP_IF) >>> wlan.active(True) and wlan.config('ifname') 'ap1' >>> wlan = network.WLAN(network.STA_IF) >>> wlan.active(True) and wlan.config('ifname') 'st1' >>> ppp = network.PPP(machine.UART(0)) >>> ppp.active(True) and ppp.config('ifname') 'pp1' >>> ppp = network.PPP(machine.UART(0)) >>> ppp.active(True) and ppp.config('ifname') 'pp2' >>> ppp = network.PPP(machine.UART(0)) >>> ppp.active(True) and ppp.config('ifname') 'pp3' Note that lo0 seems to be returned by lwIP if the interface is not yet active. The method can also return None in the case of PPP where the entire lwIP interface doesn't yet exist before being activated. Currently no effort is made to unify those cases; it is expected that whatever we receive from lwIP is valid. When the socket option is set, this forces using a specific device: import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, 'st1') setsockopt will throw (OSError: [Errno 19] ENODEV) if the specified interface does not exist. Tested with LAN, WLAN, and PPP; can specify which interface should be used and when testing with, for example, HTTP requests to ifconfig.co the returned IP address confirms a specific interface was used. Signed-off-by: Daniƫl van de Giessen --- ports/esp32/modnetwork.h | 2 ++ ports/esp32/modsocket.c | 13 +++++++++++ ports/esp32/network_common.c | 10 ++++++++ ports/esp32/network_lan.c | 4 ++++ ports/esp32/network_ppp.c | 45 ++++++++++++++++++++++++++++++++++++ ports/esp32/network_wlan.c | 4 ++++ 6 files changed, 78 insertions(+) diff --git a/ports/esp32/modnetwork.h b/ports/esp32/modnetwork.h index 79bf9973ca..e57b80657f 100644 --- a/ports/esp32/modnetwork.h +++ b/ports/esp32/modnetwork.h @@ -57,6 +57,8 @@ MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_ifconfig_obj); MP_DECLARE_CONST_FUN_OBJ_KW(esp_network_config_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_phy_mode_obj); +mp_obj_t esp_ifname(esp_netif_t *netif); + NORETURN void esp_exceptions_helper(esp_err_t e); static inline void esp_exceptions(esp_err_t e) { diff --git a/ports/esp32/modsocket.c b/ports/esp32/modsocket.c index e5a370641f..fe76b5f760 100644 --- a/ports/esp32/modsocket.c +++ b/ports/esp32/modsocket.c @@ -396,6 +396,18 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { break; } + case SO_BINDTODEVICE: { + size_t len; + const char *val = mp_obj_str_get_data(args[3], &len); + char ifname[NETIF_NAMESIZE] = {0}; + memcpy(&ifname, val, len); + int ret = lwip_setsockopt(self->fd, SOL_SOCKET, opt, &ifname, NETIF_NAMESIZE); + if (ret != 0) { + mp_raise_OSError(errno); + } + break; + } + #if MICROPY_PY_SOCKET_EVENTS // level: SOL_SOCKET // special "register callback" option @@ -860,6 +872,7 @@ STATIC const mp_rom_map_elem_t mp_module_socket_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_SOL_SOCKET), MP_ROM_INT(SOL_SOCKET) }, { MP_ROM_QSTR(MP_QSTR_SO_REUSEADDR), MP_ROM_INT(SO_REUSEADDR) }, { MP_ROM_QSTR(MP_QSTR_SO_BROADCAST), MP_ROM_INT(SO_BROADCAST) }, + { MP_ROM_QSTR(MP_QSTR_SO_BINDTODEVICE), MP_ROM_INT(SO_BINDTODEVICE) }, { MP_ROM_QSTR(MP_QSTR_IP_ADD_MEMBERSHIP), MP_ROM_INT(IP_ADD_MEMBERSHIP) }, }; diff --git a/ports/esp32/network_common.c b/ports/esp32/network_common.c index 082943e2ae..ca07f3c06b 100644 --- a/ports/esp32/network_common.c +++ b/ports/esp32/network_common.c @@ -41,6 +41,7 @@ #include "esp_log.h" #include "esp_netif.h" #include "esp_wifi.h" +#include "lwip/sockets.h" // #include "lwip/dns.h" NORETURN void esp_exceptions_helper(esp_err_t e) { @@ -153,6 +154,15 @@ STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_ifconfig_obj, 1, 2, esp_ifconfig); +mp_obj_t esp_ifname(esp_netif_t *netif) { + char ifname[NETIF_NAMESIZE + 1] = {0}; + mp_obj_t ret = mp_const_none; + if (esp_netif_get_netif_impl_name(netif, ifname) == ESP_OK && ifname[0] != 0) { + ret = mp_obj_new_str((char *)ifname, strlen(ifname)); + } + return ret; +} + STATIC mp_obj_t esp_phy_mode(size_t n_args, const mp_obj_t *args) { return mp_const_none; } diff --git a/ports/esp32/network_lan.c b/ports/esp32/network_lan.c index fe3ff6f772..8557700446 100644 --- a/ports/esp32/network_lan.c +++ b/ports/esp32/network_lan.c @@ -393,6 +393,10 @@ STATIC mp_obj_t lan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs esp_eth_ioctl(self->eth_handle, ETH_CMD_G_MAC_ADDR, mac); return mp_obj_new_bytes(mac, sizeof(mac)); } + case MP_QSTR_ifname: { + val = esp_ifname(self->base.netif); + break; + } default: mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index caad7eb48b..8ab502426b 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -266,11 +266,56 @@ STATIC mp_obj_t ppp_isconnected(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(ppp_isconnected_obj, ppp_isconnected); +STATIC mp_obj_t ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args != 1 && kwargs->used != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); + } + ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (kwargs->used != 0) { + for (size_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + default: + break; + } + } + } + return mp_const_none; + } + + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); + } + + mp_obj_t val = mp_const_none; + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_ifname: { + if (self->pcb != NULL) { + struct netif *pppif = ppp_netif(self->pcb); + char ifname[NETIF_NAMESIZE + 1] = {0}; + netif_index_to_name(netif_get_index(pppif), ifname); + if (ifname[0] != 0) { + val = mp_obj_new_str((char *)ifname, strlen(ifname)); + } + } + break; + } + default: + mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); + } + + return val; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(ppp_config_obj, 1, ppp_config); + STATIC const mp_rom_map_elem_t ppp_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&ppp_active_obj) }, { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&ppp_connect_obj) }, { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&ppp_isconnected_obj) }, { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&ppp_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&ppp_config_obj) }, { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&ppp_ifconfig_obj) }, { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ppp_delete_obj) }, { MP_ROM_QSTR(MP_QSTR_AUTH_NONE), MP_ROM_INT(PPPAUTHTYPE_NONE) }, diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index 8287731c31..58af9f3bc3 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -622,6 +622,10 @@ STATIC mp_obj_t network_wlan_config(size_t n_args, const mp_obj_t *args, mp_map_ val = MP_OBJ_NEW_SMALL_INT(channel); break; } + case MP_QSTR_ifname: { + val = esp_ifname(self->netif); + break; + } case MP_QSTR_hostname: case MP_QSTR_dhcp_hostname: { // TODO: Deprecated. Use network.hostname() instead.