mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 00:12:51 +08:00
217 lines
9.0 KiB
Ucode
217 lines
9.0 KiB
Ucode
#!/usr/bin/ucode
|
|
/*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Direct unit tests for the field validators that firewall_post.ut routes
|
|
* every UCI-derived value through (the H1 fix from the 2026-09-16 review).
|
|
*
|
|
* What this catches that the rendering test cannot:
|
|
*
|
|
* The renderer (tests/ucode/test_firewall_template.sh) needs the device-only
|
|
* `fw4` ucode module, so on a development host it only runs the source-level
|
|
* precondition check (the `{%-` glue guard). The validators themselves are
|
|
* pure functions: they take a string or a list and return either an nft
|
|
* anonymous-set spelling or null. This file exercises them directly, so a
|
|
* regex tightening, an accidental inversion of the predicate, or a missing
|
|
* drop branch shows up as a unit-test failure here even when the renderer
|
|
* layer is offline.
|
|
*
|
|
* What the tests cover:
|
|
*
|
|
* isValidIPv4 - accept dotted-quad with optional /CIDR, reject anything
|
|
* that is not (e.g. "1.2.3", "1.2.3.4.5", "1.2.3.4 ", "abc").
|
|
* isValidMAC - accept six colon-separated hex octets (case-insensitive),
|
|
* reject anything else ("GG" octet, "1:2:3:4:5", "1:2:3:4:5:6:7").
|
|
* isValidIface - accept alnum + . _ -, reject whitespace, brackets, slashes,
|
|
* quotes, semicolons, the obvious injection characters.
|
|
* isValidPort - accept 1..65535, reject 0, negatives, decimals, alpha.
|
|
*
|
|
* ipv4_to_nftarr / mac_to_nftarr / iface_to_nftarr - the canonical case
|
|
* (all entries valid), the all-poison case (every entry
|
|
* dropped, returns null), and the mixed case (poison
|
|
* entries stripped, valid entries kept).
|
|
*
|
|
* ports_to_nftarr - the comma-separated UCI string the routing_port option
|
|
* actually ships: a clean list, an all-poison list, and
|
|
* the report's example ("22,7890; reject,443,foo" ->
|
|
* "{ 22, 443 }").
|
|
*
|
|
* Reverse-proving: removing any drop branch - or relaxing a regex - makes
|
|
* the matching test fail, the way guard 11 in tests/arch-guard.sh would
|
|
* make a future regression in firewall_post.ut itself fail. The two together
|
|
* are the report's "fix + reverse-verify" loop.
|
|
*
|
|
* Run from tests/ucode/test_firewall_validators.sh, which stages
|
|
* firewall_utils.uc (and the homeproxy-pro.uc bits the validators depend on)
|
|
* in $WORK.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import {
|
|
isValidIPv4, ipv4_to_nftarr,
|
|
isValidMAC, mac_to_nftarr,
|
|
isValidIface, iface_to_nftarr,
|
|
isValidPort, ports_to_nftarr
|
|
} from 'firewall_utils';
|
|
|
|
let failures = 0, checks = 0;
|
|
|
|
function expect(name, actual, want) {
|
|
checks++;
|
|
if (sprintf('%J', actual) !== sprintf('%J', want)) {
|
|
printf('FAIL %s: expected %J, got %J\n', name, want, actual);
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
/* isValidIPv4 ------------------------------------------------------------ */
|
|
{
|
|
expect('isValidIPv4 accept 1.2.3.4', isValidIPv4('1.2.3.4'), true);
|
|
expect('isValidIPv4 accept 255.255.255.255', isValidIPv4('255.255.255.255'), true);
|
|
expect('isValidIPv4 accept 10.0.0.0/8', isValidIPv4('10.0.0.0/8'), true);
|
|
expect('isValidIPv4 accept 192.168.1.0/24', isValidIPv4('192.168.1.0/24'), true);
|
|
expect('isValidIPv4 accept 0.0.0.0/0', isValidIPv4('0.0.0.0/0'), true);
|
|
expect('isValidIPv4 reject 1.2.3', isValidIPv4('1.2.3'), false);
|
|
expect('isValidIPv4 reject 1.2.3.4.5', isValidIPv4('1.2.3.4.5'), false);
|
|
expect('isValidIPv4 reject "1.2.3.4 "', isValidIPv4('1.2.3.4 '), false);
|
|
expect('isValidIPv4 reject " 1.2.3.4"', isValidIPv4(' 1.2.3.4'), false);
|
|
expect('isValidIPv4 reject abc', isValidIPv4('abc'), false);
|
|
expect('isValidIPv4 reject empty', isValidIPv4(''), false);
|
|
expect('isValidIPv4 reject null', isValidIPv4(null), false);
|
|
expect('isValidIPv4 reject number', isValidIPv4(123), false);
|
|
/* The H1 injection: a value that closes a nft set. */
|
|
expect('isValidIPv4 reject injection',
|
|
isValidIPv4('1.2.3.4 } ; reject ; set x {'), false);
|
|
}
|
|
|
|
/* isValidMAC ------------------------------------------------------------- */
|
|
{
|
|
expect('isValidMAC accept aa:bb:cc:dd:ee:ff', isValidMAC('aa:bb:cc:dd:ee:ff'), true);
|
|
expect('isValidMAC accept AA:BB:CC:DD:EE:FF', isValidMAC('AA:BB:CC:DD:EE:FF'), true);
|
|
expect('isValidMAC reject GG octet',
|
|
isValidMAC('00:11:22:33:44:GG'), false);
|
|
expect('isValidMAC reject too few octets',
|
|
isValidMAC('aa:bb:cc:dd:ee'), false);
|
|
expect('isValidMAC reject too many octets',
|
|
isValidMAC('aa:bb:cc:dd:ee:ff:00'), false);
|
|
expect('isValidMAC reject dash separator',
|
|
isValidMAC('aa-bb-cc-dd-ee-ff'), false);
|
|
expect('isValidMAC reject empty', isValidMAC(''), false);
|
|
}
|
|
|
|
/* isValidIface ----------------------------------------------------------- */
|
|
{
|
|
expect('isValidIface accept eth0', isValidIface('eth0'), true);
|
|
expect('isValidIface accept br-lan', isValidIface('br-lan'), true);
|
|
expect('isValidIface accept eth0.10', isValidIface('eth0.10'), true);
|
|
expect('isValidIface accept lo', isValidIface('lo'), true);
|
|
expect('isValidIface reject space',
|
|
isValidIface('eth 0'), false);
|
|
expect('isValidIface reject slash',
|
|
isValidIface('eth0/1'), false);
|
|
expect('isValidIface reject semicolon',
|
|
isValidIface('eth0;reject'), false);
|
|
expect('isValidIface reject injection',
|
|
isValidIface('eth0 } reject ; set x {'), false);
|
|
}
|
|
|
|
/* isValidPort ------------------------------------------------------------ */
|
|
{
|
|
expect('isValidPort reject 0', isValidPort('0'), false);
|
|
expect('isValidPort accept 22', isValidPort('22'), true);
|
|
expect('isValidPort accept 65535', isValidPort('65535'), true);
|
|
expect('isValidPort reject 65536', isValidPort('65536'), false);
|
|
expect('isValidPort reject -1', isValidPort('-1'), false);
|
|
expect('isValidPort reject 22.5', isValidPort('22.5'), false);
|
|
expect('isValidPort reject abc', isValidPort('abc'), false);
|
|
expect('isValidPort reject empty', isValidPort(''), false);
|
|
expect('isValidPort reject injection',
|
|
isValidPort('7890; reject'), false);
|
|
}
|
|
|
|
/* ipv4_to_nftarr --------------------------------------------------------- */
|
|
{
|
|
/* Canonical case: every entry valid. */
|
|
expect('ipv4_to_nftarr clean',
|
|
ipv4_to_nftarr(['10.0.0.1', '192.168.1.0/24']),
|
|
'{ 10.0.0.1, 192.168.1.0/24 }');
|
|
/* All-poison: every entry dropped, returns null. */
|
|
expect('ipv4_to_nftarr all-poison returns null',
|
|
ipv4_to_nftarr(['1.2.3.4 } ; reject ; set x {',
|
|
'not.an.ip', '5']),
|
|
null);
|
|
/* Mixed: poison entries stripped, valid kept. */
|
|
expect('ipv4_to_nftarr mixed drops the poison',
|
|
ipv4_to_nftarr(['10.0.0.1', '1.2.3.4 } ; reject ; set x {', '192.168.1.1']),
|
|
'{ 10.0.0.1, 192.168.1.1 }');
|
|
/* Empty list. */
|
|
expect('ipv4_to_nftarr empty returns null',
|
|
ipv4_to_nftarr([]), null);
|
|
expect('ipv4_to_nftarr null returns null',
|
|
ipv4_to_nftarr(null), null);
|
|
/* Dedup. */
|
|
expect('ipv4_to_nftarr dedups',
|
|
ipv4_to_nftarr(['10.0.0.1', '10.0.0.1', '10.0.0.2']),
|
|
'{ 10.0.0.1, 10.0.0.2 }');
|
|
}
|
|
|
|
/* mac_to_nftarr ---------------------------------------------------------- */
|
|
{
|
|
expect('mac_to_nftarr clean',
|
|
mac_to_nftarr(['aa:bb:cc:dd:ee:ff', '00:11:22:33:44:55']),
|
|
'{ aa:bb:cc:dd:ee:ff, 00:11:22:33:44:55 }');
|
|
expect('mac_to_nftarr all-poison returns null',
|
|
mac_to_nftarr(['00:11:22:33:44:GG',
|
|
'aa-bb-cc-dd-ee-ff',
|
|
'too:short']),
|
|
null);
|
|
expect('mac_to_nftarr mixed drops the poison',
|
|
mac_to_nftarr(['aa:bb:cc:dd:ee:ff', '00:11:22:33:44:GG', '11:22:33:44:55:66']),
|
|
'{ aa:bb:cc:dd:ee:ff, 11:22:33:44:55:66 }');
|
|
}
|
|
|
|
/* iface_to_nftarr -------------------------------------------------------- */
|
|
{
|
|
expect('iface_to_nftarr clean',
|
|
iface_to_nftarr(['eth0', 'br-lan']),
|
|
'{ eth0, br-lan }');
|
|
expect('iface_to_nftarr all-poison returns null',
|
|
iface_to_nftarr(['eth0 } reject ; set x {',
|
|
'eth 0',
|
|
'eth0/1']),
|
|
null);
|
|
expect('iface_to_nftarr mixed drops the poison',
|
|
iface_to_nftarr(['eth0', 'eth0;reject', 'br-lan']),
|
|
'{ eth0, br-lan }');
|
|
}
|
|
|
|
/* ports_to_nftarr -------------------------------------------------------- */
|
|
{
|
|
/* The exact injection the report called out. */
|
|
expect('ports_to_nftarr clean',
|
|
ports_to_nftarr('22,53,80,443'),
|
|
'{ 22, 53, 80, 443 }');
|
|
expect('ports_to_nftarr all-poison returns null',
|
|
ports_to_nftarr('abc,7890; reject,-1,65536'),
|
|
null);
|
|
expect('ports_to_nftarr mixed drops the poison',
|
|
ports_to_nftarr('22,7890; reject,443,foo'),
|
|
'{ 22, 443 }');
|
|
expect('ports_to_nftarr empty returns null',
|
|
ports_to_nftarr(''), null);
|
|
expect('ports_to_nftarr handles whitespace',
|
|
ports_to_nftarr(' 22 , 53 , 80 '),
|
|
'{ 22, 53, 80 }');
|
|
expect('ports_to_nftarr handles common defaults',
|
|
ports_to_nftarr('22,53,80,143,443,465,587,853,873,993,995,8080,8443,9418'),
|
|
'{ 22, 53, 80, 143, 443, 465, 587, 853, 873, 993, 995, 8080, 8443, 9418 }');
|
|
}
|
|
|
|
if (failures > 0)
|
|
printf('FAIL: %d/%d checks failed\n', failures, checks);
|
|
else
|
|
printf('PASS: %d checks\n', checks);
|
|
|
|
exit(failures > 0 ? 1 : 0);
|