wowlan module: port to rtl8723cs-wowlan python script
This commit is contained in:
@@ -25,9 +25,9 @@
|
|||||||
sane.services.wg-home.ip = config.sane.hosts.by-name."moby".wg-home.ip;
|
sane.services.wg-home.ip = config.sane.hosts.by-name."moby".wg-home.ip;
|
||||||
sane.wowlan.enable = true;
|
sane.wowlan.enable = true;
|
||||||
sane.wowlan.patterns = [
|
sane.wowlan.patterns = [
|
||||||
{ ipv4.destPort = 22; } # wake on SSH
|
{ tcp.destPort = 22; } # wake on SSH
|
||||||
{ ipv4.srcPort = 2587; } # wake on `ntfy-sh` push from servo
|
{ tcp.sourcePort = 2587; } # wake on `ntfy-sh` push from servo
|
||||||
{ arp.queryIp = [ 10 78 79 54 ]; } # wake when somebody is doing an ARP query against us
|
{ arp.destIp = "10.78.79.54"; } # wake when somebody is doing an ARP query against us
|
||||||
];
|
];
|
||||||
|
|
||||||
# XXX colin: phosh doesn't work well with passwordless login,
|
# XXX colin: phosh doesn't work well with passwordless login,
|
||||||
|
@@ -63,7 +63,7 @@ let
|
|||||||
cfg = config.sane.wowlan;
|
cfg = config.sane.wowlan;
|
||||||
patternOpts = with lib; types.submodule {
|
patternOpts = with lib; types.submodule {
|
||||||
options = {
|
options = {
|
||||||
ipv4.destPort = mkOption {
|
tcp.destPort = mkOption {
|
||||||
type = types.nullOr types.port;
|
type = types.nullOr types.port;
|
||||||
default = null;
|
default = null;
|
||||||
description = ''
|
description = ''
|
||||||
@@ -72,7 +72,7 @@ let
|
|||||||
(e.g. this machine made a long-running HTTP request, and the other side finally has data for us).
|
(e.g. this machine made a long-running HTTP request, and the other side finally has data for us).
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
ipv4.srcPort = mkOption {
|
tcp.sourcePort = mkOption {
|
||||||
type = types.nullOr types.port;
|
type = types.nullOr types.port;
|
||||||
default = null;
|
default = null;
|
||||||
description = ''
|
description = ''
|
||||||
@@ -81,115 +81,16 @@ let
|
|||||||
(e.g. sshing *into* this machine).
|
(e.g. sshing *into* this machine).
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
arp.queryIp = mkOption {
|
arp.destIp = mkOption {
|
||||||
type = types.nullOr (types.listOf types.int);
|
type = types.nullOr types.string;
|
||||||
default = null;
|
default = null;
|
||||||
description = ''
|
description = ''
|
||||||
IP address being queried.
|
IP address being queried.
|
||||||
e.g. `[ 192 168 0 100 ]`
|
e.g. `"192.168.0.100"`
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
# bytesToStr: [ u8|null ] -> String
|
|
||||||
# format an array of octets into a pattern recognizable by iwpriv.
|
|
||||||
# a null byte means "don't care" at its position.
|
|
||||||
bytesToStr = bytes: lib.concatStringsSep ":" (
|
|
||||||
builtins.map
|
|
||||||
(b: if b == null then "-" else hexByte b)
|
|
||||||
bytes
|
|
||||||
);
|
|
||||||
# format a byte as hex, with leading zero to force a width of two characters.
|
|
||||||
# the wlan driver doesn't parse single-character hex bytes.
|
|
||||||
hexByte = b: if b < 16 then
|
|
||||||
"0" + (lib.toHexString b)
|
|
||||||
else
|
|
||||||
lib.toHexString b;
|
|
||||||
|
|
||||||
etherTypes.ipv4 = [ 08 00 ]; # 0x0800 = IPv4
|
|
||||||
etherTypes.arp = [ 08 06 ]; # 0x0806 = ARP
|
|
||||||
formatEthernetFrame = ethertype: dataBytes: let
|
|
||||||
bytes = [
|
|
||||||
# ethernet frame: <https://en.wikipedia.org/wiki/Ethernet_frame#Structure>
|
|
||||||
## dest MAC address (this should be the device's MAC, but i think that's implied?)
|
|
||||||
null null null null null null
|
|
||||||
## src MAC address
|
|
||||||
null null null null null null
|
|
||||||
## ethertype: <https://en.wikipedia.org/wiki/EtherType#Values>
|
|
||||||
] ++ etherTypes."${ethertype}"
|
|
||||||
++ dataBytes;
|
|
||||||
in bytesToStr bytes;
|
|
||||||
|
|
||||||
formatArpPattern = pat:
|
|
||||||
formatEthernetFrame "arp" ([
|
|
||||||
# ARP frame: <https://en.wikipedia.org/wiki/Address_Resolution_Protocol#Packet_structure>
|
|
||||||
## hardware type
|
|
||||||
null null
|
|
||||||
## protocol type. same coding as EtherType
|
|
||||||
08 00 # 0x0800 = IPv4
|
|
||||||
## hardware address length (i.e. MAC)
|
|
||||||
06
|
|
||||||
## protocol address length (i.e. IP address)
|
|
||||||
04
|
|
||||||
## operation
|
|
||||||
00 01 # 0x0001 = request
|
|
||||||
## sender hardware address
|
|
||||||
null null null null null null
|
|
||||||
## sender protocol address
|
|
||||||
null null null null
|
|
||||||
## target hardware address
|
|
||||||
## this is left as "Don't Care" because the packets we want to match
|
|
||||||
## are those mapping protocol addr -> hw addr.
|
|
||||||
## sometimes clients do include this field if they've seen the address before though
|
|
||||||
null null null null null null
|
|
||||||
## target protocol address
|
|
||||||
] ++ pat.queryIp);
|
|
||||||
|
|
||||||
# formatIpv4Pattern: patternOpts.ipv4 -> String
|
|
||||||
# produces a string like this (example matches source port=0x0a1b):
|
|
||||||
# "-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:-:0a:1b:-:-"
|
|
||||||
formatIpv4Pattern = pat: let
|
|
||||||
destPortBytes = if pat.destPort != null then {
|
|
||||||
high = pat.destPort / 256;
|
|
||||||
low = lib.mod pat.destPort 256;
|
|
||||||
} else {
|
|
||||||
high = null; low = null;
|
|
||||||
};
|
|
||||||
srcPortBytes = if pat.srcPort != null then {
|
|
||||||
high = pat.srcPort / 256;
|
|
||||||
low = lib.mod pat.srcPort 256;
|
|
||||||
} else {
|
|
||||||
high = null; low = null;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
formatEthernetFrame "ipv4" [
|
|
||||||
# IP frame: <https://en.wikipedia.org/wiki/Internet_Protocol_version_4#Header>
|
|
||||||
## Version, Internet Header Length. 0x45 = 69 decimal
|
|
||||||
null # should be 69 (0x45), but fails to wake if i include this
|
|
||||||
## Differentiated Services Code Point (DSCP), Explicit Congestion Notification (ECN)
|
|
||||||
null
|
|
||||||
## total length
|
|
||||||
null null
|
|
||||||
## identification
|
|
||||||
null null
|
|
||||||
## flags, fragment offset
|
|
||||||
null null
|
|
||||||
## Time-to-live
|
|
||||||
null
|
|
||||||
## protocol: <https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers>
|
|
||||||
6 # 6 = TCP
|
|
||||||
## header checksum
|
|
||||||
null null
|
|
||||||
## source IP addr
|
|
||||||
null null null null
|
|
||||||
## dest IP addr
|
|
||||||
null null null null
|
|
||||||
|
|
||||||
# TCP frame: <https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure>
|
|
||||||
srcPortBytes.high srcPortBytes.low
|
|
||||||
destPortBytes.high destPortBytes.low
|
|
||||||
## rest is Don't Care
|
|
||||||
];
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = with lib; {
|
options = with lib; {
|
||||||
@@ -211,24 +112,33 @@ in
|
|||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
systemd.services.wowlan = {
|
systemd.services.wowlan = {
|
||||||
description = "configure the WiFi chip to wake the system on specific activity";
|
description = "configure the WiFi chip to wake the system on specific activity";
|
||||||
path = [ pkgs.iw pkgs.wirelesstools ];
|
path = [ pkgs.rtl8723cs-wowlan ];
|
||||||
script = let
|
script = let
|
||||||
extractPatterns = pat: lib.flatten [
|
tcpArgs = { destPort, sourcePort }:
|
||||||
(lib.optional (pat.ipv4 != { destPort = null; srcPort = null; }) (formatIpv4Pattern pat.ipv4))
|
[ "tcp" ]
|
||||||
(lib.optional (pat.arp != { queryIp = null; }) (formatArpPattern pat.arp))
|
++ lib.optionals (destPort != null) [ "--dest-port" (builtins.toString destPort) ]
|
||||||
];
|
++ lib.optionals (sourcePort != null) [ "--source-port" (builtins.toString sourcePort) ]
|
||||||
allPatterns = lib.flatten (builtins.map extractPatterns cfg.patterns);
|
;
|
||||||
encodePattern = pat: ''
|
arpArgs = { destIp }:
|
||||||
iwpriv wlan0 wow_set_pattern pattern=${pat}
|
[ "arp" ]
|
||||||
'';
|
++ lib.optionals (destIp != null) [ "--dest-ip" (builtins.toString destIp) ]
|
||||||
encodedPatterns = lib.concatStringsSep
|
;
|
||||||
"\n"
|
maybeCallHelper = maybe: args:
|
||||||
(builtins.map encodePattern allPatterns);
|
lib.optionalString
|
||||||
|
maybe
|
||||||
|
((lib.escapeShellArgs ([ "rtl8723cs-wowlan" ] ++ args)) + "\n")
|
||||||
|
;
|
||||||
|
applyPattern = pat:
|
||||||
|
(maybeCallHelper (pat.tcp != { destPort = null; sourcePort = null; }) (tcpArgs pat.tcp))
|
||||||
|
+
|
||||||
|
(maybeCallHelper (pat.arp != { destIp = null; }) (arpArgs pat.arp))
|
||||||
|
;
|
||||||
|
appliedPatterns = lib.concatStringsSep
|
||||||
|
""
|
||||||
|
(builtins.map applyPattern cfg.patterns);
|
||||||
in ''
|
in ''
|
||||||
set -x
|
rtl8723cs-wowlan enable-clean
|
||||||
iw phy0 wowlan enable any
|
${appliedPatterns}
|
||||||
iwpriv wlan0 wow_set_pattern clean
|
|
||||||
${encodedPatterns}
|
|
||||||
'';
|
'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
# TODO: re-run this periodically, just to be sure?
|
# TODO: re-run this periodically, just to be sure?
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
}:
|
}:
|
||||||
|
|
||||||
static-nix-shell.mkPython3Bin {
|
static-nix-shell.mkPython3Bin {
|
||||||
pname = "rtl8723cs_wowlan";
|
pname = "rtl8723cs-wowlan";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
pkgs = {
|
pkgs = {
|
||||||
inherit iw wirelesstools;
|
inherit iw wirelesstools;
|
||||||
|
Reference in New Issue
Block a user