From 4af988600f12b93373979935d66aee29388d9c34 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Thu, 9 Nov 2023 12:00:36 +0000 Subject: [PATCH] net: lws_wol() and lws_parse_mac() Introduce a LWS_WITH_WOL and an api to wake a mac address, optionally with an address bind to the local interface to go out on. Add a helper to parse ascii mac addresses well, and add tests. Also thanks to OgreTransporter https://github.com/warmcat/libwebsockets/issues/3016 --- CMakeLists.txt | 1 + include/libwebsockets/lws-misc.h | 12 +++ include/libwebsockets/lws-network-helper.h | 14 ++- lib/core-net/CMakeLists.txt | 5 ++ lib/core-net/network.c | 59 +++++++++++++ lib/core-net/wol.c | 88 +++++++++++++++++++ .../api-tests/api-test-async-dns/main.c | 35 ++++++++ .../raw/minimal-raw-wol/CMakeLists.txt | 23 +++++ .../raw/minimal-raw-wol/README.md | 34 +++++++ .../raw/minimal-raw-wol/minimal-raw-wol.c | 52 +++++++++++ 10 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 lib/core-net/wol.c create mode 100644 minimal-examples-lowlevel/raw/minimal-raw-wol/CMakeLists.txt create mode 100644 minimal-examples-lowlevel/raw/minimal-raw-wol/README.md create mode 100644 minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 802e2a2acf..c3ef2d7845 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -326,6 +326,7 @@ option(LWS_HTTP_HEADERS_ALL "Override header reduction optimization and include option(LWS_WITH_SUL_DEBUGGING "Enable zombie lws_sul checking on object deletion" OFF) option(LWS_WITH_PLUGINS_API "Build generic lws_plugins apis (see LWS_WITH_PLUGINS to also build protocol plugins)" OFF) option(LWS_WITH_CONMON "Collect introspectable connection latency stats on individual client connections" ON) +option(LWS_WITH_WOL "Wake On Lan support" ON) option(LWS_WITHOUT_EVENTFD "Force using pipe instead of eventfd" OFF) if (UNIX OR WIN32) option(LWS_WITH_CACHE_NSCOOKIEJAR "Build file-backed lws-cache-ttl that uses netscape cookie jar format (linux-only)" ON) diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index b988c0f08e..7812b5ec88 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -1250,3 +1250,15 @@ lws_minilex_parse(const uint8_t *lex, int16_t *ps, const uint8_t c, LWS_VISIBLE LWS_EXTERN unsigned int lws_sigbits(uintptr_t u); + +/** + * lws_wol() - broadcast a magic WOL packet to MAC, optionally binding to if IP + * + * \p ctx: The lws context + * \p ip_or_NULL: The IP address to bind to at the client side, to send the + * magic packet on. If NULL, the system chooses, probably the + * interface with the default route. + * \p mac_6_bytes: Points to a 6-byte MAC address to direct the magic packet to + */ +LWS_VISIBLE LWS_EXTERN int +lws_wol(struct lws_context *ctx, const char *ip_or_NULL, uint8_t *mac_6_bytes); diff --git a/include/libwebsockets/lws-network-helper.h b/include/libwebsockets/lws-network-helper.h index 09308b8b2e..eb3f58766e 100644 --- a/include/libwebsockets/lws-network-helper.h +++ b/include/libwebsockets/lws-network-helper.h @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 - 2020 Andy Green + * Copyright (C) 2010 - 2023 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -246,4 +246,16 @@ lws_write_numeric_address(const uint8_t *ads, int size, char *buf, size_t len); LWS_VISIBLE LWS_EXTERN int lws_sa46_write_numeric_address(lws_sockaddr46 *sa46, char *buf, size_t len); +/** + * lws_parse_mac() - convert XX:XX:XX:XX:XX:XX to 6-byte MAC address + * + * \param ads: mac address as XX:XX:XX:XX:XX:XX string + * \param result_6_bytes: result buffer to take 6 bytes + * + * Converts a string representation of a 6-byte hex mac address to a 6-byte + * array. + */ +LWS_VISIBLE LWS_EXTERN int +lws_parse_mac(const char *ads, uint8_t *result_6_bytes); + ///@} diff --git a/lib/core-net/CMakeLists.txt b/lib/core-net/CMakeLists.txt index 43a47ca148..3bd636fee0 100644 --- a/lib/core-net/CMakeLists.txt +++ b/lib/core-net/CMakeLists.txt @@ -56,6 +56,11 @@ if (LWS_WITH_LWS_DSH) core-net/lws-dsh.c) endif() +if (LWS_WITH_WOL) + list(APPEND SOURCES + core-net/wol.c) +endif() + if (LWS_WITH_CLIENT) list(APPEND SOURCES core-net/client/client.c diff --git a/lib/core-net/network.c b/lib/core-net/network.c index f276a28dd9..8e2303d622 100644 --- a/lib/core-net/network.c +++ b/lib/core-net/network.c @@ -1104,3 +1104,62 @@ lws_system_get_state_manager(struct lws_context *context) return &context->mgr_system; } #endif + +int +lws_parse_mac(const char *ads, uint8_t *result_6_bytes) +{ + uint8_t *p = result_6_bytes; + struct lws_tokenize ts; + char t[3]; + size_t n; + long u; + + lws_tokenize_init(&ts, ads, LWS_TOKENIZE_F_NO_INTEGERS | + LWS_TOKENIZE_F_MINUS_NONTERM); + ts.len = strlen(ads); + + do { + ts.e = (int8_t)lws_tokenize(&ts); + switch (ts.e) { + case LWS_TOKZE_TOKEN: + if (ts.token_len != 2) + return -1; + if (p - result_6_bytes == 6) + return -2; + t[0] = ts.token[0]; + t[1] = ts.token[1]; + t[2] = '\0'; + for (n = 0; n < 2; n++) + if (t[n] < '0' || t[n] > 'f' || + (t[n] > '9' && t[n] < 'A') || + (t[n] > 'F' && t[n] < 'a')) + return -1; + u = strtol(t, NULL, 16); + if (u > 0xff) + return -5; + *p++ = (uint8_t)u; + break; + + case LWS_TOKZE_DELIMITER: + if (*ts.token != ':') + return -10; + if (p - result_6_bytes > 5) + return -11; + break; + + case LWS_TOKZE_ENDED: + if (p - result_6_bytes != 6) + return -12; + return 0; + + default: + lwsl_err("%s: malformed mac\n", __func__); + + return -13; + } + } while (ts.e > 0); + + lwsl_err("%s: ended on e %d\n", __func__, ts.e); + + return -14; +} diff --git a/lib/core-net/wol.c b/lib/core-net/wol.c new file mode 100644 index 0000000000..20c939b80b --- /dev/null +++ b/lib/core-net/wol.c @@ -0,0 +1,88 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2023 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "private-lib-core.h" + +#if defined(_WIN32) && !defined(IFHWADDRLEN) +#define IFHWADDRLEN 6 +#endif + +int +lws_wol(struct lws_context *ctx, const char *ip_or_NULL, uint8_t *mac_6_bytes) +{ + int n, m, ofs = 0, fd, optval = 1, ret = 1; + uint8_t pkt[17 * IFHWADDRLEN]; + struct sockaddr_in addr; + + fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + lwsl_cx_err(ctx, "failed to open UDP, errno %d\n", errno); + goto bail; + } + + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, + (char *)&optval, sizeof(optval)) < 0) { + lwsl_cx_err(ctx, "failed to set broadcast, errno %d\n", errno); + goto bail; + } + + /* + * Lay out the magic packet + */ + + for (n = 0; n < IFHWADDRLEN; n++) + pkt[ofs++] = 0xff; + for (m = 0; m < 16; m++) + for (n = 0; n < IFHWADDRLEN; n++) + pkt[ofs++] = mac_6_bytes[n]; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(9); + + if (!inet_aton(ip_or_NULL ? ip_or_NULL : "255.255.255.255", + &addr.sin_addr)) { + lwsl_cx_err(ctx, "failed to convert broadcast ads, errno %d\n", + errno); + goto bail; + } + + lwsl_cx_notice(ctx, "Sending WOL to %02X:%02X:%02X:%02X:%02X:%02X %s\n", + mac_6_bytes[0], mac_6_bytes[1], mac_6_bytes[2], mac_6_bytes[3], + mac_6_bytes[4], mac_6_bytes[5], ip_or_NULL ? ip_or_NULL : ""); + + if (sendto(fd, pkt, sizeof(pkt), 0, (struct sockaddr *)&addr, + sizeof(addr)) < 0) { + lwsl_cx_err(ctx, "failed to sendto broadcast ads, errno %d\n", + errno); + goto bail; + } + + ret = 0; + +bail: + close(fd); + + return ret; +} diff --git a/minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c b/minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c index 6a9a32f463..fa223ea759 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c @@ -425,6 +425,7 @@ main(int argc, const char **argv) { int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; struct lws_context_creation_info info; + uint8_t mac[6]; const char *p; /* fixup dynamic target addresses we're testing against */ @@ -517,6 +518,40 @@ main(int argc, const char **argv) ok++; } + /* mac address parser tests */ + + if (lws_parse_mac("11:ff:ce:CE:22:33", mac)) { + lwsl_err("%s: mac fail 1\n", __func__); + lwsl_hexdump_notice(mac, 6); + fail++; + } else + if (mac[0] != 0x11 || mac[1] != 0xff || mac[2] != 0xce || + mac[3] != 0xce || mac[4] != 0x22 || mac[5] != 0x33) { + lwsl_err("%s: mac fail 2\n", __func__); + lwsl_hexdump_notice(mac, 6); + fail++; + } + if (!lws_parse_mac("11:ff:ce:CE:22:3", mac)) { + lwsl_err("%s: mac fail 3\n", __func__); + lwsl_hexdump_notice(mac, 6); + fail++; + } + if (!lws_parse_mac("11:ff:ce:CE:22", mac)) { + lwsl_err("%s: mac fail 4\n", __func__); + lwsl_hexdump_notice(mac, 6); + fail++; + } + if (!lws_parse_mac("11:ff:ce:CE:22:", mac)) { + lwsl_err("%s: mac fail 5\n", __func__); + lwsl_hexdump_notice(mac, 6); + fail++; + } + if (!lws_parse_mac("11:ff:ce:CE22", mac)) { + lwsl_err("%s: mac fail 6\n", __func__); + lwsl_hexdump_notice(mac, 6); + fail++; + } + #if !defined(LWS_WITH_IPV6) _exp -= 2; #endif diff --git a/minimal-examples-lowlevel/raw/minimal-raw-wol/CMakeLists.txt b/minimal-examples-lowlevel/raw/minimal-raw-wol/CMakeLists.txt new file mode 100644 index 0000000000..b00a477343 --- /dev/null +++ b/minimal-examples-lowlevel/raw/minimal-raw-wol/CMakeLists.txt @@ -0,0 +1,23 @@ +project(lws-minimal-raw-wol C) +cmake_minimum_required(VERSION 3.6) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) +include(LwsCheckRequirements) + +set(SAMP lws-minimal-raw-wol) +set(SRCS minimal-raw-wol.c) + +set(requirements 1) +require_lws_config(LWS_WITH_WOL 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() +endif() diff --git a/minimal-examples-lowlevel/raw/minimal-raw-wol/README.md b/minimal-examples-lowlevel/raw/minimal-raw-wol/README.md new file mode 100644 index 0000000000..b2ed87eeed --- /dev/null +++ b/minimal-examples-lowlevel/raw/minimal-raw-wol/README.md @@ -0,0 +1,34 @@ +# lws minimal raw wol + +This example shows how to send a Wake On Lan magic packet to a given mac. + +## build + +``` + $ cmake . && make +``` + +## usage + +``` +$ bin/lws-minimal-raw-wol b4:2e:99:a9:22:90 +[2023/11/09 12:25:24:2255] N: lws_create_context: LWS: 4.3.99-v4.3.0-295-g60d671c7, NET CLI SRV H1 H2 WS SS-JSON-POL ConMon ASYNC_DNS IPv6-absent +[2023/11/09 12:25:24:2256] N: __lws_lc_tag: ++ [wsi|0|pipe] (1) +[2023/11/09 12:25:24:2256] N: __lws_lc_tag: ++ [vh|0|netlink] (1) +[2023/11/09 12:25:24:2256] N: __lws_lc_tag: ++ [vh|1|system||-1] (2) +[2023/11/09 12:25:24:2257] N: __lws_lc_tag: ++ [wsisrv|0|system|asyncdns] (1) +[2023/11/09 12:25:24:2257] N: __lws_lc_tag: ++ [wsisrv|1|system|asyncdns] (2) +[2023/11/09 12:25:24:2257] N: __lws_lc_tag: ++ [vh|2|default||0] (3) +[2023/11/09 12:25:24:2257] N: [vh|2|default||0]: lws_socket_bind: source ads 0.0.0.0 +[2023/11/09 12:25:24:2257] N: __lws_lc_tag: ++ [wsi|1|listen|default||33749] (2) +[2023/11/09 12:25:24:2257] N: lws_wol: Sending WOL to B4:2E:99:A9:22:90 +[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [wsi|0|pipe] (1) 190μs +[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [wsi|1|listen|default||33749] (0) 80μs +[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [wsisrv|1|system|asyncdns] (1) 118μs +[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [wsisrv|0|system|asyncdns] (0) 155μs +[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [vh|0|netlink] (2) 198μs +[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [vh|1|system||-1] (1) 182μs +[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [vh|2|default||0] (0) 125μs + +$ +``` \ No newline at end of file diff --git a/minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c b/minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c new file mode 100644 index 0000000000..a9b8136004 --- /dev/null +++ b/minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c @@ -0,0 +1,52 @@ +/* + * lws-minimal-raw-wol + * + * Written in 2010-2023 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates using lws_wol() + */ + +#include +#include + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *ctx; + const char *p, *ip = NULL; + uint8_t mac[IFHWADDRLEN]; + int ret = 1; + + memset(&info, 0, sizeof info); + lws_cmdline_option_handle_builtin(argc, argv, &info); + + if ((p = lws_cmdline_option(argc, argv, "-ip"))) + ip = p; + + if (argc < 2) { + lwsl_user("lws-minimal-raw-wol XX:XX:XX:XX:XX:XX [-ip interface IP]\n"); + goto bail1; + } + + if (lws_parse_mac(argv[1], mac)) { + lwsl_user("Failed to parse mac '%s'\n", argv[1]); + goto bail1; + } + + ctx = lws_create_context(&info); + if (!ctx) { + lwsl_err("lws init failed\n"); + goto bail1; + } + + if (!lws_wol(ctx, ip, mac)) + ret = 0; + + lws_context_destroy(ctx); + +bail1: + return ret; +}