Skip to content

Commit

Permalink
net: lws_wol() and lws_parse_mac()
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lws-team committed Nov 24, 2023
1 parent 24c37d1 commit 4af9886
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 1 deletion.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions include/libwebsockets/lws-misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
14 changes: 13 additions & 1 deletion include/libwebsockets/lws-network-helper.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2020 Andy Green <[email protected]>
* Copyright (C) 2010 - 2023 Andy Green <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
Expand Down Expand Up @@ -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);

///@}
5 changes: 5 additions & 0 deletions lib/core-net/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 59 additions & 0 deletions lib/core-net/network.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
88 changes: 88 additions & 0 deletions lib/core-net/wol.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2023 Andy Green <[email protected]>
*
* 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;
}
35 changes: 35 additions & 0 deletions minimal-examples-lowlevel/api-tests/api-test-async-dns/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions minimal-examples-lowlevel/raw/minimal-raw-wol/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
34 changes: 34 additions & 0 deletions minimal-examples-lowlevel/raw/minimal-raw-wol/README.md
Original file line number Diff line number Diff line change
@@ -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
$
```
52 changes: 52 additions & 0 deletions minimal-examples-lowlevel/raw/minimal-raw-wol/minimal-raw-wol.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* lws-minimal-raw-wol
*
* Written in 2010-2023 by Andy Green <[email protected]>
*
* 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;
}

0 comments on commit 4af9886

Please sign in to comment.