From 29fba840201287b9265888adba6298779b750af5 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Wed, 31 Mar 2021 22:09:01 +0200 Subject: [PATCH] tests: expand testing - Rewrite test framework - Add initial rule test coverage Signed-off-by: Jo-Philipp Wich --- run_tests.sh | 45 ++- tests/01_configuration/01_ruleset | 39 +-- tests/03_rules/01_direction | 148 +++++++++ tests/03_rules/02_enabled | 143 +++++++++ tests/03_rules/03_constraints | 207 +++++++++++++ tests/03_rules/04_icmp | 155 ++++++++++ tests/lib/mocklib.uc | 224 ++++++++++++++ tests/lib/mocklib/fs.uc | 140 +++++++++ tests/lib/mocklib/ubus.uc | 69 +++++ tests/lib/mocklib/uci.uc | 148 +++++++++ tests/mock.uc | 479 ------------------------------ tests/mocks/uci/firewall.json | 298 +++++++------------ tests/mocks/uci/helpers.json | 242 ++++++--------- tests/test-wrapper.uc | 5 - 14 files changed, 1495 insertions(+), 847 deletions(-) create mode 100644 tests/03_rules/01_direction create mode 100644 tests/03_rules/02_enabled create mode 100644 tests/03_rules/03_constraints create mode 100644 tests/03_rules/04_icmp create mode 100644 tests/lib/mocklib.uc create mode 100644 tests/lib/mocklib/fs.uc create mode 100644 tests/lib/mocklib/ubus.uc create mode 100644 tests/lib/mocklib/uci.uc delete mode 100644 tests/mock.uc delete mode 100644 tests/test-wrapper.uc diff --git a/run_tests.sh b/run_tests.sh index eb4dafa..8f0c293 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,7 +1,14 @@ #!/usr/bin/env bash line='........................................' -uenv='{ "REQUIRE_SEARCH_PATH": [ "/usr/local/lib/ucode/*.so", "/usr/lib/ucode/*.so", "./tests/*.uc", "./root/usr/share/ucode/*.uc" ] }' +uenv='{ + "REQUIRE_SEARCH_PATH": [ + "./tests/lib/*.uc", + "./root/usr/share/ucode/*.uc", + "/usr/local/lib/ucode/*.so", + "/usr/lib/ucode/*.so" + ] +}' extract_sections() { local file=$1 @@ -30,6 +37,14 @@ extract_sections() { outfile=$(printf "%s/%03d.%s" "$dir" $count "$tag") printf "" > "$outfile" ;; + "-- File "*" --") + tag="file" + outfile="${line#-- File }" + outfile="$(echo "${outfile% --}" | xargs)" + outfile="$dir/files$(readlink -m "/${outfile:-file}")" + mkdir -p "$(dirname "$outfile")" + printf "" > "$outfile" + ;; "-- End --") tag="" outfile="" @@ -55,7 +70,12 @@ run_testcase() { local code=$7 local fail=0 - ucode ${uenv:+-e "$uenv"} ${env:+-e "$(cat "$env")"} -i - <"$in" >"$dir/res.out" 2>"$dir/res.err" + ucode -m mocklib -m fw4 ${uenv:+-e "$uenv"} -e '{ + "MOCK_SEARCH_PATH": [ + "'"$dir"'/files", + "./tests/mocks" + ] + }' ${env:+-e "$(cat "$env")"} -i - <"$in" >"$dir/res.out" 2>"$dir/res.err" printf "%d\n" $? > "$dir/res.code" @@ -157,13 +177,31 @@ run_test() { n_tests=0 n_fails=0 +select_tests="$@" + +use_test() { + local input="$(readlink -f "$1")" + local test + + [ -f "$input" ] || return 1 + [ -n "$select_tests" ] || return 0 + + for test in "$select_tests"; do + test="$(readlink -f "$test")" + + [ "$test" != "$input" ] || return 0 + done + + return 1 +} + for catdir in tests/[0-9][0-9]_*; do [ -d "$catdir" ] || continue printf "\n##\n## Running %s tests\n##\n\n" "${catdir##*/[0-9][0-9]_}" for testfile in "$catdir/"[0-9][0-9]_*; do - [ -f "$testfile" ] || continue + use_test "$testfile" || continue n_tests=$((n_tests + 1)) run_test "$testfile" || n_fails=$((n_fails + 1)) @@ -171,3 +209,4 @@ for catdir in tests/[0-9][0-9]_*; do done printf "\nRan %d tests, %d okay, %d failures\n" $n_tests $((n_tests - n_fails)) $n_fails +exit $n_fails diff --git a/tests/01_configuration/01_ruleset b/tests/01_configuration/01_ruleset index 5725ebf..313b46a 100644 --- a/tests/01_configuration/01_ruleset +++ b/tests/01_configuration/01_ruleset @@ -2,8 +2,7 @@ Testing the ruleset rendered from the default firewall configuration. -- Testcase -- {% - include("./tests/mock.uc", { - TESTFILE: "test-wrapper.uc", + include("./root/usr/share/firewall4/main.uc", { TRACE_CALLS: "stderr", getenv: function(varname) { @@ -32,11 +31,9 @@ table inet fw4 { define lan_devices = { "br-lan" } define lan_subnets = { 192.168.26.0/24, fd63:e2f:f706::/60 } - define wan_devices = { "wan" } define wan_subnets = { 10.11.12.0/24 } - # # User includes # @@ -54,26 +51,17 @@ table inet fw4 { iifname "lo" accept comment "!fw4: Accept traffic from loopback" ct state established,related accept comment "!fw4: Allow inbound established and related flows" - - tcp flags & (fin | syn | rst | ack) == syn jump syn_flood comment "!fw4: Rate limit TCP syn packets" - - iifname "br-lan" jump input_lan comment "!fw4: Handle lan IPv4/IPv6 input traffic" iifname "wan" jump input_wan comment "!fw4: Handle wan IPv4/IPv6 input traffic" - } chain forward { type filter hook forward priority filter; policy drop; ct state established,related accept comment "!fw4: Allow forwarded established and related flows" - - - iifname "br-lan" jump forward_lan comment "!fw4: Handle lan IPv4/IPv6 forward traffic" iifname "wan" jump forward_wan comment "!fw4: Handle wan IPv4/IPv6 forward traffic" - jump handle_reject } @@ -83,12 +71,8 @@ table inet fw4 { oifname "lo" accept comment "!fw4: Accept traffic towards loopback" ct state established,related accept comment "!fw4: Allow outbound established and related flows" - - - oifname "br-lan" jump output_lan comment "!fw4: Handle lan IPv4/IPv6 output traffic" oifname "wan" jump output_wan comment "!fw4: Handle wan IPv4/IPv6 output traffic" - } chain handle_reject { @@ -101,7 +85,6 @@ table inet fw4 { drop comment "!fw4: Drop excess packets" } - chain input_lan { jump accept_from_lan } @@ -125,11 +108,12 @@ table inet fw4 { chain input_wan { meta nfproto ipv4 udp dport 68 counter accept comment "!fw4: Allow-DHCP-Renew" - meta nfproto ipv4 meta l4proto icmp counter accept comment "!fw4: Allow-Ping" + meta nfproto ipv4 icmp type 8 counter accept comment "!fw4: Allow-Ping" meta nfproto ipv4 meta l4proto igmp counter accept comment "!fw4: Allow-IGMP" ip6 saddr fc00::/6 ip6 daddr fc00::/6 udp dport 546 counter accept comment "!fw4: Allow-DHCPv6" - meta l4proto ipv6-icmp ip6 saddr fe80::/10 counter accept comment "!fw4: Allow-MLD" - meta nfproto ipv6 meta l4proto ipv6-icmp limit rate 1000/second counter accept comment "!fw4: Allow-ICMPv6-Input" + ip6 saddr fe80::/10 icmpv6 type . icmpv6 code { 130 . 0, 131 . 0, 132 . 0, 143 . 0 } counter accept comment "!fw4: Allow-MLD" + meta nfproto ipv6 icmpv6 type { 128, 129, 1, 3, 133, 134 } limit rate 1000/second counter accept comment "!fw4: Allow-ICMPv6-Input" + meta nfproto ipv6 icmpv6 type . icmpv6 code { 2 . 0, 4 . 0, 4 . 1, 135 . 0, 136 . 0 } limit rate 1000/second counter accept comment "!fw4: Allow-ICMPv6-Input" jump reject_from_wan } @@ -138,7 +122,8 @@ table inet fw4 { } chain forward_wan { - meta nfproto ipv6 meta l4proto ipv6-icmp limit rate 1000/second counter accept comment "!fw4: Allow-ICMPv6-Forward" + meta nfproto ipv6 icmpv6 type { 128, 129, 1, 3 } limit rate 1000/second counter accept comment "!fw4: Allow-ICMPv6-Forward" + meta nfproto ipv6 icmpv6 type . icmpv6 code { 2 . 0, 4 . 0, 4 . 1 } limit rate 1000/second counter accept comment "!fw4: Allow-ICMPv6-Forward" meta l4proto esp counter jump accept_to_lan comment "!fw4: Allow-IPSec-ESP" udp dport 500 counter jump accept_to_lan comment "!fw4: Allow-ISAKMP" jump reject_to_wan @@ -157,19 +142,16 @@ table inet fw4 { } - # # NAT rules # chain dstnat { type nat hook prerouting priority dstnat; policy accept; - } chain srcnat { type nat hook postrouting priority srcnat; policy accept; - oifname "wan" jump srcnat_wan comment "!fw4: Handle wan IPv4/IPv6 srcnat traffic" } @@ -184,13 +166,11 @@ table inet fw4 { chain raw_prerouting { type filter hook prerouting priority raw; policy accept; - iifname "br-lan" jump helper_lan comment "!fw4: lan IPv4/IPv6 CT helper assignment" } chain raw_output { type filter hook output priority raw; policy accept; - } ct helper amanda { @@ -241,7 +221,6 @@ table inet fw4 { type "rtsp" protocol tcp; } - chain helper_lan { meta l4proto udp udp dport 10080 ct helper set "amanda" comment "!fw4: Amanda backup and archiving proto" meta l4proto tcp tcp dport 21 ct helper set "ftp" comment "!fw4: FTP passive connection tracking" @@ -258,24 +237,20 @@ table inet fw4 { } - # # Mangle rules # chain mangle_prerouting { type filter hook prerouting priority mangle; policy accept; - } chain mangle_output { type filter hook output priority mangle; policy accept; - } chain mangle_forward { type filter hook forward priority mangle; policy accept; - iifname "wan" tcp flags syn tcp option maxseg size set rt mtu comment "!fw4: Zone wan IPv4/IPv6 ingress MTU fixing" oifname "wan" tcp flags syn tcp option maxseg size set rt mtu comment "!fw4: Zone wan IPv4/IPv6 egress MTU fixing" } diff --git a/tests/03_rules/01_direction b/tests/03_rules/01_direction new file mode 100644 index 0000000..b2079c7 --- /dev/null +++ b/tests/03_rules/01_direction @@ -0,0 +1,148 @@ +Testing that rule declarations are mapped to the proper chains depending +on src and dest options. + +-- Testcase -- +{% + include("./root/usr/share/firewall4/main.uc", { + getenv: function(varname) { + switch (varname) { + case 'ACTION': + return 'print'; + } + } + }) +%} +-- End -- + +-- File uci/helpers.json -- +{} +-- End -- + +-- File uci/firewall.json -- +{ + "rule": [ + { + ".description": "Neither source, nor dest => should result in an output rule", + "proto": "any" + }, + { + ".description": "Source any, no dest => should result in an input rule", + "proto": "any", + "src": "*" + }, + { + ".description": "Dest any, no source => should result in an output rule", + "proto": "any", + "dest": "*" + }, + { + ".description": "Source any, dest any => should result in a forward rule", + "proto": "any", + "src": "*", + "dest": "*" + } + ] +} +-- End -- + +-- Expect stdout -- +table inet fw4 +flush table inet fw4 + +table inet fw4 { + # + # Set definitions + # + + + # + # Defines + # + + + # + # User includes + # + + include "/etc/nftables.d/*.nft" + + + # + # Filter rules + # + + chain input { + type filter hook input priority filter; policy drop; + + iifname "lo" accept comment "!fw4: Accept traffic from loopback" + + ct state established,related accept comment "!fw4: Allow inbound established and related flows" + counter comment "!fw4: @rule[1]" + } + + chain forward { + type filter hook forward priority filter; policy drop; + + ct state established,related accept comment "!fw4: Allow forwarded established and related flows" + counter comment "!fw4: @rule[3]" + } + + chain output { + type filter hook output priority filter; policy drop; + + oifname "lo" accept comment "!fw4: Accept traffic towards loopback" + + ct state established,related accept comment "!fw4: Allow outbound established and related flows" + counter comment "!fw4: @rule[0]" + counter comment "!fw4: @rule[2]" + } + + chain handle_reject { + meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" + reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" + } + + + # + # NAT rules + # + + chain dstnat { + type nat hook prerouting priority dstnat; policy accept; + } + + chain srcnat { + type nat hook postrouting priority srcnat; policy accept; + } + + + # + # Raw rules (notrack & helper) + # + + chain raw_prerouting { + type filter hook prerouting priority raw; policy accept; + } + + chain raw_output { + type filter hook output priority raw; policy accept; + } + + + # + # Mangle rules + # + + chain mangle_prerouting { + type filter hook prerouting priority mangle; policy accept; + } + + chain mangle_output { + type filter hook output priority mangle; policy accept; + } + + chain mangle_forward { + type filter hook forward priority mangle; policy accept; + } +} +-- End -- diff --git a/tests/03_rules/02_enabled b/tests/03_rules/02_enabled new file mode 100644 index 0000000..247f236 --- /dev/null +++ b/tests/03_rules/02_enabled @@ -0,0 +1,143 @@ +Testing that not enabled rules are ignored. + +-- Testcase -- +{% + include("./root/usr/share/firewall4/main.uc", { + getenv: function(varname) { + switch (varname) { + case 'ACTION': + return 'print'; + } + } + }) +%} +-- End -- + +-- File uci/helpers.json -- +{} +-- End -- + +-- File uci/firewall.json -- +{ + "rule": [ + { + "proto": "any", + "name": "Implicitly enabled" + }, + { + "proto": "any", + "name": "Explicitly enabled", + "enabled": "1" + }, + { + "proto": "any", + "name": "Explicitly disabled", + "enabled": "0" + } + ] +} +-- End -- + +-- Expect stderr -- +[!] Section @rule[2] (Explicitly disabled) is disabled, ignoring section +-- End -- + +-- Expect stdout -- +table inet fw4 +flush table inet fw4 + +table inet fw4 { + # + # Set definitions + # + + + # + # Defines + # + + + # + # User includes + # + + include "/etc/nftables.d/*.nft" + + + # + # Filter rules + # + + chain input { + type filter hook input priority filter; policy drop; + + iifname "lo" accept comment "!fw4: Accept traffic from loopback" + + ct state established,related accept comment "!fw4: Allow inbound established and related flows" + } + + chain forward { + type filter hook forward priority filter; policy drop; + + ct state established,related accept comment "!fw4: Allow forwarded established and related flows" + } + + chain output { + type filter hook output priority filter; policy drop; + + oifname "lo" accept comment "!fw4: Accept traffic towards loopback" + + ct state established,related accept comment "!fw4: Allow outbound established and related flows" + counter comment "!fw4: Implicitly enabled" + counter comment "!fw4: Explicitly enabled" + } + + chain handle_reject { + meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" + reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" + } + + + # + # NAT rules + # + + chain dstnat { + type nat hook prerouting priority dstnat; policy accept; + } + + chain srcnat { + type nat hook postrouting priority srcnat; policy accept; + } + + + # + # Raw rules (notrack & helper) + # + + chain raw_prerouting { + type filter hook prerouting priority raw; policy accept; + } + + chain raw_output { + type filter hook output priority raw; policy accept; + } + + + # + # Mangle rules + # + + chain mangle_prerouting { + type filter hook prerouting priority mangle; policy accept; + } + + chain mangle_output { + type filter hook output priority mangle; policy accept; + } + + chain mangle_forward { + type filter hook forward priority mangle; policy accept; + } +} +-- End -- diff --git a/tests/03_rules/03_constraints b/tests/03_rules/03_constraints new file mode 100644 index 0000000..8b1e04b --- /dev/null +++ b/tests/03_rules/03_constraints @@ -0,0 +1,207 @@ +Testing various option constraints. + +-- Testcase -- +{% + include("./root/usr/share/firewall4/main.uc", { + getenv: function(varname) { + switch (varname) { + case 'ACTION': + return 'print'; + } + } + }) +%} +-- End -- + +-- File uci/helpers.json -- +{} +-- End -- + +-- File uci/firewall.json -- +{ + "zone": [ + { + "name": "lan" + } + ], + "rule": [ + { + ".description": "Helper rules require an explicit source zone", + "proto": "any", + "name": "Helper rule #1", + "target": "helper" + }, + { + ".description": "Helper rules require a set_helper option", + "proto": "any", + "name": "Helper rule #2", + "src": "lan", + "target": "helper" + }, + + { + ".description": "Notrack rules require an explicit source zone", + "proto": "any", + "name": "Notrack rule", + "target": "notrack" + }, + + { + ".description": "DSCP rules must not specify a destination", + "proto": "any", + "name": "DSCP rule #1", + "dest": "*", + "target": "dscp" + }, + { + ".description": "DSCP rules require a set_dscp option", + "proto": "any", + "name": "DSCP rule #2", + "target": "dscp" + }, + + { + ".description": "Mark rules must not specify a destination", + "proto": "any", + "name": "Mark rule #1", + "dest": "*", + "target": "mark" + }, + { + ".description": "Mark rules require a set_xmark or set_mark option", + "proto": "any", + "name": "Mark rule #2", + "target": "mark" + }, + ] +} +-- End -- + +-- Expect stderr -- +[!] Section @rule[0] (Helper rule #1) must specify a source zone for target 'helper' +[!] Section @rule[1] (Helper rule #2) must specify option 'set_helper' for target 'helper' +[!] Section @rule[2] (Notrack rule) must specify a source zone for target 'notrack' +[!] Section @rule[3] (DSCP rule #1) must not specify option 'dest' for target 'dscp' +[!] Section @rule[4] (DSCP rule #2) must specify option 'set_dscp' for target 'dscp' +[!] Section @rule[5] (Mark rule #1) must not specify option 'dest' for target 'mark' +[!] Section @rule[6] (Mark rule #2) must specify option 'set_mark' or 'set_xmark' for target 'mark' +-- End -- + +-- Expect stdout -- +table inet fw4 +flush table inet fw4 + +table inet fw4 { + # + # Set definitions + # + + + # + # Defines + # + + + # + # User includes + # + + include "/etc/nftables.d/*.nft" + + + # + # Filter rules + # + + chain input { + type filter hook input priority filter; policy drop; + + iifname "lo" accept comment "!fw4: Accept traffic from loopback" + + ct state established,related accept comment "!fw4: Allow inbound established and related flows" + } + + chain forward { + type filter hook forward priority filter; policy drop; + + ct state established,related accept comment "!fw4: Allow forwarded established and related flows" + } + + chain output { + type filter hook output priority filter; policy drop; + + oifname "lo" accept comment "!fw4: Accept traffic towards loopback" + + ct state established,related accept comment "!fw4: Allow outbound established and related flows" + } + + chain handle_reject { + meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" + reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" + } + + chain input_lan { + jump drop_from_lan + } + + chain output_lan { + jump drop_to_lan + } + + chain forward_lan { + jump drop_to_lan + } + + chain drop_from_lan { + } + + chain drop_to_lan { + } + + + # + # NAT rules + # + + chain dstnat { + type nat hook prerouting priority dstnat; policy accept; + } + + chain srcnat { + type nat hook postrouting priority srcnat; policy accept; + } + + + # + # Raw rules (notrack & helper) + # + + chain raw_prerouting { + type filter hook prerouting priority raw; policy accept; + } + + chain raw_output { + type filter hook output priority raw; policy accept; + } + + chain helper_lan { + } + + + # + # Mangle rules + # + + chain mangle_prerouting { + type filter hook prerouting priority mangle; policy accept; + } + + chain mangle_output { + type filter hook output priority mangle; policy accept; + } + + chain mangle_forward { + type filter hook forward priority mangle; policy accept; + } +} +-- End -- diff --git a/tests/03_rules/04_icmp b/tests/03_rules/04_icmp new file mode 100644 index 0000000..53cee11 --- /dev/null +++ b/tests/03_rules/04_icmp @@ -0,0 +1,155 @@ +Testing handling of ICMP related options. + +-- Testcase -- +{% + include("./root/usr/share/firewall4/main.uc", { + getenv: function(varname) { + switch (varname) { + case 'ACTION': + return 'print'; + } + } + }) +%} +-- End -- + +-- File uci/helpers.json -- +{} +-- End -- + +-- File uci/firewall.json -- +{ + "rule": [ + { + ".description": "Proto 'icmp' maps to IPv4 and IPv6 rules", + "proto": "icmp", + "name": "ICMP rule #1" + }, + { + ".description": "Proto 'icmpv6' maps to IPv6 rule only", + "proto": "icmpv6", + "name": "ICMP rule #2", + }, + { + ".description": "Proto 'ipv6-icmp' is an alias for 'icmpv6'", + "proto": "ipv6-icmp", + "name": "ICMP rule #3", + }, + { + ".description": "Proto 'icmp' with IPv4 specific types inhibits IPv6 rule", + "proto": "icmp", + "name": "ICMP rule #4", + "icmp_type": [ "ip-header-bad" ] + }, + { + ".description": "Proto 'icmp' with IPv6 specific types inhibits IPv4 rule", + "proto": "icmp", + "name": "ICMP rule #5", + "icmp_type": [ "neighbour-advertisement" ] + } + ] +} +-- End -- + +-- Expect stdout -- +table inet fw4 +flush table inet fw4 + +table inet fw4 { + # + # Set definitions + # + + + # + # Defines + # + + + # + # User includes + # + + include "/etc/nftables.d/*.nft" + + + # + # Filter rules + # + + chain input { + type filter hook input priority filter; policy drop; + + iifname "lo" accept comment "!fw4: Accept traffic from loopback" + + ct state established,related accept comment "!fw4: Allow inbound established and related flows" + } + + chain forward { + type filter hook forward priority filter; policy drop; + + ct state established,related accept comment "!fw4: Allow forwarded established and related flows" + } + + chain output { + type filter hook output priority filter; policy drop; + + oifname "lo" accept comment "!fw4: Accept traffic towards loopback" + + ct state established,related accept comment "!fw4: Allow outbound established and related flows" + meta l4proto icmp counter comment "!fw4: ICMP rule #1" + meta nfproto ipv6 meta l4proto ipv6-icmp counter comment "!fw4: ICMP rule #2" + meta nfproto ipv6 meta l4proto ipv6-icmp counter comment "!fw4: ICMP rule #3" + meta nfproto ipv4 icmp type . icmp code 12 . 0 counter comment "!fw4: ICMP rule #4" + meta nfproto ipv6 icmpv6 type . icmpv6 code 136 . 0 counter comment "!fw4: ICMP rule #5" + } + + chain handle_reject { + meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" + reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" + } + + + # + # NAT rules + # + + chain dstnat { + type nat hook prerouting priority dstnat; policy accept; + } + + chain srcnat { + type nat hook postrouting priority srcnat; policy accept; + } + + + # + # Raw rules (notrack & helper) + # + + chain raw_prerouting { + type filter hook prerouting priority raw; policy accept; + } + + chain raw_output { + type filter hook output priority raw; policy accept; + } + + + # + # Mangle rules + # + + chain mangle_prerouting { + type filter hook prerouting priority mangle; policy accept; + } + + chain mangle_output { + type filter hook output priority mangle; policy accept; + } + + chain mangle_forward { + type filter hook forward priority mangle; policy accept; + } +} +-- End -- diff --git a/tests/lib/mocklib.uc b/tests/lib/mocklib.uc new file mode 100644 index 0000000..db38022 --- /dev/null +++ b/tests/lib/mocklib.uc @@ -0,0 +1,224 @@ +{% + let _fs = require("fs"); + + let _log = (level, fmt, ...args) => { + let color, prefix; + + switch (level) { + case 'info': + color = 34; + prefix = '!'; + break; + + case 'warn': + color = 33; + prefix = 'W'; + break; + + case 'error': + color = 31; + prefix = 'E'; + break; + + default: + color = 0; + prefix = 'I'; + } + + let f = sprintf("\u001b[%d;1m[%s] %s\u001b[0m", color, prefix, fmt); + warn(replace(sprintf(f, ...args), "\n", "\n "), "\n"); + }; + + let read_data_file = (path) => { + for (let dir in MOCK_SEARCH_PATH) { + let fd = _fs.open(dir + '/' + path, "r"); + + if (fd) { + let data = fd.read("all"); + fd.close(); + + return data; + } + } + + return null; + }; + + let read_json_file = (path) => { + let data = read_data_file(path); + + if (data != null) { + try { + return json(data); + } + catch (e) { + _log('error', "Unable to parse JSON data in %s: %s", path, e); + + return NaN; + } + } + + return null; + }; + + let format_json = (data) => { + let rv; + + let format_value = (value) => { + switch (type(value)) { + case "object": + return sprintf("{ /* %d keys */ }", length(value)); + + case "array": + return sprintf("[ /* %d items */ ]", length(value)); + + case "string": + if (length(value) > 64) + value = substr(value, 0, 64) + "..."; + + /* fall through */ + return sprintf("%J", value); + + default: + return sprintf("%J", value); + } + }; + + switch (type(data)) { + case "object": + rv = "{"; + + let k = sort(keys(data)); + + for (let i, n in k) + rv += sprintf("%s %J: %s", i ? "," : "", n, format_value(data[n])); + + rv += " }"; + break; + + case "array": + rv = "["; + + for (let i, v in data) + rv += (i ? "," : "") + " " + format_value(v); + + rv += " ]"; + break; + + default: + rv = format_value(data); + } + + return rv; + }; + + let trace_call = (ns, func, args) => { + let msg = "[call] " + + (ns ? ns + "." : "") + + func; + + for (let k, v in args) { + msg += ' ' + k + ' <'; + + switch (type(v)) { + case "array": + case "object": + msg += format_json(v); + break; + + default: + msg += v; + } + + msg += '>'; + } + + switch (TRACE_CALLS) { + case '1': + case 'stdout': + _fs.stdout.write(msg + "\n"); + break; + + case 'stderr': + _fs.stderr.write(msg + "\n"); + break; + } + }; + + /* Prepend mocklib to REQUIRE_SEARCH_PATH */ + for (let pattern in REQUIRE_SEARCH_PATH) { + /* Only consider ucode includes */ + if (!match(pattern, /\*\.uc$/)) + continue; + + let path = replace(pattern, /\*/, 'mocklib'), + stat = _fs.stat(path); + + if (!stat || stat.type != 'file') + continue; + + if (type(MOCK_SEARCH_PATH) != 'array' || length(MOCK_SEARCH_PATH) == 0) + MOCK_SEARCH_PATH = [ replace(path, /mocklib\.uc$/, '../mocks') ]; + + unshift(REQUIRE_SEARCH_PATH, replace(path, /mocklib\.uc$/, 'mocklib/*.uc')); + break; + } + + if (type(MOCK_SEARCH_PATH) != 'array' || length(MOCK_SEARCH_PATH) == 0) + MOCK_SEARCH_PATH = [ './mocks' ]; + + /* Register global mocklib namespace */ + global.mocklib = { + require: function(module) { + let path, res, ex; + + if (type(REQUIRE_SEARCH_PATH) == "array" && index(REQUIRE_SEARCH_PATH[0], 'mocklib/*.uc') != -1) + path = shift(REQUIRE_SEARCH_PATH); + + try { + res = require(module); + } + catch (e) { + ex = e; + } + + if (path) + unshift(REQUIRE_SEARCH_PATH, path); + + if (ex) + die(ex); + + return res; + }, + + I: (...args) => _log('info', ...args), + N: (...args) => _log('notice', ...args), + W: (...args) => _log('warn', ...args), + E: (...args) => _log('error', ...args), + + format_json, + read_data_file, + read_json_file, + trace_call + }; + + /* Override stdlib functions */ + global.system = function(argv, timeout) { + trace_call(null, "system", { command: argv, timeout }); + + return 0; + }; + + global.time = function() { + trace_call(null, "time"); + + return 1615382640; + }; + + global.print = function(...args) { + if (length(args) == 1 && type(args[0]) in ["array", "object"]) + printf("%s\n", format_json(args[0])); + else + global.print(...args); + }; + diff --git a/tests/lib/mocklib/fs.uc b/tests/lib/mocklib/fs.uc new file mode 100644 index 0000000..1bde07e --- /dev/null +++ b/tests/lib/mocklib/fs.uc @@ -0,0 +1,140 @@ +{% + let mocklib = global.mocklib, + fs = mocklib.require("fs"); + + return { + readlink: function(path) { + mocklib.trace_call("fs", "readlink", { path }); + + return path + "-link"; + }, + + stat: function(path) { + let file = sprintf("fs/stat~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')), + mock = mocklib.read_json_file(file); + + if (!mock || mock != mock) { + mocklib.I("No stat result fixture defined for fs.stat() call on %s.", path); + mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file); + + if (match(path, /\/$/)) + mock = { type: "directory" }; + else + mock = { type: "file" }; + } + + mocklib.trace_call("fs", "stat", { path }); + + return mock; + }, + + unlink: function(path) { + printf("fs.unlink() path <%s>\n", path); + + return true; + }, + + popen: (cmdline, mode) => { + let read = (!mode || index(mode, "r") != -1), + path = sprintf("fs/popen~%s.txt", replace(cmdline, /[^A-Za-z0-9_-]+/g, '_')), + mock = mocklib.read_data_file(path); + + if (read && !mock) { + mocklib.I("No stdout fixture defined for fs.popen() command %s.", cmdline); + mocklib.I("Provide a mock output through the following text file:\n%s\n", path); + + return null; + } + + printf("fs.popen() cmdline <%s> mode <%s>\n", cmdline, mode); + + return { + read: function(amount) { + let rv; + + switch (amount) { + case "all": + rv = mock; + mock = ""; + break; + + case "line": + let i = index(mock, "\n"); + i = (i > -1) ? i + 1 : mock.length; + rv = substr(mock, 0, i); + mock = substr(mock, i); + break; + + default: + let n = +amount; + n = (n > 0) ? n : 0; + rv = substr(mock, 0, n); + mock = substr(mock, n); + break; + } + + return rv; + }, + + write: function() {}, + close: function() {}, + + error: function() { + return null; + } + }; + }, + + open: (fpath, mode) => { + let read = (!mode || index(mode, "r") != -1 || index(mode, "+") != -1), + path = sprintf("fs/open~%s.txt", replace(fpath, /[^A-Za-z0-9_-]+/g, '_')), + mock = read ? mocklib.read_data_file(path) : null; + + if (read && !mock) { + mocklib.I("No stdout fixture defined for fs.open() path %s.", fpath); + mocklib.I("Provide a mock output through the following text file:\n%s\n", path); + + return null; + } + + mocklib.trace_call("fs", "open", { path: fpath, mode }); + + return { + read: function(amount) { + let rv; + + switch (amount) { + case "all": + rv = mock; + mock = ""; + break; + + case "line": + let i = index(mock, "\n"); + i = (i > -1) ? i + 1 : mock.length; + rv = substr(mock, 0, i); + mock = substr(mock, i); + break; + + default: + let n = +amount; + n = (n > 0) ? n : 0; + rv = substr(mock, 0, n); + mock = substr(mock, n); + break; + } + + return rv; + }, + + write: function() {}, + close: function() {}, + + error: function() { + return null; + } + }; + }, + + error: () => "Unspecified error" + }; diff --git a/tests/lib/mocklib/ubus.uc b/tests/lib/mocklib/ubus.uc new file mode 100644 index 0000000..b56f727 --- /dev/null +++ b/tests/lib/mocklib/ubus.uc @@ -0,0 +1,69 @@ +{% + let mocklib = global.mocklib; + + return { + connect: function() { + let self = this; + + return { + call: (object, method, args) => { + let signature = [ object + "~" + method ]; + + if (type(args) == "object") { + for (let i, k in sort(keys(args))) { + switch (type(args[k])) { + case "string": + case "double": + case "bool": + case "int": + push(signature, k + "-" + replace(args[k], /[^A-Za-z0-9_-]+/g, "_")); + break; + + default: + push(signature, type(args[k])); + } + } + } + + let candidates = []; + + for (let i = length(signature); i > 0; i--) { + let path = sprintf("ubus/%s.json", join("~", signature)), + mock = mocklib.read_json_file(path); + + if (mock != mock) { + self._error = "Invalid argument"; + + return null; + } + else if (mock) { + mocklib.trace_call("ctx", "call", { object, method, args }); + + return mock; + } + + push(candidates, path); + pop(signature); + } + + mocklib.I("No response fixture defined for ubus call %s/%s with arguments %s.", object, method, args); + mocklib.I("Provide a mock response through one of the following JSON files:\n%s\n", join("\n", candidates)); + + self._error = "Method not found"; + + return null; + }, + + disconnect: () => null, + + error: () => self.error() + }; + }, + + error: function() { + let e = this._error; + delete(this._error); + + return e; + } + }; diff --git a/tests/lib/mocklib/uci.uc b/tests/lib/mocklib/uci.uc new file mode 100644 index 0000000..542eeb8 --- /dev/null +++ b/tests/lib/mocklib/uci.uc @@ -0,0 +1,148 @@ +{% + let mocklib = global.mocklib; + + let hash = (s) => { + let h = 7; + + for (let i = 0; i < length(s); i++) + h = h * 31 + ord(s, i)[0]; + + return h; + }; + + let id = (config, t, n) => { + while (true) { + let id = sprintf('cfg%08x', hash(t + n)); + + if (!exists(config, id)) + return id; + + n++; + } + }; + + let fixup_config = (config) => { + let rv = {}; + + for (let stype in config) { + switch (type(config[stype])) { + case 'object': + config[stype] = [ config[stype] ]; + /* fall through */ + + case 'array': + for (let idx, sobj in config[stype]) { + let sid, anon; + + if (exists(sobj, '.name') && !exists(rv, sobj['.name'])) { + sid = sobj['.name']; + anon = false; + } + else { + sid = id(rv, stype, idx); + anon = true; + } + + rv[sid] = { + '.index': n_section++, + ...sobj, + '.name': sid, + '.type': stype, + '.anonymous': anon + }; + } + + break; + } + } + + for (let n, sid in sort(keys(rv), (a, b) => rv[a]['.index'] - rv[b]['.index'])) + rv[sid]['.index'] = n; + + return rv; + }; + + return { + cursor: () => ({ + _configs: {}, + + load: function(file) { + let basename = replace(file, /^.+\//, ''), + path = sprintf("uci/%s.json", basename), + mock = mocklib.read_json_file(path); + + if (!mock || mock != mock) { + mocklib.I("No configuration fixture defined for uci package %s.", file); + mocklib.I("Provide a mock configuration through the following JSON file:\n%s\n", path); + + return null; + } + + this._configs[basename] = fixup_config(mock); + }, + + _get_section: function(config, section) { + if (!exists(this._configs, config)) { + this.load(config); + + if (!exists(this._configs, config)) + return null; + } + + let cfg = this._configs[config], + extended = match(section, "^@([A-Za-z0-9_-]+)\[(-?[0-9]+)\]$"); + + if (extended) { + let stype = extended[1], + sindex = +extended[2]; + + let sids = sort( + filter(keys(cfg), sid => cfg[sid]['.type'] == stype), + (a, b) => cfg[a]['.index'] - cfg[b]['.index'] + ); + + if (sindex < 0) + sindex = sids.length + sindex; + + return cfg[sids[sindex]]; + } + + return cfg[section]; + }, + + get: function(config, section, option) { + let sobj = this._get_section(config, section); + + if (option && index(option, ".") == 0) + return null; + else if (sobj && option) + return sobj[option]; + else if (sobj) + return sobj[".type"]; + }, + + get_all: function(config, section) { + return section ? this._get_section(config, section) : this._configs[config]; + }, + + foreach: function(config, stype, cb) { + let rv = false; + + if (exists(this._configs, config)) { + let cfg = this._configs[config], + sids = sort(keys(cfg), (a, b) => cfg[a]['.index'] - cfg[b]['.index']); + + for (let i, sid in sids) { + if (stype == null || cfg[sid]['.type'] == stype) { + if (cb({ ...(cfg[sid]) }) === false) + break; + + rv = true; + } + } + } + + return rv; + } + }) + }; diff --git a/tests/mock.uc b/tests/mock.uc deleted file mode 100644 index 7a5da39..0000000 --- a/tests/mock.uc +++ /dev/null @@ -1,479 +0,0 @@ -{% - let _fs = require("fs"); - - let _log = (level, fmt, ...args) => { - let color, prefix; - - switch (level) { - case 'info': - color = 34; - prefix = '!'; - break; - - case 'warn': - color = 33; - prefix = 'W'; - break; - - case 'error': - color = 31; - prefix = 'E'; - break; - - default: - color = 0; - prefix = 'I'; - } - - let f = sprintf("\u001b[%d;1m[%s] %s\u001b[0m", color, prefix, fmt); - warn(replace(sprintf(f, ...args), "\n", "\n "), "\n"); - }; - - let I = (...args) => _log('info', ...args); - let N = (...args) => _log('notice', ...args); - let W = (...args) => _log('warn', ...args); - let E = (...args) => _log('error', ...args); - - let read_json_file = (path) => { - let fd = _fs.open(path, "r"); - if (fd) { - let data = fd.read("all"); - fd.close(); - - try { - return json(data); - } - catch (e) { - E("Unable to parse JSON data in %s: %s", path, e); - - return NaN; - } - } - - return null; - }; - - let format_json = (data) => { - let rv; - - let format_value = (value) => { - switch (type(value)) { - case "object": - return sprintf("{ /* %d keys */ }", length(value)); - - case "array": - return sprintf("[ /* %d items */ ]", length(value)); - - case "string": - if (length(value) > 64) - value = substr(value, 0, 64) + "..."; - - /* fall through */ - return sprintf("%J", value); - - default: - return sprintf("%J", value); - } - }; - - switch (type(data)) { - case "object": - rv = "{"; - - let k = sort(keys(data)); - - for (let i, n in k) - rv += sprintf("%s %J: %s", i ? "," : "", n, format_value(data[n])); - - rv += " }"; - break; - - case "array": - rv = "["; - - for (let i, v in data) - rv += (i ? "," : "") + " " + format_value(v); - - rv += " ]"; - break; - - default: - rv = format_value(data); - } - - return rv; - }; - - let trace_call = (ns, func, args) => { - let msg = "[call] " + - (ns ? ns + "." : "") + - func; - - for (let k, v in args) { - msg += ' ' + k + ' <'; - - switch (type(v)) { - case "array": - case "object": - msg += format_json(v); - break; - - default: - msg += v; - } - - msg += '>'; - } - - switch (TRACE_CALLS) { - case '1': - case 'stdout': - print(msg + "\n"); - break; - - case 'stderr': - warn(msg + "\n"); - break; - } - }; - - - /* Setup mock environment */ - let mocks = { - - /* Mock ubus module */ - ubus: { - connect: function() { - let self = this; - - return { - call: (object, method, args) => { - let signature = [ object + "~" + method ]; - - if (type(args) == "object") { - for (let i, k in sort(keys(args))) { - switch (type(args[k])) { - case "string": - case "double": - case "bool": - case "int": - push(signature, k + "-" + replace(args[k], /[^A-Za-z0-9_-]+/g, "_")); - break; - - default: - push(signature, type(args[k])); - } - } - } - - let candidates = []; - - for (let i = length(signature); i > 0; i--) { - let path = sprintf("./tests/mocks/ubus/%s.json", join("~", signature)), - mock = read_json_file(path); - - if (mock != mock) { - self._error = "Invalid argument"; - - return null; - } - else if (mock) { - trace_call("ctx", "call", { object, method, args }); - - return mock; - } - - push(candidates, path); - pop(signature); - } - - I("No response fixture defined for ubus call %s/%s with arguments %s.", object, method, args); - I("Provide a mock response through one of the following JSON files:\n%s\n", join("\n", candidates)); - - self._error = "Method not found"; - - return null; - }, - - disconnect: () => null, - - error: () => self.error() - }; - }, - - error: function() { - let e = this._error; - delete(this._error); - - return e; - } - }, - - - /* Mock uci module */ - uci: { - cursor: () => ({ - _configs: {}, - - load: function(file) { - let basename = replace(file, /^.+\//, ''), - path = sprintf("./tests/mocks/uci/%s.json", basename), - mock = read_json_file(path); - - if (!mock || mock != mock) { - I("No configuration fixture defined for uci package %s.", file); - I("Provide a mock configuration through the following JSON file:\n%s\n", path); - - return null; - } - - this._configs[basename] = mock; - }, - - _get_section: function(config, section) { - if (!exists(this._configs, config)) { - this.load(config); - - if (!exists(this._configs, config)) - return null; - } - - let extended = match(section, "^@([A-Za-z0-9_-]+)\[(-?[0-9]+)\]$"); - - if (extended) { - let stype = extended[1], - sindex = +extended[2], - sections = []; - - for (let sid, sobj in this._configs[config]) - if (sobj[".type"] == stype) - push(sections, sobj); - - sort(sections, (a, b) => (a[".index"] || 999) - (b[".index"] || 999)); - - if (sindex < 0) - sindex = sections.length + sindex; - - return sections[sindex]; - } - - return this._configs[config][section]; - }, - - get: function(config, section, option) { - let sobj = this._get_section(config, section); - - if (option && index(option, ".") == 0) - return null; - else if (sobj && option) - return sobj[option]; - else if (sobj) - return sobj[".type"]; - }, - - get_all: function(config, section) { - return section ? this._get_section(config, section) : this._configs[config]; - }, - - foreach: function(config, stype, cb) { - let rv = false; - - if (exists(this._configs, config)) { - let i = 0; - - for (let sid, sobj in this._configs[config]) { - i++; - - if (stype == null || sobj[".type"] == stype) { - cb({ ".index": i - 1, ".type": stype, ".name": sid, ...sobj }); - rv = true; - } - } - } - - return rv; - } - }) - }, - - - /* Mock fs module */ - fs: { - readlink: function(path) { - trace_call("fs", "readlink", { path }); - - return path + "-link"; - }, - - stat: function(path) { - let file = sprintf("./tests/mocks/fs/stat~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')), - mock = read_json_file(file); - - if (!mock || mock != mock) { - I("No stat result fixture defined for fs.stat() call on %s.", path); - I("Provide a mock result through the following JSON file:\n%s\n", file); - - if (match(path, /\/$/)) - mock = { type: "directory" }; - else - mock = { type: "file" }; - } - - trace_call("fs", "stat", { path }); - - return mock; - }, - - unlink: function(path) { - trace_call("fs", "unlink", { path }); - - return true; - }, - - popen: (cmdline, mode) => { - let read = (!mode || index(mode, "r") != -1), - path = sprintf("./tests/mocks/fs/popen~%s.txt", replace(cmdline, /[^A-Za-z0-9_-]+/g, '_')), - fd = read ? _fs.open(path, "r") : null, - mock = null; - - if (fd) { - mock = fd.read("all"); - fd.close(); - } - - if (read && !mock) { - I("No stdout fixture defined for fs.popen() command %s.", cmdline); - I("Provide a mock output through the following text file:\n%s\n", path); - - return null; - } - - trace_call("fs", "popen", { cmdline, mode }); - - return { - read: function(amount) { - let rv; - - switch (amount) { - case "all": - rv = mock; - mock = ""; - break; - - case "line": - let i = index(mock, "\n"); - i = (i > -1) ? i + 1 : mock.length; - rv = substr(mock, 0, i); - mock = substr(mock, i); - break; - - default: - let n = +amount; - n = (n > 0) ? n : 0; - rv = substr(mock, 0, n); - mock = substr(mock, n); - break; - } - - return rv; - }, - - write: function() {}, - close: function() {}, - - error: function() { - return null; - } - }; - }, - - open: (fpath, mode) => { - let read = (!mode || index(mode, "r") != -1 || index(mode, "+") != -1), - path = sprintf("./tests/mocks/fs/open~%s.txt", replace(fpath, /[^A-Za-z0-9_-]+/g, '_')), - fd = read ? _fs.open(path, "r") : null, - mock = null; - - if (fd) { - mock = fd.read("all"); - fd.close(); - } - - if (read && !mock) { - I("No stdout fixture defined for fs.open() path %s.", fpath); - I("Provide a mock output through the following text file:\n%s\n", path); - - return null; - } - - trace_call("fs", "open", { path: fpath, mode }); - - return { - read: function(amount) { - let rv; - - switch (amount) { - case "all": - rv = mock; - mock = ""; - break; - - case "line": - let i = index(mock, "\n"); - i = (i > -1) ? i + 1 : mock.length; - rv = substr(mock, 0, i); - mock = substr(mock, i); - break; - - default: - let n = +amount; - n = (n > 0) ? n : 0; - rv = substr(mock, 0, n); - mock = substr(mock, n); - break; - } - - return rv; - }, - - write: function() {}, - close: function() {}, - - error: function() { - return null; - } - }; - }, - - error: () => "Unspecified error" - }, - - - /* Mock stdlib functions */ - - system: function(argv, timeout) { - trace_call(null, "system", { command: argv, timeout }); - - return 0; - }, - - time: function() { - printf("time()\n"); - - return 1615382640; - }, - - print: function(...args) { - if (length(args) == 1 && type(args[0]) in ["array", "object"]) - printf("%s\n", format_json(args[0])); - else - global.print(...args); - } - }; - - - /* Execute test file */ - - if (!TESTFILE) - E("The TESTFILE variable is not defined."); - - include(TESTFILE, mocks); diff --git a/tests/mocks/uci/firewall.json b/tests/mocks/uci/firewall.json index a7e7720..3a203b6 100644 --- a/tests/mocks/uci/firewall.json +++ b/tests/mocks/uci/firewall.json @@ -1,186 +1,116 @@ { - "cfg01e63d" : { - ".anonymous" : true, - ".index" : 0, - ".name" : "cfg01e63d", - ".type" : "defaults", - "forward" : "REJECT", - "input" : "ACCEPT", - "output" : "ACCEPT", - "syn_flood" : "1" - }, - "cfg02dc81" : { - ".anonymous" : true, - ".index" : 1, - ".name" : "cfg02dc81", - ".type" : "zone", - "forward" : "ACCEPT", - "input" : "ACCEPT", - "name" : "lan", - "network" : [ - "lan" - ], - "output" : "ACCEPT" - }, - "cfg03dc81" : { - ".anonymous" : true, - ".index" : 2, - ".name" : "cfg03dc81", - ".type" : "zone", - "forward" : "REJECT", - "input" : "REJECT", - "masq" : "1", - "mtu_fix" : "1", - "name" : "wan", - "network" : [ - "wan", - "wan6" - ], - "output" : "ACCEPT" - }, - "cfg04ad58" : { - ".anonymous" : true, - ".index" : 3, - ".name" : "cfg04ad58", - ".type" : "forwarding", - "dest" : "wan", - "src" : "lan" - }, - "cfg0592bd" : { - ".anonymous" : true, - ".index" : 4, - ".name" : "cfg0592bd", - ".type" : "rule", - "dest_port" : "68", - "family" : "ipv4", - "name" : "Allow-DHCP-Renew", - "proto" : "udp", - "src" : "wan", - "target" : "ACCEPT" - }, - "cfg0692bd" : { - ".anonymous" : true, - ".index" : 5, - ".name" : "cfg0692bd", - ".type" : "rule", - "family" : "ipv4", - "icmp_type" : "echo-request", - "name" : "Allow-Ping", - "proto" : "icmp", - "src" : "wan", - "target" : "ACCEPT" - }, - "cfg0792bd" : { - ".anonymous" : true, - ".index" : 6, - ".name" : "cfg0792bd", - ".type" : "rule", - "family" : "ipv4", - "name" : "Allow-IGMP", - "proto" : "igmp", - "src" : "wan", - "target" : "ACCEPT" - }, - "cfg0892bd" : { - ".anonymous" : true, - ".index" : 7, - ".name" : "cfg0892bd", - ".type" : "rule", - "dest_ip" : "fc00::/6", - "dest_port" : "546", - "family" : "ipv6", - "name" : "Allow-DHCPv6", - "proto" : "udp", - "src" : "wan", - "src_ip" : "fc00::/6", - "target" : "ACCEPT" - }, - "cfg0992bd" : { - ".anonymous" : true, - ".index" : 8, - ".name" : "cfg0992bd", - ".type" : "rule", - "family" : "ipv6", - "icmp_type" : [ - "130/0", - "131/0", - "132/0", - "143/0" - ], - "name" : "Allow-MLD", - "proto" : "icmp", - "src" : "wan", - "src_ip" : "fe80::/10", - "target" : "ACCEPT" - }, - "cfg0a92bd" : { - ".anonymous" : true, - ".index" : 9, - ".name" : "cfg0a92bd", - ".type" : "rule", - "family" : "ipv6", - "icmp_type" : [ - "echo-request", - "echo-reply", - "destination-unreachable", - "packet-too-big", - "time-exceeded", - "bad-header", - "unknown-header-type", - "router-solicitation", - "neighbour-solicitation", - "router-advertisement", - "neighbour-advertisement" - ], - "limit" : "1000/sec", - "name" : "Allow-ICMPv6-Input", - "proto" : "icmp", - "src" : "wan", - "target" : "ACCEPT" - }, - "cfg0b92bd" : { - ".anonymous" : true, - ".index" : 10, - ".name" : "cfg0b92bd", - ".type" : "rule", - "dest" : "*", - "family" : "ipv6", - "icmp_type" : [ - "echo-request", - "echo-reply", - "destination-unreachable", - "packet-too-big", - "time-exceeded", - "bad-header", - "unknown-header-type" - ], - "limit" : "1000/sec", - "name" : "Allow-ICMPv6-Forward", - "proto" : "icmp", - "src" : "wan", - "target" : "ACCEPT" - }, - "cfg0c92bd" : { - ".anonymous" : true, - ".index" : 11, - ".name" : "cfg0c92bd", - ".type" : "rule", - "dest" : "lan", - "name" : "Allow-IPSec-ESP", - "proto" : "esp", - "src" : "wan", - "target" : "ACCEPT" - }, - "cfg0d92bd" : { - ".anonymous" : true, - ".index" : 12, - ".name" : "cfg0d92bd", - ".type" : "rule", - "dest" : "lan", - "dest_port" : "500", - "name" : "Allow-ISAKMP", - "proto" : "udp", - "src" : "wan", - "target" : "ACCEPT" - } + "defaults": { + "forward": "REJECT", + "input": "ACCEPT", + "output": "ACCEPT", + "syn_flood": "1" + }, + "zone": [ + { + "name": "lan", + "input": "ACCEPT", + "output": "ACCEPT", + "forward": "ACCEPT", + "network": [ "lan" ] + }, + { + "input": "REJECT", + "output": "ACCEPT", + "forward": "REJECT", + "masq": "1", + "mtu_fix": "1", + "name": "wan", + "network": [ "wan", "wan6" ] + } + ], + "forwarding": { + "dest": "wan", + "src": "lan" + }, + "rule": [ + { + "name": "Allow-DHCP-Renew", + "family": "ipv4", + "proto": "udp", + "src": "wan", + "dest_port": "68", + "target": "ACCEPT" + }, + { + "name": "Allow-Ping", + "family": "ipv4", + "proto": "icmp", + "src": "wan", + "icmp_type": "echo-request", + "target": "ACCEPT" + }, + { + "name": "Allow-IGMP", + "family": "ipv4", + "proto": "igmp", + "src": "wan", + "target": "ACCEPT" + }, + { + "name": "Allow-DHCPv6", + "family": "ipv6", + "proto": "udp", + "src": "wan", + "src_ip": "fc00::/6", + "dest_ip": "fc00::/6", + "dest_port": "546", + "target": "ACCEPT" + }, + { + "name": "Allow-MLD", + "family": "ipv6", + "proto": "icmp", + "src": "wan", + "src_ip": "fe80::/10", + "icmp_type": [ "130/0", "131/0", "132/0", "143/0" ], + "target": "ACCEPT" + }, + { + "name": "Allow-ICMPv6-Input", + "family": "ipv6", + "proto": "icmp", + "src": "wan", + "icmp_type": [ + "echo-request", "echo-reply", "destination-unreachable", + "packet-too-big", "time-exceeded", "bad-header", "unknown-header-type", + "router-solicitation", "neighbour-solicitation", "router-advertisement", + "neighbour-advertisement" + ], + "limit": "1000/sec", + "target": "ACCEPT" + }, + { + "name": "Allow-ICMPv6-Forward", + "family": "ipv6", + "proto": "icmp", + "src": "wan", + "dest": "*", + "icmp_type": [ + "echo-request", "echo-reply", "destination-unreachable", + "packet-too-big", "time-exceeded", "bad-header", "unknown-header-type" + ], + "limit": "1000/sec", + "target": "ACCEPT" + }, + { + "name": "Allow-IPSec-ESP", + "proto": "esp", + "src": "wan", + "dest": "lan", + "target": "ACCEPT" + }, + { + "name": "Allow-ISAKMP", + "proto": "udp", + "src": "wan", + "dest": "lan", + "dest_port": "500", + "target": "ACCEPT" + } + ] } diff --git a/tests/mocks/uci/helpers.json b/tests/mocks/uci/helpers.json index 453901d..296941c 100644 --- a/tests/mocks/uci/helpers.json +++ b/tests/mocks/uci/helpers.json @@ -1,146 +1,100 @@ { - "cfg0153e5" : { - ".anonymous" : true, - ".index" : 0, - ".name" : "cfg0153e5", - ".type" : "helper", - "description" : "Amanda backup and archiving proto", - "family" : "any", - "module" : "nf_conntrack_amanda", - "name" : "amanda", - "port" : "10080", - "proto" : "udp" - }, - "cfg0253e5" : { - ".anonymous" : true, - ".index" : 1, - ".name" : "cfg0253e5", - ".type" : "helper", - "description" : "FTP passive connection tracking", - "family" : "any", - "module" : "nf_conntrack_ftp", - "name" : "ftp", - "port" : "21", - "proto" : "tcp" - }, - "cfg0353e5" : { - ".anonymous" : true, - ".index" : 2, - ".name" : "cfg0353e5", - ".type" : "helper", - "description" : "RAS proto tracking", - "family" : "any", - "module" : "nf_conntrack_h323", - "name" : "RAS", - "port" : "1719", - "proto" : "udp" - }, - "cfg0453e5" : { - ".anonymous" : true, - ".index" : 3, - ".name" : "cfg0453e5", - ".type" : "helper", - "description" : "Q.931 proto tracking", - "family" : "any", - "module" : "nf_conntrack_h323", - "name" : "Q.931", - "port" : "1720", - "proto" : "tcp" - }, - "cfg0553e5" : { - ".anonymous" : true, - ".index" : 4, - ".name" : "cfg0553e5", - ".type" : "helper", - "description" : "IRC DCC connection tracking", - "family" : "ipv4", - "module" : "nf_conntrack_irc", - "name" : "irc", - "port" : "6667", - "proto" : "tcp" - }, - "cfg0653e5" : { - ".anonymous" : true, - ".index" : 5, - ".name" : "cfg0653e5", - ".type" : "helper", - "description" : "NetBIOS name service broadcast tracking", - "family" : "ipv4", - "module" : "nf_conntrack_netbios_ns", - "name" : "netbios-ns", - "port" : "137", - "proto" : "udp" - }, - "cfg0753e5" : { - ".anonymous" : true, - ".index" : 6, - ".name" : "cfg0753e5", - ".type" : "helper", - "description" : "PPTP VPN connection tracking", - "family" : "ipv4", - "module" : "nf_conntrack_pptp", - "name" : "pptp", - "port" : "1723", - "proto" : "tcp" - }, - "cfg0853e5" : { - ".anonymous" : true, - ".index" : 7, - ".name" : "cfg0853e5", - ".type" : "helper", - "description" : "SANE scanner connection tracking", - "family" : "any", - "module" : "nf_conntrack_sane", - "name" : "sane", - "port" : "6566", - "proto" : "tcp" - }, - "cfg0953e5" : { - ".anonymous" : true, - ".index" : 8, - ".name" : "cfg0953e5", - ".type" : "helper", - "description" : "SIP VoIP connection tracking", - "family" : "any", - "module" : "nf_conntrack_sip", - "name" : "sip", - "port" : "5060", - "proto" : "udp" - }, - "cfg0a53e5" : { - ".anonymous" : true, - ".index" : 9, - ".name" : "cfg0a53e5", - ".type" : "helper", - "description" : "SNMP monitoring connection tracking", - "family" : "ipv4", - "module" : "nf_conntrack_snmp", - "name" : "snmp", - "port" : "161", - "proto" : "udp" - }, - "cfg0b53e5" : { - ".anonymous" : true, - ".index" : 10, - ".name" : "cfg0b53e5", - ".type" : "helper", - "description" : "TFTP connection tracking", - "family" : "any", - "module" : "nf_conntrack_tftp", - "name" : "tftp", - "port" : "69", - "proto" : "udp" - }, - "cfg0c53e5" : { - ".anonymous" : true, - ".index" : 11, - ".name" : "cfg0c53e5", - ".type" : "helper", - "description" : "RTSP connection tracking", - "family" : "ipv4", - "module" : "nf_conntrack_rtsp", - "name" : "rtsp", - "port" : "554", - "proto" : "tcp" - } + "helper": [ + { + "name": "amanda", + "description": "Amanda backup and archiving proto", + "family": "any", + "module": "nf_conntrack_amanda", + "port": "10080", + "proto": "udp" + }, + { + "name": "ftp", + "description": "FTP passive connection tracking", + "family": "any", + "module": "nf_conntrack_ftp", + "port": "21", + "proto": "tcp" + }, + { + "name": "RAS", + "description": "RAS proto tracking", + "family": "any", + "module": "nf_conntrack_h323", + "port": "1719", + "proto": "udp" + }, + { + "name": "Q.931", + "description": "Q.931 proto tracking", + "family": "any", + "module": "nf_conntrack_h323", + "port": "1720", + "proto": "tcp" + }, + { + "name": "irc", + "description": "IRC DCC connection tracking", + "family": "ipv4", + "module": "nf_conntrack_irc", + "port": "6667", + "proto": "tcp" + }, + { + "name": "netbios-ns", + "description": "NetBIOS name service broadcast tracking", + "family": "ipv4", + "module": "nf_conntrack_netbios_ns", + "port": "137", + "proto": "udp" + }, + { + "name": "pptp", + "description": "PPTP VPN connection tracking", + "family": "ipv4", + "module": "nf_conntrack_pptp", + "port": "1723", + "proto": "tcp" + }, + { + "name": "sane", + "description": "SANE scanner connection tracking", + "family": "any", + "module": "nf_conntrack_sane", + "port": "6566", + "proto": "tcp" + }, + { + "name": "sip", + "description": "SIP VoIP connection tracking", + "family": "any", + "module": "nf_conntrack_sip", + "port": "5060", + "proto": "udp" + }, + { + "name": "snmp", + "description": "SNMP monitoring connection tracking", + "family": "ipv4", + "module": "nf_conntrack_snmp", + "port": "161", + "proto": "udp" + }, + { + "name": "tftp", + "description": "TFTP connection tracking", + "family": "any", + "module": "nf_conntrack_tftp", + "port": "69", + "proto": "udp" + }, + { + "name": "rtsp", + "description": "RTSP connection tracking", + "family": "ipv4", + "module": "nf_conntrack_rtsp", + "port": "554", + "proto": "tcp" + } + ] } diff --git a/tests/test-wrapper.uc b/tests/test-wrapper.uc deleted file mode 100644 index a5412ae..0000000 --- a/tests/test-wrapper.uc +++ /dev/null @@ -1,5 +0,0 @@ -{% - fw4 = require("fw4"); - - include("../root/usr/share/firewall4/main.uc"); -%} -- 2.30.2