mirror of
https://github.com/openwrt/packages.git
synced 2026-06-20 12:10:15 +04:00
pbr: update to 1.2.2-r6
Update pbr from 1.2.1-r87 to 1.2.2-r6. This release adds mwan4 (Multi-WAN) integration, a diagnostic `support` command, IPv6 lease-to-nftset handling, improved split-uplink detection, stricter UCI validation, shell variable quoting fixes across 30+ locations, and a comprehensive 126-case test suite with a full mock OpenWrt sysroot. Signed-off-by: Stan Grishin <stangri@melmac.ca> --- - **31 files changed**, +1,745 / -227 lines (net +1,518) - **1 commit**: `61c8923` — `pbr: update to 1.2.2-r6` --- - Version bumped from `1.2.1-r87` to `1.2.2-r6` - URL updated from `github.com/stangri/pbr/` to `github.com/mossdef-org/pbr/` - No dependency changes --- Three options changed from scalar to list type: | Option | Old Type | New Type | |---------------------|----------|----------| | `ignored_interface` | `option` | `list` | | `lan_device` | `option` | `list` | | `resolver_instance` | `option` | `list` | Options reordered: scalars first, then lists, matching UCI convention. No values changed. --- The init script (`/etc/init.d/pbr`) received significant additions and fixes across ~660 lines (+443/-218). Bumped from `24` to `25`. **mwan4 (Multi-WAN) Integration (8 new functions):** - `mwan4_is_installed()` — Detect mwan4 package - `mwan4_is_running()` — Check service status - `mwan4_get_iface_list()` — Get enabled interfaces - `mwan4_get_strategy_list()` — Get strategies - `mwan4_get_iface_mark_chain()` — Get nft mark chain for interface - `mwan4_get_iface_nft_sets()` — Get nftset names - `mwan4_get_strategy_chain()` — Get strategy chain - `mwan4_get_mmx_mask()` — Get Multi-WAN mark mask Enables PBR to coordinate with mwan4 for combined policy routing and multi-WAN failover. **Diagnostic `support` Command:** - New `support()` function generates masked diagnostic output for troubleshooting - `print_config_masked()` redacts sensitive data (passwords, keys, tokens, PSKs, endpoints) while preserving IP addresses and structure **IPv6 Lease Handling:** - New `ipv6_leases_to_nftset()` parses DHCPv6 leases from `/tmp/hosts/odhcpd` - Complements existing `ipv4_leases_to_nftset()` **Split Uplink Detection (3 new functions):** - `is_uplink4()` — Check IPv4 uplink interface - `is_uplink6()` — Check IPv6 uplink interface - `is_uplink()` — Unified check (v4 or v6) - New `ipv6_default_lookup` variable for split IPv4/IPv6 uplink routing table assignment **ubus Integration:** - New `ubus_get_interface()` queries PBR gateway data via ubus **Shell Variable Quoting (30+ locations):** Systematic conversion of bare variable references to brace-quoted syntax throughout the script: - `$2` to `${2}` in string replacements - `$_ret` to `${_ret}` in conditional expansions - `$_mark` to `${_mark}` in nft rule generation - `$nftset6` to `${nftset6}` in dnsmasq rules - `$nft_set_timeout` to `${nft_set_timeout}` - `$xrayIfacePrefix` to `${xrayIfacePrefix}` - And many more across rule generation, output strings, and conditional expressions **Specific Fixes:** - `pbr_get_gateway6()`: Changed `is_wan` to `is_uplink4` for correct IPv4 uplink detection - `is_netifd_interface()`: Now checks both `ip4table` and `ip6table` (was IPv4 only) - `load_environment()`: Fixed inverted flag check (`-z` changed to `-n` for `loadEnvironmentFlag`) - Dnsmasq instance detection: Fixed UCI section lookup with proper variable handling - Help text URL: `#WarningMessagesDetails` changed to `#warning-messages-details` (kebab-case) - `uplink_ip_rules_priority`: Changed from `uinteger` to `range(99,32765)` to enforce valid Linux routing policy DB bounds Three options now use `config_get_list` instead of `config_get` to support multiple values: - `ignored_interface` - `lan_device` - `resolver_instance` **Rule Cleanup Refactored:** - Replaced complex awk-based rule parsing with priority-range approach - Calculates `prio_min = priority - max_ifaces` and `prio_max = priority`, iterates and deletes rules within range - Skips netifd-managed fwmark rules - Added legacy rule cleanup for `suppress_prefixlength` entries **Firewall Sync:** - Added `fw4 -q reload` after successful nft file installation to ensure fw4 state synchronizes with PBR's nftables changes **Resolver Instance Handling:** - Added robustness checks in `_dnsmasq_instance_config()`: file existence check and instance validity check - Better section name resolution with UCI query - Added missing `setup` parameter in resolver instance setup calls - `uci_get_device()` — Replaced with inline call - `uci_get_protocol()` — Replaced with inline call --- In `70-pbr`, fixed shell variable quoting: ```sh ${DEVICE:+ ($DEVICE)} ${DEVICE:+ (${DEVICE})} ``` --- In `pbr.user.netflix`, fixed two instances of bare variable expansion in parameter substitution: ```sh params="${params:+$params, }${p}" params="${params:+${params}, }${p}" ``` --- A full test suite is added in `net/pbr/tests/` (21 new files, ~1,300 lines) using the shunit2 framework with a complete mock OpenWrt sysroot. **Runner (`run_tests.sh`):** - Discovers test files via glob pattern - Supports pattern-based filtering via CLI arg - Executes each test in isolated bash subprocess - Captures output, reports pass/fail with color - Accumulates stats and lists failures at end - Requires `shunit2` package **Setup (`lib/setup.sh`):** - Creates temporary mock sysroot (`$MOCK_ROOT`) - Sets `IPKG_INSTROOT` for OpenWrt path resolution - Installs mock libraries, configs, and binaries - Stubs `rc.common`, procd, logger, resolveip, jsonfilter, pidof, sync - Sources pbr init script with `readonly` keyword stripped (allows test overrides) - Redirects all file paths to temp directories **UCI Config API (`lib/mocks/functions.sh`):** - Full `config_load` parser for UCI syntax - `config_get`, `config_get_bool`, `config_get_list`, `config_foreach`, `config_list_foreach` - `uci_set`, `uci_get`, `uci_add_list`, `uci_remove`, `uci_remove_list`, `uci_commit` - Stores state in associative arrays **Network API (`lib/mocks/network.sh`):** - `network_get_device`, `network_get_physdev`, `network_get_gateway`, `network_get_gateway6`, `network_get_protocol`, `network_get_ipaddr`, `network_get_ip6addr`, `network_get_dnsserver`, `network_flush_cache` - Backed by `MOCK_NET_*` variables that tests override to simulate different network states - Pre-configured: wan (eth0/dhcp/192.168.1.1), wan6 (eth0/dhcpv6/fd00::1), wg0 (wireguard), lan (br-lan/static), loopback (lo/static) **JSON Shell (`lib/mocks/jshn.sh`):** - Minimal JSON-in-shell implementation - `json_init`, `json_add_string/boolean/int`, `json_add_object/array`, `json_close_*`, `json_select`, `json_get_var`, `json_get_keys`, `json_dump`, `json_load` - Associative array backend with path tracking **Mock Binaries:** - `nft` — Returns fw4 table structure with standard chains (input, forward, output, dstnat, mangle_*); passes syntax checks - `dnsmasq` — Reports version with nftset support - `readlink` — Returns `/usr/libexec/ip-full` for `*/sbin/ip` (simulates ip-full installed) **Mock UCI Configs:** - `pbr` — Full config: enabled, policies (vpn_all, vpn_gaming, disabled_policy), dns_policy, nft settings, interface lists - `network` — Interfaces: loopback, lan, wan, wan6, wg0 (wireguard) - `firewall` — Zones: lan (accept all), wan (reject input/forward) - `dhcp` — DHCP server stub - `system` — Hostname and timezone **01_validation — Input Validation (67 cases):** `01_ipv4_validation` (13 cases): - Valid IPs: 192.168.1.1, 10.0.0.1, 172.16.0.1 - Valid CIDR: /8, /24, /32, /0 - Invalid: octets >255, wrong octet count, CIDR >32, IPv6 addresses, domain names `02_ipv6_validation` (21 cases): - Valid: ::1, fe80::1, 2001:db8::1, fd00::1, full addresses, ::/0 - Invalid: IPv4 addrs, plain strings, MACs - Scope detection: global (2001:db8::/32), link-local (fe80::/10), ULA (fd00::/8) `03_domain_validation` (8 cases): - Host: single labels (router, host123) - Hostname: multi-label (example.com, sub.example.com, deep.sub.example.com) - Domain: FQDN or single-label - Invalid: IPs, empty strings, MAC notation `04_misc_validators` (25 cases): - MAC addresses (colon notation, case variants) - Integer validation (positive, not negative) - Negation marker (! prefix detection) - URL schemes (http, https, ftp, file://) - Version comparison (is_greater, is_greater_or_equal) - Family mismatch (IPv4/IPv6 mixing detection) **02_string_utils — String Functions (8 cases):** `01_str_functions`: - `str_contains` — Substring search - `str_contains_word` — Word-boundary search - `str_to_lower` / `str_to_upper` — Case convert - `str_first_word` — Token extraction - `str_replace` — String substitution - `str_extras_to_underscore` — Normalize delims - `str_extras_to_space` — Expand delimiters **03_wan_detection — Interface Detection (13 cases):** `01_wan_types`: - `is_wan4` — Detects wan/wanX, not wan6/lan/wg0 - `is_wan6` — Detects wan6/mwan6 (IPv6-aware) - `is_wan6_disabled` — Disabled when ipv6 off - `is_wan` — Unified v4+v6 detection - `is_uplink4` / `is_uplink6` — Uplink detection - `is_tor` — Case-insensitive tor detection - `is_ignore_target` — Ignore target detection - `is_list` — Comma/space list vs single value **04_config — Configuration Loading (13 cases):** `01_load_config` (7 cases): - Default values from UCI config - Hex value parsing (fw_mask, uplink_mark) - XOR calculation (fw_maskXor = ~fw_mask) - List parsing (ignored_interface, resolver) - nft parameters (auto-merge, flags) - Config-loaded flag tracking `02_disabled_service` (2 cases): - Disabled: enabled option becomes unset - Enabled: enabled option is set `03_config_ipv6` (4 cases): - IPv6 enabled: config and uplink interface set - IPv6 disabled: both unset - Reload behavior verification **05_nft — nftables Integration (14 cases):** `01_nft_file_operations` (8 cases): - File creation with nft shebang - Chain creation (dstnat, forward, output, prerouting) - Jump rules and guard rules - File append, content search, file deletion `02_nft_check_element` (6 cases): - fw4 table existence - Chain existence (input, forward, output, dstnat, mangle_*) - Non-existent chain detection **06_network — Network Functions (11 cases):** `01_gateway_discovery` (4 cases): - IPv4 gateway from mock (192.168.1.1) - IPv4 gateway fallback (ip addr parsing) - IPv6 gateway from mock (fd00::1) - Interface finding for uplinks `02_supported_interfaces` (7 cases): - Ignored: loopback in ignored list - LAN detection vs non-LAN - Uplink support (wan is supported) - LAN/loopback not supported - Wireguard supported (wg0) - Explicit custom interface support --- ```sh cd net/pbr/tests && sh run_tests.sh ``` Requires: `bash`, `shunit2`. Optional filter: `sh run_tests.sh 01_validation` Signed-off-by: Stan Grishin <stangri@melmac.ca>
This commit is contained in:
+3
-3
@@ -4,8 +4,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=pbr
|
||||
PKG_VERSION:=1.2.1
|
||||
PKG_RELEASE:=87
|
||||
PKG_VERSION:=1.2.2
|
||||
PKG_RELEASE:=6
|
||||
PKG_LICENSE:=AGPL-3.0-or-later
|
||||
PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
|
||||
|
||||
@@ -16,7 +16,7 @@ define Package/pbr
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Routing and Redirection
|
||||
TITLE:=Policy Based Routing Service with nft/nft set support
|
||||
URL:=https://github.com/stangri/pbr/
|
||||
URL:=https://github.com/mossdef-org/pbr/
|
||||
PKGARCH:=all
|
||||
DEPENDS:= \
|
||||
+ip-full \
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
config pbr 'config'
|
||||
option enabled '0'
|
||||
option fw_mask '00ff0000'
|
||||
list ignored_interface 'vpnserver'
|
||||
option ipv6_enabled '0'
|
||||
option lan_device 'br-lan'
|
||||
option nft_rule_counter '0'
|
||||
option nft_set_auto_merge '1'
|
||||
option nft_set_counter '0'
|
||||
@@ -13,7 +11,6 @@ config pbr 'config'
|
||||
option nft_user_set_counter '0'
|
||||
option procd_boot_trigger_delay '5000'
|
||||
option procd_reload_delay '0'
|
||||
list resolver_instance '*'
|
||||
option resolver_set 'dnsmasq.nftset'
|
||||
option strict_enforcement '1'
|
||||
option uplink_interface 'wan'
|
||||
@@ -21,6 +18,9 @@ config pbr 'config'
|
||||
option uplink_ip_rules_priority '30000'
|
||||
option uplink_mark '00010000'
|
||||
option verbosity '2'
|
||||
list ignored_interface 'vpnserver'
|
||||
list lan_device 'br-lan'
|
||||
list resolver_instance '*'
|
||||
list webui_supported_protocol 'all'
|
||||
list webui_supported_protocol 'tcp'
|
||||
list webui_supported_protocol 'udp'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
# shellcheck disable=SC1091,SC3060
|
||||
if [ -x /etc/init.d/pbr ] && /etc/init.d/pbr enabled; then
|
||||
logger -t pbr "Sending reload signal to pbr for $INTERFACE due to $ACTION of $INTERFACE${DEVICE:+ ($DEVICE)}"
|
||||
logger -t pbr "Sending reload signal to pbr for $INTERFACE due to $ACTION of $INTERFACE${DEVICE:+ (${DEVICE})}"
|
||||
/etc/init.d/pbr on_interface_reload "$INTERFACE" "$ACTION"
|
||||
fi
|
||||
|
||||
+443
-218
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@ fi
|
||||
|
||||
if [ -s "$TARGET_DL_FILE_4" ]; then
|
||||
params=
|
||||
while read -r p; do params="${params:+$params, }${p}"; done < "$TARGET_DL_FILE_4"
|
||||
while read -r p; do params="${params:+${params}, }${p}"; done < "$TARGET_DL_FILE_4"
|
||||
[ -n "$params" ] && nft "add element $TARGET_TABLE $TARGET_NFTSET_4 { $params }" || _ret=1
|
||||
fi
|
||||
|
||||
@@ -47,7 +47,7 @@ if [ -n "$TARGET_DL_FILE_6" ] && [ ! -s "$TARGET_DL_FILE_6" ]; then
|
||||
fi
|
||||
if [ -s "$TARGET_DL_FILE_6" ]; then
|
||||
params=
|
||||
while read -r p; do params="${params:+$params, }${p}"; done < "$TARGET_DL_FILE_6"
|
||||
while read -r p; do params="${params:+${params}, }${p}"; done < "$TARGET_DL_FILE_6"
|
||||
[ -n "$params" ] && nft "add element $TARGET_TABLE $TARGET_NFTSET_6 { $params }" || _ret=1
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
# Test: IPv4 address validation
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
testIpv4ValidStandard() {
|
||||
assertTrue "Standard private IP" "is_ipv4 '192.168.1.1'"
|
||||
assertTrue "Class A private" "is_ipv4 '10.0.0.1'"
|
||||
assertTrue "Class B private" "is_ipv4 '172.16.0.1'"
|
||||
assertTrue "Google DNS" "is_ipv4 '8.8.8.8'"
|
||||
assertTrue "All zeros" "is_ipv4 '0.0.0.0'"
|
||||
assertTrue "All ones" "is_ipv4 '255.255.255.255'"
|
||||
assertTrue "Simple IP" "is_ipv4 '1.2.3.4'"
|
||||
}
|
||||
|
||||
testIpv4ValidCIDR() {
|
||||
assertTrue "CIDR /8" "is_ipv4 '10.0.0.0/8'"
|
||||
assertTrue "CIDR /24" "is_ipv4 '192.168.1.0/24'"
|
||||
assertTrue "CIDR /32" "is_ipv4 '10.0.0.1/32'"
|
||||
assertTrue "Default route" "is_ipv4 '0.0.0.0/0'"
|
||||
}
|
||||
|
||||
testIpv4Invalid() {
|
||||
assertFalse "Octet > 255" "is_ipv4 '256.1.1.1'"
|
||||
assertFalse "Last octet > 255" "is_ipv4 '1.2.3.256'"
|
||||
assertFalse "Not an IP" "is_ipv4 'not_an_ip'"
|
||||
assertFalse "Empty string" "is_ipv4 ''"
|
||||
assertFalse "Only 3 octets" "is_ipv4 '192.168.1'"
|
||||
assertFalse "5 octets" "is_ipv4 '192.168.1.1.1'"
|
||||
assertFalse "CIDR > 32" "is_ipv4 '192.168.1.1/33'"
|
||||
assertFalse "IPv6 loopback" "is_ipv4 '::1'"
|
||||
assertFalse "IPv6 link-local" "is_ipv4 'fe80::1'"
|
||||
assertFalse "Domain name" "is_ipv4 'example.com'"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# Test: IPv6 address validation and scope detection
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
testIpv6Valid() {
|
||||
assertTrue "Loopback" "is_ipv6 '::1'"
|
||||
assertTrue "Link-local" "is_ipv6 'fe80::1'"
|
||||
assertTrue "Documentation prefix" "is_ipv6 '2001:db8::1'"
|
||||
assertTrue "Unique local" "is_ipv6 'fd00::1'"
|
||||
assertTrue "Full address" "is_ipv6 '2001:0db8:85a3::8a2e:0370:7334'"
|
||||
assertTrue "Default route" "is_ipv6 '::/0'"
|
||||
}
|
||||
|
||||
testIpv6Invalid() {
|
||||
assertFalse "IPv4 address" "is_ipv6 '192.168.1.1'"
|
||||
assertFalse "Plain string" "is_ipv6 'not_ipv6'"
|
||||
assertFalse "Empty string" "is_ipv6 ''"
|
||||
assertFalse "MAC address" "is_ipv6 'AA:BB:CC:DD:EE:FF'"
|
||||
}
|
||||
|
||||
testIpv6GlobalScope() {
|
||||
assertTrue "Global scope 2001" "is_ipv6_global_scope '2001:db8::1'"
|
||||
assertFalse "Link-local not global" "is_ipv6_global_scope 'fe80::1'"
|
||||
assertFalse "ULA not global" "is_ipv6_global_scope 'fd00::1'"
|
||||
}
|
||||
|
||||
testIpv6LinkLocal() {
|
||||
assertTrue "Link-local fe80" "is_ipv6_local_link 'fe80::1'"
|
||||
assertFalse "Global not link-local" "is_ipv6_local_link '2001::1'"
|
||||
}
|
||||
|
||||
testIpv6UniqueLocal() {
|
||||
assertTrue "ULA fd" "is_ipv6_local_unique 'fd00::1'"
|
||||
assertTrue "ULA fc" "is_ipv6_local_unique 'fc00::1'"
|
||||
assertFalse "Link-local not ULA" "is_ipv6_local_unique 'fe80::1'"
|
||||
assertFalse "Global not ULA" "is_ipv6_local_unique '2001::1'"
|
||||
}
|
||||
|
||||
testIpv6LocalScope() {
|
||||
assertTrue "Link-local is local scope" "is_ipv6_local_scope 'fe80::1'"
|
||||
assertTrue "ULA is local scope" "is_ipv6_local_scope 'fd00::1'"
|
||||
assertFalse "Global not local scope" "is_ipv6_local_scope '2001::1'"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Test: Domain, host, and hostname validation
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
testIsHost() {
|
||||
assertTrue "Simple hostname" "is_host 'router'"
|
||||
assertTrue "Hostname with hyphen" "is_host 'my-host'"
|
||||
assertTrue "Hostname with numbers" "is_host 'host123'"
|
||||
assertTrue "Single character" "is_host 'A'"
|
||||
assertFalse "Empty string" "is_host ''"
|
||||
assertFalse "Starts with hyphen" "is_host '-invalid'"
|
||||
}
|
||||
|
||||
testIsHostname() {
|
||||
assertTrue "Simple domain" "is_hostname 'example.com'"
|
||||
assertTrue "Subdomain" "is_hostname 'sub.example.com'"
|
||||
assertTrue "Deep subdomain" "is_hostname 'deep.sub.example.com'"
|
||||
assertTrue "Hyphenated with ccTLD" "is_hostname 'my-site.co.uk'"
|
||||
assertFalse "Single label" "is_hostname 'localhost'"
|
||||
assertFalse "Empty string" "is_hostname ''"
|
||||
assertFalse "IP address" "is_hostname '192.168.1.1'"
|
||||
}
|
||||
|
||||
testIsDomain() {
|
||||
assertTrue "Standard domain" "is_domain 'example.com'"
|
||||
assertTrue "Single-label host" "is_domain 'router'"
|
||||
assertTrue "Local domain" "is_domain 'my-server.local'"
|
||||
assertFalse "IPv4 not a domain" "is_domain '192.168.1.1'"
|
||||
assertFalse "Empty string" "is_domain ''"
|
||||
assertFalse "Bad MAC notation" "is_domain 'AA-BB-CC-DD-EE-FF'"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
# Test: Miscellaneous validators (MAC, integer, URL, negation, version comparison)
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
testMacAddress() {
|
||||
assertTrue "Uppercase MAC" "is_mac_address 'AA:BB:CC:DD:EE:FF'"
|
||||
assertTrue "Lowercase MAC" "is_mac_address 'aa:bb:cc:dd:ee:ff'"
|
||||
assertTrue "Numeric MAC" "is_mac_address '00:11:22:33:44:55'"
|
||||
assertFalse "Too short" "is_mac_address 'AA:BB:CC:DD:EE'"
|
||||
assertFalse "Too long" "is_mac_address 'AA:BB:CC:DD:EE:FF:00'"
|
||||
assertFalse "Dash notation" "is_mac_address 'AA-BB-CC-DD-EE-FF'"
|
||||
assertFalse "Not a MAC" "is_mac_address 'not_a_mac'"
|
||||
assertFalse "Empty string" "is_mac_address ''"
|
||||
}
|
||||
|
||||
testMacAddressBadNotation() {
|
||||
assertTrue "Dash notation" "is_mac_address_bad_notation 'AA-BB-CC-DD-EE-FF'"
|
||||
assertFalse "Colon notation" "is_mac_address_bad_notation 'AA:BB:CC:DD:EE:FF'"
|
||||
}
|
||||
|
||||
testIsInteger() {
|
||||
assertTrue "Zero" "is_integer '0'"
|
||||
assertTrue "Positive" "is_integer '123'"
|
||||
assertTrue "Large number" "is_integer '999999'"
|
||||
assertFalse "Empty string" "is_integer ''"
|
||||
assertFalse "Letters" "is_integer 'abc'"
|
||||
assertFalse "Decimal" "is_integer '12.34'"
|
||||
assertFalse "Negative" "is_integer '-1'"
|
||||
}
|
||||
|
||||
testIsNegated() {
|
||||
assertTrue "Negated IP" "is_negated '!192.168.1.1'"
|
||||
assertTrue "Negated domain" "is_negated '!example.com'"
|
||||
assertFalse "Not negated" "is_negated '192.168.1.1'"
|
||||
assertFalse "Empty string" "is_negated ''"
|
||||
}
|
||||
|
||||
testUrlValidators() {
|
||||
assertTrue "HTTP URL" "is_url_http 'http://example.com'"
|
||||
assertTrue "HTTPS URL" "is_url_https 'https://example.com'"
|
||||
assertTrue "FTP URL" "is_url_ftp 'ftp://files.example.com'"
|
||||
assertTrue "File URL" "is_url_file 'file:///tmp/list.txt'"
|
||||
assertFalse "HTTPS is not HTTP" "is_url_http 'https://example.com'"
|
||||
assertFalse "HTTP is not HTTPS" "is_url_https 'http://example.com'"
|
||||
assertTrue "HTTP is URL" "is_url 'http://example.com'"
|
||||
assertTrue "HTTPS is URL" "is_url 'https://example.com'"
|
||||
assertTrue "FTP is URL" "is_url 'ftp://example.com'"
|
||||
assertTrue "File is URL" "is_url 'file:///tmp/x'"
|
||||
assertFalse "Plain domain not URL" "is_url 'example.com'"
|
||||
}
|
||||
|
||||
testVersionComparison() {
|
||||
assertTrue "2.0 > 1.0" "is_greater '2.0' '1.0'"
|
||||
assertTrue "1.10 > 1.9" "is_greater '1.10' '1.9'"
|
||||
assertFalse "1.0 not > 2.0" "is_greater '1.0' '2.0'"
|
||||
assertFalse "Equal not greater" "is_greater '1.0' '1.0'"
|
||||
assertTrue "Equal is >=" "is_greater_or_equal '1.0' '1.0'"
|
||||
assertTrue "Greater is >=" "is_greater_or_equal '2.0' '1.0'"
|
||||
assertFalse "Lesser not >=" "is_greater_or_equal '1.0' '2.0'"
|
||||
}
|
||||
|
||||
testFamilyMismatch() {
|
||||
assertTrue "IPv4 src IPv6 dst" "is_family_mismatch '192.168.1.1' '::1'"
|
||||
assertTrue "IPv6 src IPv4 dst" "is_family_mismatch '::1' '10.0.0.1'"
|
||||
assertFalse "Both IPv4" "is_family_mismatch '10.0.0.1' '10.0.0.2'"
|
||||
assertFalse "Both IPv6" "is_family_mismatch '::1' '::2'"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
# Test: String utility functions
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
testStrContains() {
|
||||
assertTrue "Contains word" "str_contains 'hello world' 'world'"
|
||||
assertTrue "Contains substring" "str_contains 'hello world' 'lo wo'"
|
||||
assertTrue "Contains middle" "str_contains 'abcdef' 'bcd'"
|
||||
assertFalse "Does not contain" "str_contains 'hello' 'xyz'"
|
||||
assertFalse "Empty haystack" "str_contains '' 'test'"
|
||||
# In bash, ${1//} with empty pattern doesn't remove anything, so returns false
|
||||
assertFalse "Empty needle returns false" "str_contains 'hello' ''"
|
||||
}
|
||||
|
||||
testStrContainsWord() {
|
||||
assertTrue "Contains exact word" "str_contains_word 'one two three' 'two'"
|
||||
assertFalse "Partial not word match" "str_contains_word 'one twothree' 'two'"
|
||||
assertTrue "Single word" "str_contains_word 'one' 'one'"
|
||||
assertFalse "Word not present" "str_contains_word 'one two three' 'four'"
|
||||
}
|
||||
|
||||
testStrToLower() {
|
||||
assertEquals "All caps to lower" "hello" "$(str_to_lower 'HELLO')"
|
||||
assertEquals "Mixed case" "hello" "$(str_to_lower 'Hello')"
|
||||
assertEquals "Already lowercase" "hello" "$(str_to_lower 'hello')"
|
||||
assertEquals "With numbers" "123abc" "$(str_to_lower '123ABC')"
|
||||
}
|
||||
|
||||
testStrToUpper() {
|
||||
assertEquals "All lower to upper" "HELLO" "$(str_to_upper 'hello')"
|
||||
assertEquals "Mixed case" "HELLO" "$(str_to_upper 'Hello')"
|
||||
assertEquals "With numbers" "123ABC" "$(str_to_upper '123abc')"
|
||||
}
|
||||
|
||||
testStrFirstWord() {
|
||||
assertEquals "First of two" "hello" "$(str_first_word 'hello world')"
|
||||
assertEquals "First of three" "one" "$(str_first_word 'one two three')"
|
||||
assertEquals "Single word" "single" "$(str_first_word 'single')"
|
||||
}
|
||||
|
||||
testStrReplace() {
|
||||
assertEquals "Replace word" "hello universe" "$(str_replace 'hello world' 'world' 'universe')"
|
||||
assertEquals "Replace dots" "aXbXc" "$(str_replace 'a.b.c' '.' 'X')"
|
||||
assertEquals "No match unchanged" "hello world" "$(str_replace 'hello world' 'xyz' 'abc')"
|
||||
}
|
||||
|
||||
testStrExtrasToUnderscore() {
|
||||
assertEquals "Dot to underscore" "hello_world" "$(str_extras_to_underscore 'hello.world')"
|
||||
assertEquals "Spaces to underscores" "a_b_c" "$(str_extras_to_underscore 'a b c')"
|
||||
assertEquals "Slash to underscore" "test_path" "$(str_extras_to_underscore 'test/path')"
|
||||
assertEquals "Multiple dots collapsed" "no_dups" "$(str_extras_to_underscore 'no..dups')"
|
||||
}
|
||||
|
||||
testStrExtrasToSpace() {
|
||||
assertEquals "Delimiters to spaces" "a b c d" "$(str_extras_to_space 'a,b;c{d')"
|
||||
assertEquals "Closing brace to space" "a b" "$(str_extras_to_space 'a}b')"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
# Test: WAN/interface type detection functions
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
testIsWan4() {
|
||||
assertTrue "Standard wan" "is_wan4 'wan'"
|
||||
assertTrue "wan prefix" "is_wan4 'wanX'"
|
||||
assertFalse "wan6 is not wan4" "is_wan4 'wan6'"
|
||||
assertFalse "Ends with wan6" "is_wan4 'mwan6'"
|
||||
assertFalse "LAN not wan4" "is_wan4 'lan'"
|
||||
assertFalse "Wireguard not wan4" "is_wan4 'wg0'"
|
||||
}
|
||||
|
||||
testIsWan6() {
|
||||
ipv6_enabled='1'
|
||||
assertTrue "Standard wan6" "is_wan6 'wan6'"
|
||||
assertTrue "Ends with wan6" "is_wan6 'mwan6'"
|
||||
assertFalse "wan is not wan6" "is_wan6 'wan'"
|
||||
assertFalse "LAN not wan6" "is_wan6 'lan'"
|
||||
}
|
||||
|
||||
testIsWan6Disabled() {
|
||||
unset ipv6_enabled
|
||||
assertFalse "wan6 without ipv6 disabled" "is_wan6 'wan6'"
|
||||
}
|
||||
|
||||
testIsWan() {
|
||||
ipv6_enabled='1'
|
||||
assertTrue "wan matches" "is_wan 'wan'"
|
||||
assertTrue "wan6 matches" "is_wan 'wan6'"
|
||||
assertFalse "LAN not wan" "is_wan 'lan'"
|
||||
assertFalse "Wireguard not wan" "is_wan 'wg0'"
|
||||
}
|
||||
|
||||
testIsUplink() {
|
||||
uplink_interface4="wan"
|
||||
uplink_interface6="wan6"
|
||||
ipv6_enabled='1'
|
||||
assertTrue "wan is uplink4" "is_uplink4 'wan'"
|
||||
assertFalse "wan6 is not uplink4" "is_uplink4 'wan6'"
|
||||
assertTrue "wan6 is uplink6" "is_uplink6 'wan6'"
|
||||
assertFalse "wan is not uplink6" "is_uplink6 'wan'"
|
||||
assertTrue "wan is uplink" "is_uplink 'wan'"
|
||||
assertTrue "wan6 is uplink" "is_uplink 'wan6'"
|
||||
assertFalse "wg0 is not uplink" "is_uplink 'wg0'"
|
||||
}
|
||||
|
||||
testIsTor() {
|
||||
assertTrue "Lowercase tor" "is_tor 'tor'"
|
||||
assertTrue "Uppercase TOR" "is_tor 'TOR'"
|
||||
assertTrue "Mixed case Tor" "is_tor 'Tor'"
|
||||
assertFalse "Not tor" "is_tor 'vpn'"
|
||||
}
|
||||
|
||||
testIsIgnoreTarget() {
|
||||
assertTrue "Lowercase ignore" "is_ignore_target 'ignore'"
|
||||
assertTrue "Uppercase IGNORE" "is_ignore_target 'IGNORE'"
|
||||
assertTrue "Mixed case" "is_ignore_target 'Ignore'"
|
||||
assertFalse "Not ignore" "is_ignore_target 'wan'"
|
||||
}
|
||||
|
||||
testIsList() {
|
||||
assertTrue "Comma-separated" "is_list 'a,b'"
|
||||
assertTrue "Space-separated" "is_list 'a b'"
|
||||
assertTrue "Multiple commas" "is_list 'a,b,c'"
|
||||
assertFalse "Single value" "is_list 'single'"
|
||||
assertFalse "Empty string" "is_list ''"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
# Test: Package config loading
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
testLoadBasicConfig() {
|
||||
load_package_config
|
||||
assertNotNull "enabled is set" "$enabled"
|
||||
assertEquals "verbosity" "2" "$verbosity"
|
||||
assertEquals "uplink_interface4" "wan" "$uplink_interface4"
|
||||
assertEquals "uplink_ip_rules_priority" "30000" "$uplink_ip_rules_priority"
|
||||
assertEquals "procd_boot_trigger_delay" "5000" "$procd_boot_trigger_delay"
|
||||
}
|
||||
|
||||
testLoadHexValues() {
|
||||
load_package_config
|
||||
assertEquals "fw_mask hex" "0x00ff0000" "$fw_mask"
|
||||
assertEquals "uplink_mark hex" "0x00010000" "$uplink_mark"
|
||||
}
|
||||
|
||||
testFwMaskXor() {
|
||||
load_package_config
|
||||
assertNotNull "fw_maskXor computed" "${fw_maskXor:-}"
|
||||
assertEquals "fw_maskXor value" "0xff00ffff" "$fw_maskXor"
|
||||
}
|
||||
|
||||
testIpv6DisabledConfig() {
|
||||
load_package_config
|
||||
assertNull "ipv6_enabled unset when 0" "${ipv6_enabled:-}"
|
||||
assertNull "uplink_interface6 unset" "${uplink_interface6:-}"
|
||||
}
|
||||
|
||||
testStrictEnforcement() {
|
||||
load_package_config
|
||||
assertNotNull "strict_enforcement set" "${strict_enforcement:-}"
|
||||
}
|
||||
|
||||
testNftSetParams() {
|
||||
load_package_config
|
||||
echo "$nftSetParams" | grep -q 'auto-merge'
|
||||
assertTrue "nft auto-merge enabled" $?
|
||||
echo "$nftSetParams" | grep -q 'flags interval'
|
||||
assertTrue "nft flags interval enabled" $?
|
||||
}
|
||||
|
||||
testLoadPackageConfigFlag() {
|
||||
load_package_config
|
||||
assertEquals "flag set" "true" "$loadPackageConfigFlag"
|
||||
}
|
||||
|
||||
testIgnoredInterfaceList() {
|
||||
load_package_config
|
||||
echo "$ignored_interface" | grep -qF 'loopback'
|
||||
assertTrue "loopback in ignored_interface" $?
|
||||
}
|
||||
|
||||
. shunit2
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Test: Disabled service detection
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
testDisabledService() {
|
||||
cp "$MOCK_ROOT/etc/config/pbr" "$MOCK_ROOT/etc/config/pbr.bak"
|
||||
sed -i "s/option enabled '1'/option enabled '0'/" "$MOCK_ROOT/etc/config/pbr"
|
||||
|
||||
_CONFIG_LOADED_PKG=""
|
||||
loadPackageConfigFlag=""
|
||||
load_package_config
|
||||
|
||||
assertNull "enabled is unset when service disabled" "${enabled:-}"
|
||||
|
||||
cp "$MOCK_ROOT/etc/config/pbr.bak" "$MOCK_ROOT/etc/config/pbr"
|
||||
}
|
||||
|
||||
testEnabledService() {
|
||||
_CONFIG_LOADED_PKG=""
|
||||
loadPackageConfigFlag=""
|
||||
load_package_config
|
||||
|
||||
assertNotNull "enabled is set when service enabled" "$enabled"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Test: IPv6 config variations
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
testIpv6Enabled() {
|
||||
cp "$MOCK_ROOT/etc/config/pbr" "$MOCK_ROOT/etc/config/pbr.bak"
|
||||
sed -i "s/option ipv6_enabled '0'/option ipv6_enabled '1'/" "$MOCK_ROOT/etc/config/pbr"
|
||||
|
||||
_CONFIG_LOADED_PKG=""
|
||||
loadPackageConfigFlag=""
|
||||
load_package_config
|
||||
|
||||
assertNotNull "ipv6_enabled is set" "${ipv6_enabled:-}"
|
||||
assertEquals "uplink_interface6" "wan6" "${uplink_interface6:-}"
|
||||
assertTrue "wan6 detected" "is_wan6 'wan6'"
|
||||
|
||||
cp "$MOCK_ROOT/etc/config/pbr.bak" "$MOCK_ROOT/etc/config/pbr"
|
||||
}
|
||||
|
||||
testIpv6Disabled() {
|
||||
_CONFIG_LOADED_PKG=""
|
||||
loadPackageConfigFlag=""
|
||||
load_package_config
|
||||
|
||||
assertNull "ipv6_enabled unset" "${ipv6_enabled:-}"
|
||||
assertNull "uplink_interface6 unset" "${uplink_interface6:-}"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Test: nft file operations (create, add, match, delete)
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
setUp() {
|
||||
mkdir -p "$(dirname "$nftTempFile")" 2>/dev/null || true
|
||||
mkdir -p "$(dirname "$nftMainFile")" 2>/dev/null || true
|
||||
rm -f "$nftTempFile" "$nftMainFile"
|
||||
load_package_config
|
||||
}
|
||||
|
||||
tearDown() {
|
||||
rm -f "$nftTempFile" "$nftMainFile"
|
||||
}
|
||||
|
||||
testNftFileCreate() {
|
||||
nft_file 'create' 'main'
|
||||
assertTrue "nft temp file created" "[ -f '$nftTempFile' ]"
|
||||
assertTrue "Has nft shebang" "grep -q '#!/usr/sbin/nft -f' '$nftTempFile'"
|
||||
}
|
||||
|
||||
testNftFileChains() {
|
||||
nft_file 'create' 'main'
|
||||
assertTrue "dstnat chain" "grep -q 'add chain inet fw4 pbr_dstnat' '$nftTempFile'"
|
||||
assertTrue "forward chain" "grep -q 'add chain inet fw4 pbr_forward' '$nftTempFile'"
|
||||
assertTrue "output chain" "grep -q 'add chain inet fw4 pbr_output' '$nftTempFile'"
|
||||
assertTrue "prerouting chain" "grep -q 'add chain inet fw4 pbr_prerouting' '$nftTempFile'"
|
||||
}
|
||||
|
||||
testNftFileJumpRules() {
|
||||
nft_file 'create' 'main'
|
||||
assertTrue "jump to dstnat" "grep -q 'jump pbr_dstnat' '$nftTempFile'"
|
||||
assertTrue "jump to prerouting" "grep -q 'jump pbr_prerouting' '$nftTempFile'"
|
||||
assertTrue "jump to output" "grep -q 'jump pbr_output' '$nftTempFile'"
|
||||
assertTrue "jump to forward" "grep -q 'jump pbr_forward' '$nftTempFile'"
|
||||
}
|
||||
|
||||
testNftFileGuardRules() {
|
||||
nft_file 'create' 'main'
|
||||
assertTrue "Guard rule" "grep -q 'meta mark & 0x00ff0000 != 0 return' '$nftTempFile'"
|
||||
}
|
||||
|
||||
testNftFileAdd() {
|
||||
nft_file 'create' 'main'
|
||||
nft_file 'add' 'main' 'add rule inet fw4 pbr_prerouting ip saddr 192.168.1.0/24 goto pbr_mark_0x00010000'
|
||||
assertTrue "Added rule present" "grep -q '192.168.1.0/24' '$nftTempFile'"
|
||||
}
|
||||
|
||||
testNftFileMatch() {
|
||||
nft_file 'create' 'main'
|
||||
assertTrue "Match existing" "nft_file 'match' 'temp' 'pbr_prerouting'"
|
||||
assertFalse "Match missing" "nft_file 'match' 'temp' 'nonexistent_xyz'"
|
||||
}
|
||||
|
||||
testNftFileDelete() {
|
||||
nft_file 'create' 'main'
|
||||
nft_file 'delete' 'main'
|
||||
assertFalse "Temp file deleted" "[ -f '$nftTempFile' ]"
|
||||
assertFalse "Main file deleted" "[ -f '$nftMainFile' ]"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# Test: nft_check_element for verifying fw4 table/chain existence
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
setUp() {
|
||||
nft_fw4_dump=""
|
||||
}
|
||||
|
||||
testTableExists() {
|
||||
assertTrue "fw4 table exists" "nft_check_element 'table' 'fw4'"
|
||||
}
|
||||
|
||||
testChainsExist() {
|
||||
assertTrue "input chain" "nft_check_element 'chain' 'input'"
|
||||
assertTrue "forward chain" "nft_check_element 'chain' 'forward'"
|
||||
assertTrue "output chain" "nft_check_element 'chain' 'output'"
|
||||
assertTrue "dstnat chain" "nft_check_element 'chain' 'dstnat'"
|
||||
assertTrue "mangle_prerouting" "nft_check_element 'chain' 'mangle_prerouting'"
|
||||
assertTrue "mangle_output" "nft_check_element 'chain' 'mangle_output'"
|
||||
assertTrue "mangle_forward" "nft_check_element 'chain' 'mangle_forward'"
|
||||
}
|
||||
|
||||
testNonExistentElements() {
|
||||
assertFalse "Non-existent chain" "nft_check_element 'chain' 'nonexistent_chain'"
|
||||
assertFalse "srcnat not present" "nft_check_element 'chain' 'srcnat'"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
# Test: Network gateway discovery
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
# Override ip function for gateway fallback tests
|
||||
ip() {
|
||||
case "$*" in
|
||||
"-4 a list dev eth0")
|
||||
echo " inet 192.168.1.100/24 brd 192.168.1.255 scope global eth0"
|
||||
;;
|
||||
"-6 a list dev eth0")
|
||||
echo " inet6 fd00::100/64 scope global"
|
||||
;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
testGateway4FromMock() {
|
||||
load_package_config
|
||||
local gw4=""
|
||||
pbr_get_gateway4 gw4 "wan" "eth0"
|
||||
assertEquals "Gateway4 from mock" "192.168.1.1" "$gw4"
|
||||
}
|
||||
|
||||
testGateway4Fallback() {
|
||||
load_package_config
|
||||
MOCK_NET_wan_gateway=""
|
||||
local gw4=""
|
||||
pbr_get_gateway4 gw4 "wan" "eth0"
|
||||
assertEquals "Gateway4 from ip fallback" "192.168.1.100" "$gw4"
|
||||
MOCK_NET_wan_gateway="192.168.1.1"
|
||||
}
|
||||
|
||||
testGateway6FromMock() {
|
||||
load_package_config
|
||||
ipv6_enabled='1'
|
||||
uplink_interface6='wan6'
|
||||
local gw6=""
|
||||
pbr_get_gateway6 gw6 "wan6" "eth0"
|
||||
assertEquals "Gateway6 from mock" "fd00::1" "$gw6"
|
||||
}
|
||||
|
||||
testPbrFindIface() {
|
||||
uplink_interface4="wan"
|
||||
uplink_interface6="wan6"
|
||||
local found=""
|
||||
pbr_find_iface found "wan"
|
||||
assertEquals "Find wan" "wan" "$found"
|
||||
pbr_find_iface found "wan6"
|
||||
assertEquals "Find wan6" "wan6" "$found"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
# Test: Interface support detection
|
||||
. "$(dirname "$0")/../lib/setup.sh"
|
||||
|
||||
oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; }
|
||||
|
||||
setUp() {
|
||||
load_package_config
|
||||
lan_device="br-lan"
|
||||
supported_interface=""
|
||||
ignored_interface="loopback"
|
||||
uplink_interface4="wan"
|
||||
uplink_interface6=""
|
||||
}
|
||||
|
||||
testIgnoredInterface() {
|
||||
assertTrue "loopback is ignored" "is_ignored_interface 'loopback'"
|
||||
assertFalse "wan is not ignored" "is_ignored_interface 'wan'"
|
||||
assertFalse "wg0 is not ignored" "is_ignored_interface 'wg0'"
|
||||
}
|
||||
|
||||
testIsLan() {
|
||||
assertTrue "lan is LAN" "is_lan 'lan'"
|
||||
assertFalse "wan is not LAN" "is_lan 'wan'"
|
||||
}
|
||||
|
||||
testWanIsSupported() {
|
||||
assertTrue "wan is supported" "is_supported_interface 'wan'"
|
||||
}
|
||||
|
||||
testLanNotSupported() {
|
||||
assertFalse "lan not supported" "is_supported_interface 'lan'"
|
||||
}
|
||||
|
||||
testLoopbackNotSupported() {
|
||||
assertFalse "loopback not supported" "is_supported_interface 'loopback'"
|
||||
}
|
||||
|
||||
testWireguardSupported() {
|
||||
assertTrue "wg0 supported" "is_supported_interface 'wg0'"
|
||||
}
|
||||
|
||||
testExplicitlySupportedInterface() {
|
||||
supported_interface="custom_iface"
|
||||
assertTrue "Explicitly supported" "is_supported_interface 'custom_iface'"
|
||||
}
|
||||
|
||||
. shunit2
|
||||
@@ -0,0 +1,161 @@
|
||||
#!/bin/bash
|
||||
# Mock /lib/functions.sh for pbr tests
|
||||
# Implements OpenWrt UCI config shell API backed by UCI-format config files
|
||||
|
||||
# Config state
|
||||
_CONFIG_LOADED_PKG=""
|
||||
declare -gA _CONFIG_TYPES # section -> type
|
||||
declare -gA _CONFIG_OPTS # section.option -> value
|
||||
declare -gA _CONFIG_LISTS # section.option -> "val1 val2 ..."
|
||||
_CONFIG_SECTIONS=""
|
||||
|
||||
config_load() {
|
||||
local package="$1"
|
||||
local file="${UCI_CONFIG_DIR:-${IPKG_INSTROOT}/etc/config}/${package}"
|
||||
|
||||
# Reset state
|
||||
_CONFIG_LOADED_PKG="$package"
|
||||
_CONFIG_TYPES=()
|
||||
_CONFIG_OPTS=()
|
||||
_CONFIG_LISTS=()
|
||||
_CONFIG_SECTIONS=""
|
||||
|
||||
[ -f "$file" ] || return 1
|
||||
|
||||
local section="" anon_counter=0
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
# Strip leading whitespace
|
||||
line="${line#"${line%%[![:space:]]*}"}"
|
||||
# Skip comments and empty lines
|
||||
[[ "$line" == \#* || -z "$line" ]] && continue
|
||||
|
||||
if [[ "$line" =~ ^config[[:space:]]+([^[:space:]\'\"]+)[[:space:]]*([\'\"]([^\'\"]*)[\'\"])?(.*)$ ]]; then
|
||||
local type="${BASH_REMATCH[1]}"
|
||||
section="${BASH_REMATCH[3]}"
|
||||
[ -z "$section" ] && section="cfg${anon_counter}" && anon_counter=$((anon_counter + 1))
|
||||
_CONFIG_TYPES["$section"]="$type"
|
||||
_CONFIG_SECTIONS="${_CONFIG_SECTIONS:+$_CONFIG_SECTIONS }$section"
|
||||
elif [[ "$line" =~ ^option[[:space:]]+([^[:space:]]+)[[:space:]]+[\'\"]([^\'\"]*)[\'\"] ]]; then
|
||||
local key="${BASH_REMATCH[1]}"
|
||||
local val="${BASH_REMATCH[2]}"
|
||||
_CONFIG_OPTS["${section}.${key}"]="$val"
|
||||
elif [[ "$line" =~ ^option[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+) ]]; then
|
||||
local key="${BASH_REMATCH[1]}"
|
||||
local val="${BASH_REMATCH[2]}"
|
||||
val="${val//\'/}"
|
||||
val="${val//\"/}"
|
||||
_CONFIG_OPTS["${section}.${key}"]="$val"
|
||||
elif [[ "$line" =~ ^list[[:space:]]+([^[:space:]]+)[[:space:]]+[\'\"]([^\'\"]*)[\'\"] ]]; then
|
||||
local key="${BASH_REMATCH[1]}"
|
||||
local val="${BASH_REMATCH[2]}"
|
||||
if [ -n "${_CONFIG_LISTS["${section}.${key}"]:-}" ]; then
|
||||
_CONFIG_LISTS["${section}.${key}"]="${_CONFIG_LISTS["${section}.${key}"]} $val"
|
||||
else
|
||||
_CONFIG_LISTS["${section}.${key}"]="$val"
|
||||
fi
|
||||
elif [[ "$line" =~ ^list[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+) ]]; then
|
||||
local key="${BASH_REMATCH[1]}"
|
||||
local val="${BASH_REMATCH[2]}"
|
||||
val="${val//\'/}"
|
||||
val="${val//\"/}"
|
||||
if [ -n "${_CONFIG_LISTS["${section}.${key}"]:-}" ]; then
|
||||
_CONFIG_LISTS["${section}.${key}"]="${_CONFIG_LISTS["${section}.${key}"]} $val"
|
||||
else
|
||||
_CONFIG_LISTS["${section}.${key}"]="$val"
|
||||
fi
|
||||
fi
|
||||
done < "$file"
|
||||
}
|
||||
|
||||
config_get() {
|
||||
local var="$1" section="$2" option="$3" default="$4"
|
||||
local key="${section}.${option}"
|
||||
local val="${_CONFIG_OPTS[$key]:-${_CONFIG_LISTS[$key]:-}}"
|
||||
[ -z "$val" ] && val="$default"
|
||||
eval "$var=\"\$val\""
|
||||
}
|
||||
|
||||
config_get_bool() {
|
||||
local var="$1" section="$2" option="$3" default="${4:-0}"
|
||||
local key="${section}.${option}"
|
||||
local val="${_CONFIG_OPTS[$key]:-$default}"
|
||||
case "$val" in
|
||||
1|yes|on|true|enabled) val=1;;
|
||||
*) val=0;;
|
||||
esac
|
||||
eval "$var=$val"
|
||||
}
|
||||
|
||||
config_get_list() {
|
||||
config_get "$@"
|
||||
}
|
||||
|
||||
config_foreach() {
|
||||
local callback="$1" type="$2"
|
||||
local section
|
||||
for section in $_CONFIG_SECTIONS; do
|
||||
[ "${_CONFIG_TYPES[$section]:-}" = "$type" ] && "$callback" "$section"
|
||||
done
|
||||
}
|
||||
|
||||
config_list_foreach() {
|
||||
local section="$1" option="$2" callback="$3"
|
||||
local key="${section}.${option}"
|
||||
local val="${_CONFIG_LISTS[$key]:-}"
|
||||
local item
|
||||
for item in $val; do
|
||||
"$callback" "$item"
|
||||
done
|
||||
}
|
||||
|
||||
uci_get() {
|
||||
local package="${1:-}" section="${2:-}" option="${3:-}" default="${4:-}"
|
||||
[ -z "$package" ] || [ -z "$section" ] && return 1
|
||||
# Auto-load if different package
|
||||
if [ "$_CONFIG_LOADED_PKG" != "$package" ]; then
|
||||
config_load "$package"
|
||||
fi
|
||||
if [ -n "$option" ]; then
|
||||
local key="${section}.${option}"
|
||||
echo "${_CONFIG_OPTS[$key]:-${_CONFIG_LISTS[$key]:-$default}}"
|
||||
else
|
||||
# Check if section exists
|
||||
[ -n "${_CONFIG_TYPES[$section]:-}" ] && echo "$section"
|
||||
fi
|
||||
}
|
||||
|
||||
uci_add_list() {
|
||||
local package="$1" section="$2" option="$3" value="$4"
|
||||
local key="${section}.${option}"
|
||||
if [ -n "${_CONFIG_LISTS[$key]:-}" ]; then
|
||||
_CONFIG_LISTS[$key]="${_CONFIG_LISTS[$key]} $value"
|
||||
else
|
||||
_CONFIG_LISTS[$key]="$value"
|
||||
fi
|
||||
}
|
||||
|
||||
uci_remove() {
|
||||
local package="$1" section="$2" option="${3:-}"
|
||||
if [ -n "$option" ]; then
|
||||
unset "_CONFIG_OPTS[${section}.${option}]"
|
||||
unset "_CONFIG_LISTS[${section}.${option}]"
|
||||
fi
|
||||
}
|
||||
|
||||
uci_remove_list() {
|
||||
local package="$1" section="$2" option="$3" value="$4"
|
||||
local key="${section}.${option}"
|
||||
local old="${_CONFIG_LISTS[$key]:-}"
|
||||
local new="" item
|
||||
for item in $old; do
|
||||
[ "$item" != "$value" ] && new="${new:+$new }$item"
|
||||
done
|
||||
_CONFIG_LISTS[$key]="$new"
|
||||
}
|
||||
|
||||
uci_commit() { :; }
|
||||
|
||||
uci_set() {
|
||||
local package="$1" section="$2" option="$3" value="$4"
|
||||
_CONFIG_OPTS["${section}.${option}"]="$value"
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/bin/bash
|
||||
# Minimal mock /usr/share/libubox/jshn.sh for pbr tests
|
||||
# Implements enough of the jshn API to support the json() function and procd_open_data
|
||||
|
||||
# Internal state
|
||||
_JSON_PREFIX=""
|
||||
_JSON_DEPTH=0
|
||||
declare -gA _JSON_DATA
|
||||
_JSON_CUR_PATH=""
|
||||
_JSON_KEYS=""
|
||||
_JSON_NS=""
|
||||
|
||||
json_set_namespace() {
|
||||
_JSON_NS="${1:-}"
|
||||
}
|
||||
|
||||
json_init() {
|
||||
_JSON_DATA=()
|
||||
_JSON_DEPTH=0
|
||||
_JSON_CUR_PATH=""
|
||||
_JSON_KEYS=""
|
||||
}
|
||||
|
||||
json_add_string() {
|
||||
local key="$1" value="$2"
|
||||
_JSON_DATA["${_JSON_CUR_PATH}${key}"]="$value"
|
||||
}
|
||||
|
||||
json_add_boolean() {
|
||||
local key="$1" value="$2"
|
||||
[ "$value" = "1" ] && value="true" || value="false"
|
||||
_JSON_DATA["${_JSON_CUR_PATH}${key}"]="$value"
|
||||
}
|
||||
|
||||
json_add_int() {
|
||||
local key="$1" value="$2"
|
||||
_JSON_DATA["${_JSON_CUR_PATH}${key}"]="$value"
|
||||
}
|
||||
|
||||
json_add_object() {
|
||||
local key="${1:-}"
|
||||
if [ -n "$key" ]; then
|
||||
_JSON_CUR_PATH="${_JSON_CUR_PATH}${key}."
|
||||
fi
|
||||
_JSON_DEPTH=$((_JSON_DEPTH + 1))
|
||||
}
|
||||
|
||||
json_close_object() {
|
||||
_JSON_DEPTH=$((_JSON_DEPTH - 1))
|
||||
# Pop last path component
|
||||
if [ -n "$_JSON_CUR_PATH" ]; then
|
||||
_JSON_CUR_PATH="${_JSON_CUR_PATH%*.}"
|
||||
_JSON_CUR_PATH="${_JSON_CUR_PATH%.*}"
|
||||
[ -n "$_JSON_CUR_PATH" ] && _JSON_CUR_PATH="${_JSON_CUR_PATH}."
|
||||
fi
|
||||
}
|
||||
|
||||
json_add_array() {
|
||||
local key="${1:-}"
|
||||
if [ -n "$key" ]; then
|
||||
_JSON_CUR_PATH="${_JSON_CUR_PATH}${key}."
|
||||
_JSON_DATA["${_JSON_CUR_PATH}_type"]="array"
|
||||
fi
|
||||
_JSON_DEPTH=$((_JSON_DEPTH + 1))
|
||||
}
|
||||
|
||||
json_close_array() {
|
||||
json_close_object
|
||||
}
|
||||
|
||||
json_select() {
|
||||
local key="$1"
|
||||
if [ "$key" = ".." ]; then
|
||||
# Go up one level
|
||||
if [ -n "$_JSON_CUR_PATH" ]; then
|
||||
_JSON_CUR_PATH="${_JSON_CUR_PATH%*.}"
|
||||
_JSON_CUR_PATH="${_JSON_CUR_PATH%.*}"
|
||||
[ -n "$_JSON_CUR_PATH" ] && _JSON_CUR_PATH="${_JSON_CUR_PATH}."
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
# Check if key exists
|
||||
local prefix="${_JSON_CUR_PATH}${key}."
|
||||
local found=0
|
||||
for k in "${!_JSON_DATA[@]}"; do
|
||||
if [[ "$k" == "${prefix}"* ]] || [ -n "${_JSON_DATA["${_JSON_CUR_PATH}${key}"]:-}" ]; then
|
||||
found=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "$found" = "1" ]; then
|
||||
_JSON_CUR_PATH="$prefix"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
json_get_var() {
|
||||
local var="$1" key="$2"
|
||||
local val="${_JSON_DATA["${_JSON_CUR_PATH}${key}"]:-}"
|
||||
eval "$var=\"\$val\""
|
||||
}
|
||||
|
||||
json_get_keys() {
|
||||
local var="$1"
|
||||
local prefix="$_JSON_CUR_PATH"
|
||||
local keys="" k
|
||||
for k in "${!_JSON_DATA[@]}"; do
|
||||
if [[ "$k" == "${prefix}"* ]]; then
|
||||
local rest="${k#"$prefix"}"
|
||||
local first="${rest%%.*}"
|
||||
if [ -n "$first" ] && ! echo " $keys " | grep -q " $first "; then
|
||||
keys="${keys:+$keys }$first"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
eval "$var=\"\$keys\""
|
||||
}
|
||||
|
||||
json_dump() {
|
||||
# Simple JSON output - enough for testing
|
||||
echo "{}"
|
||||
}
|
||||
|
||||
json_load() {
|
||||
json_init
|
||||
}
|
||||
|
||||
json_load_file() {
|
||||
local file="$1"
|
||||
[ -f "$file" ] || return 1
|
||||
json_init
|
||||
return 0
|
||||
}
|
||||
|
||||
json_cleanup() {
|
||||
json_init
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
# Mock /lib/functions/network.sh for pbr tests
|
||||
# Provides configurable network state via MOCK_NET_* variables
|
||||
|
||||
# Default mock network data - tests can override these before calling setup
|
||||
: "${MOCK_NET_wan_device:=eth0}"
|
||||
: "${MOCK_NET_wan_gateway:=192.168.1.1}"
|
||||
: "${MOCK_NET_wan_proto:=dhcp}"
|
||||
: "${MOCK_NET_wan6_device:=eth0}"
|
||||
: "${MOCK_NET_wan6_gateway6:=fd00::1}"
|
||||
: "${MOCK_NET_wan6_proto:=dhcpv6}"
|
||||
: "${MOCK_NET_wg0_device:=wg0}"
|
||||
: "${MOCK_NET_wg0_proto:=wireguard}"
|
||||
: "${MOCK_NET_lan_device:=br-lan}"
|
||||
: "${MOCK_NET_lan_proto:=static}"
|
||||
: "${MOCK_NET_loopback_device:=lo}"
|
||||
: "${MOCK_NET_loopback_proto:=static}"
|
||||
|
||||
_net_get_var() {
|
||||
local var="$1" iface="$2" field="$3"
|
||||
local iface_safe="${iface//-/_}"
|
||||
local val=""
|
||||
eval "val=\"\${MOCK_NET_${iface_safe}_${field}:-}\""
|
||||
eval "$var=\"\$val\""
|
||||
}
|
||||
|
||||
network_get_device() {
|
||||
_net_get_var "$1" "$2" "device"
|
||||
}
|
||||
|
||||
network_get_physdev() {
|
||||
_net_get_var "$1" "$2" "device"
|
||||
}
|
||||
|
||||
network_get_gateway() {
|
||||
local var="$1" iface="$2"
|
||||
_net_get_var "$var" "$iface" "gateway"
|
||||
}
|
||||
|
||||
network_get_gateway6() {
|
||||
local var="$1" iface="$2"
|
||||
_net_get_var "$var" "$iface" "gateway6"
|
||||
}
|
||||
|
||||
network_get_protocol() {
|
||||
_net_get_var "$1" "$2" "proto"
|
||||
}
|
||||
|
||||
network_get_ipaddr() {
|
||||
_net_get_var "$1" "$2" "ipaddr"
|
||||
}
|
||||
|
||||
network_get_ip6addr() {
|
||||
_net_get_var "$1" "$2" "ip6addr"
|
||||
}
|
||||
|
||||
network_flush_cache() { :; }
|
||||
|
||||
network_get_dnsserver() {
|
||||
_net_get_var "$1" "$2" "dns"
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# Common test setup for pbr shell tests (shunit2-based)
|
||||
# Source this at the top of each test file before defining test functions.
|
||||
# Each test file should end with: . shunit2
|
||||
|
||||
TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PKG_DIR="$(cd "$TESTS_DIR/.." && pwd)"
|
||||
|
||||
# Create mock sysroot
|
||||
MOCK_ROOT="$(mktemp -d)"
|
||||
export IPKG_INSTROOT="$MOCK_ROOT"
|
||||
|
||||
# Install mock libraries into sysroot
|
||||
mkdir -p "$MOCK_ROOT/lib/functions"
|
||||
mkdir -p "$MOCK_ROOT/usr/share/libubox"
|
||||
cp "$TESTS_DIR/lib/mocks/functions.sh" "$MOCK_ROOT/lib/functions.sh"
|
||||
cp "$TESTS_DIR/lib/mocks/network.sh" "$MOCK_ROOT/lib/functions/network.sh"
|
||||
cp "$TESTS_DIR/lib/mocks/jshn.sh" "$MOCK_ROOT/usr/share/libubox/jshn.sh"
|
||||
|
||||
# Install mock config files
|
||||
mkdir -p "$MOCK_ROOT/etc/config"
|
||||
if [ -d "$TESTS_DIR/mocks/etc/config" ]; then
|
||||
cp "$TESTS_DIR/mocks/etc/config/"* "$MOCK_ROOT/etc/config/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Install mock binaries and add to PATH
|
||||
mkdir -p "$MOCK_ROOT/bin"
|
||||
if [ -d "$TESTS_DIR/mocks/bin" ]; then
|
||||
cp "$TESTS_DIR/mocks/bin/"* "$MOCK_ROOT/bin/" 2>/dev/null || true
|
||||
chmod +x "$MOCK_ROOT/bin/"*
|
||||
fi
|
||||
export PATH="$MOCK_ROOT/bin:$PATH"
|
||||
|
||||
# Create required directories
|
||||
mkdir -p "$MOCK_ROOT/var/run"
|
||||
mkdir -p "$MOCK_ROOT/dev/shm"
|
||||
mkdir -p "$MOCK_ROOT/usr/share/nftables.d/ruleset-post"
|
||||
mkdir -p "$MOCK_ROOT/etc/iproute2"
|
||||
cat > "$MOCK_ROOT/etc/iproute2/rt_tables" <<'RT'
|
||||
255 local
|
||||
254 main
|
||||
253 default
|
||||
0 unspec
|
||||
RT
|
||||
|
||||
# Stub out OpenWrt rc.common / procd functions
|
||||
extra_command() { :; }
|
||||
rc_procd() { "$@"; }
|
||||
service_started() { :; }
|
||||
procd_open_instance() { :; }
|
||||
procd_set_param() { :; }
|
||||
procd_close_instance() { :; }
|
||||
procd_open_data() { :; }
|
||||
procd_close_data() { :; }
|
||||
procd_add_reload_trigger() { :; }
|
||||
procd_add_interface_trigger() { :; }
|
||||
procd_open_trigger() { :; }
|
||||
procd_close_trigger() { :; }
|
||||
|
||||
# Stub external commands
|
||||
logger() { :; }
|
||||
resolveip() { echo "127.0.0.1"; }
|
||||
jsonfilter() { echo ""; }
|
||||
pidof() { return 1; }
|
||||
sync() { :; }
|
||||
|
||||
# Prepare a test-friendly copy of the pbr script:
|
||||
# 1. Strip 'readonly' keyword to avoid collision with shunit2 internals
|
||||
# (pbr defines readonly _FAIL_, _OK_ etc. that clash with shunit2)
|
||||
# 2. Redirect file paths to temp directories we control
|
||||
_PBR_TEST_SCRIPT="$MOCK_ROOT/pbr_test.sh"
|
||||
sed 's/^readonly //' "$PKG_DIR/files/etc/init.d/pbr" > "$_PBR_TEST_SCRIPT"
|
||||
|
||||
# Source the modified pbr script
|
||||
. "$_PBR_TEST_SCRIPT"
|
||||
|
||||
# Override file paths to use test-friendly temp locations
|
||||
nftTempFile="$MOCK_ROOT/var/run/pbr.nft"
|
||||
nftMainFile="$MOCK_ROOT/usr/share/nftables.d/ruleset-post/30-pbr.nft"
|
||||
nftNetifdFile="$MOCK_ROOT/usr/share/nftables.d/ruleset-post/20-pbr-netifd.nft"
|
||||
rtTablesFile="$MOCK_ROOT/etc/iproute2/rt_tables"
|
||||
runningStatusFile="$MOCK_ROOT/dev/shm/pbr.status.json"
|
||||
packageLockFile="$MOCK_ROOT/var/run/pbr.lock"
|
||||
packageDnsmasqFile="$MOCK_ROOT/var/run/pbr.dnsmasq"
|
||||
packageDebugFile="$MOCK_ROOT/var/run/pbr.debug"
|
||||
packageConfigFile="$MOCK_ROOT/etc/config/pbr"
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# Mock dnsmasq for pbr tests
|
||||
case "$1" in
|
||||
--version)
|
||||
echo "Dnsmasq version 2.90"
|
||||
echo "Compile time options: IPv6 GNU-getopt DBus no-UBus no-i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset nftset auth cryptohash DNSSEC loop-detect inotify dumpfile"
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Mock nft binary for pbr tests
|
||||
case "$1" in
|
||||
list)
|
||||
case "$*" in
|
||||
"list table inet fw4"|"list table inet fw4 2>&1")
|
||||
cat <<'EOF'
|
||||
table inet fw4 {
|
||||
chain input { }
|
||||
chain forward { }
|
||||
chain output { }
|
||||
chain dstnat { }
|
||||
chain mangle_prerouting { }
|
||||
chain mangle_output { }
|
||||
chain mangle_forward { }
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
echo "table inet fw4 {}"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
-c)
|
||||
# Syntax check - always succeed
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# Mock readlink for pbr tests
|
||||
# Returns /usr/libexec/ip-full for /sbin/ip to pass the ip-full check
|
||||
case "$*" in
|
||||
*/sbin/ip)
|
||||
echo "/usr/libexec/ip-full"
|
||||
;;
|
||||
*)
|
||||
command readlink "$@" 2>/dev/null || echo "$1"
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,9 @@
|
||||
config dnsmasq 'cfg01411c'
|
||||
option domainneeded '1'
|
||||
|
||||
config dhcp 'lan'
|
||||
option interface 'lan'
|
||||
option start '100'
|
||||
option limit '150'
|
||||
option leasetime '12h'
|
||||
option force '1'
|
||||
@@ -0,0 +1,20 @@
|
||||
config defaults 'defaults'
|
||||
option input 'REJECT'
|
||||
option output 'ACCEPT'
|
||||
option forward 'REJECT'
|
||||
|
||||
config zone 'lan_zone'
|
||||
option name 'lan'
|
||||
list network 'lan'
|
||||
option input 'ACCEPT'
|
||||
option output 'ACCEPT'
|
||||
option forward 'ACCEPT'
|
||||
|
||||
config zone 'wan_zone'
|
||||
option name 'wan'
|
||||
list network 'wan'
|
||||
list network 'wan6'
|
||||
list network 'wg0'
|
||||
option input 'REJECT'
|
||||
option output 'ACCEPT'
|
||||
option forward 'REJECT'
|
||||
@@ -0,0 +1,21 @@
|
||||
config interface 'loopback'
|
||||
option device 'lo'
|
||||
option proto 'static'
|
||||
option ipaddr '127.0.0.1'
|
||||
|
||||
config interface 'lan'
|
||||
option device 'br-lan'
|
||||
option proto 'static'
|
||||
option ipaddr '192.168.1.1'
|
||||
|
||||
config interface 'wan'
|
||||
option device 'eth0'
|
||||
option proto 'dhcp'
|
||||
|
||||
config interface 'wan6'
|
||||
option device 'eth0'
|
||||
option proto 'dhcpv6'
|
||||
|
||||
config interface 'wg0'
|
||||
option proto 'wireguard'
|
||||
option device 'wg0'
|
||||
@@ -0,0 +1,52 @@
|
||||
config pbr 'config'
|
||||
option enabled '1'
|
||||
option verbosity '2'
|
||||
option strict_enforcement '1'
|
||||
option ipv6_enabled '0'
|
||||
option fw_mask '00ff0000'
|
||||
option resolver_set 'none'
|
||||
option uplink_interface 'wan'
|
||||
option uplink_interface6 'wan6'
|
||||
option uplink_mark '00010000'
|
||||
option uplink_ip_rules_priority '30000'
|
||||
list ignored_interface 'loopback'
|
||||
list lan_device 'br-lan'
|
||||
option procd_boot_trigger_delay '5000'
|
||||
option procd_reload_delay '0'
|
||||
option nft_set_policy 'performance'
|
||||
option nft_set_auto_merge '1'
|
||||
option nft_set_flags_interval '1'
|
||||
option nft_set_flags_timeout '0'
|
||||
option nft_rule_counter '0'
|
||||
option nft_set_counter '0'
|
||||
option nft_user_set_counter '0'
|
||||
option prefixlength '1'
|
||||
list resolver_instance '*'
|
||||
option webui_show_ignore_target '0'
|
||||
|
||||
config policy 'vpn_all'
|
||||
option name 'VPN All Traffic'
|
||||
option interface 'wg0'
|
||||
option src_addr '192.168.1.0/24'
|
||||
option dest_addr ''
|
||||
option enabled '1'
|
||||
|
||||
config policy 'vpn_gaming'
|
||||
option name 'VPN Gaming'
|
||||
option interface 'wg0'
|
||||
option src_addr ''
|
||||
option dest_addr '10.0.0.0/8'
|
||||
option src_port '27015-27030'
|
||||
option enabled '1'
|
||||
|
||||
config policy 'disabled_policy'
|
||||
option name 'Disabled Policy'
|
||||
option interface 'wan'
|
||||
option src_addr '10.10.10.0/24'
|
||||
option enabled '0'
|
||||
|
||||
config dns_policy 'dns_vpn'
|
||||
option name 'DNS via VPN'
|
||||
option interface 'wg0'
|
||||
option src_addr '192.168.1.100'
|
||||
option enabled '1'
|
||||
@@ -0,0 +1,3 @@
|
||||
config system
|
||||
option hostname 'OpenWrt'
|
||||
option timezone 'UTC'
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
# Test runner for pbr shell tests (shunit2-based)
|
||||
# Usage: bash tests/run_tests.sh [test_pattern]
|
||||
set -uo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
TESTS_DIR="$(pwd)/tests"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
TOTAL=0
|
||||
FAILED_TESTS=""
|
||||
|
||||
# Check shunit2 availability
|
||||
if ! command -v shunit2 >/dev/null 2>&1 && [ ! -f /usr/bin/shunit2 ]; then
|
||||
echo "ERROR: shunit2 not found. Install with: apt-get install shunit2" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pattern="${1:-}"
|
||||
|
||||
for test_dir in "$TESTS_DIR"/[0-9]*/; do
|
||||
[ -d "$test_dir" ] || continue
|
||||
for test_script in "$test_dir"[0-9]*; do
|
||||
[ -f "$test_script" ] || continue
|
||||
test_name="${test_dir##*tests/}${test_script##*/}"
|
||||
# Filter by pattern if provided
|
||||
if [ -n "$pattern" ] && ! echo "$test_name" | grep -q "$pattern"; then
|
||||
continue
|
||||
fi
|
||||
TOTAL=$((TOTAL + 1))
|
||||
output_file="$(mktemp)"
|
||||
if bash "$test_script" >"$output_file" 2>&1; then
|
||||
printf '\033[0;32mPASS\033[0m: %s\n' "$test_name"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
printf '\033[0;31mFAIL\033[0m: %s\n' "$test_name"
|
||||
cat "$output_file" | sed 's/^/ /'
|
||||
FAIL=$((FAIL + 1))
|
||||
FAILED_TESTS="${FAILED_TESTS:+$FAILED_TESTS\n} $test_name"
|
||||
fi
|
||||
rm -f "$output_file"
|
||||
done
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS/$TOTAL passed, $FAIL failed"
|
||||
if [ -n "$FAILED_TESTS" ]; then
|
||||
echo ""
|
||||
echo "Failed tests:"
|
||||
printf "%b\n" "$FAILED_TESTS"
|
||||
fi
|
||||
[ "$FAIL" -eq 0 ]
|
||||
Reference in New Issue
Block a user