diff --git a/net/pbr/Makefile b/net/pbr/Makefile index 53ab64311d..f9668a5ce1 100644 --- a/net/pbr/Makefile +++ b/net/pbr/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=pbr PKG_VERSION:=1.2.2 -PKG_RELEASE:=6 +PKG_RELEASE:=8 PKG_LICENSE:=AGPL-3.0-or-later PKG_MAINTAINER:=Stan Grishin diff --git a/net/pbr/files/etc/init.d/pbr b/net/pbr/files/etc/init.d/pbr index f9a9c42301..d110db0729 100755 --- a/net/pbr/files/etc/init.d/pbr +++ b/net/pbr/files/etc/init.d/pbr @@ -2218,12 +2218,6 @@ interface_routing() { try ip -4 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 fi - if ! nft_file 'match' 'temp' "${nftPrefix}_mark_${mark}"; then - nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" - nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} ${nftRuleParams} meta mark set (meta mark & ${fw_maskXor}) | ${mark}" - nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" - fi - dscp="$(uci_get "$packageName" 'config' "${iface}_dscp" '0')" if [ "$dscp" -ge '1' ] && [ "$dscp" -le '63' ]; then nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv4Flag} dscp ${dscp} ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" @@ -2231,6 +2225,12 @@ interface_routing() { if [ "$iface" = "$icmp_interface" ]; then nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv4Flag} protocol icmp ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" fi + elif [ -n "$strict_enforcement" ] && ! { is_split_uplink && [ "$iface" = "$uplink_interface6" ]; }; then + ipv4_error=0 + ip -4 rule flush table "$tid" >/dev/null 2>&1 + ip -4 route flush table "$tid" >/dev/null 2>&1 + try ip -4 route replace unreachable default table "$tid" || ipv4_error=1 + try ip -4 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 fi if [ -n "$ipv6_enabled" ] && [ -n "$dev6" ]; then @@ -2256,12 +2256,6 @@ interface_routing() { try ip -6 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1 fi - if ! nft_file 'match' 'temp' "${nftPrefix}_mark_${mark}"; then - nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" - nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} ${nftRuleParams} meta mark set (meta mark & ${fw_maskXor}) | ${mark}" - nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" - fi - dscp="$(uci_get "$packageName" 'config' "${iface}_dscp" '0')" if [ "$dscp" -ge '1' ] && [ "$dscp" -le '63' ]; then nft add rule inet "$nftTable" "${nftPrefix}_prerouting ${nftIPv6Flag} dscp ${dscp} ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" @@ -2269,6 +2263,19 @@ interface_routing() { if [ "$iface" = "$icmp_interface" ]; then nft add rule inet "$nftTable" "${nftPrefix}_output ${nftIPv6Flag} protocol icmp ${nftRuleParams} goto ${nftPrefix}_mark_${mark}" fi + elif [ -n "$ipv6_enabled" ] && [ -n "$strict_enforcement" ] && ! { is_split_uplink && [ "$iface" = "$uplink_interface4" ]; }; then + ipv6_error=0 + ip -6 rule flush table "$tid" >/dev/null 2>&1 + ip -6 route flush table "$tid" >/dev/null 2>&1 + try ip -6 route replace unreachable default table "$tid" || ipv6_error=1 + try ip -6 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1 + fi + + # Always create the nft mark chain so policies can reference it + if ! nft_file 'match' 'temp' "${nftPrefix}_mark_${mark}"; then + nft add chain inet "$nftTable" "${nftPrefix}_mark_${mark}" + nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} ${nftRuleParams} meta mark set (meta mark & ${fw_maskXor}) | ${mark}" + nft add rule inet "$nftTable" "${nftPrefix}_mark_${mark} return" fi if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then @@ -2318,6 +2325,12 @@ interface_routing() { fi try ip -4 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 fi + elif [ -n "$strict_enforcement" ] && ! { is_split_uplink && [ "$iface" = "$uplink_interface6" ]; }; then + ipv4_error=0 + ip -4 rule flush table "$tid" >/dev/null 2>&1 + ip -4 route flush table "$tid" >/dev/null 2>&1 + try ip -4 route replace unreachable default table "$tid" || ipv4_error=1 + try ip -4 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv4_error=1 fi if [ -n "$ipv6_enabled" ] && [ -n "$dev6" ]; then @@ -2341,6 +2354,12 @@ interface_routing() { fi try ip -6 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1 fi + elif [ -n "$ipv6_enabled" ] && [ -n "$strict_enforcement" ] && ! { is_split_uplink && [ "$iface" = "$uplink_interface4" ]; }; then + ipv6_error=0 + ip -6 rule flush table "$tid" >/dev/null 2>&1 + ip -6 route flush table "$tid" >/dev/null 2>&1 + try ip -6 route replace unreachable default table "$tid" || ipv6_error=1 + try ip -6 rule replace fwmark "${mark}/${fw_mask}" table "$tid" priority "$priority" || ipv6_error=1 fi if [ "$ipv4_error" -eq '0' ] || [ "$ipv6_error" -eq '0' ]; then @@ -3140,16 +3159,25 @@ print_config_masked() { { line = $0 + result = "" # Mask digits inside IPv4-looking tokens, keep dots + # RFC1918/loopback addresses are not sensitive — skip masking them while (match(line, /([0-9]{1,3}\.){3}[0-9]{1,3}/)) { ip = substr(line, RSTART, RLENGTH) - masked = ip - gsub(/[0-9]/, "*", masked) - line = substr(line, 1, RSTART-1) masked substr(line, RSTART+RLENGTH) + result = result substr(line, 1, RSTART-1) + line = substr(line, RSTART+RLENGTH) + + if (ip ~ /^(10\.|127\.|192\.168\.)/ || ip ~ /^172\.(1[6-9]|2[0-9]|3[01])\./) { + result = result ip + } else { + masked = ip + gsub(/[0-9]/, "*", masked) + result = result masked + } } - print line + print result line } ' \ | sed -E 's/([a-fA-F0-9:]{2,}:){1,7}[a-fA-F0-9]{2,}/***/g' diff --git a/net/pbr/tests/07_support/01_print_config_masked b/net/pbr/tests/07_support/01_print_config_masked new file mode 100644 index 0000000000..bf9f952481 --- /dev/null +++ b/net/pbr/tests/07_support/01_print_config_masked @@ -0,0 +1,88 @@ +#!/bin/bash +# Test: print_config_masked - IP masking logic +. "$(dirname "$0")/../lib/setup.sh" + +oneTimeTearDown() { rm -rf "${MOCK_ROOT:-}"; } + +# Apply the IP-masking awk pass from print_config_masked to a single input line. +# Mirrors the second awk block in print_config_masked so we can test it in isolation +# without needing to write files to /etc/config/. +_mask_ips() { + printf '%s\n' "$1" | awk ' + /^[ \t]*(option|list)[ \t]+allowed_ips[ \t]+/ { print; next } + { + line = $0; result = "" + while (match(line, /([0-9]{1,3}\.){3}[0-9]{1,3}/)) { + ip = substr(line, RSTART, RLENGTH) + result = result substr(line, 1, RSTART-1) + line = substr(line, RSTART+RLENGTH) + if (ip ~ /^(10\.|127\.|192\.168\.)/ || ip ~ /^172\.(1[6-9]|2[0-9]|3[01])\./) + result = result ip + else { masked = ip; gsub(/[0-9]/, "*", masked); result = result masked } + } + print result line + } + ' +} + +testPublicIPIsMasked() { + assertEquals "Public IP masked" \ + " option gateway '*.*.*.*'" \ + "$(_mask_ips " option gateway '1.2.3.4'")" +} + +testRFC1918_10_preserved() { + assertEquals "10.x not masked" \ + " option gateway '10.0.0.1'" \ + "$(_mask_ips " option gateway '10.0.0.1'")" +} + +testRFC1918_192_168_preserved() { + assertEquals "192.168.x not masked" \ + " option gateway '192.168.1.254'" \ + "$(_mask_ips " option gateway '192.168.1.254'")" +} + +testRFC1918_172_16_preserved() { + assertEquals "172.16.x not masked" \ + " option gateway '172.16.0.1'" \ + "$(_mask_ips " option gateway '172.16.0.1'")" +} + +testRFC1918_172_31_preserved() { + assertEquals "172.31.x not masked" \ + " option gateway '172.31.255.254'" \ + "$(_mask_ips " option gateway '172.31.255.254'")" +} + +testBorderBelow_172_16_masked() { + assertEquals "172.15.x is not RFC1918 - masked" \ + " option gateway '***.**.*.*'" \ + "$(_mask_ips " option gateway '172.15.0.1'")" +} + +testBorderAbove_172_31_masked() { + assertEquals "172.32.x is not RFC1918 - masked" \ + " option gateway '***.**.*.*'" \ + "$(_mask_ips " option gateway '172.32.0.1'")" +} + +testLoopbackPreserved() { + assertEquals "127.x loopback not masked" \ + " option dns '127.0.0.1'" \ + "$(_mask_ips " option dns '127.0.0.1'")" +} + +testAllowedIPsLineNotMasked() { + assertEquals "allowed_ips line bypasses IP masking" \ + " option allowed_ips '8.8.8.8/32'" \ + "$(_mask_ips " option allowed_ips '8.8.8.8/32'")" +} + +testMixedLinePrivateAndPublic() { + assertEquals "Private preserved, public masked on same line" \ + " option foo '192.168.1.1 *.*.*.*'" \ + "$(_mask_ips " option foo '192.168.1.1 8.8.8.8'")" +} + +. shunit2