aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Green <andy@warmcat.com>2023-11-09 12:00:36 +0000
committerAndy Green <andy@warmcat.com>2023-11-24 05:44:42 +0000
commit4af988600f12b93373979935d66aee29388d9c34 (patch)
tree44b259da9badc2b0705de03c5126a8961b62a59b
parent24c37d1ea16f6e306c8cbab43108f65247c32738 (diff)
downloadlibwebsockets-4af988600f12b93373979935d66aee29388d9c34.tar.gz
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
-rw-r--r--CMakeLists.txt1
-rw-r--r--include/libwebsockets/lws-misc.h12
-rw-r--r--include/libwebsockets/lws-network-helper.h14
-rw-r--r--lib/core-net/CMakeLists.txt5
-rw-r--r--lib/core-net/network.c59
-rw-r--r--lib/core-net/wol.c88
-rw-r--r--minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c35
-rw-r--r--minimal-examples-lowlevel/raw/minimal-raw-wol/CMakeLists.txt23
-rw-r--r--minimal-examples-lowlevel/raw/minimal-raw-wol/README.md34
-rw-r--r--minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c52
10 files changed, 322 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 802e2a2a..c3ef2d78 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 b988c0f0..7812b5ec 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 09308b8b..eb3f5876 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 <andy@warmcat.com>
+ * Copyright (C) 2010 - 2023 Andy Green <andy@warmcat.com>
*
* 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 43a47ca1..3bd636fe 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 f276a28d..8e2303d6 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 00000000..20c939b8
--- /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 <andy@warmcat.com>
+ *
+ * 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 6a9a32f4..fa223ea7 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 00000000..b00a4773
--- /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 00000000..b2ed87ee
--- /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 00000000..a9b81360
--- /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 <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * This demonstrates using lws_wol()
+ */
+
+#include <libwebsockets.h>
+#include <net/if.h>
+
+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;
+}