Skip to content

Commit 4af9886

Browse files
committed
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 #3016
1 parent 24c37d1 commit 4af9886

File tree

10 files changed

+322
-1
lines changed

10 files changed

+322
-1
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ option(LWS_HTTP_HEADERS_ALL "Override header reduction optimization and include
326326
option(LWS_WITH_SUL_DEBUGGING "Enable zombie lws_sul checking on object deletion" OFF)
327327
option(LWS_WITH_PLUGINS_API "Build generic lws_plugins apis (see LWS_WITH_PLUGINS to also build protocol plugins)" OFF)
328328
option(LWS_WITH_CONMON "Collect introspectable connection latency stats on individual client connections" ON)
329+
option(LWS_WITH_WOL "Wake On Lan support" ON)
329330
option(LWS_WITHOUT_EVENTFD "Force using pipe instead of eventfd" OFF)
330331
if (UNIX OR WIN32)
331332
option(LWS_WITH_CACHE_NSCOOKIEJAR "Build file-backed lws-cache-ttl that uses netscape cookie jar format (linux-only)" ON)

include/libwebsockets/lws-misc.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,3 +1250,15 @@ lws_minilex_parse(const uint8_t *lex, int16_t *ps, const uint8_t c,
12501250

12511251
LWS_VISIBLE LWS_EXTERN unsigned int
12521252
lws_sigbits(uintptr_t u);
1253+
1254+
/**
1255+
* lws_wol() - broadcast a magic WOL packet to MAC, optionally binding to if IP
1256+
*
1257+
* \p ctx: The lws context
1258+
* \p ip_or_NULL: The IP address to bind to at the client side, to send the
1259+
* magic packet on. If NULL, the system chooses, probably the
1260+
* interface with the default route.
1261+
* \p mac_6_bytes: Points to a 6-byte MAC address to direct the magic packet to
1262+
*/
1263+
LWS_VISIBLE LWS_EXTERN int
1264+
lws_wol(struct lws_context *ctx, const char *ip_or_NULL, uint8_t *mac_6_bytes);

include/libwebsockets/lws-network-helper.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* libwebsockets - small server side websockets and web server implementation
33
*
4-
* Copyright (C) 2010 - 2020 Andy Green <[email protected]>
4+
* Copyright (C) 2010 - 2023 Andy Green <[email protected]>
55
*
66
* Permission is hereby granted, free of charge, to any person obtaining a copy
77
* 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);
246246
LWS_VISIBLE LWS_EXTERN int
247247
lws_sa46_write_numeric_address(lws_sockaddr46 *sa46, char *buf, size_t len);
248248

249+
/**
250+
* lws_parse_mac() - convert XX:XX:XX:XX:XX:XX to 6-byte MAC address
251+
*
252+
* \param ads: mac address as XX:XX:XX:XX:XX:XX string
253+
* \param result_6_bytes: result buffer to take 6 bytes
254+
*
255+
* Converts a string representation of a 6-byte hex mac address to a 6-byte
256+
* array.
257+
*/
258+
LWS_VISIBLE LWS_EXTERN int
259+
lws_parse_mac(const char *ads, uint8_t *result_6_bytes);
260+
249261
///@}

lib/core-net/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ if (LWS_WITH_LWS_DSH)
5656
core-net/lws-dsh.c)
5757
endif()
5858

