#!/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
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=""
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"
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))
done
printf "\nRan %d tests, %d okay, %d failures\n" $n_tests $((n_tests - n_fails)) $n_fails
+exit $n_fails
-- Testcase --
{%
- include("./tests/mock.uc", {
- TESTFILE: "test-wrapper.uc",
+ include("./root/usr/share/firewall4/main.uc", {
TRACE_CALLS: "stderr",
getenv: function(varname) {
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
#
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
}
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 {
drop comment "!fw4: Drop excess packets"
}
-
chain input_lan {
jump accept_from_lan
}
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
}
}
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
}
-
#
# 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"
}
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 {
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"
}
-
#
# 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"
}
--- /dev/null
+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 --
--- /dev/null
+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 --
--- /dev/null
+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 --
--- /dev/null
+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 --
--- /dev/null
+{%
+ 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);
+ };
+
--- /dev/null
+{%
+ 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"
+ };
--- /dev/null
+{%
+ 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;
+ }
+ };
--- /dev/null
+{%
+ 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;
+ }
+ })
+ };
+++ /dev/null
-{%
- 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);
{
- "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"
+ }
+ ]
}
{
- "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"
+ }
+ ]
}
+++ /dev/null
-{%
- fw4 = require("fw4");
-
- include("../root/usr/share/firewall4/main.uc");
-%}