├── .editorconfig ├── root ├── etc │ ├── config │ │ └── firewall │ ├── hotplug.d │ │ └── iface │ │ │ └── 20-firewall │ ├── init.d │ │ └── firewall │ └── nftables.d │ │ ├── 10-custom-filter-chains.nft │ │ └── README ├── sbin │ ├── fw3 │ └── fw4 └── usr │ └── share │ ├── firewall4 │ ├── helpers │ ├── main.uc │ └── templates │ │ ├── mangle-rule.uc │ │ ├── redirect.uc │ │ ├── rule.uc │ │ ├── ruleset.uc │ │ ├── zone-drop-invalid.uc │ │ ├── zone-jump.uc │ │ ├── zone-masq.uc │ │ ├── zone-match.uc │ │ ├── zone-mssfix.uc │ │ ├── zone-notrack.uc │ │ └── zone-verdict.uc │ ├── nftables.d │ └── README │ └── ucode │ └── fw4.uc ├── run_tests.sh └── tests ├── 01_configuration ├── 01_ruleset └── 02_rule_order ├── 02_zones ├── 01_policies ├── 02_masq ├── 03_masq_src_dest_restrictions ├── 04_masq_allow_invalid ├── 04_wildcard_devices ├── 05_subnet_mask_matches ├── 06_family_selections ├── 07_helpers └── 08_log_limit ├── 03_rules ├── 01_direction ├── 02_enabled ├── 03_constraints ├── 04_icmp ├── 05_mangle ├── 06_subnet_mask_matches ├── 07_redirect ├── 08_family_inheritance ├── 09_time ├── 10_notrack ├── 11_log └── 12_mark ├── 04_forwardings └── 01_family_selections ├── 05_ipsets ├── 01_declaration └── 02_usage ├── 06_includes ├── 01_nft_includes ├── 02_firewall.user_include ├── 03_script_includes ├── 04_disabled_include └── 05_automatic_includes ├── lib ├── mocklib.uc └── mocklib │ ├── fs.uc │ ├── ubus.uc │ └── uci.uc └── mocks ├── fs ├── glob~_usr_share_nftables_d_ruleset-post_nft.json ├── glob~_usr_share_nftables_d_ruleset-pre_nft.json ├── glob~_usr_share_nftables_d_table-post_nft.json ├── glob~_usr_share_nftables_d_table-pre_nft.json ├── opendir~_sys_class_net_br-lan.json ├── opendir~_sys_class_net_eth0.json ├── opendir~_sys_class_net_eth1.json ├── opendir~_sys_class_net_pppoe-wan.json ├── opendir~_usr_share_nftables_d_chain-post.json ├── opendir~_usr_share_nftables_d_chain-pre.json ├── open~_proc_version.txt ├── open~_sys_class_net_br-lan_flags.txt ├── open~_sys_class_net_br-lan_uevent.txt ├── open~_sys_class_net_eth0_uevent.txt ├── open~_sys_class_net_eth1_uevent.txt ├── popen~_usr_sbin_nft_--terse_--json_list_flowtables_inet.txt ├── stat~_sys_module_nf_conntrack_amanda.json ├── stat~_sys_module_nf_conntrack_dummy.json ├── stat~_sys_module_nf_conntrack_ftp.json ├── stat~_sys_module_nf_conntrack_h323.json ├── stat~_sys_module_nf_conntrack_irc.json ├── stat~_sys_module_nf_conntrack_netbios_ns.json ├── stat~_sys_module_nf_conntrack_pptp.json ├── stat~_sys_module_nf_conntrack_rtsp.json ├── stat~_sys_module_nf_conntrack_sane.json ├── stat~_sys_module_nf_conntrack_sip.json ├── stat~_sys_module_nf_conntrack_snmp.json └── stat~_sys_module_nf_conntrack_tftp.json ├── ubus ├── network.device~status.json ├── network.interface~dump.json └── service~get_data~type-firewall.json └── uci ├── firewall.json └── helpers.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 4 6 | indent_style = tab 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /root/etc/config/firewall: -------------------------------------------------------------------------------- 1 | config defaults 2 | option syn_flood 1 3 | option input REJECT 4 | option output ACCEPT 5 | option forward REJECT 6 | # Uncomment this line to disable ipv6 rules 7 | # option disable_ipv6 1 8 | 9 | config zone 10 | option name lan 11 | list network 'lan' 12 | option input ACCEPT 13 | option output ACCEPT 14 | option forward ACCEPT 15 | 16 | config zone 17 | option name wan 18 | list network 'wan' 19 | list network 'wan6' 20 | option input REJECT 21 | option output ACCEPT 22 | option forward DROP 23 | option masq 1 24 | option mtu_fix 1 25 | 26 | config forwarding 27 | option src lan 28 | option dest wan 29 | 30 | # We need to accept udp packets on port 68, 31 | # see https://dev.openwrt.org/ticket/4108 32 | config rule 33 | option name Allow-DHCP-Renew 34 | option src wan 35 | option proto udp 36 | option dest_port 68 37 | option target ACCEPT 38 | option family ipv4 39 | 40 | # Allow IPv4 ping 41 | config rule 42 | option name Allow-Ping 43 | option src wan 44 | option proto icmp 45 | option icmp_type echo-request 46 | option family ipv4 47 | option target ACCEPT 48 | 49 | config rule 50 | option name Allow-IGMP 51 | option src wan 52 | option proto igmp 53 | option family ipv4 54 | option target ACCEPT 55 | 56 | # Allow DHCPv6 replies 57 | # see https://github.com/openwrt/openwrt/issues/5066 58 | config rule 59 | option name Allow-DHCPv6 60 | option src wan 61 | option proto udp 62 | option dest_port 546 63 | option family ipv6 64 | option target ACCEPT 65 | 66 | config rule 67 | option name Allow-MLD 68 | option src wan 69 | option proto icmp 70 | option src_ip fe80::/10 71 | list icmp_type '130/0' 72 | list icmp_type '131/0' 73 | list icmp_type '132/0' 74 | list icmp_type '143/0' 75 | option family ipv6 76 | option target ACCEPT 77 | 78 | # Allow essential incoming IPv6 ICMP traffic 79 | config rule 80 | option name Allow-ICMPv6-Input 81 | option src wan 82 | option proto icmp 83 | list icmp_type echo-request 84 | list icmp_type echo-reply 85 | list icmp_type destination-unreachable 86 | list icmp_type packet-too-big 87 | list icmp_type time-exceeded 88 | list icmp_type bad-header 89 | list icmp_type unknown-header-type 90 | list icmp_type router-solicitation 91 | list icmp_type neighbour-solicitation 92 | list icmp_type router-advertisement 93 | list icmp_type neighbour-advertisement 94 | option limit 1000/sec 95 | option family ipv6 96 | option target ACCEPT 97 | 98 | # Allow essential forwarded IPv6 ICMP traffic 99 | config rule 100 | option name Allow-ICMPv6-Forward 101 | option src wan 102 | option dest * 103 | option proto icmp 104 | list icmp_type echo-request 105 | list icmp_type echo-reply 106 | list icmp_type destination-unreachable 107 | list icmp_type packet-too-big 108 | list icmp_type time-exceeded 109 | list icmp_type bad-header 110 | list icmp_type unknown-header-type 111 | option limit 1000/sec 112 | option family ipv6 113 | option target ACCEPT 114 | 115 | config rule 116 | option name Allow-IPSec-ESP 117 | option src wan 118 | option dest lan 119 | option proto esp 120 | option target ACCEPT 121 | 122 | config rule 123 | option name Allow-ISAKMP 124 | option src wan 125 | option dest lan 126 | option dest_port 500 127 | option proto udp 128 | option target ACCEPT 129 | 130 | 131 | ### EXAMPLE CONFIG SECTIONS 132 | # do not allow a specific ip to access wan 133 | #config rule 134 | # option src lan 135 | # option src_ip 192.168.45.2 136 | # option dest wan 137 | # option proto tcp 138 | # option target REJECT 139 | 140 | # block a specific mac on wan 141 | #config rule 142 | # option dest wan 143 | # option src_mac 00:11:22:33:44:66 144 | # option target REJECT 145 | 146 | # block incoming ICMP traffic on a zone 147 | #config rule 148 | # option src lan 149 | # option proto ICMP 150 | # option target DROP 151 | 152 | # port redirect port coming in on wan to lan 153 | #config redirect 154 | # option src wan 155 | # option src_dport 80 156 | # option dest lan 157 | # option dest_ip 192.168.16.235 158 | # option dest_port 80 159 | # option proto tcp 160 | 161 | # port redirect of remapped ssh port (22001) on wan 162 | #config redirect 163 | # option src wan 164 | # option src_dport 22001 165 | # option dest lan 166 | # option dest_port 22 167 | # option proto tcp 168 | 169 | ### FULL CONFIG SECTIONS 170 | #config rule 171 | # option src lan 172 | # option src_ip 192.168.45.2 173 | # option src_mac 00:11:22:33:44:55 174 | # option src_port 80 175 | # option dest wan 176 | # option dest_ip 194.25.2.129 177 | # option dest_port 120 178 | # option proto tcp 179 | # option target REJECT 180 | 181 | #config redirect 182 | # option src lan 183 | # option src_ip 192.168.45.2 184 | # option src_mac 00:11:22:33:44:55 185 | # option src_port 1024 186 | # option src_dport 80 187 | # option dest_ip 194.25.2.129 188 | # option dest_port 120 189 | # option proto tcp 190 | -------------------------------------------------------------------------------- /root/etc/hotplug.d/iface/20-firewall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | has_zone() { 4 | fw4 -q network "$INTERFACE" >/dev/null && return 0 5 | eval $(ubus call "network.interface.$INTERFACE" status | jsonfilter -e 'ZONE=@.data.zone') 6 | fw4 -q zone "$ZONE" >/dev/null 7 | } 8 | 9 | [ "$ACTION" = ifup -o "$ACTION" = ifupdate ] || exit 0 10 | [ "$ACTION" = ifupdate -a -z "$IFUPDATE_ADDRESSES" -a -z "$IFUPDATE_DATA" ] && exit 0 11 | 12 | /etc/init.d/firewall enabled || exit 0 13 | 14 | has_zone || exit 0 15 | 16 | logger -t firewall "Reloading firewall due to $ACTION of $INTERFACE ($DEVICE)" 17 | fw4 -q reload 18 | -------------------------------------------------------------------------------- /root/etc/init.d/firewall: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=19 4 | USE_PROCD=1 5 | QUIET="" 6 | 7 | service_triggers() { 8 | procd_add_reload_trigger firewall 9 | procd_add_reload_data_trigger firewall 10 | } 11 | 12 | restart() { 13 | fw4 restart 14 | } 15 | 16 | start_service() { 17 | fw4 ${QUIET} start 18 | } 19 | 20 | stop_service() { 21 | fw4 flush 22 | } 23 | 24 | reload_service() { 25 | fw4 reload 26 | } 27 | 28 | boot() { 29 | # Be silent on boot, firewall might be started by hotplug already, 30 | # so don't complain in syslog. 31 | QUIET=-q 32 | start 33 | } 34 | -------------------------------------------------------------------------------- /root/etc/nftables.d/10-custom-filter-chains.nft: -------------------------------------------------------------------------------- 1 | ## The firewall4 input, forward and output chains are registered with 2 | ## priority `filter` (0). 3 | 4 | 5 | ## Uncomment the chains below if you want to stage rules *before* the 6 | ## default firewall input, forward and output chains. 7 | 8 | # chain user_pre_input { 9 | # type filter hook input priority -1; policy accept; 10 | # tcp dport ssh ct state new log prefix "SSH connection attempt: " 11 | # } 12 | # 13 | # chain user_pre_forward { 14 | # type filter hook forward priority -1; policy accept; 15 | # } 16 | # 17 | # chain user_pre_output { 18 | # type filter hook output priority -1; policy accept; 19 | # } 20 | 21 | 22 | ## Uncomment the chains below if you want to stage rules *after* the 23 | ## default firewall input, forward and output chains. 24 | 25 | # chain user_post_input { 26 | # type filter hook input priority 1; policy accept; 27 | # ct state new log prefix "Firewall4 accepted ingress: " 28 | # } 29 | # 30 | # chain user_post_forward { 31 | # type filter hook forward priority 1; policy accept; 32 | # ct state new log prefix "Firewall4 accepted forward: " 33 | # } 34 | # 35 | # chain user_post_output { 36 | # type filter hook output priority 1; policy accept; 37 | # ct state new log prefix "Firewall4 accepted egress: " 38 | # } 39 | 40 | -------------------------------------------------------------------------------- /root/etc/nftables.d/README: -------------------------------------------------------------------------------- 1 | All *.nft files in this directory are included by the firewall4 ruleset 2 | within the inet/fw4 table context which allows referencing named sets 3 | declared and populated by the firewall configuration. 4 | 5 | -------------------------------------------------------------------------------- /root/sbin/fw3: -------------------------------------------------------------------------------- 1 | fw4 -------------------------------------------------------------------------------- /root/sbin/fw4: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -o pipefail 4 | 5 | MAIN=/usr/share/firewall4/main.uc 6 | LOCK=/var/run/fw4.lock 7 | STATE=/var/run/fw4.state 8 | VERBOSE= 9 | 10 | [ -e /dev/stdin ] && STDIN=/dev/stdin || STDIN=/proc/self/fd/0 11 | 12 | [ -t 2 ] && export TTY=1 13 | 14 | die() { 15 | [ -n "$QUIET" ] || echo "$@" >&2 16 | exit 1 17 | } 18 | 19 | start() { 20 | { 21 | flock -x 1000 22 | 23 | case "$1" in 24 | start) 25 | [ -f $STATE ] && die "The fw4 firewall appears to be already loaded." 26 | ;; 27 | reload) 28 | [ ! -f $STATE ] && die "The fw4 firewall does not appear to be loaded." 29 | 30 | # Delete state to force reloading ubus state 31 | rm -f $STATE 32 | ;; 33 | esac 34 | 35 | ACTION=start \ 36 | utpl -S $MAIN | nft $VERBOSE -f $STDIN 37 | 38 | ACTION=includes \ 39 | utpl -S $MAIN 40 | } 1000>$LOCK 41 | } 42 | 43 | print() { 44 | ACTION=print \ 45 | utpl -S $MAIN 46 | } 47 | 48 | stop() { 49 | { 50 | flock -x 1000 51 | 52 | nft delete table inet fw4 53 | rm -f $STATE 54 | 55 | } 1000>$LOCK 56 | } 57 | 58 | flush() { 59 | { 60 | flock -x 1000 61 | 62 | local dummy family table 63 | nft list tables | while read dummy family table; do 64 | nft delete table "$family" "$table" 65 | done 66 | 67 | rm -f $STATE 68 | } 1000>$LOCK 69 | } 70 | 71 | reload_sets() { 72 | ACTION=reload-sets \ 73 | flock -x $LOCK utpl -S $MAIN | nft $VERBOSE -f $STDIN 74 | } 75 | 76 | lookup() { 77 | ACTION=$1 OBJECT=$2 DEVICE=$3 \ 78 | flock -x $LOCK utpl -S $MAIN 79 | } 80 | 81 | while [ -n "$1" ]; do 82 | case "$1" in 83 | -q) 84 | export QUIET=1 85 | shift 86 | ;; 87 | -v) 88 | export VERBOSE=-e 89 | shift 90 | ;; 91 | *) 92 | break 93 | ;; 94 | esac 95 | done 96 | 97 | case "$1" in 98 | start|reload) 99 | start "$1" 100 | ;; 101 | stop) 102 | stop || die "The fw4 firewall does not appear to be loaded, try fw4 flush to delete all rules." 103 | ;; 104 | flush) 105 | flush 106 | ;; 107 | restart) 108 | QUIET=1 print | nft ${VERBOSE} -c -f $STDIN || die "The rendered ruleset contains errors, not doing firewall restart." 109 | stop || rm -f $STATE 110 | start 111 | ;; 112 | check) 113 | if [ -n "$QUIET" ]; then 114 | exec 1>/dev/null 115 | exec 2>/dev/null 116 | fi 117 | 118 | print | nft ${VERBOSE} -c -f $STDIN && echo "Ruleset passes nftables check." 119 | ;; 120 | print) 121 | print 122 | ;; 123 | reload-sets) 124 | reload_sets 125 | ;; 126 | network|device|zone) 127 | lookup "$@" 128 | ;; 129 | *) 130 | cat < { 35 | if (first) { 36 | print(`add element inet fw4 ${set.name} {\n`); 37 | first = false; 38 | } 39 | 40 | print(` ${join(" . ", entry)},\n`); 41 | }; 42 | 43 | print(`flush set inet fw4 ${set.name}\n`); 44 | 45 | map(set.entries, printer); 46 | 47 | if (set.loadfile) 48 | fw4.parse_setfile(set, printer); 49 | 50 | if (!first) 51 | print("}\n\n"); 52 | } 53 | } 54 | 55 | function render_ruleset(use_statefile) { 56 | fw4.load(use_statefile); 57 | 58 | include("templates/ruleset.uc", { fw4, type, exists, length, include }); 59 | } 60 | 61 | function lookup_network(net) { 62 | let state = read_state(); 63 | 64 | for (let zone in state.zones) { 65 | for (let network in (zone.network || [])) { 66 | if (network.device == net) { 67 | print(zone.name, "\n"); 68 | exit(0); 69 | } 70 | } 71 | } 72 | 73 | exit(1); 74 | } 75 | 76 | function lookup_device(dev) { 77 | let state = read_state(); 78 | 79 | for (let zone in state.zones) { 80 | for (let rule in (zone.match_rules || [])) { 81 | if (dev in rule.devices_pos) { 82 | print(zone.name, "\n"); 83 | exit(0); 84 | } 85 | } 86 | } 87 | 88 | exit(1); 89 | } 90 | 91 | function lookup_zone(name, dev) { 92 | let state = read_state(); 93 | 94 | for (let zone in state.zones) { 95 | if (zone.name == name) { 96 | let devices = []; 97 | map(zone.match_rules, (r) => push(devices, ...(r.devices_pos || []))); 98 | 99 | if (dev) { 100 | if (dev in devices) { 101 | print(dev, "\n"); 102 | exit(0); 103 | } 104 | 105 | exit(1); 106 | } 107 | 108 | if (length(devices)) 109 | print(join("\n", devices), "\n"); 110 | 111 | exit(0); 112 | } 113 | } 114 | 115 | exit(1); 116 | } 117 | 118 | function run_includes() { 119 | let state = read_state(), 120 | paths = []; 121 | 122 | for (let inc in state.includes) { 123 | if (inc.type != 'script') 124 | continue; 125 | 126 | let path = replace(inc.path, "'", "'\\''"); 127 | let rc = system([ 128 | 'sh', '-c', 129 | `exec 1000>&-; config() { echo "You cannot use UCI in firewall includes!" >&2; exit 1; }; . '${path}'` 130 | ], 30000); 131 | 132 | if (rc != 0) 133 | warn(`Include '${inc.path}' failed with exit code ${rc}\n`); 134 | } 135 | } 136 | 137 | 138 | switch (getenv("ACTION")) { 139 | case "start": 140 | return render_ruleset(true); 141 | 142 | case "print": 143 | return render_ruleset(false); 144 | 145 | case "reload-sets": 146 | return reload_sets(); 147 | 148 | case "network": 149 | return lookup_network(getenv("OBJECT")); 150 | 151 | case "device": 152 | return lookup_device(getenv("OBJECT")); 153 | 154 | case "zone": 155 | return lookup_zone(getenv("OBJECT"), getenv("DEVICE")); 156 | 157 | case "includes": 158 | return run_includes(); 159 | } 160 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/mangle-rule.uc: -------------------------------------------------------------------------------- 1 | {%+ for (let src_devices in rule.src?.zone) } 2 | 3 | {%+ if (rule.family && !rule.has_addrs): -%} 4 | meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%} 5 | {%+ if (!rule.proto.any && !rule.has_ports && !rule.icmp_types && !rule.icmp_codes): -%} 6 | meta l4proto {{ 7 | (rule.proto.name == 'icmp' && rule.family == 6) ? 'ipv6-icmp' : rule.proto.name 8 | }} {%+ endif -%} 9 | {%+ if (rule.saddrs_pos): -%} 10 | {{ fw4.ipproto(rule.family) }} saddr {{ fw4.set(rule.saddrs_pos) }} {%+ endif -%} 11 | {%+ if (rule.saddrs_neg): -%} 12 | {{ fw4.ipproto(rule.family) }} saddr != {{ fw4.set(rule.saddrs_neg) }} {%+ endif -%} 13 | {%+ if (rule.daddrs_pos): -%} 14 | {{ fw4.ipproto(rule.family) }} daddr {{ fw4.set(rule.daddrs_pos) }} {%+ endif -%} 15 | {%+ if (rule.daddrs_neg): -%} 16 | {{ fw4.ipproto(rule.family) }} daddr != {{ fw4.set(rule.daddrs_neg) }} {%+ endif -%} 17 | {%+ if (rule.sports_pos): -%} 18 | {{ rule.proto.name }} sport {{ fw4.set(rule.sports_pos) }} {%+ endif -%} 19 | {%+ if (rule.sports_neg): -%} 20 | {{ rule.proto.name }} sport != {{ fw4.set(rule.sports_neg) }} {%+ endif -%} 21 | {%+ if (rule.dports_pos): -%} 22 | {{ rule.proto.name }} dport {{ fw4.set(rule.dports_pos) }} {%+ endif -%} 23 | {%+ if (rule.dports_neg): -%} 24 | {{ rule.proto.name }} dport != {{ fw4.set(rule.dports_neg) }} {%+ endif -%} 25 | {%+ if (rule.smacs_pos): -%} 26 | ether saddr {{ fw4.set(rule.smacs_pos) }} {%+ endif -%} 27 | {%+ if (rule.smacs_neg): -%} 28 | ether saddr != {{ fw4.set(rule.smacs_neg) }} {%+ endif -%} 29 | {%+ if (rule.icmp_types): -%} 30 | {{ (rule.family == 4) ? "icmp" : "icmpv6" }} type {{ fw4.set(rule.icmp_types) }} {%+ endif -%} 31 | {%+ if (rule.icmp_codes): -%} 32 | {{ (rule.family == 4) ? "icmp" : "icmpv6" }} type . {{ (rule.family == 4) ? "icmp" : "icmpv6" }} code {{ 33 | fw4.set(rule.icmp_codes, true) 34 | }} {%+ endif -%} 35 | {%+ if (rule.helper): -%} 36 | ct helper{% if (rule.helper.invert): %} !={% endif %} {{ fw4.quote(rule.helper.name, true) }} {%+ endif -%} 37 | {%+ if (rule.limit): -%} 38 | limit rate {{ rule.limit.rate }}/{{ rule.limit.unit }} 39 | {%- if (rule.limit_burst): %} burst {{ rule.limit_burst }} packets{% endif %} {%+ endif -%} 40 | {%+ if (rule.start_date): -%} 41 | meta time >= {{ 42 | exists(rule.start_date, "hour") ? fw4.datetime(rule.start_date) : fw4.date(rule.start_date) 43 | }} {%+ endif -%} 44 | {%+ if (rule.stop_date): -%} 45 | meta time <= {{ 46 | exists(rule.stop_date, "hour") ? fw4.datetime(rule.stop_date) : fw4.date(rule.stop_date) 47 | }} {%+ endif -%} 48 | {%+ if (rule.start_time): -%} 49 | meta hour >= {{ fw4.time(rule.start_time) }} {%+ endif -%} 50 | {%+ if (rule.stop_time): -%} 51 | meta hour <= {{ fw4.time(rule.stop_time) }} {%+ endif -%} 52 | {%+ if (rule.weekdays): -%} 53 | meta day{% if (rule.weekdays.invert): %} !={% endif %} {{ fw4.set(rule.weekdays.days) }} {%+ endif -%} 54 | {%+ if (rule.mark && rule.mark.mask < 0xFFFFFFFF): -%} 55 | meta mark and {{ fw4.hex(rule.mark.mask) }} {{ 56 | rule.mark.invert ? '!=' : '==' 57 | }} {{ fw4.hex(rule.mark.mark) }} {%+ endif -%} 58 | {%+ if (rule.mark && rule.mark.mask == 0xFFFFFFFF): -%} 59 | meta mark{% if (rule.mark.invert): %} !={% endif %} {{ fw4.hex(rule.mark.mark) }} {%+ endif -%} 60 | {%+ if (rule.dscp): -%} 61 | dscp{% if (rule.dscp.invert): %} !={% endif %} {{ fw4.hex(rule.dscp.dscp) }} {%+ endif -%} 62 | {%+ if (rule.ipset): -%} 63 | {{ fw4.concat(rule.ipset.fields) }}{{ 64 | rule.ipset.invert ? ' !=' : '' 65 | }} @{{ rule.ipset.name }} {%+ endif -%} 66 | {%+ if (rule.counter): -%} 67 | counter {%+ endif -%} 68 | {%+ if (rule.log): -%} 69 | log prefix {{ fw4.quote(rule.log, true) }} {%+ endif -%} 70 | {%+ if (rule.target == "mark"): -%} 71 | meta mark set {{ 72 | (rule.set_xmark.mask == 0xFFFFFFFF) 73 | ? fw4.hex(rule.set_xmark.mark) 74 | : (rule.set_xmark.mark == 0) 75 | ? 'mark and ' + fw4.hex(~rule.set_xmark.mask & 0xFFFFFFFF) 76 | : (rule.set_xmark.mark == rule.set_xmark.mask) 77 | ? 'mark or ' + fw4.hex(rule.set_xmark.mark) 78 | : (rule.set_xmark.mask == 0) 79 | ? 'mark xor ' + fw4.hex(rule.set_xmark.mark) 80 | : 'mark and ' + fw4.hex(~r.set_xmark.mask & 0xFFFFFFFF) + ' xor ' + fw4.hex(r.set_xmark.mark) 81 | }} {%+ 82 | elif (rule.target == "dscp"): -%} 83 | {{ fw4.ipproto(rule.family) }} dscp set {{ fw4.hex(rule.set_dscp.dscp) }} {%+ 84 | elif (rule.target == "notrack"): -%} 85 | notrack {%+ 86 | elif (rule.target == "helper"): -%} 87 | ct helper set {{ fw4.quote(rule.set_helper.name, true) }} {%+ 88 | elif (rule.jump_chain): -%} 89 | jump {{ rule.jump_chain }} {%+ 90 | elif (rule.target): -%} 91 | {{ rule.target }} {%+ 92 | endif -%} 93 | comment {{ fw4.quote(`!fw4: ${rule.name}`, true) }} 94 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/redirect.uc: -------------------------------------------------------------------------------- 1 | {%+ if (redirect.family && !redirect.has_addrs): -%} 2 | meta nfproto {{ fw4.nfproto(redirect.family) }} {%+ endif -%} 3 | {%+ if (!redirect.proto.any && !redirect.has_ports): -%} 4 | meta l4proto {{ 5 | (redirect.proto.name == 'icmp' && redirect.family == 6) ? 'ipv6-icmp' : redirect.proto.name 6 | }} {%+ endif -%} 7 | {%+ if (redirect.device): -%} 8 | oifname {{ fw4.quote(redirect.device, true) }} {%+ endif -%} 9 | {%+ if (redirect.saddrs_pos): -%} 10 | {{ fw4.ipproto(redirect.family) }} saddr {{ fw4.set(redirect.saddrs_pos) }} {%+ endif -%} 11 | {%+ if (redirect.saddrs_neg): -%} 12 | {{ fw4.ipproto(redirect.family) }} saddr != {{ fw4.set(redirect.saddrs_neg) }} {%+ endif -%} 13 | {%+ for (let a in redirect.saddrs_masked): -%} 14 | {{ fw4.ipproto(redirect.family) }} saddr & {{ a.mask }} {{ a.invert ? '!=' : '==' }} {{ a.addr }} {%+ endfor -%} 15 | {%+ if (redirect.daddrs_pos): -%} 16 | {{ fw4.ipproto(redirect.family) }} daddr {{ fw4.set(redirect.daddrs_pos) }} {%+ endif -%} 17 | {%+ if (redirect.daddrs_neg): -%} 18 | {{ fw4.ipproto(redirect.family) }} daddr != {{ fw4.set(redirect.daddrs_neg) }} {%+ endif -%} 19 | {%+ for (let a in redirect.daddrs_masked): -%} 20 | {{ fw4.ipproto(redirect.family) }} daddr & {{ a.mask }} {{ a.invert ? '!=' : '==' }} {{ a.addr }} {%+ endfor -%} 21 | {%+ if (redirect.sports_pos): -%} 22 | {{ redirect.proto.name }} sport {{ fw4.set(redirect.sports_pos) }} {%+ endif -%} 23 | {%+ if (redirect.sports_neg): -%} 24 | {{ redirect.proto.name }} sport != {{ fw4.set(redirect.sports_neg) }} {%+ endif -%} 25 | {%+ if (redirect.dports_pos): -%} 26 | {{ redirect.proto.name }} dport {{ fw4.set(redirect.dports_pos) }} {%+ endif -%} 27 | {%+ if (redirect.dports_neg): -%} 28 | {{ redirect.proto.name }} dport != {{ fw4.set(redirect.dports_neg) }} {%+ endif -%} 29 | {%+ if (redirect.smacs_pos): -%} 30 | ether saddr {{ fw4.set(redirect.smacs_pos) }} {%+ endif -%} 31 | {%+ if (redirect.smacs_neg): -%} 32 | ether saddr != {{ fw4.set(redirect.smacs_neg) }} {%+ endif -%} 33 | {%+ if (redirect.helper): -%} 34 | ct helper{% if (redirect.helper.invert): %} !={% endif %} {{ fw4.quote(redirect.helper.name, true) }} {%+ endif -%} 35 | {%+ if (redirect.limit): -%} 36 | limit rate {{ redirect.limit.rate }}/{{ redirect.limit.unit }} 37 | {%- if (redirect.limit_burst): %} burst {{ redirect.limit_burst }} packets{% endif %} {%+ endif -%} 38 | {%+ if (redirect.start_date && redirect.stop_date): -%} 39 | meta time {{ fw4.datestamp(redirect.start_date) }}-{{ fw4.datestamp(redirect.stop_date) }} {%+ 40 | elif (redirect.start_date): -%} 41 | meta time >= {{ fw4.datestamp(redirect.start_date) }} {%+ 42 | elif (redirect.stop_date): -%} 43 | meta time <= {{ fw4.datestamp(redirect.stop_date) }} {%+ 44 | endif -%} 45 | {%+ if (redirect.start_time && redirect.stop_time): -%} 46 | meta hour {{ fw4.time(redirect.start_time) }}-{{ fw4.time(redirect.stop_time) }} {%+ 47 | elif (redirect.start_time): -%} 48 | meta hour >= {{ fw4.time(redirect.start_time) }} {%+ 49 | elif (redirect.stop_time): -%} 50 | meta hour <= {{ fw4.time(redirect.stop_time) }} {%+ 51 | endif -%} 52 | {%+ if (redirect.weekdays): -%} 53 | meta day{% if (redirect.weekdays.invert): %} !={% endif %} {{ fw4.set(redirect.weekdays.days) }} {%+ endif -%} 54 | {%+ if (redirect.mark && redirect.mark.mask < 0xFFFFFFFF): -%} 55 | meta mark and {{ fw4.hex(redirect.mark.mask) }} {{ 56 | redirect.mark.invert ? '!=' : '==' 57 | }} {{ fw4.hex(redirect.mark.mark) }} {%+ endif -%} 58 | {%+ if (redirect.mark && redirect.mark.mask == 0xFFFFFFFF): -%} 59 | meta mark{% if (redirect.mark.invert): %} !={% endif %} {{ fw4.hex(redirect.mark.mark) }} {%+ endif -%} 60 | {%+ if (redirect.ipset): -%} 61 | {{ fw4.concat(redirect.ipset.fields) }}{{ 62 | redirect.ipset.invert ? ' !=' : '' 63 | }} @{{ redirect.ipset.name }} {%+ endif -%} 64 | {%+ if (redirect.log && redirect.log_limit): -%} 65 | limit rate {{ redirect.log_limit.rate }}/{{ redirect.log_limit.unit }} log prefix {{ fw4.quote(redirect.log, true) }} 66 | {%+ include("redirect.uc", { fw4, zone, redirect: { ...redirect, log: 0 } }) %} 67 | {%+ elif (redirect.log && zone?.log_limit): -%} 68 | limit name "{{ zone.name }}.log_limit" log prefix {{ fw4.quote(redirect.log, true) }} 69 | {%+ include("redirect.uc", { fw4, zone, redirect: { ...redirect, log: 0 } }) %} 70 | {%+ else -%} 71 | {%+ if (redirect.counter): -%} 72 | counter {%+ endif -%} 73 | {%+ if (redirect.log): -%} 74 | log prefix {{ fw4.quote(redirect.log, true) }} {%+ endif -%} 75 | {% if (redirect.target == "redirect"): -%} 76 | redirect{% if (redirect.rport): %} to {{ fw4.port(redirect.rport) }}{% endif %} 77 | {%- elif (redirect.target == "accept" || redirect.target == "masquerade"): -%} 78 | {{ redirect.target }} 79 | {%- else -%} 80 | {{ redirect.target }} {{ redirect.raddr ? fw4.host(redirect.raddr, redirect.rport != null) : '' }} 81 | {%- if (redirect.rport): %}:{{ fw4.port(redirect.rport) }}{% endif %} 82 | {% endif %} comment {{ fw4.quote(`!fw4: ${redirect.name}`, true) }} 83 | {% endif -%} 84 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/rule.uc: -------------------------------------------------------------------------------- 1 | {%+ if (rule.family && !rule.has_addrs): -%} 2 | meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%} 3 | {%+ if (!rule.proto.any && !rule.has_ports && !rule.icmp_types && !rule.icmp_codes): -%} 4 | meta l4proto {{ fw4.l4proto(rule.family, rule.proto) }} {%+ endif -%} 5 | {%+ if (rule.iifnames): -%} 6 | iifname {{ fw4.set(rule.iifnames) }} {%+ endif -%} 7 | {%+ if (rule.oifnames): -%} 8 | oifname {{ fw4.set(rule.oifnames) }} {%+ endif -%} 9 | {%+ if (rule.saddrs_pos): -%} 10 | {{ fw4.ipproto(rule.family) }} saddr {{ fw4.set(rule.saddrs_pos) }} {%+ endif -%} 11 | {%+ if (rule.saddrs_neg): -%} 12 | {{ fw4.ipproto(rule.family) }} saddr != {{ fw4.set(rule.saddrs_neg) }} {%+ endif -%} 13 | {%+ for (let a in rule.saddrs_masked): -%} 14 | {{ fw4.ipproto(rule.family) }} saddr & {{ a.mask }} {{ a.invert ? '!=' : '==' }} {{ a.addr }} {%+ endfor -%} 15 | {%+ if (rule.daddrs_pos): -%} 16 | {{ fw4.ipproto(rule.family) }} daddr {{ fw4.set(rule.daddrs_pos) }} {%+ endif -%} 17 | {%+ if (rule.daddrs_neg): -%} 18 | {{ fw4.ipproto(rule.family) }} daddr != {{ fw4.set(rule.daddrs_neg) }} {%+ endif -%} 19 | {%+ for (let a in rule.daddrs_masked): -%} 20 | {{ fw4.ipproto(rule.family) }} daddr & {{ a.mask }} {{ a.invert ? '!=' : '==' }} {{ a.addr }} {%+ endfor -%} 21 | {%+ if (rule.sports_pos): -%} 22 | {{ rule.proto.name }} sport {{ fw4.set(rule.sports_pos) }} {%+ endif -%} 23 | {%+ if (rule.sports_neg): -%} 24 | {{ rule.proto.name }} sport != {{ fw4.set(rule.sports_neg) }} {%+ endif -%} 25 | {%+ if (rule.dports_pos): -%} 26 | {{ rule.proto.name }} dport {{ fw4.set(rule.dports_pos) }} {%+ endif -%} 27 | {%+ if (rule.dports_neg): -%} 28 | {{ rule.proto.name }} dport != {{ fw4.set(rule.dports_neg) }} {%+ endif -%} 29 | {%+ if (rule.smacs_pos): -%} 30 | ether saddr {{ fw4.set(rule.smacs_pos) }} {%+ endif -%} 31 | {%+ if (rule.smacs_neg): -%} 32 | ether saddr != {{ fw4.set(rule.smacs_neg) }} {%+ endif -%} 33 | {%+ if (rule.icmp_types): -%} 34 | {{ (rule.family == 4) ? "icmp" : "icmpv6" }} type {{ fw4.set(rule.icmp_types) }} {%+ endif -%} 35 | {%+ if (rule.icmp_codes): -%} 36 | {{ (rule.family == 4) ? "icmp" : "icmpv6" }} type . {{ (rule.family == 4) ? "icmp" : "icmpv6" }} code {{ 37 | fw4.set(rule.icmp_codes, true) 38 | }} {%+ endif -%} 39 | {%+ if (rule.helper): -%} 40 | ct helper{% if (rule.helper.invert): %} !={% endif %} {{ fw4.quote(rule.helper.name, true) }} {%+ endif -%} 41 | {%+ if (rule.limit): -%} 42 | limit rate {{ rule.limit.rate }}/{{ rule.limit.unit }} 43 | {%- if (rule.limit_burst): %} burst {{ rule.limit_burst }} packets{% endif %} {%+ endif -%} 44 | {%+ if (rule.start_date && rule.stop_date): -%} 45 | meta time {{ fw4.datestamp(rule.start_date) }}-{{ fw4.datestamp(rule.stop_date) }} {%+ 46 | elif (rule.start_date): -%} 47 | meta time >= {{ fw4.datestamp(rule.start_date) }} {%+ 48 | elif (rule.stop_date): -%} 49 | meta time <= {{ fw4.datestamp(rule.stop_date) }} {%+ 50 | endif -%} 51 | {%+ if (rule.start_time && rule.stop_time): -%} 52 | meta hour {{ fw4.time(rule.start_time) }}-{{ fw4.time(rule.stop_time) }} {%+ 53 | elif (rule.start_time): -%} 54 | meta hour >= {{ fw4.time(rule.start_time) }} {%+ 55 | elif (rule.stop_time): -%} 56 | meta hour <= {{ fw4.time(rule.stop_time) }} {%+ 57 | endif -%} 58 | {%+ if (rule.weekdays): -%} 59 | meta day{% if (rule.weekdays.invert): %} !={% endif %} {{ fw4.set(rule.weekdays.days) }} {%+ endif -%} 60 | {%+ if (rule.mark && rule.mark.mask < 0xFFFFFFFF): -%} 61 | meta mark and {{ fw4.hex(rule.mark.mask) }} {{ 62 | rule.mark.invert ? '!=' : '==' 63 | }} {{ fw4.hex(rule.mark.mark) }} {%+ endif -%} 64 | {%+ if (rule.mark && rule.mark.mask == 0xFFFFFFFF): -%} 65 | meta mark{% if (rule.mark.invert): %} !={% endif %} {{ fw4.hex(rule.mark.mark) }} {%+ endif -%} 66 | {%+ if (rule.dscp): -%} 67 | {{ fw4.ipproto(rule.family) }} dscp{% if (rule.dscp.invert): %} !={% endif %} {{ fw4.hex(rule.dscp.dscp) }} {%+ endif -%} 68 | {%+ if (rule.ipset): -%} 69 | {{ fw4.concat(rule.ipset.fields) }}{{ 70 | rule.ipset.invert ? ' !=' : '' 71 | }} @{{ rule.ipset.name }} {%+ endif -%} 72 | {%+ if (rule.log && rule.log_limit): -%} 73 | limit rate {{ rule.log_limit.rate }}/{{ rule.log_limit.unit }} log prefix {{ fw4.quote(rule.log, true) }} 74 | {%+ include("rule.uc", { fw4, zone, rule: { ...rule, log: 0 } }) %} 75 | {%+ elif (rule.log && zone?.log_limit): -%} 76 | limit name "{{ zone.name }}.log_limit" log prefix {{ fw4.quote(rule.log, true) }} 77 | {%+ include("rule.uc", { fw4, zone, rule: { ...rule, log: 0 } }) %} 78 | {%+ else -%} 79 | {%+ if (rule.counter): -%} 80 | counter {%+ endif -%} 81 | {%+ if (rule.log): -%} 82 | log prefix {{ fw4.quote(rule.log, true) }} {%+ endif -%} 83 | {%+ if (rule.target == "mark"): -%} 84 | meta mark set {{ 85 | (rule.set_xmark.mask == 0xFFFFFFFF) 86 | ? fw4.hex(rule.set_xmark.mark) 87 | : (rule.set_xmark.mark == 0) 88 | ? `mark and ${fw4.hex(~rule.set_xmark.mask & 0xFFFFFFFF)}` 89 | : (rule.set_xmark.mark == rule.set_xmark.mask) 90 | ? `mark or ${fw4.hex(rule.set_xmark.mark)}` 91 | : (rule.set_xmark.mask == 0) 92 | ? `mark xor ${fw4.hex(rule.set_xmark.mark)}` 93 | : `mark and ${fw4.hex(~rule.set_xmark.mask & 0xFFFFFFFF)} xor ${fw4.hex(rule.set_xmark.mark)}` 94 | }} {%+ 95 | elif (rule.target == "dscp"): -%} 96 | {{ fw4.ipproto(rule.family) }} dscp set {{ fw4.hex(rule.set_dscp.dscp) }} {%+ 97 | elif (rule.target == "notrack"): -%} 98 | notrack {%+ 99 | elif (rule.target == "helper"): -%} 100 | ct helper set {{ fw4.quote(rule.set_helper.name, true) }} {%+ 101 | elif (rule.jump_chain): -%} 102 | jump {{ rule.jump_chain }} {%+ 103 | elif (rule.target): -%} 104 | {{ rule.target }} {%+ 105 | endif -%} 106 | comment {{ fw4.quote(`!fw4: ${rule.name}`, true) }} 107 | {%+ endif -%} 108 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/zone-drop-invalid.uc: -------------------------------------------------------------------------------- 1 | {%+ if (zone.masq ^ zone.masq6): -%} 2 | meta nfproto {{ fw4.nfproto(zone.masq ? 4 : 6) }} {%+ endif -%} 3 | {%+ include("zone-match.uc", { egress: true, rule }) -%} 4 | ct state invalid {%+ if ((zone.log & 1) && zone.log_limit): -%} 5 | limit name "{{ zone.name }}.log_limit" log prefix "drop {{ zone.name }} invalid ct state: " 6 | {%+ include("zone-drop-invalid.uc", { fw4, zone: { ...zone, log: 0 }, rule }) %} 7 | {%+ else -%} 8 | {%+ if (zone.counter): -%} 9 | counter {%+ endif -%} 10 | {%+ if (zone.log & 1): -%} 11 | log prefix "drop {{ zone.name }} invalid ct state: " {%+ endif -%} 12 | drop comment "!fw4: Prevent NAT leakage" 13 | {%+ endif -%} 14 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/zone-jump.uc: -------------------------------------------------------------------------------- 1 | {%+ if (rule.family): -%} 2 | meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%} 3 | {%+ include("zone-match.uc", { egress: (direction in ["output", "srcnat"]), rule }) -%} 4 | jump {{ direction }}_{{ zone.name }} comment "!fw4: Handle {{ zone.name }} {{ 5 | fw4.nfproto(rule.family, true) 6 | }} {{ direction }} {{ (direction == 'helper') ? "assignment" : "traffic" }}" 7 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/zone-masq.uc: -------------------------------------------------------------------------------- 1 | meta nfproto {{ fw4.nfproto(family) }} {%+ if (saddrs && saddrs[0]): -%} 2 | {{ fw4.ipproto(family) }} saddr {{ fw4.set(map(saddrs[0], fw4.cidr)) }} {%+ endif -%} 3 | {%+ if (saddrs && saddrs[1]): -%} 4 | {{ fw4.ipproto(family) }} saddr != {{ fw4.set(map(saddrs[1], fw4.cidr)) }} {%+ endif -%} 5 | {%+ for (let a in (saddrs ? saddrs[2] : [])): -%} 6 | {{ fw4.ipproto(family) }} saddr & {{ a.mask }} {{ a.invert ? '!=' : '==' }} {{ a.addr }} {%+ endfor -%} 7 | {%+ if (daddrs && daddrs[0]): -%} 8 | {{ fw4.ipproto(family) }} daddr {{ fw4.set(map(daddrs[0], fw4.cidr)) }} {%+ endif -%} 9 | {%+ if (daddrs && daddrs[1]): -%} 10 | {{ fw4.ipproto(family) }} daddr != {{ fw4.set(map(daddrs[1], fw4.cidr)) }} {%+ endif -%} 11 | {%+ for (let a in (daddrs ? daddrs[2] : [])): -%} 12 | {{ fw4.ipproto(family) }} daddr & {{ a.mask }} {{ a.invert ? '!=' : '==' }} {{ a.addr }} {%+ endfor -%} 13 | masquerade comment "!fw4: Masquerade {{ fw4.nfproto(family, true) }} {{ zone.name }} traffic" 14 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/zone-match.uc: -------------------------------------------------------------------------------- 1 | {%+ if (rule.devices_pos): -%} 2 | {{ egress ? "oifname" : "iifname" }} {{ fw4.set(rule.devices_pos) }} {%+ endif -%} 3 | {%+ if (rule.devices_neg): -%} 4 | {{ egress ? "oifname" : "iifname" }} != {{ fw4.set(rule.devices_neg) }} {%+ endif -%} 5 | {%+ for (let wcndev in rule.devices_neg_wildcard): -%} 6 | {{ egress ? "oifname" : "iifname" }} != {{ fw4.quote(wcndev) }} {%+ endfor -%} 7 | {%+ if (rule.subnets_pos): -%} 8 | {{ fw4.ipproto(rule.family) }} {{ egress ? "daddr" : "saddr" }} {{ fw4.set(rule.subnets_pos) }} {%+ endif -%} 9 | {%+ if (rule.subnets_neg): -%} 10 | {{ fw4.ipproto(rule.family) }} {{ egress ? "daddr" : "saddr" }} != {{ fw4.set(rule.subnets_neg) }} {%+ endif -%} 11 | {%+ for (let subnet in rule.subnets_masked): -%} 12 | {{ fw4.ipproto(rule.family) }} {{ egress ? "daddr" : "saddr" }} & {{ subnet.mask }} {{ subnet.invert ? '!=' : '==' }} {{ subnet.addr }} {%+ endfor -%} 13 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/zone-mssfix.uc: -------------------------------------------------------------------------------- 1 | {%+ if (rule.family): -%} 2 | meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%} 3 | {%+ include("zone-match.uc", { egress, rule }) -%} 4 | tcp flags syn / syn,fin,rst tcp option maxseg size set rt mtu {%+ if (zone.log & 2): -%} 5 | log prefix "MSSFIX {{ zone.name }} out: " {%+ endif -%} 6 | comment "!fw4: Zone {{ zone.name }} {{ 7 | fw4.nfproto(rule.family, true) 8 | }} {{ egress ? "egress" : "ingress" }} MTU fixing" 9 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/zone-notrack.uc: -------------------------------------------------------------------------------- 1 | {%+ 2 | let devs = fw4.filter_loopback_devs(fw4.devices_pos, output), 3 | nets = fw4.filter_loopback_addrs(fw4.subnets_pos, output); 4 | 5 | if (!((output && (length(devs) || length(nets))) || 6 | (!output && (rule.devices_neg || rule.subnets_neg || length(devs) || length(nets))))) 7 | return; 8 | -%} 9 | {%+ if (rule.family): -%} 10 | meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%} 11 | {%+ if (length(devs)): -%} 12 | iifname {{ fw4.set(devs) }} {%+ endif -%} 13 | {%+ if (rule.devices_neg): -%} 14 | iifname != {{ fw4.set(rule.devices_neg) }} {%+ endif -%} 15 | {%+ if (length(nets)): -%} 16 | {{ fw4.ipproto(rule.family) }} saddr {{ fw4.set(nets) }} {%+ endif -%} 17 | {%+ if (rule.subnets_neg): -%} 18 | {{ fw4.ipproto(rule.family) }} saddr != {{ fw4.set(rule.subnets_neg) }} {%+ endif -%} 19 | jump notrack_{{ zone.name }} comment "!fw4: {{ zone.name }} {{ fw4.nfproto(rule.family, true) }} CT bypass" 20 | -------------------------------------------------------------------------------- /root/usr/share/firewall4/templates/zone-verdict.uc: -------------------------------------------------------------------------------- 1 | {%+ if (rule.family): -%} 2 | meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%} 3 | {%+ include("zone-match.uc", { egress, rule }) -%} 4 | {%+ if (verdict != "accept" && (zone.log & 1) && zone.log_limit): -%} 5 | limit name "{{ zone.name }}.log_limit" log prefix "{{ verdict }} {{ zone.name }} {{ egress ? "out" : "in" }}: " 6 | {%+ include("zone-verdict.uc", { fw4, zone: { ...zone, log: 0 }, rule, egress, verdict }) %} 7 | {%+ else -%} 8 | {%+ if (zone.counter): -%} 9 | counter {%+ endif -%} 10 | {%+ if (verdict != "accept" && (zone.log & 1)): -%} 11 | log prefix "{{ verdict }} {{ zone.name }} {{ egress ? "out" : "in" }}: " {%+ endif -%} 12 | {% if (verdict == "reject"): -%} 13 | jump handle_reject comment "!fw4: reject {{ zone.name }} {{ fw4.nfproto(rule.family, true) }} traffic" 14 | {% else -%} 15 | {{ verdict }} comment "!fw4: {{ verdict }} {{ zone.name }} {{ fw4.nfproto(rule.family, true) }} traffic" 16 | {% endif -%} 17 | {% endif -%} 18 | -------------------------------------------------------------------------------- /root/usr/share/nftables.d/README: -------------------------------------------------------------------------------- 1 | This directory may contain partial nftables files which are automatically 2 | included into the nftables ruleset generated by the fw4 program. 3 | 4 | Only accessible files (no broken symlinks, no files with insufficient 5 | permissions) with an `*.nft` file extension are considered. 6 | 7 | The include position of each file within the overall ruleset is derived 8 | from the file path: 9 | 10 | - Files in ./ruleset-pre/ and ./ruleset-post/ are included before and 11 | after the `table inet fw4 { ... }` declaration respectively 12 | 13 | - Files in ./table-pre/ and ./table-post/ are included before the first 14 | chain and after the last chain declaration within the fw4 table 15 | respectively 16 | 17 | - Files in ./chain-pre/${chain}/ and ./chain-post/${chain}/ are included 18 | before the first and after the last rule within the mentioned chain of 19 | the fw4 table respectively 20 | 21 | Automatic inclusion of these files can be disabled by setting the global 22 | `auto_includes` option to `0` within the defaults section of 23 | /etc/config/firewall. 24 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | line='........................................' 4 | ucode='ucode -S -T, -L./tests/lib -L./root/usr/share/ucode' 5 | 6 | extract_sections() { 7 | local file=$1 8 | local dir=$2 9 | local count=0 10 | local tag line outfile 11 | 12 | while IFS= read -r line; do 13 | case "$line" in 14 | "-- Testcase --") 15 | tag="test" 16 | count=$((count + 1)) 17 | outfile=$(printf "%s/%03d.in" "$dir" $count) 18 | printf "" > "$outfile" 19 | ;; 20 | "-- Environment --") 21 | tag="env" 22 | count=$((count + 1)) 23 | outfile=$(printf "%s/%03d.env" "$dir" $count) 24 | printf "" > "$outfile" 25 | ;; 26 | "-- Expect stdout --"|"-- Expect stderr --"|"-- Expect exitcode --") 27 | tag="${line#-- Expect }" 28 | tag="${tag% --}" 29 | count=$((count + 1)) 30 | outfile=$(printf "%s/%03d.%s" "$dir" $count "$tag") 31 | printf "" > "$outfile" 32 | ;; 33 | "-- File "*" --") 34 | tag="file" 35 | outfile="${line#-- File }" 36 | outfile="$(echo "${outfile% --}" | xargs)" 37 | outfile="$dir/files$(readlink -m "/${outfile:-file}")" 38 | mkdir -p "$(dirname "$outfile")" 39 | printf "" > "$outfile" 40 | ;; 41 | "-- End --") 42 | tag="" 43 | outfile="" 44 | ;; 45 | *) 46 | if [ -n "$tag" ]; then 47 | printf "%s\\n" "$line" >> "$outfile" 48 | fi 49 | ;; 50 | esac 51 | done < "$file" 52 | 53 | return $(ls -l "$dir/"*.in 2>/dev/null | wc -l) 54 | } 55 | 56 | run_testcase() { 57 | local num=$1 58 | local dir=$2 59 | local in=$3 60 | local env=$4 61 | local out=$5 62 | local err=$6 63 | local code=$7 64 | local fail=0 65 | 66 | $ucode \ 67 | -D MOCK_SEARCH_PATH='["'"$dir"'/files", "./tests/mocks"]' \ 68 | ${env:+-F "$env"} \ 69 | -l mocklib -l fw4 \ 70 | - <"$in" >"$dir/res.out" 2>"$dir/res.err" 71 | 72 | printf "%d\n" $? > "$dir/res.code" 73 | 74 | touch "$dir/empty" 75 | 76 | if ! cmp -s "$dir/res.err" "${err:-$dir/empty}"; then 77 | [ $fail = 0 ] && printf "!\n" 78 | printf "Testcase #%d: Expected stderr did not match:\n" $num 79 | diff -u --color=always --label="Expected stderr" --label="Resulting stderr" "${err:-$dir/empty}" "$dir/res.err" 80 | printf -- "---\n" 81 | fail=1 82 | fi 83 | 84 | if ! cmp -s "$dir/res.out" "${out:-$dir/empty}"; then 85 | [ $fail = 0 ] && printf "!\n" 86 | printf "Testcase #%d: Expected stdout did not match:\n" $num 87 | diff -u --color=always --label="Expected stdout" --label="Resulting stdout" "${out:-$dir/empty}" "$dir/res.out" 88 | printf -- "---\n" 89 | fail=1 90 | fi 91 | 92 | if [ -n "$code" ] && ! cmp -s "$dir/res.code" "$code"; then 93 | [ $fail = 0 ] && printf "!\n" 94 | printf "Testcase #%d: Expected exit code did not match:\n" $num 95 | diff -u --color=always --label="Expected code" --label="Resulting code" "$code" "$dir/res.code" 96 | printf -- "---\n" 97 | fail=1 98 | fi 99 | 100 | return $fail 101 | } 102 | 103 | run_test() { 104 | local file=$1 105 | local name=${file##*/} 106 | local res ecode eout eerr ein eenv tests 107 | local testcase_first=0 failed=0 count=0 108 | 109 | printf "%s %s " "$name" "${line:${#name}}" 110 | 111 | mkdir "/tmp/test.$$" 112 | 113 | extract_sections "$file" "/tmp/test.$$" 114 | tests=$? 115 | 116 | [ -f "/tmp/test.$$/001.in" ] && testcase_first=1 117 | 118 | for res in "/tmp/test.$$/"[0-9]*; do 119 | case "$res" in 120 | *.in) 121 | count=$((count + 1)) 122 | 123 | if [ $testcase_first = 1 ]; then 124 | # Flush previous test 125 | if [ -n "$ein" ]; then 126 | run_testcase $count "/tmp/test.$$" "$ein" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1)) 127 | 128 | eout="" 129 | eerr="" 130 | ecode="" 131 | eenv="" 132 | fi 133 | 134 | ein=$res 135 | else 136 | run_testcase $count "/tmp/test.$$" "$res" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1)) 137 | 138 | eout="" 139 | eerr="" 140 | ecode="" 141 | eenv="" 142 | fi 143 | 144 | ;; 145 | *.env) eenv=$res ;; 146 | *.stdout) eout=$res ;; 147 | *.stderr) eerr=$res ;; 148 | *.exitcode) ecode=$res ;; 149 | esac 150 | done 151 | 152 | # Flush last test 153 | if [ $testcase_first = 1 ] && [ -n "$eout$eerr$ecode" ]; then 154 | run_testcase $count "/tmp/test.$$" "$ein" "$eenv" "$eout" "$eerr" "$ecode" || failed=$((failed + 1)) 155 | fi 156 | 157 | rm -r "/tmp/test.$$" 158 | 159 | if [ $failed = 0 ]; then 160 | printf "OK\n" 161 | else 162 | printf "%s %s FAILED (%d/%d)\n" "$name" "${line:${#name}}" $failed $tests 163 | fi 164 | 165 | return $failed 166 | } 167 | 168 | 169 | n_tests=0 170 | n_fails=0 171 | 172 | select_tests="$@" 173 | 174 | use_test() { 175 | local input="$(readlink -f "$1")" 176 | local test 177 | 178 | [ -f "$input" ] || return 1 179 | [ -n "$select_tests" ] || return 0 180 | 181 | for test in "$select_tests"; do 182 | test="$(readlink -f "$test")" 183 | 184 | [ "$test" != "$input" ] || return 0 185 | done 186 | 187 | return 1 188 | } 189 | 190 | for catdir in tests/[0-9][0-9]_*; do 191 | [ -d "$catdir" ] || continue 192 | 193 | printf "\n##\n## Running %s tests\n##\n\n" "${catdir##*/[0-9][0-9]_}" 194 | 195 | for testfile in "$catdir/"[0-9][0-9]_*; do 196 | use_test "$testfile" || continue 197 | 198 | n_tests=$((n_tests + 1)) 199 | run_test "$testfile" || n_fails=$((n_fails + 1)) 200 | done 201 | done 202 | 203 | printf "\nRan %d tests, %d okay, %d failures\n" $n_tests $((n_tests - n_fails)) $n_fails 204 | exit $n_fails 205 | -------------------------------------------------------------------------------- /tests/01_configuration/02_rule_order: -------------------------------------------------------------------------------- 1 | Testing that `config rule` rules are rendered before `config forwarding` ones 2 | and that rules are rendered in the order they're declared. 3 | 4 | -- Testcase -- 5 | {% 6 | include("./root/usr/share/firewall4/main.uc", { 7 | TRACE_CALLS: "stderr", 8 | 9 | getenv: function(varname) { 10 | switch (varname) { 11 | case 'ACTION': 12 | return 'print'; 13 | } 14 | } 15 | }) 16 | %} 17 | -- End -- 18 | 19 | -- File uci/helpers.json -- 20 | {} 21 | -- End -- 22 | 23 | -- File uci/firewall.json -- 24 | { 25 | "zone": [ 26 | { 27 | "name": "lan", 28 | "network": "lan", 29 | "auto_helper": 0 30 | }, 31 | { 32 | "name": "wan", 33 | "network": "wan", 34 | "auto_helper": 0 35 | } 36 | ], 37 | "forwarding": [ 38 | { 39 | "src": "lan", 40 | "dest": "wan" 41 | } 42 | ], 43 | "rule": [ 44 | { 45 | "name": "Deny rule #1", 46 | "proto": "any", 47 | "src": "lan", 48 | "dest": "wan", 49 | "src_ip": [ "192.168.1.2" ], 50 | "target": "drop" 51 | }, 52 | { 53 | "name": "Deny rule #2", 54 | "proto": "icmp", 55 | "src": "lan", 56 | "dest": "wan", 57 | "src_ip": [ "192.168.1.3" ], 58 | "target": "drop" 59 | } 60 | ] 61 | } 62 | -- End -- 63 | 64 | -- Expect stdout -- 65 | table inet fw4 66 | flush table inet fw4 67 | 68 | table inet fw4 { 69 | # 70 | # Defines 71 | # 72 | 73 | define lan_devices = { "br-lan" } 74 | define lan_subnets = { 10.0.0.0/24, 192.168.26.0/24, 2001:db8:1000::/60, fd63:e2f:f706::/60 } 75 | 76 | define wan_devices = { "pppoe-wan" } 77 | define wan_subnets = { 10.11.12.0/24 } 78 | 79 | 80 | # 81 | # User includes 82 | # 83 | 84 | include "/etc/nftables.d/*.nft" 85 | 86 | 87 | # 88 | # Filter rules 89 | # 90 | 91 | chain input { 92 | type filter hook input priority filter; policy drop; 93 | 94 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 95 | 96 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 97 | iifname "br-lan" jump input_lan comment "!fw4: Handle lan IPv4/IPv6 input traffic" 98 | iifname "pppoe-wan" jump input_wan comment "!fw4: Handle wan IPv4/IPv6 input traffic" 99 | } 100 | 101 | chain forward { 102 | type filter hook forward priority filter; policy drop; 103 | 104 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 105 | iifname "br-lan" jump forward_lan comment "!fw4: Handle lan IPv4/IPv6 forward traffic" 106 | iifname "pppoe-wan" jump forward_wan comment "!fw4: Handle wan IPv4/IPv6 forward traffic" 107 | } 108 | 109 | chain output { 110 | type filter hook output priority filter; policy drop; 111 | 112 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 113 | 114 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 115 | oifname "br-lan" jump output_lan comment "!fw4: Handle lan IPv4/IPv6 output traffic" 116 | oifname "pppoe-wan" jump output_wan comment "!fw4: Handle wan IPv4/IPv6 output traffic" 117 | } 118 | 119 | chain prerouting { 120 | type filter hook prerouting priority filter; policy accept; 121 | } 122 | 123 | chain handle_reject { 124 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 125 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 126 | } 127 | 128 | chain input_lan { 129 | jump drop_from_lan 130 | } 131 | 132 | chain output_lan { 133 | jump drop_to_lan 134 | } 135 | 136 | chain forward_lan { 137 | ip saddr 192.168.1.2 counter jump drop_to_wan comment "!fw4: Deny rule #1" 138 | meta l4proto icmp ip saddr 192.168.1.3 counter jump drop_to_wan comment "!fw4: Deny rule #2" 139 | jump accept_to_wan comment "!fw4: Accept lan to wan forwarding" 140 | jump drop_to_lan 141 | } 142 | 143 | chain drop_from_lan { 144 | iifname "br-lan" counter drop comment "!fw4: drop lan IPv4/IPv6 traffic" 145 | } 146 | 147 | chain drop_to_lan { 148 | oifname "br-lan" counter drop comment "!fw4: drop lan IPv4/IPv6 traffic" 149 | } 150 | 151 | chain input_wan { 152 | jump drop_from_wan 153 | } 154 | 155 | chain output_wan { 156 | jump drop_to_wan 157 | } 158 | 159 | chain forward_wan { 160 | jump drop_to_wan 161 | } 162 | 163 | chain accept_to_wan { 164 | oifname "pppoe-wan" counter accept comment "!fw4: accept wan IPv4/IPv6 traffic" 165 | } 166 | 167 | chain drop_from_wan { 168 | iifname "pppoe-wan" counter drop comment "!fw4: drop wan IPv4/IPv6 traffic" 169 | } 170 | 171 | chain drop_to_wan { 172 | oifname "pppoe-wan" counter drop comment "!fw4: drop wan IPv4/IPv6 traffic" 173 | } 174 | 175 | 176 | # 177 | # NAT rules 178 | # 179 | 180 | chain dstnat { 181 | type nat hook prerouting priority dstnat; policy accept; 182 | } 183 | 184 | chain srcnat { 185 | type nat hook postrouting priority srcnat; policy accept; 186 | } 187 | 188 | 189 | # 190 | # Raw rules (notrack) 191 | # 192 | 193 | chain raw_prerouting { 194 | type filter hook prerouting priority raw; policy accept; 195 | } 196 | 197 | chain raw_output { 198 | type filter hook output priority raw; policy accept; 199 | } 200 | 201 | 202 | # 203 | # Mangle rules 204 | # 205 | 206 | chain mangle_prerouting { 207 | type filter hook prerouting priority mangle; policy accept; 208 | } 209 | 210 | chain mangle_postrouting { 211 | type filter hook postrouting priority mangle; policy accept; 212 | } 213 | 214 | chain mangle_input { 215 | type filter hook input priority mangle; policy accept; 216 | } 217 | 218 | chain mangle_output { 219 | type route hook output priority mangle; policy accept; 220 | } 221 | 222 | chain mangle_forward { 223 | type filter hook forward priority mangle; policy accept; 224 | } 225 | } 226 | -- End -- 227 | 228 | -- Expect stderr -- 229 | [call] ctx.call object method args 230 | [call] ctx.call object method args <{ "type": "firewall" }> 231 | [call] fs.open path mode 232 | [call] fs.glob pattern 233 | [call] fs.glob pattern 234 | [call] fs.glob pattern 235 | [call] fs.glob pattern 236 | [call] fs.lsdir path 237 | [call] fs.lsdir path 238 | [call] fs.popen cmdline mode 239 | -- End -- 240 | -------------------------------------------------------------------------------- /tests/02_zones/01_policies: -------------------------------------------------------------------------------- 1 | Testing that zone policies are properly mapped to chains. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File fs/open~_sys_class_net_zone1_flags.txt -- 21 | 0x1103 22 | -- End -- 23 | 24 | -- File fs/open~_sys_class_net_zone2_flags.txt -- 25 | 0x1103 26 | -- End -- 27 | 28 | -- File fs/open~_sys_class_net_zone3_flags.txt -- 29 | 0x1103 30 | -- End -- 31 | 32 | -- File uci/firewall.json -- 33 | { 34 | "zone": [ 35 | { 36 | ".description": "Zone accept policies should map to accept rules from/to covered interfaces", 37 | "name": "test1", 38 | "input": "ACCEPT", 39 | "output": "ACCEPT", 40 | "forward": "ACCEPT", 41 | "device": "zone1" 42 | }, 43 | { 44 | ".description": "Zone drop policies should map to drop rules from/to covered interfaces", 45 | "name": "test2", 46 | "input": "DROP", 47 | "output": "DROP", 48 | "forward": "DROP", 49 | "device": "zone2" 50 | }, 51 | { 52 | ".description": "Zone reject policies should map to reject rules from/to covered interfaces", 53 | "name": "test3", 54 | "input": "REJECT", 55 | "output": "REJECT", 56 | "forward": "REJECT", 57 | "device": "zone3" 58 | } 59 | ] 60 | } 61 | -- End -- 62 | 63 | -- Expect stdout -- 64 | table inet fw4 65 | flush table inet fw4 66 | 67 | table inet fw4 { 68 | # 69 | # Defines 70 | # 71 | 72 | define test1_devices = { "zone1" } 73 | define test1_subnets = { } 74 | 75 | define test2_devices = { "zone2" } 76 | define test2_subnets = { } 77 | 78 | define test3_devices = { "zone3" } 79 | define test3_subnets = { } 80 | 81 | 82 | # 83 | # User includes 84 | # 85 | 86 | include "/etc/nftables.d/*.nft" 87 | 88 | 89 | # 90 | # Filter rules 91 | # 92 | 93 | chain input { 94 | type filter hook input priority filter; policy drop; 95 | 96 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 97 | 98 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 99 | iifname "zone1" jump input_test1 comment "!fw4: Handle test1 IPv4/IPv6 input traffic" 100 | iifname "zone2" jump input_test2 comment "!fw4: Handle test2 IPv4/IPv6 input traffic" 101 | iifname "zone3" jump input_test3 comment "!fw4: Handle test3 IPv4/IPv6 input traffic" 102 | } 103 | 104 | chain forward { 105 | type filter hook forward priority filter; policy drop; 106 | 107 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 108 | iifname "zone1" jump forward_test1 comment "!fw4: Handle test1 IPv4/IPv6 forward traffic" 109 | iifname "zone2" jump forward_test2 comment "!fw4: Handle test2 IPv4/IPv6 forward traffic" 110 | iifname "zone3" jump forward_test3 comment "!fw4: Handle test3 IPv4/IPv6 forward traffic" 111 | } 112 | 113 | chain output { 114 | type filter hook output priority filter; policy drop; 115 | 116 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 117 | 118 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 119 | oifname "zone1" jump output_test1 comment "!fw4: Handle test1 IPv4/IPv6 output traffic" 120 | oifname "zone2" jump output_test2 comment "!fw4: Handle test2 IPv4/IPv6 output traffic" 121 | oifname "zone3" jump output_test3 comment "!fw4: Handle test3 IPv4/IPv6 output traffic" 122 | } 123 | 124 | chain prerouting { 125 | type filter hook prerouting priority filter; policy accept; 126 | iifname "zone1" jump helper_test1 comment "!fw4: Handle test1 IPv4/IPv6 helper assignment" 127 | iifname "zone2" jump helper_test2 comment "!fw4: Handle test2 IPv4/IPv6 helper assignment" 128 | iifname "zone3" jump helper_test3 comment "!fw4: Handle test3 IPv4/IPv6 helper assignment" 129 | } 130 | 131 | chain handle_reject { 132 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 133 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 134 | } 135 | 136 | chain input_test1 { 137 | jump accept_from_test1 138 | } 139 | 140 | chain output_test1 { 141 | jump accept_to_test1 142 | } 143 | 144 | chain forward_test1 { 145 | jump accept_to_test1 146 | } 147 | 148 | chain helper_test1 { 149 | } 150 | 151 | chain accept_from_test1 { 152 | iifname "zone1" counter accept comment "!fw4: accept test1 IPv4/IPv6 traffic" 153 | } 154 | 155 | chain accept_to_test1 { 156 | oifname "zone1" counter accept comment "!fw4: accept test1 IPv4/IPv6 traffic" 157 | } 158 | 159 | chain input_test2 { 160 | jump drop_from_test2 161 | } 162 | 163 | chain output_test2 { 164 | jump drop_to_test2 165 | } 166 | 167 | chain forward_test2 { 168 | jump drop_to_test2 169 | } 170 | 171 | chain helper_test2 { 172 | } 173 | 174 | chain drop_from_test2 { 175 | iifname "zone2" counter drop comment "!fw4: drop test2 IPv4/IPv6 traffic" 176 | } 177 | 178 | chain drop_to_test2 { 179 | oifname "zone2" counter drop comment "!fw4: drop test2 IPv4/IPv6 traffic" 180 | } 181 | 182 | chain input_test3 { 183 | jump reject_from_test3 184 | } 185 | 186 | chain output_test3 { 187 | jump reject_to_test3 188 | } 189 | 190 | chain forward_test3 { 191 | jump reject_to_test3 192 | } 193 | 194 | chain helper_test3 { 195 | } 196 | 197 | chain reject_from_test3 { 198 | iifname "zone3" counter jump handle_reject comment "!fw4: reject test3 IPv4/IPv6 traffic" 199 | } 200 | 201 | chain reject_to_test3 { 202 | oifname "zone3" counter jump handle_reject comment "!fw4: reject test3 IPv4/IPv6 traffic" 203 | } 204 | 205 | 206 | # 207 | # NAT rules 208 | # 209 | 210 | chain dstnat { 211 | type nat hook prerouting priority dstnat; policy accept; 212 | } 213 | 214 | chain srcnat { 215 | type nat hook postrouting priority srcnat; policy accept; 216 | } 217 | 218 | 219 | # 220 | # Raw rules (notrack) 221 | # 222 | 223 | chain raw_prerouting { 224 | type filter hook prerouting priority raw; policy accept; 225 | } 226 | 227 | chain raw_output { 228 | type filter hook output priority raw; policy accept; 229 | } 230 | 231 | 232 | # 233 | # Mangle rules 234 | # 235 | 236 | chain mangle_prerouting { 237 | type filter hook prerouting priority mangle; policy accept; 238 | } 239 | 240 | chain mangle_postrouting { 241 | type filter hook postrouting priority mangle; policy accept; 242 | } 243 | 244 | chain mangle_input { 245 | type filter hook input priority mangle; policy accept; 246 | } 247 | 248 | chain mangle_output { 249 | type route hook output priority mangle; policy accept; 250 | } 251 | 252 | chain mangle_forward { 253 | type filter hook forward priority mangle; policy accept; 254 | } 255 | } 256 | -- End -- 257 | -------------------------------------------------------------------------------- /tests/02_zones/02_masq: -------------------------------------------------------------------------------- 1 | Testing that zone masquerading is properly mapped to chains. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File fs/open~_sys_class_net_zone1_flags.txt -- 21 | 0x1103 22 | -- End -- 23 | 24 | -- File fs/open~_sys_class_net_zone2_flags.txt -- 25 | 0x1103 26 | -- End -- 27 | 28 | -- File fs/open~_sys_class_net_zone3_flags.txt -- 29 | 0x1103 30 | -- End -- 31 | 32 | -- File uci/firewall.json -- 33 | { 34 | "zone": [ 35 | { 36 | ".description": "Setting masq to true should emit an IPv4 masquerading rule and inhibit default helper assignment", 37 | "name": "test1", 38 | "input": "ACCEPT", 39 | "output": "ACCEPT", 40 | "forward": "ACCEPT", 41 | "device": "zone1", 42 | "masq": "1" 43 | }, 44 | { 45 | ".description": "Setting masq6 to true should emit an IPv6 masquerading rule and inhibit default helper assignment", 46 | "name": "test2", 47 | "input": "DROP", 48 | "output": "DROP", 49 | "forward": "DROP", 50 | "device": "zone2", 51 | "masq6": "1" 52 | }, 53 | { 54 | ".description": "Setting both masq and masq6 should emit IPv4 and IPv6 masquerading and inhibit default helper assignment", 55 | "name": "test3", 56 | "input": "REJECT", 57 | "output": "REJECT", 58 | "forward": "REJECT", 59 | "device": "zone3", 60 | "masq": "1", 61 | "masq6": "1" 62 | } 63 | ] 64 | } 65 | -- End -- 66 | 67 | -- Expect stdout -- 68 | table inet fw4 69 | flush table inet fw4 70 | 71 | table inet fw4 { 72 | # 73 | # Defines 74 | # 75 | 76 | define test1_devices = { "zone1" } 77 | define test1_subnets = { } 78 | 79 | define test2_devices = { "zone2" } 80 | define test2_subnets = { } 81 | 82 | define test3_devices = { "zone3" } 83 | define test3_subnets = { } 84 | 85 | 86 | # 87 | # User includes 88 | # 89 | 90 | include "/etc/nftables.d/*.nft" 91 | 92 | 93 | # 94 | # Filter rules 95 | # 96 | 97 | chain input { 98 | type filter hook input priority filter; policy drop; 99 | 100 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 101 | 102 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 103 | iifname "zone1" jump input_test1 comment "!fw4: Handle test1 IPv4/IPv6 input traffic" 104 | iifname "zone2" jump input_test2 comment "!fw4: Handle test2 IPv4/IPv6 input traffic" 105 | iifname "zone3" jump input_test3 comment "!fw4: Handle test3 IPv4/IPv6 input traffic" 106 | } 107 | 108 | chain forward { 109 | type filter hook forward priority filter; policy drop; 110 | 111 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 112 | iifname "zone1" jump forward_test1 comment "!fw4: Handle test1 IPv4/IPv6 forward traffic" 113 | iifname "zone2" jump forward_test2 comment "!fw4: Handle test2 IPv4/IPv6 forward traffic" 114 | iifname "zone3" jump forward_test3 comment "!fw4: Handle test3 IPv4/IPv6 forward traffic" 115 | } 116 | 117 | chain output { 118 | type filter hook output priority filter; policy drop; 119 | 120 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 121 | 122 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 123 | oifname "zone1" jump output_test1 comment "!fw4: Handle test1 IPv4/IPv6 output traffic" 124 | oifname "zone2" jump output_test2 comment "!fw4: Handle test2 IPv4/IPv6 output traffic" 125 | oifname "zone3" jump output_test3 comment "!fw4: Handle test3 IPv4/IPv6 output traffic" 126 | } 127 | 128 | chain prerouting { 129 | type filter hook prerouting priority filter; policy accept; 130 | } 131 | 132 | chain handle_reject { 133 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 134 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 135 | } 136 | 137 | chain input_test1 { 138 | jump accept_from_test1 139 | } 140 | 141 | chain output_test1 { 142 | jump accept_to_test1 143 | } 144 | 145 | chain forward_test1 { 146 | jump accept_to_test1 147 | } 148 | 149 | chain accept_from_test1 { 150 | iifname "zone1" counter accept comment "!fw4: accept test1 IPv4/IPv6 traffic" 151 | } 152 | 153 | chain accept_to_test1 { 154 | meta nfproto ipv4 oifname "zone1" ct state invalid counter drop comment "!fw4: Prevent NAT leakage" 155 | oifname "zone1" counter accept comment "!fw4: accept test1 IPv4/IPv6 traffic" 156 | } 157 | 158 | chain input_test2 { 159 | jump drop_from_test2 160 | } 161 | 162 | chain output_test2 { 163 | jump drop_to_test2 164 | } 165 | 166 | chain forward_test2 { 167 | jump drop_to_test2 168 | } 169 | 170 | chain drop_from_test2 { 171 | iifname "zone2" counter drop comment "!fw4: drop test2 IPv4/IPv6 traffic" 172 | } 173 | 174 | chain drop_to_test2 { 175 | oifname "zone2" counter drop comment "!fw4: drop test2 IPv4/IPv6 traffic" 176 | } 177 | 178 | chain input_test3 { 179 | jump reject_from_test3 180 | } 181 | 182 | chain output_test3 { 183 | jump reject_to_test3 184 | } 185 | 186 | chain forward_test3 { 187 | jump reject_to_test3 188 | } 189 | 190 | chain reject_from_test3 { 191 | iifname "zone3" counter jump handle_reject comment "!fw4: reject test3 IPv4/IPv6 traffic" 192 | } 193 | 194 | chain reject_to_test3 { 195 | oifname "zone3" counter jump handle_reject comment "!fw4: reject test3 IPv4/IPv6 traffic" 196 | } 197 | 198 | 199 | # 200 | # NAT rules 201 | # 202 | 203 | chain dstnat { 204 | type nat hook prerouting priority dstnat; policy accept; 205 | } 206 | 207 | chain srcnat { 208 | type nat hook postrouting priority srcnat; policy accept; 209 | oifname "zone1" jump srcnat_test1 comment "!fw4: Handle test1 IPv4/IPv6 srcnat traffic" 210 | oifname "zone2" jump srcnat_test2 comment "!fw4: Handle test2 IPv4/IPv6 srcnat traffic" 211 | oifname "zone3" jump srcnat_test3 comment "!fw4: Handle test3 IPv4/IPv6 srcnat traffic" 212 | } 213 | 214 | chain srcnat_test1 { 215 | meta nfproto ipv4 masquerade comment "!fw4: Masquerade IPv4 test1 traffic" 216 | } 217 | 218 | chain srcnat_test2 { 219 | meta nfproto ipv6 masquerade comment "!fw4: Masquerade IPv6 test2 traffic" 220 | } 221 | 222 | chain srcnat_test3 { 223 | meta nfproto ipv4 masquerade comment "!fw4: Masquerade IPv4 test3 traffic" 224 | meta nfproto ipv6 masquerade comment "!fw4: Masquerade IPv6 test3 traffic" 225 | } 226 | 227 | 228 | # 229 | # Raw rules (notrack) 230 | # 231 | 232 | chain raw_prerouting { 233 | type filter hook prerouting priority raw; policy accept; 234 | } 235 | 236 | chain raw_output { 237 | type filter hook output priority raw; policy accept; 238 | } 239 | 240 | 241 | # 242 | # Mangle rules 243 | # 244 | 245 | chain mangle_prerouting { 246 | type filter hook prerouting priority mangle; policy accept; 247 | } 248 | 249 | chain mangle_postrouting { 250 | type filter hook postrouting priority mangle; policy accept; 251 | } 252 | 253 | chain mangle_input { 254 | type filter hook input priority mangle; policy accept; 255 | } 256 | 257 | chain mangle_output { 258 | type route hook output priority mangle; policy accept; 259 | } 260 | 261 | chain mangle_forward { 262 | type filter hook forward priority mangle; policy accept; 263 | } 264 | } 265 | -- End -- 266 | -------------------------------------------------------------------------------- /tests/02_zones/03_masq_src_dest_restrictions: -------------------------------------------------------------------------------- 1 | Testing that zone masquerading restrictions source and destination restrictions are properly applied. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File fs/open~_sys_class_net_zone1_flags.txt -- 21 | 0x1103 22 | -- End -- 23 | 24 | -- File fs/open~_sys_class_net_zone2_flags.txt -- 25 | 0x1103 26 | -- End -- 27 | 28 | -- File uci/firewall.json -- 29 | { 30 | "zone": [ 31 | { 32 | ".description": "Positive and negative entries should be handled properly and IPv6 addresses should be filtered out for IPv4 masquerading", 33 | "name": "test1", 34 | "input": "ACCEPT", 35 | "output": "ACCEPT", 36 | "forward": "ACCEPT", 37 | "device": "zone1", 38 | "masq": "1", 39 | "masq_src": [ 40 | "10.1.0.0/24", 41 | "10.1.1.1", 42 | "!10.1.0.1", 43 | "!10.1.0.2", 44 | "2001:db8:0:1::/64", 45 | "2001:db8:0:2::/64", 46 | "!2001:db8:0:1::1", 47 | "!2001:db8:0:1::2" 48 | ], 49 | "masq_dest": [ 50 | "10.2.0.0/24", 51 | "10.2.1.1", 52 | "!10.2.0.1", 53 | "!10.2.0.2", 54 | "2001:db8:1:1::/64", 55 | "2001:db8:1:2::/64", 56 | "!2001:db8:1:1::1", 57 | "!2001:db8:1:1::2" 58 | ] 59 | }, 60 | { 61 | ".description": "Positive and negative entries should be handled properly and IPv4 addresses should be filtered out for IPv6 masquerading", 62 | "name": "test2", 63 | "input": "DROP", 64 | "output": "DROP", 65 | "forward": "DROP", 66 | "device": "zone2", 67 | "masq6": "1", 68 | "masq_src": [ 69 | "10.1.0.0/24", 70 | "10.1.1.1", 71 | "!10.1.0.1", 72 | "!10.1.0.2", 73 | "2001:db8:0:1::/64", 74 | "2001:db8:0:2::/64", 75 | "!2001:db8:0:1::1", 76 | "!2001:db8:0:1::2" 77 | ], 78 | "masq_dest": [ 79 | "10.2.0.0/24", 80 | "10.2.1.1", 81 | "!10.2.0.1", 82 | "!10.2.0.2", 83 | "2001:db8:1:1::/64", 84 | "2001:db8:1:2::/64", 85 | "!2001:db8:1:1::1", 86 | "!2001:db8:1:1::2" 87 | ] 88 | } 89 | ] 90 | } 91 | -- End -- 92 | 93 | -- Expect stdout -- 94 | table inet fw4 95 | flush table inet fw4 96 | 97 | table inet fw4 { 98 | # 99 | # Defines 100 | # 101 | 102 | define test1_devices = { "zone1" } 103 | define test1_subnets = { } 104 | 105 | define test2_devices = { "zone2" } 106 | define test2_subnets = { } 107 | 108 | 109 | # 110 | # User includes 111 | # 112 | 113 | include "/etc/nftables.d/*.nft" 114 | 115 | 116 | # 117 | # Filter rules 118 | # 119 | 120 | chain input { 121 | type filter hook input priority filter; policy drop; 122 | 123 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 124 | 125 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 126 | iifname "zone1" jump input_test1 comment "!fw4: Handle test1 IPv4/IPv6 input traffic" 127 | iifname "zone2" jump input_test2 comment "!fw4: Handle test2 IPv4/IPv6 input traffic" 128 | } 129 | 130 | chain forward { 131 | type filter hook forward priority filter; policy drop; 132 | 133 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 134 | iifname "zone1" jump forward_test1 comment "!fw4: Handle test1 IPv4/IPv6 forward traffic" 135 | iifname "zone2" jump forward_test2 comment "!fw4: Handle test2 IPv4/IPv6 forward traffic" 136 | } 137 | 138 | chain output { 139 | type filter hook output priority filter; policy drop; 140 | 141 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 142 | 143 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 144 | oifname "zone1" jump output_test1 comment "!fw4: Handle test1 IPv4/IPv6 output traffic" 145 | oifname "zone2" jump output_test2 comment "!fw4: Handle test2 IPv4/IPv6 output traffic" 146 | } 147 | 148 | chain prerouting { 149 | type filter hook prerouting priority filter; policy accept; 150 | } 151 | 152 | chain handle_reject { 153 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 154 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 155 | } 156 | 157 | chain input_test1 { 158 | jump accept_from_test1 159 | } 160 | 161 | chain output_test1 { 162 | jump accept_to_test1 163 | } 164 | 165 | chain forward_test1 { 166 | jump accept_to_test1 167 | } 168 | 169 | chain accept_from_test1 { 170 | iifname "zone1" counter accept comment "!fw4: accept test1 IPv4/IPv6 traffic" 171 | } 172 | 173 | chain accept_to_test1 { 174 | meta nfproto ipv4 oifname "zone1" ct state invalid counter drop comment "!fw4: Prevent NAT leakage" 175 | oifname "zone1" counter accept comment "!fw4: accept test1 IPv4/IPv6 traffic" 176 | } 177 | 178 | chain input_test2 { 179 | jump drop_from_test2 180 | } 181 | 182 | chain output_test2 { 183 | jump drop_to_test2 184 | } 185 | 186 | chain forward_test2 { 187 | jump drop_to_test2 188 | } 189 | 190 | chain drop_from_test2 { 191 | iifname "zone2" counter drop comment "!fw4: drop test2 IPv4/IPv6 traffic" 192 | } 193 | 194 | chain drop_to_test2 { 195 | oifname "zone2" counter drop comment "!fw4: drop test2 IPv4/IPv6 traffic" 196 | } 197 | 198 | 199 | # 200 | # NAT rules 201 | # 202 | 203 | chain dstnat { 204 | type nat hook prerouting priority dstnat; policy accept; 205 | } 206 | 207 | chain srcnat { 208 | type nat hook postrouting priority srcnat; policy accept; 209 | oifname "zone1" jump srcnat_test1 comment "!fw4: Handle test1 IPv4/IPv6 srcnat traffic" 210 | oifname "zone2" jump srcnat_test2 comment "!fw4: Handle test2 IPv4/IPv6 srcnat traffic" 211 | } 212 | 213 | chain srcnat_test1 { 214 | meta nfproto ipv4 ip saddr { 10.1.0.0/24, 10.1.1.1 } ip saddr != { 10.1.0.1, 10.1.0.2 } ip daddr { 10.2.0.0/24, 10.2.1.1 } ip daddr != { 10.2.0.1, 10.2.0.2 } masquerade comment "!fw4: Masquerade IPv4 test1 traffic" 215 | } 216 | 217 | chain srcnat_test2 { 218 | meta nfproto ipv6 ip6 saddr { 2001:db8:0:1::/64, 2001:db8:0:2::/64 } ip6 saddr != { 2001:db8:0:1::1, 2001:db8:0:1::2 } ip6 daddr { 2001:db8:1:1::/64, 2001:db8:1:2::/64 } ip6 daddr != { 2001:db8:1:1::1, 2001:db8:1:1::2 } masquerade comment "!fw4: Masquerade IPv6 test2 traffic" 219 | } 220 | 221 | 222 | # 223 | # Raw rules (notrack) 224 | # 225 | 226 | chain raw_prerouting { 227 | type filter hook prerouting priority raw; policy accept; 228 | } 229 | 230 | chain raw_output { 231 | type filter hook output priority raw; policy accept; 232 | } 233 | 234 | 235 | # 236 | # Mangle rules 237 | # 238 | 239 | chain mangle_prerouting { 240 | type filter hook prerouting priority mangle; policy accept; 241 | } 242 | 243 | chain mangle_postrouting { 244 | type filter hook postrouting priority mangle; policy accept; 245 | } 246 | 247 | chain mangle_input { 248 | type filter hook input priority mangle; policy accept; 249 | } 250 | 251 | chain mangle_output { 252 | type route hook output priority mangle; policy accept; 253 | } 254 | 255 | chain mangle_forward { 256 | type filter hook forward priority mangle; policy accept; 257 | } 258 | } 259 | -- End -- 260 | -------------------------------------------------------------------------------- /tests/02_zones/04_masq_allow_invalid: -------------------------------------------------------------------------------- 1 | Testing that dropping of invalid conntrack state traffic can be inhibited. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File fs/open~_sys_class_net_zone1_flags.txt -- 21 | 0x1103 22 | -- End -- 23 | 24 | -- File fs/open~_sys_class_net_zone2_flags.txt -- 25 | 0x1103 26 | -- End -- 27 | 28 | -- File uci/firewall.json -- 29 | { 30 | "zone": [ 31 | { 32 | ".description": "No ct state invalid drop rule should be generated", 33 | "name": "test1", 34 | "input": "ACCEPT", 35 | "output": "ACCEPT", 36 | "forward": "ACCEPT", 37 | "device": "zone1", 38 | "masq": "1", 39 | "masq_allow_invalid": 1 40 | } 41 | ] 42 | } 43 | -- End -- 44 | 45 | -- Expect stdout -- 46 | table inet fw4 47 | flush table inet fw4 48 | 49 | table inet fw4 { 50 | # 51 | # Defines 52 | # 53 | 54 | define test1_devices = { "zone1" } 55 | define test1_subnets = { } 56 | 57 | 58 | # 59 | # User includes 60 | # 61 | 62 | include "/etc/nftables.d/*.nft" 63 | 64 | 65 | # 66 | # Filter rules 67 | # 68 | 69 | chain input { 70 | type filter hook input priority filter; policy drop; 71 | 72 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 73 | 74 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 75 | iifname "zone1" jump input_test1 comment "!fw4: Handle test1 IPv4/IPv6 input traffic" 76 | } 77 | 78 | chain forward { 79 | type filter hook forward priority filter; policy drop; 80 | 81 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 82 | iifname "zone1" jump forward_test1 comment "!fw4: Handle test1 IPv4/IPv6 forward traffic" 83 | } 84 | 85 | chain output { 86 | type filter hook output priority filter; policy drop; 87 | 88 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 89 | 90 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 91 | oifname "zone1" jump output_test1 comment "!fw4: Handle test1 IPv4/IPv6 output traffic" 92 | } 93 | 94 | chain prerouting { 95 | type filter hook prerouting priority filter; policy accept; 96 | } 97 | 98 | chain handle_reject { 99 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 100 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 101 | } 102 | 103 | chain input_test1 { 104 | jump accept_from_test1 105 | } 106 | 107 | chain output_test1 { 108 | jump accept_to_test1 109 | } 110 | 111 | chain forward_test1 { 112 | jump accept_to_test1 113 | } 114 | 115 | chain accept_from_test1 { 116 | iifname "zone1" counter accept comment "!fw4: accept test1 IPv4/IPv6 traffic" 117 | } 118 | 119 | chain accept_to_test1 { 120 | oifname "zone1" counter accept comment "!fw4: accept test1 IPv4/IPv6 traffic" 121 | } 122 | 123 | 124 | # 125 | # NAT rules 126 | # 127 | 128 | chain dstnat { 129 | type nat hook prerouting priority dstnat; policy accept; 130 | } 131 | 132 | chain srcnat { 133 | type nat hook postrouting priority srcnat; policy accept; 134 | oifname "zone1" jump srcnat_test1 comment "!fw4: Handle test1 IPv4/IPv6 srcnat traffic" 135 | } 136 | 137 | chain srcnat_test1 { 138 | meta nfproto ipv4 masquerade comment "!fw4: Masquerade IPv4 test1 traffic" 139 | } 140 | 141 | 142 | # 143 | # Raw rules (notrack) 144 | # 145 | 146 | chain raw_prerouting { 147 | type filter hook prerouting priority raw; policy accept; 148 | } 149 | 150 | chain raw_output { 151 | type filter hook output priority raw; policy accept; 152 | } 153 | 154 | 155 | # 156 | # Mangle rules 157 | # 158 | 159 | chain mangle_prerouting { 160 | type filter hook prerouting priority mangle; policy accept; 161 | } 162 | 163 | chain mangle_postrouting { 164 | type filter hook postrouting priority mangle; policy accept; 165 | } 166 | 167 | chain mangle_input { 168 | type filter hook input priority mangle; policy accept; 169 | } 170 | 171 | chain mangle_output { 172 | type route hook output priority mangle; policy accept; 173 | } 174 | 175 | chain mangle_forward { 176 | type filter hook forward priority mangle; policy accept; 177 | } 178 | } 179 | -- End -- 180 | -------------------------------------------------------------------------------- /tests/02_zones/05_subnet_mask_matches: -------------------------------------------------------------------------------- 1 | Test that non-contiguous subnet masks are properly handled. Such masks need 2 | to be translated into bitwise expressions which may not appear as part of 3 | sets, so various permutations of rules need to be emitted. 4 | 5 | -- Testcase -- 6 | {% 7 | include("./root/usr/share/firewall4/main.uc", { 8 | getenv: function(varname) { 9 | switch (varname) { 10 | case 'ACTION': 11 | return 'print'; 12 | } 13 | } 14 | }) 15 | %} 16 | -- End -- 17 | 18 | -- File uci/helpers.json -- 19 | {} 20 | -- End -- 21 | 22 | -- File uci/firewall.json -- 23 | { 24 | "zone": [ 25 | { 26 | ".description": "IP addrs with non-contiguous masks should be translated to bitwise comparisons", 27 | "name": "test1", 28 | "subnet": [ 29 | "::1/::ffff", 30 | "!::2/::ffff" 31 | ] 32 | }, 33 | 34 | { 35 | ".description": "IP addrs with non-contiguous masks should not be part of sets", 36 | "name": "test2", 37 | "subnet": [ 38 | "::1/::ffff", 39 | "::2/::ffff", 40 | "::3/128", 41 | "::4/128", 42 | "!::5/::ffff", 43 | "!::6/::ffff", 44 | "!::7/128", 45 | "!::8/128" 46 | ] 47 | } 48 | ] 49 | } 50 | -- End -- 51 | 52 | -- Expect stdout -- 53 | table inet fw4 54 | flush table inet fw4 55 | 56 | table inet fw4 { 57 | # 58 | # Defines 59 | # 60 | 61 | define test1_devices = { } 62 | define test1_subnets = { } 63 | 64 | define test2_devices = { } 65 | define test2_subnets = { ::3, ::4 } 66 | 67 | 68 | # 69 | # User includes 70 | # 71 | 72 | include "/etc/nftables.d/*.nft" 73 | 74 | 75 | # 76 | # Filter rules 77 | # 78 | 79 | chain input { 80 | type filter hook input priority filter; policy drop; 81 | 82 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 83 | 84 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 85 | meta nfproto ipv6 ip6 saddr & ::ffff == ::1 ip6 saddr & ::ffff != ::2 jump input_test1 comment "!fw4: Handle test1 IPv6 input traffic" 86 | meta nfproto ipv6 ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff == ::1 ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 jump input_test2 comment "!fw4: Handle test2 IPv6 input traffic" 87 | meta nfproto ipv6 ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff == ::2 ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 jump input_test2 comment "!fw4: Handle test2 IPv6 input traffic" 88 | meta nfproto ipv6 ip6 saddr { ::3, ::4 } ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 jump input_test2 comment "!fw4: Handle test2 IPv6 input traffic" 89 | } 90 | 91 | chain forward { 92 | type filter hook forward priority filter; policy drop; 93 | 94 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 95 | meta nfproto ipv6 ip6 saddr & ::ffff == ::1 ip6 saddr & ::ffff != ::2 jump forward_test1 comment "!fw4: Handle test1 IPv6 forward traffic" 96 | meta nfproto ipv6 ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff == ::1 ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 jump forward_test2 comment "!fw4: Handle test2 IPv6 forward traffic" 97 | meta nfproto ipv6 ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff == ::2 ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 jump forward_test2 comment "!fw4: Handle test2 IPv6 forward traffic" 98 | meta nfproto ipv6 ip6 saddr { ::3, ::4 } ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 jump forward_test2 comment "!fw4: Handle test2 IPv6 forward traffic" 99 | } 100 | 101 | chain output { 102 | type filter hook output priority filter; policy drop; 103 | 104 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 105 | 106 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 107 | meta nfproto ipv6 ip6 daddr & ::ffff == ::1 ip6 daddr & ::ffff != ::2 jump output_test1 comment "!fw4: Handle test1 IPv6 output traffic" 108 | meta nfproto ipv6 ip6 daddr != { ::7, ::8 } ip6 daddr & ::ffff == ::1 ip6 daddr & ::ffff != ::5 ip6 daddr & ::ffff != ::6 jump output_test2 comment "!fw4: Handle test2 IPv6 output traffic" 109 | meta nfproto ipv6 ip6 daddr != { ::7, ::8 } ip6 daddr & ::ffff == ::2 ip6 daddr & ::ffff != ::5 ip6 daddr & ::ffff != ::6 jump output_test2 comment "!fw4: Handle test2 IPv6 output traffic" 110 | meta nfproto ipv6 ip6 daddr { ::3, ::4 } ip6 daddr != { ::7, ::8 } ip6 daddr & ::ffff != ::5 ip6 daddr & ::ffff != ::6 jump output_test2 comment "!fw4: Handle test2 IPv6 output traffic" 111 | } 112 | 113 | chain prerouting { 114 | type filter hook prerouting priority filter; policy accept; 115 | meta nfproto ipv6 ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff == ::1 ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 jump helper_test2 comment "!fw4: Handle test2 IPv6 helper assignment" 116 | meta nfproto ipv6 ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff == ::2 ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 jump helper_test2 comment "!fw4: Handle test2 IPv6 helper assignment" 117 | meta nfproto ipv6 ip6 saddr { ::3, ::4 } ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 jump helper_test2 comment "!fw4: Handle test2 IPv6 helper assignment" 118 | } 119 | 120 | chain handle_reject { 121 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 122 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 123 | } 124 | 125 | chain input_test1 { 126 | jump drop_from_test1 127 | } 128 | 129 | chain output_test1 { 130 | jump drop_to_test1 131 | } 132 | 133 | chain forward_test1 { 134 | jump drop_to_test1 135 | } 136 | 137 | chain helper_test1 { 138 | } 139 | 140 | chain drop_from_test1 { 141 | meta nfproto ipv6 ip6 saddr & ::ffff == ::1 ip6 saddr & ::ffff != ::2 counter drop comment "!fw4: drop test1 IPv6 traffic" 142 | } 143 | 144 | chain drop_to_test1 { 145 | meta nfproto ipv6 ip6 daddr & ::ffff == ::1 ip6 daddr & ::ffff != ::2 counter drop comment "!fw4: drop test1 IPv6 traffic" 146 | } 147 | 148 | chain input_test2 { 149 | jump drop_from_test2 150 | } 151 | 152 | chain output_test2 { 153 | jump drop_to_test2 154 | } 155 | 156 | chain forward_test2 { 157 | jump drop_to_test2 158 | } 159 | 160 | chain helper_test2 { 161 | } 162 | 163 | chain drop_from_test2 { 164 | meta nfproto ipv6 ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff == ::1 ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 counter drop comment "!fw4: drop test2 IPv6 traffic" 165 | meta nfproto ipv6 ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff == ::2 ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 counter drop comment "!fw4: drop test2 IPv6 traffic" 166 | meta nfproto ipv6 ip6 saddr { ::3, ::4 } ip6 saddr != { ::7, ::8 } ip6 saddr & ::ffff != ::5 ip6 saddr & ::ffff != ::6 counter drop comment "!fw4: drop test2 IPv6 traffic" 167 | } 168 | 169 | chain drop_to_test2 { 170 | meta nfproto ipv6 ip6 daddr != { ::7, ::8 } ip6 daddr & ::ffff == ::1 ip6 daddr & ::ffff != ::5 ip6 daddr & ::ffff != ::6 counter drop comment "!fw4: drop test2 IPv6 traffic" 171 | meta nfproto ipv6 ip6 daddr != { ::7, ::8 } ip6 daddr & ::ffff == ::2 ip6 daddr & ::ffff != ::5 ip6 daddr & ::ffff != ::6 counter drop comment "!fw4: drop test2 IPv6 traffic" 172 | meta nfproto ipv6 ip6 daddr { ::3, ::4 } ip6 daddr != { ::7, ::8 } ip6 daddr & ::ffff != ::5 ip6 daddr & ::ffff != ::6 counter drop comment "!fw4: drop test2 IPv6 traffic" 173 | } 174 | 175 | 176 | # 177 | # NAT rules 178 | # 179 | 180 | chain dstnat { 181 | type nat hook prerouting priority dstnat; policy accept; 182 | } 183 | 184 | chain srcnat { 185 | type nat hook postrouting priority srcnat; policy accept; 186 | } 187 | 188 | 189 | # 190 | # Raw rules (notrack) 191 | # 192 | 193 | chain raw_prerouting { 194 | type filter hook prerouting priority raw; policy accept; 195 | } 196 | 197 | chain raw_output { 198 | type filter hook output priority raw; policy accept; 199 | } 200 | 201 | 202 | # 203 | # Mangle rules 204 | # 205 | 206 | chain mangle_prerouting { 207 | type filter hook prerouting priority mangle; policy accept; 208 | } 209 | 210 | chain mangle_postrouting { 211 | type filter hook postrouting priority mangle; policy accept; 212 | } 213 | 214 | chain mangle_input { 215 | type filter hook input priority mangle; policy accept; 216 | } 217 | 218 | chain mangle_output { 219 | type route hook output priority mangle; policy accept; 220 | } 221 | 222 | chain mangle_forward { 223 | type filter hook forward priority mangle; policy accept; 224 | } 225 | } 226 | -- End -- 227 | -------------------------------------------------------------------------------- /tests/03_rules/01_direction: -------------------------------------------------------------------------------- 1 | Testing that rule declarations are mapped to the proper chains depending 2 | on src and dest options. 3 | 4 | -- Testcase -- 5 | {% 6 | include("./root/usr/share/firewall4/main.uc", { 7 | getenv: function(varname) { 8 | switch (varname) { 9 | case 'ACTION': 10 | return 'print'; 11 | } 12 | } 13 | }) 14 | %} 15 | -- End -- 16 | 17 | -- File uci/helpers.json -- 18 | {} 19 | -- End -- 20 | 21 | -- File uci/firewall.json -- 22 | { 23 | "rule": [ 24 | { 25 | ".description": "Neither source, nor dest => should result in an output rule", 26 | "proto": "any" 27 | }, 28 | { 29 | ".description": "Source any, no dest => should result in an input rule", 30 | "proto": "any", 31 | "src": "*" 32 | }, 33 | { 34 | ".description": "Dest any, no source => should result in an output rule", 35 | "proto": "any", 36 | "dest": "*" 37 | }, 38 | { 39 | ".description": "Source any, dest any => should result in a forward rule", 40 | "proto": "any", 41 | "src": "*", 42 | "dest": "*" 43 | } 44 | ] 45 | } 46 | -- End -- 47 | 48 | -- Expect stdout -- 49 | table inet fw4 50 | flush table inet fw4 51 | 52 | table inet fw4 { 53 | # 54 | # Defines 55 | # 56 | 57 | 58 | # 59 | # User includes 60 | # 61 | 62 | include "/etc/nftables.d/*.nft" 63 | 64 | 65 | # 66 | # Filter rules 67 | # 68 | 69 | chain input { 70 | type filter hook input priority filter; policy drop; 71 | 72 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 73 | 74 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 75 | counter comment "!fw4: @rule[1]" 76 | } 77 | 78 | chain forward { 79 | type filter hook forward priority filter; policy drop; 80 | 81 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 82 | counter comment "!fw4: @rule[3]" 83 | } 84 | 85 | chain output { 86 | type filter hook output priority filter; policy drop; 87 | 88 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 89 | 90 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 91 | counter comment "!fw4: @rule[0]" 92 | counter comment "!fw4: @rule[2]" 93 | } 94 | 95 | chain prerouting { 96 | type filter hook prerouting priority filter; policy accept; 97 | } 98 | 99 | chain handle_reject { 100 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 101 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 102 | } 103 | 104 | 105 | # 106 | # NAT rules 107 | # 108 | 109 | chain dstnat { 110 | type nat hook prerouting priority dstnat; policy accept; 111 | } 112 | 113 | chain srcnat { 114 | type nat hook postrouting priority srcnat; policy accept; 115 | } 116 | 117 | 118 | # 119 | # Raw rules (notrack) 120 | # 121 | 122 | chain raw_prerouting { 123 | type filter hook prerouting priority raw; policy accept; 124 | } 125 | 126 | chain raw_output { 127 | type filter hook output priority raw; policy accept; 128 | } 129 | 130 | 131 | # 132 | # Mangle rules 133 | # 134 | 135 | chain mangle_prerouting { 136 | type filter hook prerouting priority mangle; policy accept; 137 | } 138 | 139 | chain mangle_postrouting { 140 | type filter hook postrouting priority mangle; policy accept; 141 | } 142 | 143 | chain mangle_input { 144 | type filter hook input priority mangle; policy accept; 145 | } 146 | 147 | chain mangle_output { 148 | type route hook output priority mangle; policy accept; 149 | } 150 | 151 | chain mangle_forward { 152 | type filter hook forward priority mangle; policy accept; 153 | } 154 | } 155 | -- End -- 156 | -------------------------------------------------------------------------------- /tests/03_rules/02_enabled: -------------------------------------------------------------------------------- 1 | Testing that not enabled rules are ignored. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File uci/firewall.json -- 21 | { 22 | "rule": [ 23 | { 24 | "proto": "any", 25 | "name": "Implicitly enabled" 26 | }, 27 | { 28 | "proto": "any", 29 | "name": "Explicitly enabled", 30 | "enabled": "1" 31 | }, 32 | { 33 | "proto": "any", 34 | "name": "Explicitly disabled", 35 | "enabled": "0" 36 | } 37 | ] 38 | } 39 | -- End -- 40 | 41 | -- Expect stderr -- 42 | [!] Section @rule[2] (Explicitly disabled) is disabled, ignoring section 43 | -- End -- 44 | 45 | -- Expect stdout -- 46 | table inet fw4 47 | flush table inet fw4 48 | 49 | table inet fw4 { 50 | # 51 | # Defines 52 | # 53 | 54 | 55 | # 56 | # User includes 57 | # 58 | 59 | include "/etc/nftables.d/*.nft" 60 | 61 | 62 | # 63 | # Filter rules 64 | # 65 | 66 | chain input { 67 | type filter hook input priority filter; policy drop; 68 | 69 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 70 | 71 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 72 | } 73 | 74 | chain forward { 75 | type filter hook forward priority filter; policy drop; 76 | 77 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 78 | } 79 | 80 | chain output { 81 | type filter hook output priority filter; policy drop; 82 | 83 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 84 | 85 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 86 | counter comment "!fw4: Implicitly enabled" 87 | counter comment "!fw4: Explicitly enabled" 88 | } 89 | 90 | chain prerouting { 91 | type filter hook prerouting priority filter; policy accept; 92 | } 93 | 94 | chain handle_reject { 95 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 96 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 97 | } 98 | 99 | 100 | # 101 | # NAT rules 102 | # 103 | 104 | chain dstnat { 105 | type nat hook prerouting priority dstnat; policy accept; 106 | } 107 | 108 | chain srcnat { 109 | type nat hook postrouting priority srcnat; policy accept; 110 | } 111 | 112 | 113 | # 114 | # Raw rules (notrack) 115 | # 116 | 117 | chain raw_prerouting { 118 | type filter hook prerouting priority raw; policy accept; 119 | } 120 | 121 | chain raw_output { 122 | type filter hook output priority raw; policy accept; 123 | } 124 | 125 | 126 | # 127 | # Mangle rules 128 | # 129 | 130 | chain mangle_prerouting { 131 | type filter hook prerouting priority mangle; policy accept; 132 | } 133 | 134 | chain mangle_postrouting { 135 | type filter hook postrouting priority mangle; policy accept; 136 | } 137 | 138 | chain mangle_input { 139 | type filter hook input priority mangle; policy accept; 140 | } 141 | 142 | chain mangle_output { 143 | type route hook output priority mangle; policy accept; 144 | } 145 | 146 | chain mangle_forward { 147 | type filter hook forward priority mangle; policy accept; 148 | } 149 | } 150 | -- End -- 151 | -------------------------------------------------------------------------------- /tests/03_rules/03_constraints: -------------------------------------------------------------------------------- 1 | Testing various option constraints. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File uci/firewall.json -- 21 | { 22 | "zone": [ 23 | { 24 | "name": "lan" 25 | } 26 | ], 27 | "rule": [ 28 | { 29 | ".description": "Helper rules require an explicit source zone", 30 | "proto": "any", 31 | "name": "Helper rule #1", 32 | "target": "helper" 33 | }, 34 | { 35 | ".description": "Helper rules require a set_helper option", 36 | "proto": "any", 37 | "name": "Helper rule #2", 38 | "src": "lan", 39 | "target": "helper" 40 | }, 41 | 42 | { 43 | ".description": "Notrack rules require an explicit source zone", 44 | "proto": "any", 45 | "name": "Notrack rule", 46 | "target": "notrack" 47 | }, 48 | 49 | { 50 | ".description": "DSCP target rules require a set_dscp option", 51 | "proto": "any", 52 | "name": "DSCP target rule #1", 53 | "target": "dscp" 54 | }, 55 | 56 | { 57 | ".description": "DSCP matches enforce AF specific rules due to required ip/ip6 prefix", 58 | "proto": "any", 59 | "name": "DSCP match rule #1", 60 | "dscp": "0x0" 61 | }, 62 | 63 | { 64 | ".description": "Mark rules require a set_xmark or set_mark option", 65 | "proto": "any", 66 | "name": "Mark rule #1", 67 | "target": "mark" 68 | }, 69 | ] 70 | } 71 | -- End -- 72 | 73 | -- Expect stderr -- 74 | [!] Section @rule[0] (Helper rule #1) must specify a source zone for target 'helper' 75 | [!] Section @rule[1] (Helper rule #2) must specify option 'set_helper' for target 'helper' 76 | [!] Section @rule[2] (Notrack rule) must specify a source zone for target 'notrack' 77 | [!] Section @rule[3] (DSCP target rule #1) must specify option 'set_dscp' for target 'dscp' 78 | [!] Section @rule[5] (Mark rule #1) must specify option 'set_mark' or 'set_xmark' for target 'mark' 79 | -- End -- 80 | 81 | -- Expect stdout -- 82 | table inet fw4 83 | flush table inet fw4 84 | 85 | table inet fw4 { 86 | # 87 | # Defines 88 | # 89 | 90 | define lan_devices = { } 91 | define lan_subnets = { } 92 | 93 | 94 | # 95 | # User includes 96 | # 97 | 98 | include "/etc/nftables.d/*.nft" 99 | 100 | 101 | # 102 | # Filter rules 103 | # 104 | 105 | chain input { 106 | type filter hook input priority filter; policy drop; 107 | 108 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 109 | 110 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 111 | } 112 | 113 | chain forward { 114 | type filter hook forward priority filter; policy drop; 115 | 116 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 117 | } 118 | 119 | chain output { 120 | type filter hook output priority filter; policy drop; 121 | 122 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 123 | 124 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 125 | meta nfproto ipv4 ip dscp 0x0 counter comment "!fw4: DSCP match rule #1" 126 | meta nfproto ipv6 ip6 dscp 0x0 counter comment "!fw4: DSCP match rule #1" 127 | } 128 | 129 | chain prerouting { 130 | type filter hook prerouting priority filter; policy accept; 131 | } 132 | 133 | chain handle_reject { 134 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 135 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 136 | } 137 | 138 | chain input_lan { 139 | jump drop_from_lan 140 | } 141 | 142 | chain output_lan { 143 | jump drop_to_lan 144 | } 145 | 146 | chain forward_lan { 147 | jump drop_to_lan 148 | } 149 | 150 | chain helper_lan { 151 | } 152 | 153 | chain drop_from_lan { 154 | } 155 | 156 | chain drop_to_lan { 157 | } 158 | 159 | 160 | # 161 | # NAT rules 162 | # 163 | 164 | chain dstnat { 165 | type nat hook prerouting priority dstnat; policy accept; 166 | } 167 | 168 | chain srcnat { 169 | type nat hook postrouting priority srcnat; policy accept; 170 | } 171 | 172 | 173 | # 174 | # Raw rules (notrack) 175 | # 176 | 177 | chain raw_prerouting { 178 | type filter hook prerouting priority raw; policy accept; 179 | } 180 | 181 | chain raw_output { 182 | type filter hook output priority raw; policy accept; 183 | } 184 | 185 | 186 | # 187 | # Mangle rules 188 | # 189 | 190 | chain mangle_prerouting { 191 | type filter hook prerouting priority mangle; policy accept; 192 | } 193 | 194 | chain mangle_postrouting { 195 | type filter hook postrouting priority mangle; policy accept; 196 | } 197 | 198 | chain mangle_input { 199 | type filter hook input priority mangle; policy accept; 200 | } 201 | 202 | chain mangle_output { 203 | type route hook output priority mangle; policy accept; 204 | } 205 | 206 | chain mangle_forward { 207 | type filter hook forward priority mangle; policy accept; 208 | } 209 | } 210 | -- End -- 211 | -------------------------------------------------------------------------------- /tests/03_rules/04_icmp: -------------------------------------------------------------------------------- 1 | Testing handling of ICMP related options. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File uci/firewall.json -- 21 | { 22 | "rule": [ 23 | { 24 | ".description": "Proto 'icmp' maps to a single IPv4 and IPv6 rule", 25 | "proto": "icmp", 26 | "name": "ICMP rule #1" 27 | }, 28 | { 29 | ".description": "Proto 'icmpv6' maps to IPv6 rule only", 30 | "proto": "icmpv6", 31 | "name": "ICMP rule #2", 32 | }, 33 | { 34 | ".description": "Proto 'ipv6-icmp' is an alias for 'icmpv6'", 35 | "proto": "ipv6-icmp", 36 | "name": "ICMP rule #3", 37 | }, 38 | { 39 | ".description": "Proto 'icmp' with IPv4 specific types inhibits IPv6 rule", 40 | "proto": "icmp", 41 | "name": "ICMP rule #4", 42 | "icmp_type": [ "ip-header-bad" ] 43 | }, 44 | { 45 | ".description": "Proto 'icmp' with IPv6 specific types inhibits IPv4 rule", 46 | "proto": "icmp", 47 | "name": "ICMP rule #5", 48 | "icmp_type": [ "neighbour-advertisement" ] 49 | } 50 | ] 51 | } 52 | -- End -- 53 | 54 | -- Expect stdout -- 55 | table inet fw4 56 | flush table inet fw4 57 | 58 | table inet fw4 { 59 | # 60 | # Defines 61 | # 62 | 63 | 64 | # 65 | # User includes 66 | # 67 | 68 | include "/etc/nftables.d/*.nft" 69 | 70 | 71 | # 72 | # Filter rules 73 | # 74 | 75 | chain input { 76 | type filter hook input priority filter; policy drop; 77 | 78 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 79 | 80 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 81 | } 82 | 83 | chain forward { 84 | type filter hook forward priority filter; policy drop; 85 | 86 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 87 | } 88 | 89 | chain output { 90 | type filter hook output priority filter; policy drop; 91 | 92 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 93 | 94 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 95 | meta l4proto { "icmp", "ipv6-icmp" } counter comment "!fw4: ICMP rule #1" 96 | meta nfproto ipv6 meta l4proto ipv6-icmp counter comment "!fw4: ICMP rule #2" 97 | meta nfproto ipv6 meta l4proto ipv6-icmp counter comment "!fw4: ICMP rule #3" 98 | meta nfproto ipv4 icmp type . icmp code { 12 . 0 } counter comment "!fw4: ICMP rule #4" 99 | meta nfproto ipv6 icmpv6 type . icmpv6 code { 136 . 0 } counter comment "!fw4: ICMP rule #5" 100 | } 101 | 102 | chain prerouting { 103 | type filter hook prerouting priority filter; policy accept; 104 | } 105 | 106 | chain handle_reject { 107 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 108 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 109 | } 110 | 111 | 112 | # 113 | # NAT rules 114 | # 115 | 116 | chain dstnat { 117 | type nat hook prerouting priority dstnat; policy accept; 118 | } 119 | 120 | chain srcnat { 121 | type nat hook postrouting priority srcnat; policy accept; 122 | } 123 | 124 | 125 | # 126 | # Raw rules (notrack) 127 | # 128 | 129 | chain raw_prerouting { 130 | type filter hook prerouting priority raw; policy accept; 131 | } 132 | 133 | chain raw_output { 134 | type filter hook output priority raw; policy accept; 135 | } 136 | 137 | 138 | # 139 | # Mangle rules 140 | # 141 | 142 | chain mangle_prerouting { 143 | type filter hook prerouting priority mangle; policy accept; 144 | } 145 | 146 | chain mangle_postrouting { 147 | type filter hook postrouting priority mangle; policy accept; 148 | } 149 | 150 | chain mangle_input { 151 | type filter hook input priority mangle; policy accept; 152 | } 153 | 154 | chain mangle_output { 155 | type route hook output priority mangle; policy accept; 156 | } 157 | 158 | chain mangle_forward { 159 | type filter hook forward priority mangle; policy accept; 160 | } 161 | } 162 | -- End -- 163 | -------------------------------------------------------------------------------- /tests/03_rules/09_time: -------------------------------------------------------------------------------- 1 | Ensure that time constraints are properly rendered. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File uci/firewall.json -- 21 | { 22 | "rule": [ 23 | { 24 | ".description": "Check parsing a complete ISO datetime stamp", 25 | "name": "Time rule #1", 26 | "proto": "all", 27 | "start_date": "2022-05-30T21:51:23", 28 | "target": "ACCEPT" 29 | }, 30 | { 31 | ".description": "Check parsing a datetime stamp without seconds", 32 | "name": "Time rule #2", 33 | "proto": "all", 34 | "start_date": "2022-05-30T21:51", 35 | "target": "ACCEPT" 36 | }, 37 | { 38 | ".description": "Check parsing a datetime stamp without minutes and seconds", 39 | "name": "Time rule #3", 40 | "proto": "all", 41 | "start_date": "2022-05-30T21", 42 | "target": "ACCEPT" 43 | }, 44 | { 45 | ".description": "Check parsing a datetime stamp without time", 46 | "name": "Time rule #4", 47 | "proto": "all", 48 | "start_date": "2022-05-30", 49 | "target": "ACCEPT" 50 | }, 51 | { 52 | ".description": "Check parsing a datetime stamp without day and time", 53 | "name": "Time rule #5", 54 | "proto": "all", 55 | "start_date": "2022-05", 56 | "target": "ACCEPT" 57 | }, 58 | { 59 | ".description": "Check parsing a datetime stamp without month, day and time", 60 | "name": "Time rule #6", 61 | "proto": "all", 62 | "start_date": "2022", 63 | "target": "ACCEPT" 64 | }, 65 | 66 | { 67 | ".description": "Check parsing a complete timestamp", 68 | "name": "Time rule #7", 69 | "proto": "all", 70 | "start_time": "21:51:23", 71 | "target": "ACCEPT" 72 | }, 73 | { 74 | ".description": "Check parsing a timestamp without seconds", 75 | "name": "Time rule #8", 76 | "proto": "all", 77 | "start_time": "21:51", 78 | "target": "ACCEPT" 79 | }, 80 | { 81 | ".description": "Check parsing a timestamp without minutes and seconds", 82 | "name": "Time rule #9", 83 | "proto": "all", 84 | "start_time": "21", 85 | "target": "ACCEPT" 86 | }, 87 | 88 | { 89 | ".description": "Check emitting datetime ranges", 90 | "name": "Time rule #10", 91 | "proto": "all", 92 | "start_date": "2022-05-30T21:51:23", 93 | "stop_date": "2022-06-01T23:51:23", 94 | "target": "ACCEPT" 95 | }, 96 | { 97 | ".description": "Check emitting time ranges", 98 | "name": "Time rule #11", 99 | "proto": "all", 100 | "start_time": "21:51:23", 101 | "stop_time": "23:51:23", 102 | "target": "ACCEPT" 103 | }, 104 | 105 | { 106 | ".description": "Check parsing weekdays", 107 | "name": "Time rule #12", 108 | "proto": "all", 109 | "weekdays": "Monday tuEsday wed SUN Th", 110 | "target": "ACCEPT" 111 | }, 112 | ] 113 | } 114 | -- End -- 115 | 116 | -- Expect stdout -- 117 | table inet fw4 118 | flush table inet fw4 119 | 120 | table inet fw4 { 121 | # 122 | # Defines 123 | # 124 | 125 | 126 | # 127 | # User includes 128 | # 129 | 130 | include "/etc/nftables.d/*.nft" 131 | 132 | 133 | # 134 | # Filter rules 135 | # 136 | 137 | chain input { 138 | type filter hook input priority filter; policy drop; 139 | 140 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 141 | 142 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 143 | } 144 | 145 | chain forward { 146 | type filter hook forward priority filter; policy drop; 147 | 148 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 149 | } 150 | 151 | chain output { 152 | type filter hook output priority filter; policy drop; 153 | 154 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 155 | 156 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 157 | meta time >= "2022-05-30 21:51:23" counter accept comment "!fw4: Time rule #1" 158 | meta time >= "2022-05-30 21:51:00" counter accept comment "!fw4: Time rule #2" 159 | meta time >= "2022-05-30 21:00:00" counter accept comment "!fw4: Time rule #3" 160 | meta time >= "2022-05-30 00:00:00" counter accept comment "!fw4: Time rule #4" 161 | meta time >= "2022-05-01 00:00:00" counter accept comment "!fw4: Time rule #5" 162 | meta time >= "2022-01-01 00:00:00" counter accept comment "!fw4: Time rule #6" 163 | meta hour >= "21:51:23" counter accept comment "!fw4: Time rule #7" 164 | meta hour >= "21:51:00" counter accept comment "!fw4: Time rule #8" 165 | meta hour >= "21:00:00" counter accept comment "!fw4: Time rule #9" 166 | meta time "2022-05-30 21:51:23"-"2022-06-01 23:51:23" counter accept comment "!fw4: Time rule #10" 167 | meta hour "21:51:23"-"23:51:23" counter accept comment "!fw4: Time rule #11" 168 | meta day { "Monday", "Tuesday", "Wednesday", "Sunday", "Thursday" } counter accept comment "!fw4: Time rule #12" 169 | } 170 | 171 | chain prerouting { 172 | type filter hook prerouting priority filter; policy accept; 173 | } 174 | 175 | chain handle_reject { 176 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 177 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 178 | } 179 | 180 | 181 | # 182 | # NAT rules 183 | # 184 | 185 | chain dstnat { 186 | type nat hook prerouting priority dstnat; policy accept; 187 | } 188 | 189 | chain srcnat { 190 | type nat hook postrouting priority srcnat; policy accept; 191 | } 192 | 193 | 194 | # 195 | # Raw rules (notrack) 196 | # 197 | 198 | chain raw_prerouting { 199 | type filter hook prerouting priority raw; policy accept; 200 | } 201 | 202 | chain raw_output { 203 | type filter hook output priority raw; policy accept; 204 | } 205 | 206 | 207 | # 208 | # Mangle rules 209 | # 210 | 211 | chain mangle_prerouting { 212 | type filter hook prerouting priority mangle; policy accept; 213 | } 214 | 215 | chain mangle_postrouting { 216 | type filter hook postrouting priority mangle; policy accept; 217 | } 218 | 219 | chain mangle_input { 220 | type filter hook input priority mangle; policy accept; 221 | } 222 | 223 | chain mangle_output { 224 | type route hook output priority mangle; policy accept; 225 | } 226 | 227 | chain mangle_forward { 228 | type filter hook forward priority mangle; policy accept; 229 | } 230 | } 231 | -- End -- 232 | -------------------------------------------------------------------------------- /tests/03_rules/10_notrack: -------------------------------------------------------------------------------- 1 | Ensure that NOTRACK rules end up in the appropriate chains, depending on 2 | the src and dest options. 3 | 4 | -- Testcase -- 5 | {% 6 | include("./root/usr/share/firewall4/main.uc", { 7 | getenv: function(varname) { 8 | switch (varname) { 9 | case 'ACTION': 10 | return 'print'; 11 | } 12 | } 13 | }) 14 | %} 15 | -- End -- 16 | 17 | -- File uci/helpers.json -- 18 | {} 19 | -- End -- 20 | 21 | -- File fs/open~_sys_class_net_eth0_flags.txt -- 22 | 0x1103 23 | -- End -- 24 | 25 | -- File fs/open~_sys_class_net_lo_flags.txt -- 26 | 0x9 27 | -- End -- 28 | 29 | -- File uci/firewall.json -- 30 | { 31 | "zone": [ 32 | { 33 | "name": "zone1", 34 | "device": [ "eth0" ], 35 | "auto_helper": 0 36 | }, 37 | { 38 | "name": "zone2", 39 | "device": [ "lo" ], 40 | "auto_helper": 0 41 | }, 42 | { 43 | "name": "zone3", 44 | "subnet": [ "127.0.0.1/8", "::1/128" ], 45 | "auto_helper": 0 46 | } 47 | ], 48 | "rule": [ 49 | { 50 | ".description": "An ordinary notrack rule should end up in the raw_prerouting chain", 51 | "name": "Notrack rule #1", 52 | "src": "zone1", 53 | "target": "NOTRACK" 54 | }, 55 | { 56 | ".description": "A notrack rule with loopback source device should end up in the raw_output chain", 57 | "name": "Notrack rule #2", 58 | "src": "zone2", 59 | "target": "NOTRACK" 60 | }, 61 | { 62 | ".description": "A notrack rule with loopback source address should end up in the raw_output chain", 63 | "name": "Notrack rule #3", 64 | "src": "zone3", 65 | "target": "NOTRACK" 66 | } 67 | ] 68 | } 69 | -- End -- 70 | 71 | -- Expect stdout -- 72 | table inet fw4 73 | flush table inet fw4 74 | 75 | table inet fw4 { 76 | # 77 | # Defines 78 | # 79 | 80 | define zone1_devices = { "eth0" } 81 | define zone1_subnets = { } 82 | 83 | define zone2_devices = { "lo" } 84 | define zone2_subnets = { } 85 | 86 | define zone3_devices = { } 87 | define zone3_subnets = { 127.0.0.0/8, ::1 } 88 | 89 | 90 | # 91 | # User includes 92 | # 93 | 94 | include "/etc/nftables.d/*.nft" 95 | 96 | 97 | # 98 | # Filter rules 99 | # 100 | 101 | chain input { 102 | type filter hook input priority filter; policy drop; 103 | 104 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 105 | 106 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 107 | iifname "eth0" jump input_zone1 comment "!fw4: Handle zone1 IPv4/IPv6 input traffic" 108 | iifname "lo" jump input_zone2 comment "!fw4: Handle zone2 IPv4/IPv6 input traffic" 109 | meta nfproto ipv4 ip saddr 127.0.0.0/8 jump input_zone3 comment "!fw4: Handle zone3 IPv4 input traffic" 110 | meta nfproto ipv6 ip6 saddr ::1 jump input_zone3 comment "!fw4: Handle zone3 IPv6 input traffic" 111 | } 112 | 113 | chain forward { 114 | type filter hook forward priority filter; policy drop; 115 | 116 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 117 | iifname "eth0" jump forward_zone1 comment "!fw4: Handle zone1 IPv4/IPv6 forward traffic" 118 | iifname "lo" jump forward_zone2 comment "!fw4: Handle zone2 IPv4/IPv6 forward traffic" 119 | meta nfproto ipv4 ip saddr 127.0.0.0/8 jump forward_zone3 comment "!fw4: Handle zone3 IPv4 forward traffic" 120 | meta nfproto ipv6 ip6 saddr ::1 jump forward_zone3 comment "!fw4: Handle zone3 IPv6 forward traffic" 121 | } 122 | 123 | chain output { 124 | type filter hook output priority filter; policy drop; 125 | 126 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 127 | 128 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 129 | oifname "eth0" jump output_zone1 comment "!fw4: Handle zone1 IPv4/IPv6 output traffic" 130 | oifname "lo" jump output_zone2 comment "!fw4: Handle zone2 IPv4/IPv6 output traffic" 131 | meta nfproto ipv4 ip daddr 127.0.0.0/8 jump output_zone3 comment "!fw4: Handle zone3 IPv4 output traffic" 132 | meta nfproto ipv6 ip6 daddr ::1 jump output_zone3 comment "!fw4: Handle zone3 IPv6 output traffic" 133 | } 134 | 135 | chain prerouting { 136 | type filter hook prerouting priority filter; policy accept; 137 | } 138 | 139 | chain handle_reject { 140 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 141 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 142 | } 143 | 144 | chain input_zone1 { 145 | jump drop_from_zone1 146 | } 147 | 148 | chain output_zone1 { 149 | jump drop_to_zone1 150 | } 151 | 152 | chain forward_zone1 { 153 | jump drop_to_zone1 154 | } 155 | 156 | chain drop_from_zone1 { 157 | iifname "eth0" counter drop comment "!fw4: drop zone1 IPv4/IPv6 traffic" 158 | } 159 | 160 | chain drop_to_zone1 { 161 | oifname "eth0" counter drop comment "!fw4: drop zone1 IPv4/IPv6 traffic" 162 | } 163 | 164 | chain input_zone2 { 165 | jump drop_from_zone2 166 | } 167 | 168 | chain output_zone2 { 169 | jump drop_to_zone2 170 | } 171 | 172 | chain forward_zone2 { 173 | jump drop_to_zone2 174 | } 175 | 176 | chain drop_from_zone2 { 177 | iifname "lo" counter drop comment "!fw4: drop zone2 IPv4/IPv6 traffic" 178 | } 179 | 180 | chain drop_to_zone2 { 181 | oifname "lo" counter drop comment "!fw4: drop zone2 IPv4/IPv6 traffic" 182 | } 183 | 184 | chain input_zone3 { 185 | jump drop_from_zone3 186 | } 187 | 188 | chain output_zone3 { 189 | jump drop_to_zone3 190 | } 191 | 192 | chain forward_zone3 { 193 | jump drop_to_zone3 194 | } 195 | 196 | chain drop_from_zone3 { 197 | meta nfproto ipv4 ip saddr 127.0.0.0/8 counter drop comment "!fw4: drop zone3 IPv4 traffic" 198 | meta nfproto ipv6 ip6 saddr ::1 counter drop comment "!fw4: drop zone3 IPv6 traffic" 199 | } 200 | 201 | chain drop_to_zone3 { 202 | meta nfproto ipv4 ip daddr 127.0.0.0/8 counter drop comment "!fw4: drop zone3 IPv4 traffic" 203 | meta nfproto ipv6 ip6 daddr ::1 counter drop comment "!fw4: drop zone3 IPv6 traffic" 204 | } 205 | 206 | 207 | # 208 | # NAT rules 209 | # 210 | 211 | chain dstnat { 212 | type nat hook prerouting priority dstnat; policy accept; 213 | } 214 | 215 | chain srcnat { 216 | type nat hook postrouting priority srcnat; policy accept; 217 | } 218 | 219 | 220 | # 221 | # Raw rules (notrack) 222 | # 223 | 224 | chain raw_prerouting { 225 | type filter hook prerouting priority raw; policy accept; 226 | iifname "eth0" jump notrack_zone1 comment "!fw4: Handle zone1 IPv4/IPv6 notrack traffic" 227 | } 228 | 229 | chain raw_output { 230 | type filter hook output priority raw; policy accept; 231 | iifname "lo" jump notrack_zone2 comment "!fw4: Handle zone2 IPv4/IPv6 notrack traffic" 232 | meta nfproto ipv4 ip saddr 127.0.0.0/8 jump notrack_zone3 comment "!fw4: Handle zone3 IPv4 notrack traffic" 233 | meta nfproto ipv6 ip6 saddr ::1 jump notrack_zone3 comment "!fw4: Handle zone3 IPv6 notrack traffic" 234 | } 235 | 236 | chain notrack_zone1 { 237 | meta l4proto tcp counter notrack comment "!fw4: Notrack rule #1" 238 | meta l4proto udp counter notrack comment "!fw4: Notrack rule #1" 239 | } 240 | 241 | chain notrack_zone2 { 242 | meta l4proto tcp counter notrack comment "!fw4: Notrack rule #2" 243 | meta l4proto udp counter notrack comment "!fw4: Notrack rule #2" 244 | } 245 | 246 | chain notrack_zone3 { 247 | meta l4proto tcp counter notrack comment "!fw4: Notrack rule #3" 248 | meta l4proto udp counter notrack comment "!fw4: Notrack rule #3" 249 | } 250 | 251 | 252 | # 253 | # Mangle rules 254 | # 255 | 256 | chain mangle_prerouting { 257 | type filter hook prerouting priority mangle; policy accept; 258 | } 259 | 260 | chain mangle_postrouting { 261 | type filter hook postrouting priority mangle; policy accept; 262 | } 263 | 264 | chain mangle_input { 265 | type filter hook input priority mangle; policy accept; 266 | } 267 | 268 | chain mangle_output { 269 | type route hook output priority mangle; policy accept; 270 | } 271 | 272 | chain mangle_forward { 273 | type filter hook forward priority mangle; policy accept; 274 | } 275 | } 276 | -- End -- 277 | -------------------------------------------------------------------------------- /tests/03_rules/11_log: -------------------------------------------------------------------------------- 1 | Testing that `option log 1` enables rule logging and sets the rule name as 2 | log prefix. Also testing that setting settin `option log` to a non-boolean 3 | string uses that string verbatim as log prefix. 4 | 5 | -- Testcase -- 6 | {% 7 | include("./root/usr/share/firewall4/main.uc", { 8 | getenv: function(varname) { 9 | switch (varname) { 10 | case 'ACTION': 11 | return 'print'; 12 | } 13 | } 14 | }) 15 | %} 16 | -- End -- 17 | 18 | -- File uci/helpers.json -- 19 | {} 20 | -- End -- 21 | 22 | -- File uci/firewall.json -- 23 | { 24 | "zone": [ 25 | { 26 | "name": "wan" 27 | } 28 | ], 29 | "rule": [ 30 | { 31 | "proto": "any", 32 | "log": "1" 33 | }, 34 | { 35 | "name": "Explicit rule name", 36 | "proto": "any", 37 | "log": "1" 38 | }, 39 | { "proto": "any", 40 | "log": "Explicit prefix: " 41 | } 42 | ], 43 | "redirect": [ 44 | { 45 | "proto": "tcp", 46 | "src": "wan", 47 | "dest_ip": "10.0.0.2", 48 | "dest_port": "22", 49 | "log": "1" 50 | }, 51 | { 52 | "name": "Explicit redirect name", 53 | "proto": "tcp", 54 | "src": "wan", 55 | "dest_ip": "10.0.0.3", 56 | "dest_port": "23", 57 | "log": "1" 58 | }, 59 | { 60 | "proto": "tcp", 61 | "src": "wan", 62 | "dest_ip": "10.0.0.4", 63 | "dest_port": "24", 64 | "log": "Explicit prefix: " 65 | } 66 | ], 67 | "nat": [ 68 | { 69 | "src": "wan", 70 | "target": "MASQUERADE", 71 | "log": "1" 72 | }, 73 | { 74 | "name": "Explicit nat name", 75 | "src": "wan", 76 | "target": "MASQUERADE", 77 | "log": "1" 78 | }, 79 | { 80 | "src": "wan", 81 | "target": "MASQUERADE", 82 | "log": "Explicit log prefix: " 83 | } 84 | ] 85 | } 86 | -- End -- 87 | 88 | -- Expect stdout -- 89 | table inet fw4 90 | flush table inet fw4 91 | 92 | table inet fw4 { 93 | # 94 | # Defines 95 | # 96 | 97 | define wan_devices = { } 98 | define wan_subnets = { } 99 | 100 | 101 | # 102 | # User includes 103 | # 104 | 105 | include "/etc/nftables.d/*.nft" 106 | 107 | 108 | # 109 | # Filter rules 110 | # 111 | 112 | chain input { 113 | type filter hook input priority filter; policy drop; 114 | 115 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 116 | 117 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 118 | } 119 | 120 | chain forward { 121 | type filter hook forward priority filter; policy drop; 122 | 123 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 124 | } 125 | 126 | chain output { 127 | type filter hook output priority filter; policy drop; 128 | 129 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 130 | 131 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 132 | counter log prefix "@rule[0]: " comment "!fw4: @rule[0]" 133 | counter log prefix "Explicit rule name: " comment "!fw4: Explicit rule name" 134 | counter log prefix "Explicit prefix: " comment "!fw4: @rule[2]" 135 | } 136 | 137 | chain prerouting { 138 | type filter hook prerouting priority filter; policy accept; 139 | } 140 | 141 | chain handle_reject { 142 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 143 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 144 | } 145 | 146 | chain input_wan { 147 | ct status dnat accept comment "!fw4: Accept port redirections" 148 | jump drop_from_wan 149 | } 150 | 151 | chain output_wan { 152 | jump drop_to_wan 153 | } 154 | 155 | chain forward_wan { 156 | ct status dnat accept comment "!fw4: Accept port forwards" 157 | jump drop_to_wan 158 | } 159 | 160 | chain helper_wan { 161 | } 162 | 163 | chain drop_from_wan { 164 | } 165 | 166 | chain drop_to_wan { 167 | } 168 | 169 | 170 | # 171 | # NAT rules 172 | # 173 | 174 | chain dstnat { 175 | type nat hook prerouting priority dstnat; policy accept; 176 | } 177 | 178 | chain srcnat { 179 | type nat hook postrouting priority srcnat; policy accept; 180 | } 181 | 182 | chain dstnat_wan { 183 | meta nfproto ipv4 counter log prefix "@redirect[0]: " dnat 10.0.0.2:22 comment "!fw4: @redirect[0]" 184 | meta nfproto ipv4 counter log prefix "Explicit redirect name: " dnat 10.0.0.3:23 comment "!fw4: Explicit redirect name" 185 | meta nfproto ipv4 counter log prefix "Explicit prefix: " dnat 10.0.0.4:24 comment "!fw4: @redirect[2]" 186 | } 187 | 188 | chain srcnat_wan { 189 | meta nfproto ipv4 counter log prefix "@nat[0]: " masquerade comment "!fw4: @nat[0]" 190 | meta nfproto ipv4 counter log prefix "Explicit nat name: " masquerade comment "!fw4: Explicit nat name" 191 | meta nfproto ipv4 counter log prefix "Explicit log prefix: " masquerade comment "!fw4: @nat[2]" 192 | } 193 | 194 | 195 | # 196 | # Raw rules (notrack) 197 | # 198 | 199 | chain raw_prerouting { 200 | type filter hook prerouting priority raw; policy accept; 201 | } 202 | 203 | chain raw_output { 204 | type filter hook output priority raw; policy accept; 205 | } 206 | 207 | 208 | # 209 | # Mangle rules 210 | # 211 | 212 | chain mangle_prerouting { 213 | type filter hook prerouting priority mangle; policy accept; 214 | } 215 | 216 | chain mangle_postrouting { 217 | type filter hook postrouting priority mangle; policy accept; 218 | } 219 | 220 | chain mangle_input { 221 | type filter hook input priority mangle; policy accept; 222 | } 223 | 224 | chain mangle_output { 225 | type route hook output priority mangle; policy accept; 226 | } 227 | 228 | chain mangle_forward { 229 | type filter hook forward priority mangle; policy accept; 230 | } 231 | } 232 | -- End -- 233 | -------------------------------------------------------------------------------- /tests/03_rules/12_mark: -------------------------------------------------------------------------------- 1 | Testing various MARK rules. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File uci/firewall.json -- 21 | { 22 | "rule": [ 23 | { 24 | ".description": "Test setting mark", 25 | "name": "Mark rule #1", 26 | "proto": "all", 27 | "src": "*", 28 | "target": "MARK", 29 | "set_mark": "0xaa" 30 | }, 31 | { 32 | ".description": "Test setting mark with mask", 33 | "name": "Mark rule #2", 34 | "proto": "all", 35 | "src": "*", 36 | "target": "MARK", 37 | "set_mark": "0xab/0xff00" 38 | }, 39 | { 40 | ".description": "Test setting xor mark", 41 | "name": "Mark rule #3", 42 | "proto": "all", 43 | "src": "*", 44 | "target": "MARK", 45 | "set_xmark": "0xac" 46 | }, 47 | { 48 | ".description": "Test setting xor mark with mask", 49 | "name": "Mark rule #4", 50 | "proto": "all", 51 | "src": "*", 52 | "target": "MARK", 53 | "set_xmark": "0xad/0xff00" 54 | }, 55 | { 56 | ".description": "Test ANDing bits (set xmark 0/~bits)", 57 | "name": "Mark rule #5", 58 | "proto": "all", 59 | "src": "*", 60 | "target": "MARK", 61 | "set_xmark": "0/0xffffff51" 62 | }, 63 | { 64 | ".description": "Test ORing bits (set xmark bits/bits)", 65 | "name": "Mark rule #6", 66 | "proto": "all", 67 | "src": "*", 68 | "target": "MARK", 69 | "set_xmark": "0xaf/0xaf" 70 | } 71 | ] 72 | } 73 | -- End -- 74 | 75 | -- Expect stdout -- 76 | table inet fw4 77 | flush table inet fw4 78 | 79 | table inet fw4 { 80 | # 81 | # Defines 82 | # 83 | 84 | 85 | # 86 | # User includes 87 | # 88 | 89 | include "/etc/nftables.d/*.nft" 90 | 91 | 92 | # 93 | # Filter rules 94 | # 95 | 96 | chain input { 97 | type filter hook input priority filter; policy drop; 98 | 99 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 100 | 101 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 102 | } 103 | 104 | chain forward { 105 | type filter hook forward priority filter; policy drop; 106 | 107 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 108 | } 109 | 110 | chain output { 111 | type filter hook output priority filter; policy drop; 112 | 113 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 114 | 115 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 116 | } 117 | 118 | chain prerouting { 119 | type filter hook prerouting priority filter; policy accept; 120 | } 121 | 122 | chain handle_reject { 123 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 124 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 125 | } 126 | 127 | 128 | # 129 | # NAT rules 130 | # 131 | 132 | chain dstnat { 133 | type nat hook prerouting priority dstnat; policy accept; 134 | } 135 | 136 | chain srcnat { 137 | type nat hook postrouting priority srcnat; policy accept; 138 | } 139 | 140 | 141 | # 142 | # Raw rules (notrack) 143 | # 144 | 145 | chain raw_prerouting { 146 | type filter hook prerouting priority raw; policy accept; 147 | } 148 | 149 | chain raw_output { 150 | type filter hook output priority raw; policy accept; 151 | } 152 | 153 | 154 | # 155 | # Mangle rules 156 | # 157 | 158 | chain mangle_prerouting { 159 | type filter hook prerouting priority mangle; policy accept; 160 | } 161 | 162 | chain mangle_postrouting { 163 | type filter hook postrouting priority mangle; policy accept; 164 | } 165 | 166 | chain mangle_input { 167 | type filter hook input priority mangle; policy accept; 168 | counter meta mark set 0xaa comment "!fw4: Mark rule #1" 169 | counter meta mark set mark and 0xffff0054 xor 0xab comment "!fw4: Mark rule #2" 170 | counter meta mark set 0xac comment "!fw4: Mark rule #3" 171 | counter meta mark set mark and 0xffff00ff xor 0xad comment "!fw4: Mark rule #4" 172 | counter meta mark set mark and 0xae comment "!fw4: Mark rule #5" 173 | counter meta mark set mark or 0xaf comment "!fw4: Mark rule #6" 174 | } 175 | 176 | chain mangle_output { 177 | type route hook output priority mangle; policy accept; 178 | } 179 | 180 | chain mangle_forward { 181 | type filter hook forward priority mangle; policy accept; 182 | } 183 | } 184 | -- End -- 185 | -------------------------------------------------------------------------------- /tests/04_forwardings/01_family_selections: -------------------------------------------------------------------------------- 1 | Test that the zone family is honoured when setting up inter-zone forwarding rules. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File uci/firewall.json -- 21 | { 22 | "zone": [ 23 | { 24 | "name": "wanA", 25 | "device": [ "eth0" ], 26 | "auto_helper": 0 27 | }, 28 | 29 | { 30 | "name": "wanB", 31 | "device": [ "eth1" ], 32 | "auto_helper": 0 33 | }, 34 | 35 | { 36 | "name": "lan", 37 | "device": [ "eth2" ], 38 | "auto_helper": 0 39 | } 40 | ], 41 | 42 | "forwarding": [ 43 | { 44 | ".description": "This should only allow IPv6 forwarding from lan to wanA", 45 | "src": "lan", 46 | "dest": "wanA", 47 | "family": "IPv6" 48 | }, 49 | 50 | { 51 | ".description": "This should only allow IPv4 forwarding from lan to wanB", 52 | "src": "lan", 53 | "dest": "wanB", 54 | "family": "IPv4" 55 | } 56 | ] 57 | } 58 | -- End -- 59 | 60 | -- Expect stdout -- 61 | table inet fw4 62 | flush table inet fw4 63 | 64 | table inet fw4 { 65 | # 66 | # Defines 67 | # 68 | 69 | define wanA_devices = { "eth0" } 70 | define wanA_subnets = { } 71 | 72 | define wanB_devices = { "eth1" } 73 | define wanB_subnets = { } 74 | 75 | define lan_devices = { "eth2" } 76 | define lan_subnets = { } 77 | 78 | 79 | # 80 | # User includes 81 | # 82 | 83 | include "/etc/nftables.d/*.nft" 84 | 85 | 86 | # 87 | # Filter rules 88 | # 89 | 90 | chain input { 91 | type filter hook input priority filter; policy drop; 92 | 93 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 94 | 95 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 96 | iifname "eth0" jump input_wanA comment "!fw4: Handle wanA IPv4/IPv6 input traffic" 97 | iifname "eth1" jump input_wanB comment "!fw4: Handle wanB IPv4/IPv6 input traffic" 98 | iifname "eth2" jump input_lan comment "!fw4: Handle lan IPv4/IPv6 input traffic" 99 | } 100 | 101 | chain forward { 102 | type filter hook forward priority filter; policy drop; 103 | 104 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 105 | iifname "eth0" jump forward_wanA comment "!fw4: Handle wanA IPv4/IPv6 forward traffic" 106 | iifname "eth1" jump forward_wanB comment "!fw4: Handle wanB IPv4/IPv6 forward traffic" 107 | iifname "eth2" jump forward_lan comment "!fw4: Handle lan IPv4/IPv6 forward traffic" 108 | } 109 | 110 | chain output { 111 | type filter hook output priority filter; policy drop; 112 | 113 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 114 | 115 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 116 | oifname "eth0" jump output_wanA comment "!fw4: Handle wanA IPv4/IPv6 output traffic" 117 | oifname "eth1" jump output_wanB comment "!fw4: Handle wanB IPv4/IPv6 output traffic" 118 | oifname "eth2" jump output_lan comment "!fw4: Handle lan IPv4/IPv6 output traffic" 119 | } 120 | 121 | chain prerouting { 122 | type filter hook prerouting priority filter; policy accept; 123 | } 124 | 125 | chain handle_reject { 126 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 127 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 128 | } 129 | 130 | chain input_wanA { 131 | jump drop_from_wanA 132 | } 133 | 134 | chain output_wanA { 135 | jump drop_to_wanA 136 | } 137 | 138 | chain forward_wanA { 139 | jump drop_to_wanA 140 | } 141 | 142 | chain accept_to_wanA { 143 | oifname "eth0" counter accept comment "!fw4: accept wanA IPv4/IPv6 traffic" 144 | } 145 | 146 | chain drop_from_wanA { 147 | iifname "eth0" counter drop comment "!fw4: drop wanA IPv4/IPv6 traffic" 148 | } 149 | 150 | chain drop_to_wanA { 151 | oifname "eth0" counter drop comment "!fw4: drop wanA IPv4/IPv6 traffic" 152 | } 153 | 154 | chain input_wanB { 155 | jump drop_from_wanB 156 | } 157 | 158 | chain output_wanB { 159 | jump drop_to_wanB 160 | } 161 | 162 | chain forward_wanB { 163 | jump drop_to_wanB 164 | } 165 | 166 | chain accept_to_wanB { 167 | oifname "eth1" counter accept comment "!fw4: accept wanB IPv4/IPv6 traffic" 168 | } 169 | 170 | chain drop_from_wanB { 171 | iifname "eth1" counter drop comment "!fw4: drop wanB IPv4/IPv6 traffic" 172 | } 173 | 174 | chain drop_to_wanB { 175 | oifname "eth1" counter drop comment "!fw4: drop wanB IPv4/IPv6 traffic" 176 | } 177 | 178 | chain input_lan { 179 | jump drop_from_lan 180 | } 181 | 182 | chain output_lan { 183 | jump drop_to_lan 184 | } 185 | 186 | chain forward_lan { 187 | meta nfproto ipv6 jump accept_to_wanA comment "!fw4: Accept lan to wanA IPv6 forwarding" 188 | meta nfproto ipv4 jump accept_to_wanB comment "!fw4: Accept lan to wanB IPv4 forwarding" 189 | jump drop_to_lan 190 | } 191 | 192 | chain drop_from_lan { 193 | iifname "eth2" counter drop comment "!fw4: drop lan IPv4/IPv6 traffic" 194 | } 195 | 196 | chain drop_to_lan { 197 | oifname "eth2" counter drop comment "!fw4: drop lan IPv4/IPv6 traffic" 198 | } 199 | 200 | 201 | # 202 | # NAT rules 203 | # 204 | 205 | chain dstnat { 206 | type nat hook prerouting priority dstnat; policy accept; 207 | } 208 | 209 | chain srcnat { 210 | type nat hook postrouting priority srcnat; policy accept; 211 | } 212 | 213 | 214 | # 215 | # Raw rules (notrack) 216 | # 217 | 218 | chain raw_prerouting { 219 | type filter hook prerouting priority raw; policy accept; 220 | } 221 | 222 | chain raw_output { 223 | type filter hook output priority raw; policy accept; 224 | } 225 | 226 | 227 | # 228 | # Mangle rules 229 | # 230 | 231 | chain mangle_prerouting { 232 | type filter hook prerouting priority mangle; policy accept; 233 | } 234 | 235 | chain mangle_postrouting { 236 | type filter hook postrouting priority mangle; policy accept; 237 | } 238 | 239 | chain mangle_input { 240 | type filter hook input priority mangle; policy accept; 241 | } 242 | 243 | chain mangle_output { 244 | type route hook output priority mangle; policy accept; 245 | } 246 | 247 | chain mangle_forward { 248 | type filter hook forward priority mangle; policy accept; 249 | } 250 | } 251 | -- End -- 252 | -------------------------------------------------------------------------------- /tests/05_ipsets/01_declaration: -------------------------------------------------------------------------------- 1 | Testing an ipset declaration. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File fs/open~set-entries_txt.txt -- 21 | 10.11.12.13 53 22 | 172.16.27.1 443 23 | -- End -- 24 | 25 | -- File uci/firewall.json -- 26 | { 27 | "ipset": [ 28 | { 29 | "name": "test-set", 30 | "comment": "A simple set", 31 | "counters": "1", 32 | "family": "IPv4", 33 | "match": [ "src_ip", "dest_port" ], 34 | "timeout": "600", 35 | "maxelem": "1000", 36 | "entry": [ 37 | "1.2.3.4 80", 38 | "5.6.7.8 22" 39 | ], 40 | "loadfile": "set-entries.txt" 41 | } 42 | ] 43 | } 44 | -- End -- 45 | 46 | -- Expect stdout -- 47 | table inet fw4 48 | flush table inet fw4 49 | 50 | table inet fw4 { 51 | # 52 | # Set definitions 53 | # 54 | 55 | set test-set { 56 | comment "A simple set" 57 | type ipv4_addr . inet_service 58 | size 1000 59 | timeout 600s 60 | flags timeout 61 | elements = { 62 | 1.2.3.4 . 80, 63 | 5.6.7.8 . 22, 64 | 10.11.12.13 . 53, 65 | 172.16.27.1 . 443, 66 | } 67 | } 68 | 69 | 70 | # 71 | # Defines 72 | # 73 | 74 | 75 | # 76 | # User includes 77 | # 78 | 79 | include "/etc/nftables.d/*.nft" 80 | 81 | 82 | # 83 | # Filter rules 84 | # 85 | 86 | chain input { 87 | type filter hook input priority filter; policy drop; 88 | 89 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 90 | 91 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 92 | } 93 | 94 | chain forward { 95 | type filter hook forward priority filter; policy drop; 96 | 97 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 98 | } 99 | 100 | chain output { 101 | type filter hook output priority filter; policy drop; 102 | 103 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 104 | 105 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 106 | } 107 | 108 | chain prerouting { 109 | type filter hook prerouting priority filter; policy accept; 110 | } 111 | 112 | chain handle_reject { 113 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 114 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 115 | } 116 | 117 | 118 | # 119 | # NAT rules 120 | # 121 | 122 | chain dstnat { 123 | type nat hook prerouting priority dstnat; policy accept; 124 | } 125 | 126 | chain srcnat { 127 | type nat hook postrouting priority srcnat; policy accept; 128 | } 129 | 130 | 131 | # 132 | # Raw rules (notrack) 133 | # 134 | 135 | chain raw_prerouting { 136 | type filter hook prerouting priority raw; policy accept; 137 | } 138 | 139 | chain raw_output { 140 | type filter hook output priority raw; policy accept; 141 | } 142 | 143 | 144 | # 145 | # Mangle rules 146 | # 147 | 148 | chain mangle_prerouting { 149 | type filter hook prerouting priority mangle; policy accept; 150 | } 151 | 152 | chain mangle_postrouting { 153 | type filter hook postrouting priority mangle; policy accept; 154 | } 155 | 156 | chain mangle_input { 157 | type filter hook input priority mangle; policy accept; 158 | } 159 | 160 | chain mangle_output { 161 | type route hook output priority mangle; policy accept; 162 | } 163 | 164 | chain mangle_forward { 165 | type filter hook forward priority mangle; policy accept; 166 | } 167 | } 168 | -- End -- 169 | -------------------------------------------------------------------------------- /tests/05_ipsets/02_usage: -------------------------------------------------------------------------------- 1 | Test matching an ipset in rules. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File fs/open~_proc_version.txt -- 21 | Linux version 5.10.113 (jow@j7) (mipsel-openwrt-linux-musl-gcc (OpenWrt GCC 11.2.0 r17858+262-2c3e8bed3f) 11.2.0, GNU ld (GNU Binutils) 2.37) #0 SMP Tue May 17 19:05:07 2022 22 | -- End -- 23 | 24 | -- File uci/firewall.json -- 25 | { 26 | "ipset": [ 27 | { 28 | "name": "test-set-1", 29 | "comment": "Test set #1 with traffic direction in type declaration", 30 | "match": [ "src_ip", "dest_port" ], 31 | "entry": [ 32 | "1.2.3.4 80", 33 | "5.6.7.8 22" 34 | ] 35 | }, 36 | { 37 | "name": "test-set-2", 38 | "comment": "Test set #2 with unspecified traffic direction", 39 | "match": [ "ip", "port" ], 40 | "entry": [ 41 | "1.2.3.4 80", 42 | "5.6.7.8 22" 43 | ] 44 | }, 45 | { 46 | "name": "test-set-3", 47 | "comment": "Test set #3 with IPv6 addresses", 48 | "family": "IPv6", 49 | "match": [ "net", "net", "port" ], 50 | "entry": [ 51 | "db80:1234:4567::1/64 db80:1234:abcd::1/64 80", 52 | "db80:8765:aaaa::1/64 db80:8765:ffff::1/64 22", 53 | "db80:1:2:3:4:0:0:1 0:0:0:0:0:0:0:0/0 443", 54 | ] 55 | } 56 | ], 57 | "rule": [ 58 | { 59 | "name": "Rule using test set #1", 60 | "src": "*", 61 | "dest": "*", 62 | "proto": "tcp", 63 | "ipset": "test-set-1" 64 | }, 65 | { 66 | "name": "Rule using test set #2, match direction should default to 'source'", 67 | "src": "*", 68 | "dest": "*", 69 | "proto": "tcp", 70 | "ipset": "test-set-2" 71 | }, 72 | { 73 | "name": "Rule using test set #1, overriding match direction", 74 | "src": "*", 75 | "dest": "*", 76 | "proto": "tcp", 77 | "ipset": "test-set-1 dst src" 78 | }, 79 | { 80 | "name": "Rule using test set #2, specifiying match direction", 81 | "src": "*", 82 | "dest": "*", 83 | "proto": "tcp", 84 | "ipset": "test-set-2 dst dst" 85 | }, 86 | { 87 | "name": "Rule using test set #1, overriding direction and inverting match", 88 | "src": "*", 89 | "dest": "*", 90 | "proto": "tcp", 91 | "ipset": "!test-set-1 dst src" 92 | }, 93 | { 94 | "name": "Rule using test set #3 with alternative direction notation, should inherit IPv6 family", 95 | "src": "*", 96 | "dest": "*", 97 | "proto": "tcp", 98 | "ipset": "test-set-3 src,dest,dest" 99 | }, 100 | ] 101 | } 102 | -- End -- 103 | 104 | -- Expect stdout -- 105 | table inet fw4 106 | flush table inet fw4 107 | 108 | table inet fw4 { 109 | # 110 | # Set definitions 111 | # 112 | 113 | set test-set-1 { 114 | comment "Test set #1 with traffic direction in type declaration" 115 | type ipv4_addr . inet_service 116 | elements = { 117 | 1.2.3.4 . 80, 118 | 5.6.7.8 . 22, 119 | } 120 | } 121 | 122 | set test-set-2 { 123 | comment "Test set #2 with unspecified traffic direction" 124 | type ipv4_addr . inet_service 125 | elements = { 126 | 1.2.3.4 . 80, 127 | 5.6.7.8 . 22, 128 | } 129 | } 130 | 131 | set test-set-3 { 132 | comment "Test set #3 with IPv6 addresses" 133 | type ipv6_addr . ipv6_addr . inet_service 134 | auto-merge 135 | flags interval 136 | elements = { 137 | db80:1234:4567::1/64 . db80:1234:abcd::1/64 . 80, 138 | db80:8765:aaaa::1/64 . db80:8765:ffff::1/64 . 22, 139 | db80:1:2:3:4::1/128 . ::/0 . 443, 140 | } 141 | } 142 | 143 | 144 | # 145 | # Defines 146 | # 147 | 148 | 149 | # 150 | # User includes 151 | # 152 | 153 | include "/etc/nftables.d/*.nft" 154 | 155 | 156 | # 157 | # Filter rules 158 | # 159 | 160 | chain input { 161 | type filter hook input priority filter; policy drop; 162 | 163 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 164 | 165 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 166 | } 167 | 168 | chain forward { 169 | type filter hook forward priority filter; policy drop; 170 | 171 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 172 | meta nfproto ipv4 meta l4proto tcp ip saddr . tcp dport @test-set-1 counter comment "!fw4: Rule using test set #1" 173 | meta nfproto ipv4 meta l4proto tcp ip saddr . tcp sport @test-set-2 counter comment "!fw4: Rule using test set #2, match direction should default to 'source'" 174 | meta nfproto ipv4 meta l4proto tcp ip daddr . tcp sport @test-set-1 counter comment "!fw4: Rule using test set #1, overriding match direction" 175 | meta nfproto ipv4 meta l4proto tcp ip daddr . tcp dport @test-set-2 counter comment "!fw4: Rule using test set #2, specifiying match direction" 176 | meta nfproto ipv4 meta l4proto tcp ip daddr . tcp sport != @test-set-1 counter comment "!fw4: Rule using test set #1, overriding direction and inverting match" 177 | meta nfproto ipv6 meta l4proto tcp ip6 saddr . ip6 daddr . tcp dport @test-set-3 counter comment "!fw4: Rule using test set #3 with alternative direction notation, should inherit IPv6 family" 178 | } 179 | 180 | chain output { 181 | type filter hook output priority filter; policy drop; 182 | 183 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 184 | 185 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 186 | } 187 | 188 | chain prerouting { 189 | type filter hook prerouting priority filter; policy accept; 190 | } 191 | 192 | chain handle_reject { 193 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 194 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 195 | } 196 | 197 | 198 | # 199 | # NAT rules 200 | # 201 | 202 | chain dstnat { 203 | type nat hook prerouting priority dstnat; policy accept; 204 | } 205 | 206 | chain srcnat { 207 | type nat hook postrouting priority srcnat; policy accept; 208 | } 209 | 210 | 211 | # 212 | # Raw rules (notrack) 213 | # 214 | 215 | chain raw_prerouting { 216 | type filter hook prerouting priority raw; policy accept; 217 | } 218 | 219 | chain raw_output { 220 | type filter hook output priority raw; policy accept; 221 | } 222 | 223 | 224 | # 225 | # Mangle rules 226 | # 227 | 228 | chain mangle_prerouting { 229 | type filter hook prerouting priority mangle; policy accept; 230 | } 231 | 232 | chain mangle_postrouting { 233 | type filter hook postrouting priority mangle; policy accept; 234 | } 235 | 236 | chain mangle_input { 237 | type filter hook input priority mangle; policy accept; 238 | } 239 | 240 | chain mangle_output { 241 | type route hook output priority mangle; policy accept; 242 | } 243 | 244 | chain mangle_forward { 245 | type filter hook forward priority mangle; policy accept; 246 | } 247 | } 248 | -- End -- 249 | -------------------------------------------------------------------------------- /tests/06_includes/01_nft_includes: -------------------------------------------------------------------------------- 1 | Testing the correct placement of potential include positions. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File fs/open~_sys_class_net_eth0_flags.txt -- 21 | 0x1103 22 | -- End -- 23 | 24 | -- File fs/open~_usr_share_nftables_d_include-ruleset-start_nft.txt -- 25 | # dummy 26 | -- End -- 27 | 28 | -- File fs/open~_usr_share_nftables_d_include-table-start_nft.txt -- 29 | # dummy 30 | -- End -- 31 | 32 | -- File fs/open~_usr_share_nftables_d_include-chain-start-forward_nft.txt -- 33 | # dummy 34 | -- End -- 35 | 36 | -- File fs/open~_usr_share_nftables_d_include-chain-end-forward_nft.txt -- 37 | # dummy 38 | -- End -- 39 | 40 | -- File fs/open~_usr_share_nftables_d_include-table-end-1_nft.txt -- 41 | # dummy 42 | -- End -- 43 | 44 | -- File fs/open~_usr_share_nftables_d_include-table-end-2_nft.txt -- 45 | # dummy 46 | -- End -- 47 | 48 | -- File fs/open~_usr_share_nftables_d_include-ruleset-end_nft.txt -- 49 | # dummy 50 | -- End -- 51 | 52 | -- File uci/firewall.json -- 53 | { 54 | "zone": [ 55 | { 56 | "name": "test", 57 | "device": [ "eth0" ], 58 | "auto_helper": 0 59 | } 60 | ], 61 | "include": [ 62 | { 63 | ".description": "Position 'table-pre' (or 'table-prepend') will place an include before the first chain", 64 | "path": "/usr/share/nftables.d/include-table-start.nft", 65 | "type": "nftables", 66 | "position": "table-pre" 67 | }, 68 | 69 | { 70 | ".description": "Position defaults to 'table-append', means after the last chain in the table scope", 71 | "path": "/usr/share/nftables.d/include-table-end-1.nft", 72 | "type": "nftables" 73 | }, 74 | 75 | { 76 | ".description": "Position 'table-post' (or 'table-postpend') may be used as alias for 'table-append'", 77 | "path": "/usr/share/nftables.d/include-table-end-2.nft", 78 | "type": "nftables", 79 | "position": "table-post" 80 | }, 81 | 82 | { 83 | ".description": "Position 'ruleset-pre' (or 'ruleset-prepend') will place an include before the table declaration", 84 | "path": "/usr/share/nftables.d/include-ruleset-start.nft", 85 | "type": "nftables", 86 | "position": "ruleset-pre" 87 | }, 88 | 89 | { 90 | ".description": "Position 'ruleset-post' (or 'ruleset-append') will place an include after the table declaration", 91 | "path": "/usr/share/nftables.d/include-ruleset-end.nft", 92 | "type": "nftables", 93 | "position": "ruleset-post" 94 | }, 95 | 96 | { 97 | ".description": "Position 'chain-pre' (or 'chain-prepend') will place an include at the top of a specified chain", 98 | "path": "/usr/share/nftables.d/include-chain-start-forward.nft", 99 | "type": "nftables", 100 | "position": "chain-pre", 101 | "chain": "forward" 102 | }, 103 | 104 | { 105 | ".description": "Position 'chain-post' (or 'chain-append') will place an include at the bottom of a specified chain", 106 | "path": "/usr/share/nftables.d/include-chain-end-forward.nft", 107 | "type": "nftables", 108 | "position": "chain-post", 109 | "chain": "forward" 110 | }, 111 | 112 | { 113 | ".description": "Position 'chain-pre' or 'chain-post' without chain option will yield and error", 114 | "path": "/usr/share/nftables.d/include-chain-end-forward.nft", 115 | "type": "nftables", 116 | "position": "chain-post" 117 | }, 118 | ] 119 | } 120 | -- End -- 121 | 122 | -- Expect stderr -- 123 | [!] Section @include[7] must specify 'chain' for position chain-append, ignoring section 124 | -- End -- 125 | 126 | -- Expect stdout -- 127 | table inet fw4 128 | flush table inet fw4 129 | 130 | include "/usr/share/nftables.d/include-ruleset-start.nft" 131 | 132 | table inet fw4 { 133 | # 134 | # Defines 135 | # 136 | 137 | define test_devices = { "eth0" } 138 | define test_subnets = { } 139 | 140 | 141 | # 142 | # User includes 143 | # 144 | 145 | include "/etc/nftables.d/*.nft" 146 | 147 | include "/usr/share/nftables.d/include-table-start.nft" 148 | 149 | 150 | # 151 | # Filter rules 152 | # 153 | 154 | chain input { 155 | type filter hook input priority filter; policy drop; 156 | 157 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 158 | 159 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 160 | iifname "eth0" jump input_test comment "!fw4: Handle test IPv4/IPv6 input traffic" 161 | } 162 | 163 | chain forward { 164 | type filter hook forward priority filter; policy drop; 165 | 166 | include "/usr/share/nftables.d/include-chain-start-forward.nft" 167 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 168 | iifname "eth0" jump forward_test comment "!fw4: Handle test IPv4/IPv6 forward traffic" 169 | include "/usr/share/nftables.d/include-chain-end-forward.nft" 170 | } 171 | 172 | chain output { 173 | type filter hook output priority filter; policy drop; 174 | 175 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 176 | 177 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 178 | oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic" 179 | } 180 | 181 | chain prerouting { 182 | type filter hook prerouting priority filter; policy accept; 183 | } 184 | 185 | chain handle_reject { 186 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 187 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 188 | } 189 | 190 | chain input_test { 191 | jump drop_from_test 192 | } 193 | 194 | chain output_test { 195 | jump drop_to_test 196 | } 197 | 198 | chain forward_test { 199 | jump drop_to_test 200 | } 201 | 202 | chain drop_from_test { 203 | iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic" 204 | } 205 | 206 | chain drop_to_test { 207 | oifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic" 208 | } 209 | 210 | 211 | # 212 | # NAT rules 213 | # 214 | 215 | chain dstnat { 216 | type nat hook prerouting priority dstnat; policy accept; 217 | } 218 | 219 | chain srcnat { 220 | type nat hook postrouting priority srcnat; policy accept; 221 | } 222 | 223 | 224 | # 225 | # Raw rules (notrack) 226 | # 227 | 228 | chain raw_prerouting { 229 | type filter hook prerouting priority raw; policy accept; 230 | } 231 | 232 | chain raw_output { 233 | type filter hook output priority raw; policy accept; 234 | } 235 | 236 | 237 | # 238 | # Mangle rules 239 | # 240 | 241 | chain mangle_prerouting { 242 | type filter hook prerouting priority mangle; policy accept; 243 | } 244 | 245 | chain mangle_postrouting { 246 | type filter hook postrouting priority mangle; policy accept; 247 | } 248 | 249 | chain mangle_input { 250 | type filter hook input priority mangle; policy accept; 251 | } 252 | 253 | chain mangle_output { 254 | type route hook output priority mangle; policy accept; 255 | } 256 | 257 | chain mangle_forward { 258 | type filter hook forward priority mangle; policy accept; 259 | } 260 | 261 | include "/usr/share/nftables.d/include-table-end-1.nft" 262 | include "/usr/share/nftables.d/include-table-end-2.nft" 263 | } 264 | 265 | include "/usr/share/nftables.d/include-ruleset-end.nft" 266 | -- End -- 267 | -------------------------------------------------------------------------------- /tests/06_includes/02_firewall.user_include: -------------------------------------------------------------------------------- 1 | Testing that /etc/firewall.user is treated specially and requires an extra 2 | option to be marked compatible. 3 | 4 | -- Testcase -- 5 | {% 6 | include("./root/usr/share/firewall4/main.uc", { 7 | getenv: function(varname) { 8 | switch (varname) { 9 | case 'ACTION': 10 | return 'print'; 11 | } 12 | } 13 | }) 14 | %} 15 | -- End -- 16 | 17 | -- File uci/helpers.json -- 18 | {} 19 | -- End -- 20 | 21 | -- File fs/open~_sys_class_net_eth0_flags.txt -- 22 | 0x1103 23 | -- End -- 24 | 25 | -- File fs/open~_etc_firewall_user.txt -- 26 | # dummy 27 | -- End -- 28 | 29 | -- File fs/open~_usr_share_miniupnpd_firewall_include.txt -- 30 | # dummy 31 | -- End -- 32 | 33 | -- File uci/firewall.json -- 34 | { 35 | "zone": [ 36 | { 37 | "name": "test", 38 | "device": [ "eth0" ], 39 | "auto_helper": 0 40 | } 41 | ], 42 | "include": [ 43 | { 44 | ".description": "By default, this /etc/firewall.user include should be skipped with a warning", 45 | "path": "/etc/firewall.user" 46 | }, 47 | 48 | { 49 | ".description": "This /etc/firewall.user include should be added due to the compatible flag", 50 | "path": "/etc/firewall.user", 51 | "fw4_compatible": 1 52 | }, 53 | 54 | { 55 | ".description": "An include of another path should not require a compatible flag", 56 | "path": "/usr/share/miniupnpd/firewall.include" 57 | } 58 | ] 59 | } 60 | -- End -- 61 | 62 | -- Expect stderr -- 63 | [!] Section @include[0] is not marked as compatible with fw4, ignoring section 64 | [!] Section @include[0] requires 'option fw4_compatible 1' to be considered compatible 65 | -- End -- 66 | 67 | -- Expect stdout -- 68 | table inet fw4 69 | flush table inet fw4 70 | 71 | table inet fw4 { 72 | # 73 | # Defines 74 | # 75 | 76 | define test_devices = { "eth0" } 77 | define test_subnets = { } 78 | 79 | 80 | # 81 | # User includes 82 | # 83 | 84 | include "/etc/nftables.d/*.nft" 85 | 86 | 87 | # 88 | # Filter rules 89 | # 90 | 91 | chain input { 92 | type filter hook input priority filter; policy drop; 93 | 94 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 95 | 96 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 97 | iifname "eth0" jump input_test comment "!fw4: Handle test IPv4/IPv6 input traffic" 98 | } 99 | 100 | chain forward { 101 | type filter hook forward priority filter; policy drop; 102 | 103 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 104 | iifname "eth0" jump forward_test comment "!fw4: Handle test IPv4/IPv6 forward traffic" 105 | } 106 | 107 | chain output { 108 | type filter hook output priority filter; policy drop; 109 | 110 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 111 | 112 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 113 | oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic" 114 | } 115 | 116 | chain prerouting { 117 | type filter hook prerouting priority filter; policy accept; 118 | } 119 | 120 | chain handle_reject { 121 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 122 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 123 | } 124 | 125 | chain input_test { 126 | jump drop_from_test 127 | } 128 | 129 | chain output_test { 130 | jump drop_to_test 131 | } 132 | 133 | chain forward_test { 134 | jump drop_to_test 135 | } 136 | 137 | chain drop_from_test { 138 | iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic" 139 | } 140 | 141 | chain drop_to_test { 142 | oifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic" 143 | } 144 | 145 | 146 | # 147 | # NAT rules 148 | # 149 | 150 | chain dstnat { 151 | type nat hook prerouting priority dstnat; policy accept; 152 | } 153 | 154 | chain srcnat { 155 | type nat hook postrouting priority srcnat; policy accept; 156 | } 157 | 158 | 159 | # 160 | # Raw rules (notrack) 161 | # 162 | 163 | chain raw_prerouting { 164 | type filter hook prerouting priority raw; policy accept; 165 | } 166 | 167 | chain raw_output { 168 | type filter hook output priority raw; policy accept; 169 | } 170 | 171 | 172 | # 173 | # Mangle rules 174 | # 175 | 176 | chain mangle_prerouting { 177 | type filter hook prerouting priority mangle; policy accept; 178 | } 179 | 180 | chain mangle_postrouting { 181 | type filter hook postrouting priority mangle; policy accept; 182 | } 183 | 184 | chain mangle_input { 185 | type filter hook input priority mangle; policy accept; 186 | } 187 | 188 | chain mangle_output { 189 | type route hook output priority mangle; policy accept; 190 | } 191 | 192 | chain mangle_forward { 193 | type filter hook forward priority mangle; policy accept; 194 | } 195 | } 196 | -- End -- 197 | -------------------------------------------------------------------------------- /tests/06_includes/03_script_includes: -------------------------------------------------------------------------------- 1 | Testing the execution of include scripts. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | TRACE_CALLS: "stderr", 7 | 8 | getenv: function(varname) { 9 | switch (varname) { 10 | case 'ACTION': 11 | return 'includes'; 12 | } 13 | } 14 | }) 15 | %} 16 | -- End -- 17 | 18 | -- File fs/open~_var_run_fw4_state.txt -- 19 | { 20 | "includes": [ 21 | { 22 | "enabled": true, 23 | "path": "/usr/share/miniupnpd/firewall.include", 24 | "type": "script", 25 | "fw4_compatible": true, 26 | "position": "table-append" 27 | }, 28 | 29 | { 30 | "enabled": true, 31 | "path": "/etc/example.sh", 32 | "type": "script", 33 | "fw4_compatible": true, 34 | "position": "table-append" 35 | } 36 | ] 37 | } 38 | -- End -- 39 | 40 | -- Expect stderr -- 41 | [call] fs.open path mode 42 | [call] system command <[ "sh", "-c", "exec 1000>&-; config() { echo \"You cannot use UCI in firewall in..." ]> timeout <30000> 43 | [call] system command <[ "sh", "-c", "exec 1000>&-; config() { echo \"You cannot use UCI in firewall in..." ]> timeout <30000> 44 | -- End -- 45 | -------------------------------------------------------------------------------- /tests/06_includes/04_disabled_include: -------------------------------------------------------------------------------- 1 | Testing that include sections with `option enabled 0` are skipped. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File fs/open~_sys_class_net_eth0_flags.txt -- 21 | 0x1103 22 | -- End -- 23 | 24 | -- File fs/open~_etc_testinclude1_nft.txt -- 25 | # dummy 26 | -- End -- 27 | 28 | -- File fs/open~_etc_testinclude2_nft.txt -- 29 | # dummy 30 | -- End -- 31 | 32 | -- File fs/open~_etc_testinclude3_nft.txt -- 33 | # dummy 34 | -- End -- 35 | 36 | -- File uci/firewall.json -- 37 | { 38 | "zone": [ 39 | { 40 | "name": "test", 41 | "device": [ "eth0" ], 42 | "auto_helper": 0 43 | } 44 | ], 45 | "include": [ 46 | { 47 | ".description": "By default, this include should be processed due to implicit enabled 1", 48 | "path": "/etc/testinclude1.nft", 49 | "type": "nftables" 50 | }, 51 | 52 | { 53 | ".description": "This include should be processed due to explicit enabled 1", 54 | "path": "/etc/testinclude2.nft", 55 | "type": "nftables", 56 | "enabled": "1" 57 | }, 58 | 59 | { 60 | ".description": "This include should be skipped due to explicit enabled 0", 61 | "path": "/etc/testinclude3.nft", 62 | "type": "nftables", 63 | "enabled": "0" 64 | } 65 | ] 66 | } 67 | -- End -- 68 | 69 | -- Expect stderr -- 70 | [!] Section @include[2] is disabled, ignoring section 71 | -- End -- 72 | 73 | -- Expect stdout -- 74 | table inet fw4 75 | flush table inet fw4 76 | 77 | table inet fw4 { 78 | # 79 | # Defines 80 | # 81 | 82 | define test_devices = { "eth0" } 83 | define test_subnets = { } 84 | 85 | 86 | # 87 | # User includes 88 | # 89 | 90 | include "/etc/nftables.d/*.nft" 91 | 92 | 93 | # 94 | # Filter rules 95 | # 96 | 97 | chain input { 98 | type filter hook input priority filter; policy drop; 99 | 100 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 101 | 102 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 103 | iifname "eth0" jump input_test comment "!fw4: Handle test IPv4/IPv6 input traffic" 104 | } 105 | 106 | chain forward { 107 | type filter hook forward priority filter; policy drop; 108 | 109 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 110 | iifname "eth0" jump forward_test comment "!fw4: Handle test IPv4/IPv6 forward traffic" 111 | } 112 | 113 | chain output { 114 | type filter hook output priority filter; policy drop; 115 | 116 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 117 | 118 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 119 | oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic" 120 | } 121 | 122 | chain prerouting { 123 | type filter hook prerouting priority filter; policy accept; 124 | } 125 | 126 | chain handle_reject { 127 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 128 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 129 | } 130 | 131 | chain input_test { 132 | jump drop_from_test 133 | } 134 | 135 | chain output_test { 136 | jump drop_to_test 137 | } 138 | 139 | chain forward_test { 140 | jump drop_to_test 141 | } 142 | 143 | chain drop_from_test { 144 | iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic" 145 | } 146 | 147 | chain drop_to_test { 148 | oifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic" 149 | } 150 | 151 | 152 | # 153 | # NAT rules 154 | # 155 | 156 | chain dstnat { 157 | type nat hook prerouting priority dstnat; policy accept; 158 | } 159 | 160 | chain srcnat { 161 | type nat hook postrouting priority srcnat; policy accept; 162 | } 163 | 164 | 165 | # 166 | # Raw rules (notrack) 167 | # 168 | 169 | chain raw_prerouting { 170 | type filter hook prerouting priority raw; policy accept; 171 | } 172 | 173 | chain raw_output { 174 | type filter hook output priority raw; policy accept; 175 | } 176 | 177 | 178 | # 179 | # Mangle rules 180 | # 181 | 182 | chain mangle_prerouting { 183 | type filter hook prerouting priority mangle; policy accept; 184 | } 185 | 186 | chain mangle_postrouting { 187 | type filter hook postrouting priority mangle; policy accept; 188 | } 189 | 190 | chain mangle_input { 191 | type filter hook input priority mangle; policy accept; 192 | } 193 | 194 | chain mangle_output { 195 | type route hook output priority mangle; policy accept; 196 | } 197 | 198 | chain mangle_forward { 199 | type filter hook forward priority mangle; policy accept; 200 | } 201 | 202 | include "/etc/testinclude1.nft" 203 | include "/etc/testinclude2.nft" 204 | } 205 | -- End -- 206 | -------------------------------------------------------------------------------- /tests/06_includes/05_automatic_includes: -------------------------------------------------------------------------------- 1 | Testing that /usr/share/nftables.d/ includes are automatically processed. 2 | 3 | -- Testcase -- 4 | {% 5 | include("./root/usr/share/firewall4/main.uc", { 6 | getenv: function(varname) { 7 | switch (varname) { 8 | case 'ACTION': 9 | return 'print'; 10 | } 11 | } 12 | }) 13 | %} 14 | -- End -- 15 | 16 | -- File uci/helpers.json -- 17 | {} 18 | -- End -- 19 | 20 | -- File fs/open~_sys_class_net_eth0_flags.txt -- 21 | 0x1103 22 | -- End -- 23 | 24 | -- File fs/open~_etc_testinclude1_nft.txt -- 25 | # dummy 26 | -- End -- 27 | 28 | -- File fs/open~_etc_testinclude2_nft.txt -- 29 | # dummy 30 | -- End -- 31 | 32 | -- File fs/open~_etc_testinclude3_nft.txt -- 33 | # dummy 34 | -- End -- 35 | 36 | -- File uci/firewall.json -- 37 | { 38 | "zone": [ 39 | { 40 | "name": "test", 41 | "device": [ "eth0" ], 42 | "auto_helper": 0 43 | } 44 | ], 45 | "include": [ 46 | { 47 | ".description": "By default, this include should be processed due to implicit enabled 1", 48 | "path": "/etc/testinclude1.nft", 49 | "type": "nftables" 50 | }, 51 | 52 | { 53 | ".description": "This include should be processed due to explicit enabled 1", 54 | "path": "/etc/testinclude2.nft", 55 | "type": "nftables", 56 | "enabled": "1" 57 | }, 58 | 59 | { 60 | ".description": "This include should be skipped due to explicit enabled 0", 61 | "path": "/etc/testinclude3.nft", 62 | "type": "nftables", 63 | "enabled": "0" 64 | } 65 | ] 66 | } 67 | -- End -- 68 | 69 | -- Expect stderr -- 70 | [!] Section @include[2] is disabled, ignoring section 71 | -- End -- 72 | 73 | -- Expect stdout -- 74 | table inet fw4 75 | flush table inet fw4 76 | 77 | table inet fw4 { 78 | # 79 | # Defines 80 | # 81 | 82 | define test_devices = { "eth0" } 83 | define test_subnets = { } 84 | 85 | 86 | # 87 | # User includes 88 | # 89 | 90 | include "/etc/nftables.d/*.nft" 91 | 92 | 93 | # 94 | # Filter rules 95 | # 96 | 97 | chain input { 98 | type filter hook input priority filter; policy drop; 99 | 100 | iif "lo" accept comment "!fw4: Accept traffic from loopback" 101 | 102 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle inbound flows" 103 | iifname "eth0" jump input_test comment "!fw4: Handle test IPv4/IPv6 input traffic" 104 | } 105 | 106 | chain forward { 107 | type filter hook forward priority filter; policy drop; 108 | 109 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle forwarded flows" 110 | iifname "eth0" jump forward_test comment "!fw4: Handle test IPv4/IPv6 forward traffic" 111 | } 112 | 113 | chain output { 114 | type filter hook output priority filter; policy drop; 115 | 116 | oif "lo" accept comment "!fw4: Accept traffic towards loopback" 117 | 118 | ct state vmap { established : accept, related : accept } comment "!fw4: Handle outbound flows" 119 | oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic" 120 | } 121 | 122 | chain prerouting { 123 | type filter hook prerouting priority filter; policy accept; 124 | } 125 | 126 | chain handle_reject { 127 | meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic" 128 | reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic" 129 | } 130 | 131 | chain input_test { 132 | jump drop_from_test 133 | } 134 | 135 | chain output_test { 136 | jump drop_to_test 137 | } 138 | 139 | chain forward_test { 140 | jump drop_to_test 141 | } 142 | 143 | chain drop_from_test { 144 | iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic" 145 | } 146 | 147 | chain drop_to_test { 148 | oifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic" 149 | } 150 | 151 | 152 | # 153 | # NAT rules 154 | # 155 | 156 | chain dstnat { 157 | type nat hook prerouting priority dstnat; policy accept; 158 | } 159 | 160 | chain srcnat { 161 | type nat hook postrouting priority srcnat; policy accept; 162 | } 163 | 164 | 165 | # 166 | # Raw rules (notrack) 167 | # 168 | 169 | chain raw_prerouting { 170 | type filter hook prerouting priority raw; policy accept; 171 | } 172 | 173 | chain raw_output { 174 | type filter hook output priority raw; policy accept; 175 | } 176 | 177 | 178 | # 179 | # Mangle rules 180 | # 181 | 182 | chain mangle_prerouting { 183 | type filter hook prerouting priority mangle; policy accept; 184 | } 185 | 186 | chain mangle_postrouting { 187 | type filter hook postrouting priority mangle; policy accept; 188 | } 189 | 190 | chain mangle_input { 191 | type filter hook input priority mangle; policy accept; 192 | } 193 | 194 | chain mangle_output { 195 | type route hook output priority mangle; policy accept; 196 | } 197 | 198 | chain mangle_forward { 199 | type filter hook forward priority mangle; policy accept; 200 | } 201 | 202 | include "/etc/testinclude1.nft" 203 | include "/etc/testinclude2.nft" 204 | } 205 | -- End -- 206 | -------------------------------------------------------------------------------- /tests/lib/mocklib.uc: -------------------------------------------------------------------------------- 1 | /* strict mode compliance: ensure that global variabes are defined */ 2 | if (!exists(global, 'REQUIRE_SEARCH_PATH')) 3 | global.MOCK_SEARCH_PATH = null; 4 | 5 | if (!exists(global, 'MOCK_SEARCH_PATH')) 6 | global.MOCK_SEARCH_PATH = null; 7 | 8 | if (!exists(global, 'TRACE_CALLS')) 9 | global.TRACE_CALLS = null; 10 | 11 | let _fs = require("fs"); 12 | 13 | /* Force reloading fs module on next require */ 14 | delete global.modules.fs; 15 | 16 | let _log = (level, fmt, ...args) => { 17 | let color, prefix; 18 | 19 | switch (level) { 20 | case 'info': 21 | color = 34; 22 | prefix = '!'; 23 | break; 24 | 25 | case 'warn': 26 | color = 33; 27 | prefix = 'W'; 28 | break; 29 | 30 | case 'error': 31 | color = 31; 32 | prefix = 'E'; 33 | break; 34 | 35 | default: 36 | color = 0; 37 | prefix = 'I'; 38 | } 39 | 40 | let f = sprintf("\u001b[%d;1m[%s] %s\u001b[0m", color, prefix, fmt); 41 | warn(replace(sprintf(f, ...args), "\n", "\n "), "\n"); 42 | }; 43 | 44 | let read_data_file = (path) => { 45 | for (let dir in MOCK_SEARCH_PATH) { 46 | let fd = _fs.open(dir + '/' + path, "r"); 47 | 48 | if (fd) { 49 | let data = fd.read("all"); 50 | fd.close(); 51 | 52 | return data; 53 | } 54 | } 55 | 56 | return null; 57 | }; 58 | 59 | let read_json_file = (path) => { 60 | let data = read_data_file(path); 61 | 62 | if (data != null) { 63 | try { 64 | return json(data); 65 | } 66 | catch (e) { 67 | _log('error', "Unable to parse JSON data in %s: %s", path, e); 68 | 69 | return NaN; 70 | } 71 | } 72 | 73 | return null; 74 | }; 75 | 76 | let format_json = (data) => { 77 | let rv; 78 | 79 | let format_value = (value) => { 80 | switch (type(value)) { 81 | case "object": 82 | return sprintf("{ /* %d keys */ }", length(value)); 83 | 84 | case "array": 85 | return sprintf("[ /* %d items */ ]", length(value)); 86 | 87 | case "string": 88 | if (length(value) > 64) 89 | value = substr(value, 0, 64) + "..."; 90 | 91 | /* fall through */ 92 | return sprintf("%J", value); 93 | 94 | default: 95 | return sprintf("%J", value); 96 | } 97 | }; 98 | 99 | switch (type(data)) { 100 | case "object": 101 | rv = "{"; 102 | 103 | let k = sort(keys(data)); 104 | 105 | for (let i, n in k) 106 | rv += sprintf("%s %J: %s", i ? "," : "", n, format_value(data[n])); 107 | 108 | rv += " }"; 109 | break; 110 | 111 | case "array": 112 | rv = "["; 113 | 114 | for (let i, v in data) 115 | rv += (i ? "," : "") + " " + format_value(v); 116 | 117 | rv += " ]"; 118 | break; 119 | 120 | default: 121 | rv = format_value(data); 122 | } 123 | 124 | return rv; 125 | }; 126 | 127 | let trace_call = (ns, func, args) => { 128 | let msg = "[call] " + 129 | (ns ? ns + "." : "") + 130 | func; 131 | 132 | for (let k, v in args) { 133 | msg += ' ' + k + ' <'; 134 | 135 | switch (type(v)) { 136 | case "array": 137 | case "object": 138 | msg += format_json(v); 139 | break; 140 | 141 | default: 142 | msg += v; 143 | } 144 | 145 | msg += '>'; 146 | } 147 | 148 | switch (TRACE_CALLS) { 149 | case '1': 150 | case 'stdout': 151 | _fs.stdout.write(msg + "\n"); 152 | break; 153 | 154 | case 'stderr': 155 | _fs.stderr.write(msg + "\n"); 156 | break; 157 | } 158 | }; 159 | 160 | /* Prepend mocklib to REQUIRE_SEARCH_PATH */ 161 | for (let pattern in REQUIRE_SEARCH_PATH) { 162 | /* Only consider ucode includes */ 163 | if (!match(pattern, /\*\.uc$/)) 164 | continue; 165 | 166 | let path = replace(pattern, /\*/, 'mocklib'), 167 | stat = _fs.stat(path); 168 | 169 | if (!stat || stat.type != 'file') 170 | continue; 171 | 172 | if (type(MOCK_SEARCH_PATH) != 'array' || length(MOCK_SEARCH_PATH) == 0) 173 | MOCK_SEARCH_PATH = [ replace(path, /mocklib\.uc$/, '../mocks') ]; 174 | 175 | unshift(REQUIRE_SEARCH_PATH, replace(path, /mocklib\.uc$/, 'mocklib/*.uc')); 176 | break; 177 | } 178 | 179 | if (type(MOCK_SEARCH_PATH) != 'array' || length(MOCK_SEARCH_PATH) == 0) 180 | MOCK_SEARCH_PATH = [ './mocks' ]; 181 | 182 | let _print = global.print; 183 | 184 | /* Register global mocklib namespace */ 185 | global.mocklib = { 186 | require: function(module) { 187 | let path, res, ex; 188 | 189 | if (type(REQUIRE_SEARCH_PATH) == "array" && index(REQUIRE_SEARCH_PATH[0], 'mocklib/*.uc') != -1) 190 | path = shift(REQUIRE_SEARCH_PATH); 191 | 192 | try { 193 | res = require(module); 194 | } 195 | catch (e) { 196 | ex = e; 197 | } 198 | 199 | if (path) 200 | unshift(REQUIRE_SEARCH_PATH, path); 201 | 202 | if (ex) 203 | die(ex); 204 | 205 | return res; 206 | }, 207 | 208 | I: (...args) => _log('info', ...args), 209 | N: (...args) => _log('notice', ...args), 210 | W: (...args) => _log('warn', ...args), 211 | E: (...args) => _log('error', ...args), 212 | 213 | format_json, 214 | read_data_file, 215 | read_json_file, 216 | trace_call 217 | }; 218 | 219 | /* Override stdlib functions */ 220 | global.system = function(argv, timeout) { 221 | trace_call(null, "system", { command: argv, timeout }); 222 | 223 | return 0; 224 | }; 225 | 226 | global.time = function() { 227 | trace_call(null, "time"); 228 | 229 | return 1615382640; 230 | }; 231 | 232 | global.print = function(...args) { 233 | if (length(args) == 1 && type(args[0]) in ["array", "object"]) 234 | printf("%s\n", format_json(args[0])); 235 | else 236 | _print(...args); 237 | }; 238 | 239 | return global.mocklib; 240 | 241 | -------------------------------------------------------------------------------- /tests/lib/mocklib/fs.uc: -------------------------------------------------------------------------------- 1 | let mocklib = global.mocklib, 2 | fs = mocklib.require("fs"); 3 | 4 | return { 5 | readlink: function(path) { 6 | mocklib.trace_call("fs", "readlink", { path }); 7 | 8 | return path; 9 | }, 10 | 11 | stat: function(path) { 12 | let file = sprintf("fs/stat~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')), 13 | mock = mocklib.read_json_file(file); 14 | 15 | if (!mock || mock != mock) { 16 | mocklib.I("No stat result fixture defined for fs.stat() call on %s.", path); 17 | mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file); 18 | 19 | if (match(path, /\/$/)) 20 | mock = { type: "directory" }; 21 | else 22 | mock = { type: "file" }; 23 | } 24 | 25 | mocklib.trace_call("fs", "stat", { path }); 26 | 27 | return mock; 28 | }, 29 | 30 | unlink: function(path) { 31 | printf("fs.unlink() path <%s>\n", path); 32 | 33 | return true; 34 | }, 35 | 36 | popen: (cmdline, mode) => { 37 | let read = (!mode || index(mode, "r") != -1), 38 | path = sprintf("fs/popen~%s.txt", replace(cmdline, /[^A-Za-z0-9_-]+/g, '_')), 39 | mock = mocklib.read_data_file(path); 40 | 41 | if (read && !mock) { 42 | mocklib.I("No stdout fixture defined for fs.popen() command %s.", cmdline); 43 | mocklib.I("Provide a mock output through the following text file:\n%s\n", path); 44 | 45 | return null; 46 | } 47 | 48 | mocklib.trace_call("fs", "popen", { cmdline, mode }); 49 | 50 | return { 51 | read: function(amount) { 52 | let rv; 53 | 54 | switch (amount) { 55 | case "all": 56 | rv = mock; 57 | mock = ""; 58 | break; 59 | 60 | case "line": 61 | let i = index(mock, "\n"); 62 | i = (i > -1) ? i + 1 : mock.length; 63 | rv = substr(mock, 0, i); 64 | mock = substr(mock, i); 65 | break; 66 | 67 | default: 68 | let n = +amount; 69 | n = (n > 0) ? n : 0; 70 | rv = substr(mock, 0, n); 71 | mock = substr(mock, n); 72 | break; 73 | } 74 | 75 | return rv; 76 | }, 77 | 78 | write: function() {}, 79 | close: function() {}, 80 | 81 | error: function() { 82 | return null; 83 | } 84 | }; 85 | }, 86 | 87 | open: (fpath, mode) => { 88 | let read = (!mode || index(mode, "r") != -1 || index(mode, "+") != -1), 89 | path = sprintf("fs/open~%s.txt", replace(fpath, /[^A-Za-z0-9_-]+/g, '_')), 90 | mock = read ? mocklib.read_data_file(path) : null; 91 | 92 | if (read && !mock) { 93 | mocklib.I("No stdout fixture defined for fs.open() path %s.", fpath); 94 | mocklib.I("Provide a mock output through the following text file:\n%s\n", path); 95 | 96 | return null; 97 | } 98 | 99 | mocklib.trace_call("fs", "open", { path: fpath, mode }); 100 | 101 | return { 102 | read: function(amount) { 103 | let rv; 104 | 105 | switch (amount) { 106 | case "all": 107 | rv = mock; 108 | mock = ""; 109 | break; 110 | 111 | case "line": 112 | let i = index(mock, "\n"); 113 | i = (i > -1) ? i + 1 : length(mock); 114 | rv = substr(mock, 0, i); 115 | mock = length(mock) ? substr(mock, i) : null; 116 | break; 117 | 118 | default: 119 | let n = +amount; 120 | n = (n > 0) ? n : 0; 121 | rv = substr(mock, 0, n); 122 | mock = substr(mock, n); 123 | break; 124 | } 125 | 126 | return rv; 127 | }, 128 | 129 | write: function() {}, 130 | close: function() {}, 131 | 132 | error: function() { 133 | return null; 134 | } 135 | }; 136 | }, 137 | 138 | readfile: (fpath, limit) => { 139 | let path = sprintf("fs/open~%s.txt", replace(fpath, /[^A-Za-z0-9_-]+/g, '_')), 140 | mock = mocklib.read_data_file(path); 141 | 142 | if (!mock) { 143 | mocklib.I("No stdout fixture defined for fs.readfile() path %s.", fpath); 144 | mocklib.I("Provide a mock output through the following text file:\n%s\n", path); 145 | 146 | return null; 147 | } 148 | 149 | mocklib.trace_call("fs", "readfile", { path: fpath, limit }); 150 | 151 | return limit ? substr(mock, 0, limit) : mock; 152 | }, 153 | 154 | access: (fpath) => { 155 | let path = sprintf("fs/open~%s.txt", replace(fpath, /[^A-Za-z0-9_-]+/g, '_')), 156 | mock = mocklib.read_data_file(path); 157 | 158 | if (!mock) { 159 | mocklib.I("No stdout fixture defined for fs.access() path %s.", fpath); 160 | mocklib.I("Provide a mock output through the following text file:\n%s\n", path); 161 | 162 | return false; 163 | } 164 | 165 | return true; 166 | }, 167 | 168 | opendir: (path) => { 169 | let file = sprintf("fs/opendir~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')), 170 | mock = mocklib.read_json_file(file), 171 | index = 0; 172 | 173 | if (!mock || mock != mock) { 174 | mocklib.I("No stat result fixture defined for fs.opendir() call on %s.", path); 175 | mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file); 176 | 177 | mock = []; 178 | } 179 | 180 | mocklib.trace_call("fs", "opendir", { path }); 181 | 182 | return { 183 | read: function() { 184 | return mock[index++]; 185 | }, 186 | 187 | tell: function() { 188 | return index; 189 | }, 190 | 191 | seek: function(i) { 192 | index = i; 193 | }, 194 | 195 | close: function() {}, 196 | 197 | error: function() { 198 | return null; 199 | } 200 | }; 201 | }, 202 | 203 | glob: (pattern) => { 204 | let file = sprintf("fs/glob~%s.json", replace(pattern, /[^A-Za-z0-9_-]+/g, '_')), 205 | mock = mocklib.read_json_file(file), 206 | index = 0; 207 | 208 | if (!mock || mock != mock) { 209 | mocklib.I("No stat result fixture defined for fs.glob() call on %s.", pattern); 210 | mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file); 211 | 212 | mock = []; 213 | } 214 | 215 | mocklib.trace_call("fs", "glob", { pattern }); 216 | 217 | return mock; 218 | }, 219 | 220 | lsdir: (path) => { 221 | let file = sprintf("fs/opendir~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')), 222 | mock = mocklib.read_json_file(file), 223 | index = 0; 224 | 225 | if (!mock || mock != mock) { 226 | mocklib.I("No stat result fixture defined for fs.lsdir() call on %s.", path); 227 | mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file); 228 | 229 | mock = []; 230 | } 231 | 232 | mocklib.trace_call("fs", "lsdir", { path }); 233 | 234 | return mock; 235 | }, 236 | 237 | error: () => "Unspecified error" 238 | }; 239 | -------------------------------------------------------------------------------- /tests/lib/mocklib/ubus.uc: -------------------------------------------------------------------------------- 1 | let mocklib = global.mocklib; 2 | 3 | return { 4 | connect: function() { 5 | let self = this; 6 | 7 | return { 8 | call: (object, method, args) => { 9 | let signature = [ object + "~" + method ]; 10 | 11 | if (type(args) == "object") { 12 | for (let i, k in sort(keys(args))) { 13 | switch (type(args[k])) { 14 | case "string": 15 | case "double": 16 | case "bool": 17 | case "int": 18 | push(signature, k + "-" + replace(args[k], /[^A-Za-z0-9_-]+/g, "_")); 19 | break; 20 | 21 | default: 22 | push(signature, type(args[k])); 23 | } 24 | } 25 | } 26 | 27 | let candidates = []; 28 | 29 | for (let i = length(signature); i > 0; i--) { 30 | let path = sprintf("ubus/%s.json", join("~", signature)), 31 | mock = mocklib.read_json_file(path); 32 | 33 | if (mock != mock) { 34 | self._error = "Invalid argument"; 35 | 36 | return null; 37 | } 38 | else if (mock) { 39 | mocklib.trace_call("ctx", "call", { object, method, args }); 40 | 41 | return mock; 42 | } 43 | 44 | push(candidates, path); 45 | pop(signature); 46 | } 47 | 48 | mocklib.I("No response fixture defined for ubus call %s/%s with arguments %s.", object, method, args); 49 | mocklib.I("Provide a mock response through one of the following JSON files:\n%s\n", join("\n", candidates)); 50 | 51 | self._error = "Method not found"; 52 | 53 | return null; 54 | }, 55 | 56 | disconnect: () => null, 57 | 58 | error: () => self.error() 59 | }; 60 | }, 61 | 62 | error: function() { 63 | let e = this._error; 64 | delete this._error; 65 | 66 | return e; 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /tests/lib/mocklib/uci.uc: -------------------------------------------------------------------------------- 1 | let mocklib = global.mocklib; 2 | 3 | let byte = (str, off) => { 4 | let v = ord(str, off); 5 | return length(v) ? v[0] : v; 6 | }; 7 | 8 | let hash = (s) => { 9 | let h = 7; 10 | 11 | for (let i = 0; i < length(s); i++) 12 | h = h * 31 + byte(s, i); 13 | 14 | return h; 15 | }; 16 | 17 | let id = (config, t, n) => { 18 | while (true) { 19 | let id = sprintf('cfg%08x', hash(t + n)); 20 | 21 | if (!exists(config, id)) 22 | return id; 23 | 24 | n++; 25 | } 26 | }; 27 | 28 | let fixup_config = (config) => { 29 | let rv = {}; 30 | let n_section = 0; 31 | 32 | for (let stype in config) { 33 | switch (type(config[stype])) { 34 | case 'object': 35 | config[stype] = [ config[stype] ]; 36 | /* fall through */ 37 | 38 | case 'array': 39 | for (let idx, sobj in config[stype]) { 40 | let sid, anon; 41 | 42 | if (exists(sobj, '.name') && !exists(rv, sobj['.name'])) { 43 | sid = sobj['.name']; 44 | anon = false; 45 | } 46 | else { 47 | sid = id(rv, stype, idx); 48 | anon = true; 49 | } 50 | 51 | rv[sid] = { 52 | '.index': n_section++, 53 | ...sobj, 54 | '.name': sid, 55 | '.type': stype, 56 | '.anonymous': anon 57 | }; 58 | } 59 | 60 | break; 61 | } 62 | } 63 | 64 | for (let n, sid in sort(keys(rv), (a, b) => rv[a]['.index'] - rv[b]['.index'])) 65 | rv[sid]['.index'] = n; 66 | 67 | return rv; 68 | }; 69 | 70 | return { 71 | cursor: () => ({ 72 | _configs: {}, 73 | 74 | load: function(file) { 75 | let basename = replace(file, /^.+\//, ''), 76 | path = sprintf("uci/%s.json", basename), 77 | mock = mocklib.read_json_file(path); 78 | 79 | if (!mock || mock != mock) { 80 | mocklib.I("No configuration fixture defined for uci package %s.", file); 81 | mocklib.I("Provide a mock configuration through the following JSON file:\n%s\n", path); 82 | 83 | return null; 84 | } 85 | 86 | this._configs[basename] = fixup_config(mock); 87 | }, 88 | 89 | _get_section: function(config, section) { 90 | if (!exists(this._configs, config)) { 91 | this.load(config); 92 | 93 | if (!exists(this._configs, config)) 94 | return null; 95 | } 96 | 97 | let cfg = this._configs[config], 98 | extended = match(section, "^@([A-Za-z0-9_-]+)\[(-?[0-9]+)\]$"); 99 | 100 | if (extended) { 101 | let stype = extended[1], 102 | sindex = +extended[2]; 103 | 104 | let sids = sort( 105 | filter(keys(cfg), sid => cfg[sid]['.type'] == stype), 106 | (a, b) => cfg[a]['.index'] - cfg[b]['.index'] 107 | ); 108 | 109 | if (sindex < 0) 110 | sindex = sids.length + sindex; 111 | 112 | return cfg[sids[sindex]]; 113 | } 114 | 115 | return cfg[section]; 116 | }, 117 | 118 | get: function(config, section, option) { 119 | let sobj = this._get_section(config, section); 120 | 121 | if (option && index(option, ".") == 0) 122 | return null; 123 | else if (sobj && option) 124 | return sobj[option]; 125 | else if (sobj) 126 | return sobj[".type"]; 127 | }, 128 | 129 | get_all: function(config, section) { 130 | return section ? this._get_section(config, section) : this._configs[config]; 131 | }, 132 | 133 | foreach: function(config, stype, cb) { 134 | let rv = false; 135 | 136 | if (exists(this._configs, config)) { 137 | let cfg = this._configs[config], 138 | sids = sort(keys(cfg), (a, b) => cfg[a]['.index'] - cfg[b]['.index']); 139 | 140 | for (let i, sid in sids) { 141 | if (stype == null || cfg[sid]['.type'] == stype) { 142 | if (cb({ ...(cfg[sid]) }) === false) 143 | break; 144 | 145 | rv = true; 146 | } 147 | } 148 | } 149 | 150 | return rv; 151 | } 152 | }) 153 | }; 154 | -------------------------------------------------------------------------------- /tests/mocks/fs/glob~_usr_share_nftables_d_ruleset-post_nft.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/glob~_usr_share_nftables_d_ruleset-pre_nft.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/glob~_usr_share_nftables_d_table-post_nft.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/glob~_usr_share_nftables_d_table-pre_nft.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/opendir~_sys_class_net_br-lan.json: -------------------------------------------------------------------------------- 1 | [ 2 | "lower_eth0" 3 | ] 4 | -------------------------------------------------------------------------------- /tests/mocks/fs/opendir~_sys_class_net_eth0.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/opendir~_sys_class_net_eth1.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/opendir~_sys_class_net_pppoe-wan.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/opendir~_usr_share_nftables_d_chain-post.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/opendir~_usr_share_nftables_d_chain-pre.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/open~_proc_version.txt: -------------------------------------------------------------------------------- 1 | Linux version 5.4.101 (jow@j7) (gcc version 8.4.0 (OpenWrt GCC 8.4.0 r12978-7c2e0fa586)) #0 SMP Tue Mar 2 14:41:54 2021 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/open~_sys_class_net_br-lan_flags.txt: -------------------------------------------------------------------------------- 1 | 0x1003 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/open~_sys_class_net_br-lan_uevent.txt: -------------------------------------------------------------------------------- 1 | DEVTYPE=bridge 2 | INTERFACE=switch0 3 | IFINDEX=12 4 | -------------------------------------------------------------------------------- /tests/mocks/fs/open~_sys_class_net_eth0_uevent.txt: -------------------------------------------------------------------------------- 1 | DEVTYPE=dsa 2 | OF_NAME=port 3 | OF_FULLNAME=/ethernet@1e100000/mdio-bus/switch@1f/ports/port@0 4 | OF_COMPATIBLE_N=0 5 | INTERFACE=eth0 6 | IFINDEX=3 7 | -------------------------------------------------------------------------------- /tests/mocks/fs/open~_sys_class_net_eth1_uevent.txt: -------------------------------------------------------------------------------- 1 | DEVTYPE=dsa 2 | OF_NAME=port 3 | OF_FULLNAME=/ethernet@1e100000/mdio-bus/switch@1f/ports/port@1 4 | OF_COMPATIBLE_N=0 5 | INTERFACE=eth1 6 | IFINDEX=4 7 | -------------------------------------------------------------------------------- /tests/mocks/fs/popen~_usr_sbin_nft_--terse_--json_list_flowtables_inet.txt: -------------------------------------------------------------------------------- 1 | {"nftables": [{"metainfo": {"version": "1.0.1", "release_name": "Fearless Fosdick #3", "json_schema_version": 1}}]} 2 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_amanda.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_dummy.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_ftp.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_h323.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_irc.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_netbios_ns.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_pptp.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_rtsp.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_sane.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_sip.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_snmp.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/fs/stat~_sys_module_nf_conntrack_tftp.json: -------------------------------------------------------------------------------- 1 | { 2 | "atime" : 1616175834, 3 | "blksize" : 4096, 4 | "blocks" : 0, 5 | "ctime" : 1616175834, 6 | "dev" : { 7 | "major" : 0, 8 | "minor" : 11 9 | }, 10 | "gid" : 0, 11 | "inode" : 6586, 12 | "mode" : 493, 13 | "mtime" : 1616175834, 14 | "nlink" : 6, 15 | "perm" : { 16 | "group_exec" : true, 17 | "group_read" : true, 18 | "group_write" : false, 19 | "other_exec" : true, 20 | "other_read" : true, 21 | "other_write" : false, 22 | "setgid" : false, 23 | "setuid" : false, 24 | "sticky" : false, 25 | "user_exec" : true, 26 | "user_read" : true, 27 | "user_write" : true 28 | }, 29 | "size" : 0, 30 | "type" : "directory", 31 | "uid" : 0 32 | } 33 | -------------------------------------------------------------------------------- /tests/mocks/ubus/network.device~status.json: -------------------------------------------------------------------------------- 1 | { 2 | "eth0": { 3 | "external": false, 4 | "present": true, 5 | "type": "Network device", 6 | "up": true, 7 | "carrier": true, 8 | "auth_status": false, 9 | "link-advertising": [ 10 | "10baseT-H", 11 | "10baseT-F", 12 | "100baseT-H", 13 | "100baseT-F", 14 | "1000baseT-F" 15 | ], 16 | "link-partner-advertising": [ 17 | "10baseT-H", 18 | "10baseT-F", 19 | "100baseT-H", 20 | "100baseT-F", 21 | "1000baseT-H", 22 | "1000baseT-F" 23 | ], 24 | "link-supported": [ 25 | "10baseT-H", 26 | "10baseT-F", 27 | "100baseT-H", 28 | "100baseT-F", 29 | "1000baseT-F" 30 | ], 31 | "speed": "1000F", 32 | "autoneg": true, 33 | "hw-tc-offload": true, 34 | "devtype": "dsa", 35 | "mtu": 1500, 36 | "mtu6": 1500, 37 | "macaddr": "74:ac:b9:a1:84:46", 38 | "txqueuelen": 1000, 39 | "ipv6": false, 40 | "ip6segmentrouting": false, 41 | "promisc": false, 42 | "rpfilter": 0, 43 | "acceptlocal": false, 44 | "igmpversion": 0, 45 | "mldversion": 0, 46 | "neigh4reachabletime": 30000, 47 | "neigh6reachabletime": 30000, 48 | "neigh4gcstaletime": 60, 49 | "neigh6gcstaletime": 60, 50 | "neigh4locktime": 100, 51 | "dadtransmits": 1, 52 | "multicast": true, 53 | "sendredirects": true, 54 | "drop_v4_unicast_in_l2_multicast": false, 55 | "drop_v6_unicast_in_l2_multicast": false, 56 | "drop_gratuitous_arp": false, 57 | "drop_unsolicited_na": false, 58 | "arp_accept": false, 59 | "statistics": { 60 | "collisions": 0, 61 | "rx_frame_errors": 0, 62 | "tx_compressed": 0, 63 | "multicast": 0, 64 | "rx_length_errors": 0, 65 | "tx_dropped": 0, 66 | "rx_bytes": 53206036390, 67 | "rx_missed_errors": 0, 68 | "tx_errors": 0, 69 | "rx_compressed": 0, 70 | "rx_over_errors": 0, 71 | "tx_fifo_errors": 0, 72 | "rx_crc_errors": 0, 73 | "rx_packets": 115554141, 74 | "tx_heartbeat_errors": 0, 75 | "rx_dropped": 0, 76 | "tx_aborted_errors": 0, 77 | "tx_packets": 44534107, 78 | "rx_errors": 0, 79 | "tx_bytes": 41926154781, 80 | "tx_window_errors": 0, 81 | "rx_fifo_errors": 0, 82 | "tx_carrier_errors": 0 83 | } 84 | }, 85 | "eth1": { 86 | "external": false, 87 | "present": true, 88 | "type": "Network device", 89 | "up": true, 90 | "carrier": true, 91 | "auth_status": false, 92 | "link-advertising": [ 93 | "10baseT-H", 94 | "10baseT-F", 95 | "100baseT-H", 96 | "100baseT-F", 97 | "1000baseT-F" 98 | ], 99 | "link-partner-advertising": [ 100 | "10baseT-H", 101 | "10baseT-F", 102 | "100baseT-H", 103 | "100baseT-F", 104 | "1000baseT-F" 105 | ], 106 | "link-supported": [ 107 | "10baseT-H", 108 | "10baseT-F", 109 | "100baseT-H", 110 | "100baseT-F", 111 | "1000baseT-F" 112 | ], 113 | "speed": "1000F", 114 | "autoneg": true, 115 | "hw-tc-offload": true, 116 | "devtype": "dsa", 117 | "mtu": 1500, 118 | "mtu6": 1500, 119 | "macaddr": "74:ac:b9:a1:84:47", 120 | "txqueuelen": 1000, 121 | "ipv6": false, 122 | "ip6segmentrouting": false, 123 | "promisc": false, 124 | "rpfilter": 0, 125 | "acceptlocal": false, 126 | "igmpversion": 0, 127 | "mldversion": 0, 128 | "neigh4reachabletime": 30000, 129 | "neigh6reachabletime": 30000, 130 | "neigh4gcstaletime": 60, 131 | "neigh6gcstaletime": 60, 132 | "neigh4locktime": 100, 133 | "dadtransmits": 1, 134 | "multicast": true, 135 | "sendredirects": true, 136 | "drop_v4_unicast_in_l2_multicast": false, 137 | "drop_v6_unicast_in_l2_multicast": false, 138 | "drop_gratuitous_arp": false, 139 | "drop_unsolicited_na": false, 140 | "arp_accept": false, 141 | "statistics": { 142 | "collisions": 0, 143 | "rx_frame_errors": 0, 144 | "tx_compressed": 0, 145 | "multicast": 0, 146 | "rx_length_errors": 0, 147 | "tx_dropped": 0, 148 | "rx_bytes": 470925492, 149 | "rx_missed_errors": 0, 150 | "tx_errors": 0, 151 | "rx_compressed": 0, 152 | "rx_over_errors": 0, 153 | "tx_fifo_errors": 0, 154 | "rx_crc_errors": 0, 155 | "rx_packets": 4591620, 156 | "tx_heartbeat_errors": 0, 157 | "rx_dropped": 0, 158 | "tx_aborted_errors": 0, 159 | "tx_packets": 4210181, 160 | "rx_errors": 0, 161 | "tx_bytes": 7724980929, 162 | "tx_window_errors": 0, 163 | "rx_fifo_errors": 0, 164 | "tx_carrier_errors": 0 165 | } 166 | }, 167 | "lo": { 168 | "external": false, 169 | "present": true, 170 | "type": "Network device", 171 | "up": true, 172 | "carrier": true, 173 | "auth_status": false, 174 | "hw-tc-offload": false, 175 | "devtype": "loopback", 176 | "mtu": 65536, 177 | "mtu6": 65536, 178 | "macaddr": "00:00:00:00:00:00", 179 | "txqueuelen": 1000, 180 | "ipv6": true, 181 | "ip6segmentrouting": false, 182 | "promisc": false, 183 | "rpfilter": 0, 184 | "acceptlocal": false, 185 | "igmpversion": 0, 186 | "mldversion": 0, 187 | "neigh4reachabletime": 30000, 188 | "neigh6reachabletime": 30000, 189 | "neigh4gcstaletime": 60, 190 | "neigh6gcstaletime": 60, 191 | "neigh4locktime": 100, 192 | "dadtransmits": 1, 193 | "multicast": false, 194 | "sendredirects": true, 195 | "drop_v4_unicast_in_l2_multicast": false, 196 | "drop_v6_unicast_in_l2_multicast": false, 197 | "drop_gratuitous_arp": false, 198 | "drop_unsolicited_na": false, 199 | "arp_accept": false, 200 | "statistics": { 201 | "collisions": 0, 202 | "rx_frame_errors": 0, 203 | "tx_compressed": 0, 204 | "multicast": 0, 205 | "rx_length_errors": 0, 206 | "tx_dropped": 0, 207 | "rx_bytes": 2993476, 208 | "rx_missed_errors": 0, 209 | "tx_errors": 0, 210 | "rx_compressed": 0, 211 | "rx_over_errors": 0, 212 | "tx_fifo_errors": 0, 213 | "rx_crc_errors": 0, 214 | "rx_packets": 34056, 215 | "tx_heartbeat_errors": 0, 216 | "rx_dropped": 0, 217 | "tx_aborted_errors": 0, 218 | "tx_packets": 34056, 219 | "rx_errors": 0, 220 | "tx_bytes": 2993476, 221 | "tx_window_errors": 0, 222 | "rx_fifo_errors": 0, 223 | "tx_carrier_errors": 0 224 | } 225 | }, 226 | "br-lan": { 227 | "external": false, 228 | "present": true, 229 | "type": "bridge", 230 | "up": true, 231 | "carrier": true, 232 | "auth_status": false, 233 | "link-advertising": [ 234 | 235 | ], 236 | "link-partner-advertising": [ 237 | 238 | ], 239 | "link-supported": [ 240 | 241 | ], 242 | "speed": "1000F", 243 | "autoneg": false, 244 | "hw-tc-offload": false, 245 | "devtype": "bridge", 246 | "bridge-members": [ 247 | "eth0" 248 | ], 249 | "bridge-vlans": [ 250 | 251 | ], 252 | "mtu": 1500, 253 | "mtu6": 1500, 254 | "macaddr": "74:ac:b9:a1:84:46", 255 | "txqueuelen": 1000, 256 | "ipv6": true, 257 | "ip6segmentrouting": false, 258 | "promisc": false, 259 | "rpfilter": 0, 260 | "acceptlocal": false, 261 | "igmpversion": 0, 262 | "mldversion": 0, 263 | "neigh4reachabletime": 30000, 264 | "neigh6reachabletime": 30000, 265 | "neigh4gcstaletime": 60, 266 | "neigh6gcstaletime": 60, 267 | "neigh4locktime": 100, 268 | "dadtransmits": 1, 269 | "multicast": true, 270 | "sendredirects": true, 271 | "drop_v4_unicast_in_l2_multicast": false, 272 | "drop_v6_unicast_in_l2_multicast": false, 273 | "drop_gratuitous_arp": false, 274 | "drop_unsolicited_na": false, 275 | "arp_accept": false, 276 | "statistics": { 277 | "collisions": 0, 278 | "rx_frame_errors": 0, 279 | "tx_compressed": 0, 280 | "multicast": 27625, 281 | "rx_length_errors": 0, 282 | "tx_dropped": 0, 283 | "rx_bytes": 9505679939, 284 | "rx_missed_errors": 0, 285 | "tx_errors": 0, 286 | "rx_compressed": 0, 287 | "rx_over_errors": 0, 288 | "tx_fifo_errors": 0, 289 | "rx_crc_errors": 0, 290 | "rx_packets": 13282955, 291 | "tx_heartbeat_errors": 0, 292 | "rx_dropped": 0, 293 | "tx_aborted_errors": 0, 294 | "tx_packets": 12647462, 295 | "rx_errors": 0, 296 | "tx_bytes": 12983448170, 297 | "tx_window_errors": 0, 298 | "rx_fifo_errors": 0, 299 | "tx_carrier_errors": 0 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /tests/mocks/ubus/network.interface~dump.json: -------------------------------------------------------------------------------- 1 | { 2 | "interface": [ 3 | { 4 | "interface": "loopback", 5 | "up": true, 6 | "pending": false, 7 | "available": true, 8 | "autostart": true, 9 | "dynamic": false, 10 | "uptime": 89939, 11 | "l3_device": "lo", 12 | "proto": "static", 13 | "device": "lo", 14 | "updated": [ 15 | "addresses" 16 | ], 17 | "metric": 0, 18 | "dns_metric": 0, 19 | "delegation": true, 20 | "ipv4-address": [ 21 | { 22 | "address": "127.0.0.1", 23 | "mask": 8 24 | } 25 | ], 26 | "ipv6-address": [ 27 | 28 | ], 29 | "ipv6-prefix": [ 30 | 31 | ], 32 | "ipv6-prefix-assignment": [ 33 | 34 | ], 35 | "route": [ 36 | 37 | ], 38 | "dns-server": [ 39 | 40 | ], 41 | "dns-search": [ 42 | 43 | ], 44 | "neighbors": [ 45 | 46 | ], 47 | "inactive": { 48 | "ipv4-address": [ 49 | 50 | ], 51 | "ipv6-address": [ 52 | 53 | ], 54 | "route": [ 55 | 56 | ], 57 | "dns-server": [ 58 | 59 | ], 60 | "dns-search": [ 61 | 62 | ], 63 | "neighbors": [ 64 | 65 | ] 66 | }, 67 | "data": { 68 | 69 | } 70 | }, 71 | { 72 | "interface": "lan", 73 | "up": true, 74 | "pending": false, 75 | "available": true, 76 | "autostart": true, 77 | "dynamic": false, 78 | "uptime": 89940, 79 | "l3_device": "br-lan", 80 | "proto": "static", 81 | "device": "br-lan", 82 | "updated": [ 83 | "addresses" 84 | ], 85 | "metric": 0, 86 | "dns_metric": 0, 87 | "delegation": true, 88 | "ipv4-address": [ 89 | { 90 | "address": "10.0.0.1", 91 | "mask": 24 92 | }, 93 | { 94 | "address": "192.168.26.1", 95 | "mask": 24 96 | } 97 | ], 98 | "ipv6-address": [ 99 | 100 | ], 101 | "ipv6-prefix": [ 102 | 103 | ], 104 | "ipv6-prefix-assignment": [ 105 | { 106 | "address": "2001:db8:1000:1::", 107 | "mask": 60, 108 | "local-address": { 109 | "address": "2001:db8:1000:1::1", 110 | "mask": 60 111 | } 112 | }, 113 | { 114 | "address": "fd63:e2f:f706:1::", 115 | "mask": 60, 116 | "local-address": { 117 | "address": "fd63:e2f:f706:1::1", 118 | "mask": 60 119 | } 120 | } 121 | ], 122 | "route": [ 123 | 124 | ], 125 | "dns-server": [ 126 | 127 | ], 128 | "dns-search": [ 129 | 130 | ], 131 | "neighbors": [ 132 | 133 | ], 134 | "inactive": { 135 | "ipv4-address": [ 136 | 137 | ], 138 | "ipv6-address": [ 139 | 140 | ], 141 | "route": [ 142 | 143 | ], 144 | "dns-server": [ 145 | 146 | ], 147 | "dns-search": [ 148 | 149 | ], 150 | "neighbors": [ 151 | 152 | ] 153 | }, 154 | "data": { 155 | 156 | } 157 | }, 158 | { 159 | "interface": "guest", 160 | "up": true, 161 | "pending": false, 162 | "available": true, 163 | "autostart": true, 164 | "dynamic": false, 165 | "uptime": 89940, 166 | "l3_device": "br-guest", 167 | "proto": "static", 168 | "device": "br-guest", 169 | "updated": [ 170 | "addresses" 171 | ], 172 | "metric": 0, 173 | "dns_metric": 0, 174 | "delegation": true, 175 | "ipv4-address": [ 176 | { 177 | "address": "10.1.0.1", 178 | "mask": 24 179 | }, 180 | { 181 | "address": "192.168.27.1", 182 | "mask": 24 183 | } 184 | ], 185 | "ipv6-address": [ 186 | 187 | ], 188 | "ipv6-prefix": [ 189 | 190 | ], 191 | "ipv6-prefix-assignment": [ 192 | { 193 | "address": "2001:db8:1000:2::", 194 | "mask": 60, 195 | "local-address": { 196 | "address": "2001:db8:1000:2::1", 197 | "mask": 60 198 | } 199 | }, 200 | { 201 | "address": "fd63:e2f:f706:2::", 202 | "mask": 60, 203 | "local-address": { 204 | "address": "fd63:e2f:f706:2::1", 205 | "mask": 60 206 | } 207 | } 208 | ], 209 | "route": [ 210 | 211 | ], 212 | "dns-server": [ 213 | 214 | ], 215 | "dns-search": [ 216 | 217 | ], 218 | "neighbors": [ 219 | 220 | ], 221 | "inactive": { 222 | "ipv4-address": [ 223 | 224 | ], 225 | "ipv6-address": [ 226 | 227 | ], 228 | "route": [ 229 | 230 | ], 231 | "dns-server": [ 232 | 233 | ], 234 | "dns-search": [ 235 | 236 | ], 237 | "neighbors": [ 238 | 239 | ] 240 | }, 241 | "data": { 242 | 243 | } 244 | }, 245 | { 246 | "interface": "wan", 247 | "up": true, 248 | "pending": false, 249 | "available": true, 250 | "autostart": true, 251 | "dynamic": false, 252 | "uptime": 35968, 253 | "l3_device": "pppoe-wan", 254 | "proto": "pppoe", 255 | "device": "eth1", 256 | "metric": 0, 257 | "dns_metric": 0, 258 | "delegation": true, 259 | "ipv4-address": [ 260 | { 261 | "address": "10.11.12.194", 262 | "mask": 24 263 | } 264 | ], 265 | "ipv6-address": [ 266 | 267 | ], 268 | "ipv6-prefix": [ 269 | 270 | ], 271 | "ipv6-prefix-assignment": [ 272 | 273 | ], 274 | "route": [ 275 | { 276 | "target": "0.0.0.0", 277 | "mask": 0, 278 | "nexthop": "10.11.12.13", 279 | "source": "10.11.12.194/32" 280 | } 281 | ], 282 | "dns-server": [ 283 | "10.11.12.13" 284 | ], 285 | "dns-search": [ 286 | "lan" 287 | ], 288 | "neighbors": [ 289 | 290 | ], 291 | "inactive": { 292 | "ipv4-address": [ 293 | 294 | ], 295 | "ipv6-address": [ 296 | 297 | ], 298 | "route": [ 299 | 300 | ], 301 | "dns-server": [ 302 | 303 | ], 304 | "dns-search": [ 305 | 306 | ], 307 | "neighbors": [ 308 | 309 | ] 310 | }, 311 | "data": { 312 | 313 | } 314 | }, 315 | { 316 | "interface": "wan6", 317 | "up": true, 318 | "pending": false, 319 | "available": true, 320 | "autostart": true, 321 | "dynamic": false, 322 | "uptime": 16264, 323 | "l3_device": "pppoe-wan", 324 | "proto": "dhcpv6", 325 | "updated": [ 326 | "addresses", 327 | "routes", 328 | "prefixes" 329 | ], 330 | "metric": 0, 331 | "dns_metric": 0, 332 | "delegation": true, 333 | "ipv4-address": [ 334 | 335 | ], 336 | "ipv6-address": [ 337 | { 338 | "address": "2001:db8:54:321::2", 339 | "mask": 64 340 | } 341 | ], 342 | "ipv6-prefix": [ 343 | { 344 | "address": "2001:db8:1000::", 345 | "mask": 48, 346 | "class": "wan6", 347 | "assigned": { 348 | "lan": { 349 | "address": "2001:db8:1000:1::", 350 | "mask": 60 351 | }, 352 | "guest": { 353 | "address": "2001:db8:1000:2::", 354 | "mask": 60 355 | } 356 | } 357 | } 358 | ], 359 | "ipv6-prefix-assignment": [ 360 | 361 | ], 362 | "route": [ 363 | { 364 | "target": "::", 365 | "mask": 0, 366 | "nexthop": "::", 367 | "source": "2001:db8:1000::/48" 368 | }, 369 | { 370 | "target": "::", 371 | "mask": 0, 372 | "nexthop": "::", 373 | "source": "2001:db8:54:321::2/64" 374 | } 375 | ], 376 | "dns-server": [ 377 | 378 | ], 379 | "dns-search": [ 380 | 381 | ], 382 | "neighbors": [ 383 | 384 | ], 385 | "inactive": { 386 | "ipv4-address": [ 387 | 388 | ], 389 | "ipv6-address": [ 390 | 391 | ], 392 | "route": [ 393 | 394 | ], 395 | "dns-server": [ 396 | 397 | ], 398 | "dns-search": [ 399 | 400 | ], 401 | "neighbors": [ 402 | 403 | ] 404 | }, 405 | "data": { 406 | 407 | } 408 | }, 409 | { 410 | "interface": "noaddr", 411 | "up": true, 412 | "pending": false, 413 | "available": true, 414 | "autostart": true, 415 | "dynamic": false, 416 | "uptime": 89940, 417 | "l3_device": "wwan0", 418 | "proto": "static", 419 | "device": "wwan0", 420 | "updated": [ 421 | 422 | ], 423 | "metric": 0, 424 | "dns_metric": 0, 425 | "delegation": true, 426 | "ipv4-address": [ 427 | 428 | ], 429 | "ipv6-address": [ 430 | 431 | ], 432 | "ipv6-prefix": [ 433 | 434 | ], 435 | "ipv6-prefix-assignment": [ 436 | 437 | ], 438 | "route": [ 439 | 440 | ], 441 | "dns-server": [ 442 | 443 | ], 444 | "dns-search": [ 445 | 446 | ], 447 | "neighbors": [ 448 | 449 | ], 450 | "inactive": { 451 | "ipv4-address": [ 452 | 453 | ], 454 | "ipv6-address": [ 455 | 456 | ], 457 | "route": [ 458 | 459 | ], 460 | "dns-server": [ 461 | 462 | ], 463 | "dns-search": [ 464 | 465 | ], 466 | "neighbors": [ 467 | 468 | ] 469 | }, 470 | "data": { 471 | 472 | } 473 | } 474 | ] 475 | } 476 | -------------------------------------------------------------------------------- /tests/mocks/ubus/service~get_data~type-firewall.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /tests/mocks/uci/firewall.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaults": { 3 | "flow_offloading": "1", 4 | "flow_offloading_hw": "1", 5 | "forward": "REJECT", 6 | "input": "REJECT", 7 | "output": "ACCEPT", 8 | "syn_flood": "1", 9 | "unknown_defaults_option": "foo" 10 | }, 11 | "zone": [ 12 | { 13 | "name": "lan", 14 | "input": "ACCEPT", 15 | "output": "ACCEPT", 16 | "forward": "ACCEPT", 17 | "network": [ "lan" ] 18 | }, 19 | { 20 | "input": "REJECT", 21 | "output": "ACCEPT", 22 | "forward": "REJECT", 23 | "masq": "1", 24 | "mtu_fix": "1", 25 | "name": "wan", 26 | "network": [ "wan", "wan6" ] 27 | } 28 | ], 29 | "forwarding": { 30 | "dest": "wan", 31 | "src": "lan" 32 | }, 33 | "rule": [ 34 | { 35 | "name": "Allow-DHCP-Renew", 36 | "family": "ipv4", 37 | "proto": "udp", 38 | "src": "wan", 39 | "dest_port": "68", 40 | "target": "ACCEPT" 41 | }, 42 | { 43 | "name": "Allow-Ping", 44 | "family": "ipv4", 45 | "proto": "icmp", 46 | "src": "wan", 47 | "icmp_type": "echo-request", 48 | "target": "ACCEPT" 49 | }, 50 | { 51 | "name": "Allow-IGMP", 52 | "family": "ipv4", 53 | "proto": "igmp", 54 | "src": "wan", 55 | "target": "ACCEPT" 56 | }, 57 | { 58 | "name": "Allow-DHCPv6", 59 | "family": "ipv6", 60 | "proto": "udp", 61 | "src": "wan", 62 | "src_ip": "fc00::/6", 63 | "dest_ip": "fc00::/6", 64 | "dest_port": "546", 65 | "target": "ACCEPT" 66 | }, 67 | { 68 | "name": "Allow-MLD", 69 | "family": "ipv6", 70 | "proto": "icmp", 71 | "src": "wan", 72 | "src_ip": "fe80::/10", 73 | "icmp_type": [ "130/0", "131/0", "132/0", "143/0" ], 74 | "target": "ACCEPT" 75 | }, 76 | { 77 | "name": "Allow-ICMPv6-Input", 78 | "family": "ipv6", 79 | "proto": "icmp", 80 | "src": "wan", 81 | "icmp_type": [ 82 | "echo-request", "echo-reply", "destination-unreachable", 83 | "packet-too-big", "time-exceeded", "bad-header", "unknown-header-type", 84 | "router-solicitation", "neighbour-solicitation", "router-advertisement", 85 | "neighbour-advertisement" 86 | ], 87 | "limit": "1000/sec", 88 | "target": "ACCEPT" 89 | }, 90 | { 91 | "name": "Allow-ICMPv6-Forward", 92 | "family": "ipv6", 93 | "proto": "icmp", 94 | "src": "wan", 95 | "dest": "*", 96 | "icmp_type": [ 97 | "echo-request", "echo-reply", "destination-unreachable", 98 | "packet-too-big", "time-exceeded", "bad-header", "unknown-header-type" 99 | ], 100 | "limit": "1000/sec", 101 | "target": "ACCEPT" 102 | }, 103 | { 104 | "name": "Allow-IPSec-ESP", 105 | "proto": "esp", 106 | "src": "wan", 107 | "dest": "lan", 108 | "target": "ACCEPT" 109 | }, 110 | { 111 | "name": "Allow-ISAKMP", 112 | "proto": "udp", 113 | "src": "wan", 114 | "dest": "lan", 115 | "dest_port": "500", 116 | "target": "ACCEPT" 117 | }, 118 | { 119 | "name": "Test-Deprecated-Rule-Option", 120 | "_name": "Test-Deprecated-Rule-Option", 121 | "proto": "tcp", 122 | "unknown_rule_option": "foo" 123 | } 124 | ] 125 | } 126 | -------------------------------------------------------------------------------- /tests/mocks/uci/helpers.json: -------------------------------------------------------------------------------- 1 | { 2 | "helper": [ 3 | { 4 | "name": "amanda", 5 | "description": "Amanda backup and archiving proto", 6 | "family": "any", 7 | "module": "nf_conntrack_amanda", 8 | "port": "10080", 9 | "proto": "udp" 10 | }, 11 | { 12 | "name": "ftp", 13 | "description": "FTP passive connection tracking", 14 | "family": "any", 15 | "module": "nf_conntrack_ftp", 16 | "port": "21", 17 | "proto": "tcp" 18 | }, 19 | { 20 | "name": "RAS", 21 | "description": "RAS proto tracking", 22 | "family": "any", 23 | "module": "nf_conntrack_h323", 24 | "port": "1719", 25 | "proto": "udp" 26 | }, 27 | { 28 | "name": "Q.931", 29 | "description": "Q.931 proto tracking", 30 | "family": "any", 31 | "module": "nf_conntrack_h323", 32 | "port": "1720", 33 | "proto": "tcp" 34 | }, 35 | { 36 | "name": "irc", 37 | "description": "IRC DCC connection tracking", 38 | "family": "ipv4", 39 | "module": "nf_conntrack_irc", 40 | "port": "6667", 41 | "proto": "tcp" 42 | }, 43 | { 44 | "name": "netbios-ns", 45 | "description": "NetBIOS name service broadcast tracking", 46 | "family": "ipv4", 47 | "module": "nf_conntrack_netbios_ns", 48 | "port": "137", 49 | "proto": "udp" 50 | }, 51 | { 52 | "name": "pptp", 53 | "description": "PPTP VPN connection tracking", 54 | "family": "ipv4", 55 | "module": "nf_conntrack_pptp", 56 | "port": "1723", 57 | "proto": "tcp" 58 | }, 59 | { 60 | "name": "sane", 61 | "description": "SANE scanner connection tracking", 62 | "family": "any", 63 | "module": "nf_conntrack_sane", 64 | "port": "6566", 65 | "proto": "tcp" 66 | }, 67 | { 68 | "name": "sip", 69 | "description": "SIP VoIP connection tracking", 70 | "family": "any", 71 | "module": "nf_conntrack_sip", 72 | "port": "5060", 73 | "proto": "udp" 74 | }, 75 | { 76 | "name": "snmp", 77 | "description": "SNMP monitoring connection tracking", 78 | "family": "ipv4", 79 | "module": "nf_conntrack_snmp", 80 | "port": "161", 81 | "proto": "udp" 82 | }, 83 | { 84 | "name": "tftp", 85 | "description": "TFTP connection tracking", 86 | "family": "any", 87 | "module": "nf_conntrack_tftp", 88 | "port": "69", 89 | "proto": "udp" 90 | }, 91 | { 92 | "name": "rtsp", 93 | "description": "RTSP connection tracking", 94 | "family": "ipv4", 95 | "module": "nf_conntrack_rtsp", 96 | "port": "554", 97 | "proto": "tcp" 98 | } 99 | ] 100 | } 101 | --------------------------------------------------------------------------------