59+
if (LWS_WITH_WOL)
60+
list(APPEND SOURCES
61+
core-net/wol.c)
62+
endif()
63+
5964
if (LWS_WITH_CLIENT)
6065
list(APPEND SOURCES
6166
core-net/client/client.c

lib/core-net/network.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,3 +1104,62 @@ lws_system_get_state_manager(struct lws_context *context)
11041104
return &context->mgr_system;
11051105
}
11061106
#endif
1107+
1108+
int
1109+
lws_parse_mac(const char *ads, uint8_t *result_6_bytes)
1110+
{
1111+
uint8_t *p = result_6_bytes;
1112+
struct lws_tokenize ts;
1113+
char t[3];
1114+
size_t n;
1115+
long u;
1116+
1117+
lws_tokenize_init(&ts, ads, LWS_TOKENIZE_F_NO_INTEGERS |
1118+
LWS_TOKENIZE_F_MINUS_NONTERM);
1119+
ts.len = strlen(ads);
1120+
1121+
do {
1122+
ts.e = (int8_t)lws_tokenize(&ts);
1123+
switch (ts.e) {
1124+
case LWS_TOKZE_TOKEN:
1125+
if (ts.token_len != 2)
1126+
return -1;
1127+
if (p - result_6_bytes == 6)
1128+
return -2;
1129+
t[0] = ts.token[0];
1130+
t[1] = ts.token[1];
1131+
t[2] = '\0';
1132+
for (n = 0; n < 2; n++)
1133+
if (t[n] < '0' || t[n] > 'f' ||
1134+
(t[n] > '9' && t[n] < 'A') ||
1135+
(t[n] > 'F' && t[n] < 'a'))
1136+
return -1;
1137+
u = strtol(t, NULL, 16);
1138+
if (u > 0xff)
1139+
return -5;
1140+
*p++ = (uint8_t)u;
1141+
break;
1142+
1143+
case LWS_TOKZE_DELIMITER:
1144+
if (*ts.token != ':')
1145+
return -10;
1146+
if (p - result_6_bytes > 5)
1147+
return -11;
1148+
break;
1149+
1150+
case LWS_TOKZE_ENDED:
1151+
if (p - result_6_bytes != 6)
1152+
return -12;
1153+
return 0;
1154+
1155+
default:
1156+
lwsl_err("%s: malformed mac\n", __func__);
1157+
1158+
return -13;
1159+
}
1160+
} while (ts.e > 0);
1161+
1162+
lwsl_err("%s: ended on e %d\n", __func__, ts.e);
1163+
1164+
return -14;
1165+
}

lib/core-net/wol.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* libwebsockets - small server side websockets and web server implementation
3+
*
4+
* Copyright (C) 2010 - 2023 Andy Green <[email protected]>
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to
8+
* deal in the Software without restriction, including without limitation the
9+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10+
* sell copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22+
* IN THE SOFTWARE.
23+
*/
24+
25+
#include "private-lib-core.h"
26+
27+
#if defined(_WIN32) && !defined(IFHWADDRLEN)
28+
#define IFHWADDRLEN 6
29+
#endif
30+
31+
int
32+
lws_wol(struct lws_context *ctx, const char *ip_or_NULL, uint8_t *mac_6_bytes)
33+
{
34+
int n, m, ofs = 0, fd, optval = 1, ret = 1;
35+
uint8_t pkt[17 * IFHWADDRLEN];
36+
struct sockaddr_in addr;
37+
38+
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
39+
if (fd < 0) {
40+
lwsl_cx_err(ctx, "failed to open UDP, errno %d\n", errno);
41+
goto bail;
42+
}
43+
44+
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST,
45+
(char *)&optval, sizeof(optval)) < 0) {
46+
lwsl_cx_err(ctx, "failed to set broadcast, errno %d\n", errno);
47+
goto bail;
48+
}
49+
50+
/*
51+
* Lay out the magic packet
52+
*/
53+
54+
for (n = 0; n < IFHWADDRLEN; n++)
55+
pkt[ofs++] = 0xff;
56+
for (m = 0; m < 16; m++)
57+
for (n = 0; n < IFHWADDRLEN; n++)
58+
pkt[ofs++] = mac_6_bytes[n];
59+
60+
memset(&addr, 0, sizeof(addr));
61+
addr.sin_family = AF_INET;
62+
addr.sin_port = htons(9);
63+
64+
if (!inet_aton(ip_or_NULL ? ip_or_NULL : "255.255.255.255",
65+
&addr.sin_addr)) {
66+
lwsl_cx_err(ctx, "failed to convert broadcast ads, errno %d\n",
67+
errno);
68+
goto bail;
69+
}
70+
71+
lwsl_cx_notice(ctx, "Sending WOL to %02X:%02X:%02X:%02X:%02X:%02X %s\n",
72+
mac_6_bytes[0], mac_6_bytes[1], mac_6_bytes[2], mac_6_bytes[3],
73+
mac_6_bytes[4], mac_6_bytes[5], ip_or_NULL ? ip_or_NULL : "");
74+
75+
if (sendto(fd, pkt, sizeof(pkt), 0, (struct sockaddr *)&addr,
76+
sizeof(addr)) < 0) {
77+
lwsl_cx_err(ctx, "failed to sendto broadcast ads, errno %d\n",
78+
errno);
79+
goto bail;
80+
}
81+
82+
ret = 0;
83+
84+
bail:
85+
close(fd);
86+
87+
return ret;
88+
}

minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ main(int argc, const char **argv)
425425
{
426426
int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
427427
struct lws_context_creation_info info;
428+
uint8_t mac[6];
428429
const char *p;
429430

430431
/* fixup dynamic target addresses we're testing against */
@@ -517,6 +518,40 @@ main(int argc, const char **argv)
517518
ok++;
518519
}
519520

521+
/* mac address parser tests */
522+
523+
if (lws_parse_mac("11:ff:ce:CE:22:33", mac)) {
524+
lwsl_err("%s: mac fail 1\n", __func__);
525+
lwsl_hexdump_notice(mac, 6);
526+
fail++;
527+
} else
528+
if (mac[0] != 0x11 || mac[1] != 0xff || mac[2] != 0xce ||
529+
mac[3] != 0xce || mac[4] != 0x22 || mac[5] != 0x33) {
530+
lwsl_err("%s: mac fail 2\n", __func__);
531+
lwsl_hexdump_notice(mac, 6);
532+
fail++;
533+
}
534+
if (!lws_parse_mac("11:ff:ce:CE:22:3", mac)) {
535+
lwsl_err("%s: mac fail 3\n", __func__);
536+
lwsl_hexdump_notice(mac, 6);
537+
fail++;
538+
}
539+
if (!lws_parse_mac("11:ff:ce:CE:22", mac)) {
540+
lwsl_err("%s: mac fail 4\n", __func__);
541+
lwsl_hexdump_notice(mac, 6);
542+
fail++;
543+
}
544+
if (!lws_parse_mac("11:ff:ce:CE:22:", mac)) {
545+
lwsl_err("%s: mac fail 5\n", __func__);
546+
lwsl_hexdump_notice(mac, 6);
547+
fail++;
548+
}
549+
if (!lws_parse_mac("11:ff:ce:CE22", mac)) {
550+
lwsl_err("%s: mac fail 6\n", __func__);
551+
lwsl_hexdump_notice(mac, 6);
552+
fail++;
553+
}
554+
520555
#if !defined(LWS_WITH_IPV6)
521556
_exp -= 2;
522557
#endif
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
project(lws-minimal-raw-wol C)
2+
cmake_minimum_required(VERSION 3.6)
3+
find_package(libwebsockets CONFIG REQUIRED)
4+
list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR})
5+
include(CheckCSourceCompiles)
6+
include(LwsCheckRequirements)
7+
8+
set(SAMP lws-minimal-raw-wol)
9+
set(SRCS minimal-raw-wol.c)
10+
11+
set(requirements 1)
12+
require_lws_config(LWS_WITH_WOL 1 requirements)
13+
14+
if (requirements)
15+
add_executable(${SAMP} ${SRCS})
16+
17+
if (websockets_shared)
18+
target_link_libraries(${SAMP} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS})
19+
add_dependencies(${SAMP} websockets_shared)
20+
else()
21+
target_link_libraries(${SAMP} websockets ${LIBWEBSOCKETS_DEP_LIBS})
22+
endif()
23+
endif()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# lws minimal raw wol
2+
3+
This example shows how to send a Wake On Lan magic packet to a given mac.
4+
5+
## build
6+
7+
```
8+
$ cmake . && make
9+
```
10+
11+
## usage
12+
13+
```
14+
$ bin/lws-minimal-raw-wol b4:2e:99:a9:22:90
15+
[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
16+
[2023/11/09 12:25:24:2256] N: __lws_lc_tag: ++ [wsi|0|pipe] (1)
17+
[2023/11/09 12:25:24:2256] N: __lws_lc_tag: ++ [vh|0|netlink] (1)
18+
[2023/11/09 12:25:24:2256] N: __lws_lc_tag: ++ [vh|1|system||-1] (2)
19+
[2023/11/09 12:25:24:2257] N: __lws_lc_tag: ++ [wsisrv|0|system|asyncdns] (1)
20+
[2023/11/09 12:25:24:2257] N: __lws_lc_tag: ++ [wsisrv|1|system|asyncdns] (2)
21+
[2023/11/09 12:25:24:2257] N: __lws_lc_tag: ++ [vh|2|default||0] (3)
22+
[2023/11/09 12:25:24:2257] N: [vh|2|default||0]: lws_socket_bind: source ads 0.0.0.0
23+
[2023/11/09 12:25:24:2257] N: __lws_lc_tag: ++ [wsi|1|listen|default||33749] (2)
24+
[2023/11/09 12:25:24:2257] N: lws_wol: Sending WOL to B4:2E:99:A9:22:90
25+
[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [wsi|0|pipe] (1) 190μs
26+
[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [wsi|1|listen|default||33749] (0) 80μs
27+
[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [wsisrv|1|system|asyncdns] (1) 118μs
28+
[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [wsisrv|0|system|asyncdns] (0) 155μs
29+
[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [vh|0|netlink] (2) 198μs
30+
[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [vh|1|system||-1] (1) 182μs
31+
[2023/11/09 12:25:24:2258] N: __lws_lc_untag: -- [vh|2|default||0] (0) 125μs
32+
33+
$
34+
```
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* lws-minimal-raw-wol
3+
*
4+
* Written in 2010-2023 by Andy Green <[email protected]>
5+
*
6+
* This file is made available under the Creative Commons CC0 1.0
7+
* Universal Public Domain Dedication.
8+
*
9+
* This demonstrates using lws_wol()
10+
*/
11+
12+
#include <libwebsockets.h>
13+
#include <net/if.h>
14+
15+
int main(int argc, const char **argv)
16+
{
17+
struct lws_context_creation_info info;
18+
struct lws_context *ctx;
19+
const char *p, *ip = NULL;
20+
uint8_t mac[IFHWADDRLEN];
21+
int ret = 1;
22+
23+
memset(&info, 0, sizeof info);
24+
lws_cmdline_option_handle_builtin(argc, argv, &info);
25+
26+
if ((p = lws_cmdline_option(argc, argv, "-ip")))
27+
ip = p;
28+
29+
if (argc < 2) {
30+
lwsl_user("lws-minimal-raw-wol XX:XX:XX:XX:XX:XX [-ip interface IP]\n");
31+
goto bail1;
32+
}
33+
34+
if (lws_parse_mac(argv[1], mac)) {
35+
lwsl_user("Failed to parse mac '%s'\n", argv[1]);
36+
goto bail1;
37+
}
38+
39+
ctx = lws_create_context(&info);
40+
if (!ctx) {
41+
lwsl_err("lws init failed\n");
42+
goto bail1;
43+
}
44+
45+
if (!lws_wol(ctx, ip, mac))
46+
ret = 0;
47+
48+
lws_context_destroy(ctx);
49+
50+
bail1:
51+
return ret;
52+
}

0 commit comments

Comments
 (0)