diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index b0da7b9be7..eff388ad87 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -472,6 +472,7 @@ PSEUDOMODULES += shell_cmd_gnrc_sixlowpan_frag_stats PSEUDOMODULES += shell_cmd_gnrc_udp PSEUDOMODULES += shell_cmd_heap PSEUDOMODULES += shell_cmd_i2c_scan +PSEUDOMODULES += shell_cmd_iw PSEUDOMODULES += shell_cmd_lwip_netif PSEUDOMODULES += shell_cmd_mci PSEUDOMODULES += shell_cmd_md5sum diff --git a/sys/shell/Makefile.dep b/sys/shell/Makefile.dep index 0254da82b2..862ada7b93 100644 --- a/sys/shell/Makefile.dep +++ b/sys/shell/Makefile.dep @@ -208,6 +208,9 @@ endif ifneq (,$(filter shell_cmd_i2c_scan,$(USEMODULE))) FEATURES_REQUIRED += periph_i2c endif +ifneq (,$(filter shell_cmd_iw,$(USEMODULE))) + USEMODULE += ztimer_sec +endif ifneq (,$(filter shell_cmd_lwip_netif,$(USEMODULE))) USEMODULE += lwip_netif endif diff --git a/sys/shell/cmds/Kconfig b/sys/shell/cmds/Kconfig index 794bca53d0..1a663693f6 100644 --- a/sys/shell/cmds/Kconfig +++ b/sys/shell/cmds/Kconfig @@ -176,6 +176,10 @@ config MODULE_SHELL_CMD_I2C_SCAN depends on MODULE_SHELL_CMDS depends on MODULE_PERIPH_I2C +config MODULE_SHELL_CMD_IW + bool "Command to interact with WiFi interfaces" + depends on MODULE_ZTIMER_SEC + config MODULE_SHELL_CMD_LWIP_NETIF bool "Command to manage lwIP network interfaces (ifconfig)" default y if MODULE_SHELL_CMDS_DEFAULT diff --git a/sys/shell/cmds/iw.c b/sys/shell/cmds/iw.c new file mode 100644 index 0000000000..c46312b03f --- /dev/null +++ b/sys/shell/cmds/iw.c @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2023 ML!PA Consulting Gmbh + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_shell_commands + * @{ + * + * @file + * @brief Shell commands for interacting with Wi-Fi interfaces + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include + +#include "kernel_defines.h" +#include "mutex.h" +#include "net/wifi_scan_list.h" +#include "net/netdev/wifi.h" +#include "net/netopt.h" +#include "net/wifi.h" +#include "sched.h" +#include "shell.h" +#include "net/netif.h" +#include "net/netdev.h" +#include "fmt.h" +#include "string_utils.h" +#include "ztimer.h" + +#define SC_IW_AP_NUMOF 10 +#define SC_IW_AP_SCAN_TIMEOUT_SEC_MAX 30 +#define SC_IW_AP_CONNECT_TIMEOUT_SEC_MAX 60 + +static mutex_t _sync = MUTEX_INIT; + +static struct { + unsigned numof; + wifi_scan_result_t ap[SC_IW_AP_NUMOF]; +} _aps; + +static union { + wifi_security_wep_psk_t wep; + wifi_security_wpa_psk_t wpa; + wifi_security_wpa_enterprise_t eap; +} _cred; + +static const wifi_scan_result_t *_get_ap(const char *ssid) +{ + for (unsigned i = 0; i < _aps.numof; i++) { + if (!strcmp(_aps.ap[i].ssid, ssid)) { + return &_aps.ap[i]; + } + } + return NULL; +} + +static const char *_ssec(wifi_security_mode_t mode) +{ + switch (mode) { + case WIFI_SECURITY_MODE_OPEN: + return "open"; + case WIFI_SECURITY_MODE_WEP_PSK: + return "WEP"; + case WIFI_SECURITY_MODE_WPA2_PERSONAL: + return "WPA"; + case WIFI_SECURITY_MODE_WPA2_ENTERPRISE: + return "WPA2 Enterprise"; + default: + return "unknown"; + } +} + +static void _list_ap(void) +{ + puts(" SSID | SEC | RSSI | CHANNEL"); + puts("---------------------------------+-----------------+-------+--------"); + for (unsigned i = 0; i < _aps.numof; i++) { + printf(" %-31s | %-15s | %-5"PRId16" | %-6d \n", + _aps.ap[i].ssid, + _ssec(_aps.ap[i].sec_mode), + _aps.ap[i].base.strength, + _aps.ap[i].base.channel); + } +} + +static void _wifi_scan_cb(void *netif, const wifi_scan_list_t *result) +{ + (void)netif; + _aps.numof = wifi_scan_list_to_array(result, _aps.ap, ARRAY_SIZE(_aps.ap)); + _list_ap(); + mutex_unlock(&_sync); +} + +static void _wifi_connect_cb(void *netif, const wifi_connect_result_t *result) +{ + (void)netif; + if (result) { + printf("connected to %s\n", result->ssid); + } + mutex_unlock(&_sync); +} + +static void _wifi_disconnect_cb(void *netif, const wifi_disconnect_result_t *result) +{ + (void)netif; + if (result) { + printf("could not connect to %s\n", result->ssid); + } + mutex_unlock(&_sync); +} + +static int _iw_probe(netif_t *iface) +{ + long ret; + uint16_t val16; + if ((ret = netif_get_opt(iface, NETOPT_IS_WIRED, 0, &val16, sizeof(&val16))) < 0) { + if (ret != -ENOTSUP) { /* -ENOTSUP means wireless */ + return -EIO; + } + } + else { + return -ENOTSUP; /* wired */ + } + if ((ret = netif_get_opt(iface, NETOPT_DEVICE_TYPE, 0, &val16, sizeof(val16))) < 0) { + return -EIO; + } + if (val16 != NETDEV_TYPE_ETHERNET) { + return -ENOTSUP; + } + return 0; +} + +static int _iw_disconnect(netif_t *iface) +{ + long ret; + wifi_disconnect_request_t request = WIFI_DISCONNECT_REQUEST_INITIALIZER(NULL); + if ((ret = netif_set_opt(iface, NETOPT_DISCONNECT, 0, &request, sizeof(request))) < 0) { + return ret; + } + return 0; +} + +static int _iw_cmd_disconnect(netif_t *iface, int argc, char **argv) +{ + (void)argc; (void)argv; + return _iw_disconnect(iface); +} + +static int _iw_connect(netif_t *iface, wifi_connect_request_t *request) +{ + long ret; + /* this should not block! */ + mutex_lock(&_sync); + if ((ret = netif_set_opt(iface, NETOPT_CONNECT, 0, request, sizeof(*request))) < 0) { + mutex_unlock(&_sync); + return ret; + } + /* callback unlocks mutex */ + ztimer_mutex_lock_timeout(ZTIMER_SEC, &_sync, SC_IW_AP_CONNECT_TIMEOUT_SEC_MAX); + mutex_unlock(&_sync); + return 0; +} + +static int _iw_cmd_connect(netif_t *iface, int argc, char **argv) +{ + (void)iface; (void)argc; (void)argv; + if (argc < 1) { + return -EINVAL; + } + wifi_connect_request_t request = WIFI_CONNECT_REQUEST_INITIALIZER(0, _wifi_connect_cb, + _wifi_disconnect_cb, NULL); + if (strlen(argv[0]) > sizeof(request.ssid) - 1) { + printf("SSID too long\n"); + return -EINVAL; + } + strcpy(request.ssid, argv[0]); + argc--; + argv++; + const char *user = NULL; + const char *pwd = NULL; + const char *psk = NULL; + bool wep = false; + for (int i = 0; i < argc; i++) { + if (argv[i][0] != '-') { + return -EINVAL; + } + if (!strcmp("-u", argv[i])) { + if (++i >= argc) { + printf("-u requires an argument\n"); + return -EINVAL; + } + user = argv[i]; + } + else if (!strcmp("-p", argv[i])) { + if (++i >= argc) { + printf("-p requires an argument\n"); + return -EINVAL; + } + pwd = argv[i]; + } + else if (!strcmp("-k", argv[i])) { + if (++i >= argc) { + printf("-k requires an argument\n"); + return -EINVAL; + } + if (!strcmp("WEP", argv[i])) { + if (++i >= argc) { + printf("-k requires an argument\n"); + return -EINVAL; + } + wep = true; + } + psk = argv[i]; + } + else { + printf("unknown option %s\n", argv[i]); + return -EINVAL; + } + } + const wifi_scan_result_t *ap = _get_ap(request.ssid); + if (ap) { + if (ap->sec_mode == WIFI_SECURITY_MODE_WEP_PSK) { + if (!psk) { + printf("%s requires -k WEP\n", request.ssid); + return -EPERM; + } + } + else if (ap->sec_mode == WIFI_SECURITY_MODE_WPA2_PERSONAL) { + if (!psk) { + printf("%s requires -k\n", request.ssid); + return -EPERM; + } + } + else if (ap->sec_mode == WIFI_SECURITY_MODE_WPA2_ENTERPRISE) { + if (!user || !pwd) { + printf("%s requires -u and -p\n", request.ssid); + return -EPERM; + } + } + request.base.channel = ap->base.channel; + } + if (psk) { + if (strlen(psk) > sizeof(_cred.wpa.psk) - 1) { + printf("Key too long\n"); + return -EINVAL; + } + /* also copies to WEP key */ + strcpy(_cred.wpa.psk, psk); + if (wep) { + _cred.wep.sec = WIFI_SECURITY_MODE_WEP_PSK; + request.cred = &_cred.wep.sec; + } + else { + _cred.wpa.sec = WIFI_SECURITY_MODE_WPA2_PERSONAL; + request.cred = &_cred.wpa.sec; + } + } + if (user) { + if (strlen(user) > sizeof(_cred.eap.user) - 1) { + printf("username too long\n"); + return -EINVAL; + } + strcpy(_cred.eap.user, user); + _cred.eap.sec = WIFI_SECURITY_MODE_WPA2_ENTERPRISE; + request.cred = &_cred.eap.sec; + explicit_bzero(_cred.eap.pwd, sizeof(_cred.eap.pwd)); + } + if (pwd) { + if (strlen(pwd) > sizeof(_cred.eap.pwd) - 1) { + printf("password too long\n"); + return -EINVAL; + } + strcpy(_cred.eap.pwd, pwd); + } + return _iw_connect(iface, &request); +} + +static int _iw_scan(netif_t *iface, wifi_scan_request_t *request) +{ + int ret; + /* this should not block! */ + mutex_lock(&_sync); + if ((ret = netif_set_opt(iface, NETOPT_SCAN, 0, request, sizeof(*request))) < 0) { + mutex_unlock(&_sync); + return ret; + } + /* callback unlocks mutex */ + ztimer_mutex_lock_timeout(ZTIMER_SEC, &_sync, SC_IW_AP_SCAN_TIMEOUT_SEC_MAX); + mutex_unlock(&_sync); + return 0; +} + +static int _iw_cmd_scan(netif_t *iface, int argc, char **argv) +{ + long ret; + netopt_channel_t ch = NETOPT_SCAN_REQ_ALL_CH; + uint32_t timeout_ms = 0; + for (int i = 0; i < argc; i++) { + if (argv[i][0] != '-') { + return -EINVAL; + } + if (!strcmp("-c", argv[i])) { + if (++i >= argc) { + printf("-c requires an argument\n"); + return -EINVAL; + } + if (!fmt_is_number(argv[i]) || + (ret = strtol(argv[i], NULL, 10)) < 0) { + printf("-c argument %s is not a number\n", argv[i]); + return -EINVAL; + } + ch = (netopt_channel_t)ret; + } + else if (!strcmp("-t", argv[i])) { + if (++i >= argc) { + printf("-t requires an argument\n"); + return -EINVAL; + } + if (!fmt_is_number(argv[i]) || + (ret = strtol(argv[i], NULL, 10)) < 0) { + printf("-t argument %s is not a number\n", argv[i]); + return -EINVAL; + } + timeout_ms = (uint32_t)ret; + } + else { + printf("unknown option %s\n", argv[i]); + return -EINVAL; + } + } + wifi_scan_request_t request = WIFI_SCAN_REQUEST_INITIALIZER(ch, _wifi_scan_cb, timeout_ms); + return _iw_scan(iface, &request); +} + +static void _iw_usage(const char *cmd) +{ + printf("usage: %s \n", cmd); + printf("commands:\n" + " scan [-c ] [-t ]\n" + " connect [-u -p ] | -k [WEP] ]\n" + " disconnect\n" + ); +} + +static void _iw_error(const char *cmd, int error) +{ + printf("%s: error (%d) %s\n", cmd, error, strerror(error)); +} + +int _iw_cmd(int argc, char **argv) +{ + if (argc < 3) { + goto exit_help; + } + int ret = -EINVAL; + netif_t *iface = netif_get_by_name(argv[1]); + if (!iface) { + printf("%s: invalid interface given\n", argv[0]); + goto exit_failure; + } + if ((ret = _iw_probe(iface)) != 0) { + if (ret == -ENOTSUP) { + printf("%s: interface is not Wi-Fi\n", argv[0]); + goto exit_failure; + } + else { + printf("%s: interface communication error\n", argv[0]); + goto exit_failure; + } + } + + if (!strcmp(argv[2], "scan")) { + if ((ret = _iw_cmd_scan(iface, argc - 3, argv + 3)) != 0) { + goto exit_failure; + } + } + else if (!strcmp(argv[2], "connect")) { + if ((ret = _iw_cmd_connect(iface, argc - 3, argv + 3)) != 0) { + goto exit_failure; + } + } + else if (!strcmp(argv[2], "disconnect")) { + if ((ret = _iw_cmd_disconnect(iface, argc - 3, argv + 3)) != 0) { + goto exit_failure; + } + } + else { + goto exit_help; + } + + printf("%s: ok\n", argv[0]); + return EXIT_SUCCESS; + +exit_help: + _iw_usage(argv[0]); + return EXIT_FAILURE; + +exit_failure: + _iw_error(argv[0], ret); + return EXIT_FAILURE; + +} + +SHELL_COMMAND(iw, "Control Wi-Fi interfaces", _iw_cmd);