mirror of
https://gitlab.com/openconnect/ocserv.git
synced 2026-08-09 09:51:49 +08:00
ocserv-fw-nftables: replace ipcalc with pure-shell mask_to_prefix
ipcalc was used only to convert dotted-decimal subnet masks to CIDR prefix lengths (e.g. 255.255.0.0 -> 16), replaced with a POSIX shell script. Relates: #709 Signed-off-by: Nikos Mavrogiannopoulos <n.mavrogiannopoulos@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
* Version 1.5.0 (unreleased)
|
* Version 1.5.0 (unreleased)
|
||||||
|
- ocserv-fw-nftables: dropped runtime dependency on ipcalc/ipcalc-ng (#709)
|
||||||
- Fixed sudden disconnects after authentication for AnyConnect clients (#706)
|
- Fixed sudden disconnects after authentication for AnyConnect clients (#706)
|
||||||
- Vhosts now inherit configuration options from the default vhost if
|
- Vhosts now inherit configuration options from the default vhost if
|
||||||
they are not overridden (#705)
|
they are not overridden (#705)
|
||||||
|
|||||||
+2
-1
@@ -40,7 +40,8 @@ endif
|
|||||||
|
|
||||||
gperf = find_program('gperf', required: false)
|
gperf = find_program('gperf', required: false)
|
||||||
protoc_c = find_program('protoc-c', required: false)
|
protoc_c = find_program('protoc-c', required: false)
|
||||||
ipcalc = find_program(['ipcalc', 'ipcalc-ng'], required: true)
|
ipcalc = find_program(['ipcalc', 'ipcalc-ng'], required: false)
|
||||||
|
|
||||||
# Firewall script selection
|
# Firewall script selection
|
||||||
_fw_opt = get_option('firewall-script')
|
_fw_opt = get_option('firewall-script')
|
||||||
fw_script_type = (_fw_opt == 'auto') ? 'nftables' : _fw_opt
|
fw_script_type = (_fw_opt == 'auto') ? 'nftables' : _fw_opt
|
||||||
|
|||||||
+37
-11
@@ -52,16 +52,6 @@
|
|||||||
|
|
||||||
PATH=/sbin:/usr/sbin:$PATH
|
PATH=/sbin:/usr/sbin:$PATH
|
||||||
|
|
||||||
IPCALC=$(which ipcalc-ng 2>/dev/null)
|
|
||||||
if test -z "${IPCALC}"; then
|
|
||||||
IPCALC=$(which ipcalc 2>/dev/null)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test -z "${IPCALC}"; then
|
|
||||||
logger -t ocserv-fw "ipcalc or ipcalc-ng is required but not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# nft table names cannot contain hyphens, dots, or other non-identifier chars
|
# nft table names cannot contain hyphens, dots, or other non-identifier chars
|
||||||
TABLE="ocserv_$(echo "${DEVICE}" | sed 's/[^a-zA-Z0-9_]/_/g')"
|
TABLE="ocserv_$(echo "${DEVICE}" | sed 's/[^a-zA-Z0-9_]/_/g')"
|
||||||
|
|
||||||
@@ -127,13 +117,49 @@ emit_route_rules() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Convert a dotted-decimal subnet mask (e.g. 255.255.0.0) to a CIDR prefix
|
||||||
|
# length (16). Valid subnet masks are a closed set so a case table suffices.
|
||||||
|
# Returns 1 and logs an error for invalid masks.
|
||||||
|
mask_to_prefix() {
|
||||||
|
bits=0
|
||||||
|
seen_partial=0
|
||||||
|
n=0
|
||||||
|
IFS=.
|
||||||
|
for octet in $1; do
|
||||||
|
n=$((n+1))
|
||||||
|
if test "$seen_partial" = "1" && test "$octet" != "0"; then
|
||||||
|
logger -t ocserv-fw "invalid subnet mask: $1"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
case $octet in
|
||||||
|
255) bits=$((bits+8)) ;;
|
||||||
|
254) bits=$((bits+7)); seen_partial=1 ;;
|
||||||
|
252) bits=$((bits+6)); seen_partial=1 ;;
|
||||||
|
248) bits=$((bits+5)); seen_partial=1 ;;
|
||||||
|
240) bits=$((bits+4)); seen_partial=1 ;;
|
||||||
|
224) bits=$((bits+3)); seen_partial=1 ;;
|
||||||
|
192) bits=$((bits+2)); seen_partial=1 ;;
|
||||||
|
128) bits=$((bits+1)); seen_partial=1 ;;
|
||||||
|
0) seen_partial=1 ;;
|
||||||
|
*) logger -t ocserv-fw "invalid subnet mask: $1"
|
||||||
|
return 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if test "$n" != "4"; then
|
||||||
|
logger -t ocserv-fw "invalid subnet mask: $1"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "$bits"
|
||||||
|
}
|
||||||
|
|
||||||
# Convert a route that may use a dotted-decimal subnet mask
|
# Convert a route that may use a dotted-decimal subnet mask
|
||||||
# (e.g. 10.0.0.0/255.0.0.0, as ocserv normalises IPv4 routes) to
|
# (e.g. 10.0.0.0/255.0.0.0, as ocserv normalises IPv4 routes) to
|
||||||
# CIDR prefix-length notation (10.0.0.0/8) required by nftables.
|
# CIDR prefix-length notation (10.0.0.0/8) required by nftables.
|
||||||
# IPv6 routes and routes already in CIDR notation are passed through.
|
# IPv6 routes and routes already in CIDR notation are passed through.
|
||||||
normalize_route() {
|
normalize_route() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
*/*.*.*.*) eval $(${IPCALC} -p "$1"); echo "${1%%/*}/${PREFIX}" ;;
|
*/*.*.*.*) prefix=$(mask_to_prefix "${1#*/}") || return 1
|
||||||
|
echo "${1%%/*}/${prefix}" ;;
|
||||||
*) echo "$1" ;;
|
*) echo "$1" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ always_scripts = [
|
|||||||
'ocpasswd-test',
|
'ocpasswd-test',
|
||||||
'test-owasp-headers',
|
'test-owasp-headers',
|
||||||
'test-replay',
|
'test-replay',
|
||||||
|
'test-fw-normalize-route',
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach s : always_scripts
|
foreach s : always_scripts
|
||||||
|
|||||||
Executable
+182
@@ -0,0 +1,182 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Nikos Mavrogiannopoulos
|
||||||
|
#
|
||||||
|
# This file is part of ocserv.
|
||||||
|
#
|
||||||
|
# ocserv is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the
|
||||||
|
# Free Software Foundation; either version 2 of the License, or (at
|
||||||
|
# your option) any later version.
|
||||||
|
#
|
||||||
|
# ocserv is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Unit test for the mask_to_prefix / normalize_route shell functions in
|
||||||
|
# ocserv-fw-nftables. The functions are extracted directly from the script
|
||||||
|
# so this test always exercises the real implementation.
|
||||||
|
|
||||||
|
srcdir=${srcdir:-.}
|
||||||
|
FW_SCRIPT="${srcdir}/../src/ocserv-fw-nftables"
|
||||||
|
|
||||||
|
if ! test -f "${FW_SCRIPT}"; then
|
||||||
|
echo "cannot find ${FW_SCRIPT}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Testing mask_to_prefix and normalize_route from ${FW_SCRIPT}..."
|
||||||
|
|
||||||
|
# Load the two functions directly from the firewall script. awk extracts
|
||||||
|
# each function body (from "funcname() {" to the closing "}" at column 0).
|
||||||
|
eval "$(awk '/^mask_to_prefix\(\)/,/^\}/' "${FW_SCRIPT}")"
|
||||||
|
eval "$(awk '/^normalize_route\(\)/,/^\}/' "${FW_SCRIPT}")"
|
||||||
|
|
||||||
|
if ! type mask_to_prefix > /dev/null 2>&1 || \
|
||||||
|
! type normalize_route > /dev/null 2>&1; then
|
||||||
|
echo "failed to load functions from ${FW_SCRIPT}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- test harness -----------------------------------------------------------
|
||||||
|
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
check() {
|
||||||
|
input="$1"
|
||||||
|
expected="$2"
|
||||||
|
got=$(normalize_route "$input")
|
||||||
|
if test "$got" != "$expected"; then
|
||||||
|
echo "FAIL: normalize_route '$input' -> '$got', expected '$expected'"
|
||||||
|
FAIL=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_mask() {
|
||||||
|
mask="$1"
|
||||||
|
expected="$2"
|
||||||
|
got=$(mask_to_prefix "$mask")
|
||||||
|
if test "$got" != "$expected"; then
|
||||||
|
echo "FAIL: mask_to_prefix '$mask' -> '$got', expected '$expected'"
|
||||||
|
FAIL=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Expect mask_to_prefix to fail (return non-zero, produce no output)
|
||||||
|
check_mask_invalid() {
|
||||||
|
mask="$1"
|
||||||
|
got=$(mask_to_prefix "$mask" 2>/dev/null)
|
||||||
|
if test $? = 0 || test -n "$got"; then
|
||||||
|
echo "FAIL: mask_to_prefix '$mask' should have failed but returned '$got'"
|
||||||
|
FAIL=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Expect normalize_route to fail (return non-zero, produce no output)
|
||||||
|
check_route_invalid() {
|
||||||
|
route="$1"
|
||||||
|
got=$(normalize_route "$route" 2>/dev/null)
|
||||||
|
if test $? = 0 || test -n "$got"; then
|
||||||
|
echo "FAIL: normalize_route '$route' should have failed but returned '$got'"
|
||||||
|
FAIL=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- mask_to_prefix: all 33 valid prefix lengths ----------------------------
|
||||||
|
|
||||||
|
check_mask "0.0.0.0" 0
|
||||||
|
check_mask "128.0.0.0" 1
|
||||||
|
check_mask "192.0.0.0" 2
|
||||||
|
check_mask "224.0.0.0" 3
|
||||||
|
check_mask "240.0.0.0" 4
|
||||||
|
check_mask "248.0.0.0" 5
|
||||||
|
check_mask "252.0.0.0" 6
|
||||||
|
check_mask "254.0.0.0" 7
|
||||||
|
check_mask "255.0.0.0" 8
|
||||||
|
check_mask "255.128.0.0" 9
|
||||||
|
check_mask "255.192.0.0" 10
|
||||||
|
check_mask "255.224.0.0" 11
|
||||||
|
check_mask "255.240.0.0" 12
|
||||||
|
check_mask "255.248.0.0" 13
|
||||||
|
check_mask "255.252.0.0" 14
|
||||||
|
check_mask "255.254.0.0" 15
|
||||||
|
check_mask "255.255.0.0" 16
|
||||||
|
check_mask "255.255.128.0" 17
|
||||||
|
check_mask "255.255.192.0" 18
|
||||||
|
check_mask "255.255.224.0" 19
|
||||||
|
check_mask "255.255.240.0" 20
|
||||||
|
check_mask "255.255.248.0" 21
|
||||||
|
check_mask "255.255.252.0" 22
|
||||||
|
check_mask "255.255.254.0" 23
|
||||||
|
check_mask "255.255.255.0" 24
|
||||||
|
check_mask "255.255.255.128" 25
|
||||||
|
check_mask "255.255.255.192" 26
|
||||||
|
check_mask "255.255.255.224" 27
|
||||||
|
check_mask "255.255.255.240" 28
|
||||||
|
check_mask "255.255.255.248" 29
|
||||||
|
check_mask "255.255.255.252" 30
|
||||||
|
check_mask "255.255.255.254" 31
|
||||||
|
check_mask "255.255.255.255" 32
|
||||||
|
|
||||||
|
# ---- normalize_route: dotted-decimal mask conversion ------------------------
|
||||||
|
|
||||||
|
check "10.0.0.0/255.0.0.0" "10.0.0.0/8"
|
||||||
|
check "172.16.0.0/255.240.0.0" "172.16.0.0/12"
|
||||||
|
check "192.168.0.0/255.255.0.0" "192.168.0.0/16"
|
||||||
|
check "192.168.1.0/255.255.255.0" "192.168.1.0/24"
|
||||||
|
check "192.168.1.128/255.255.255.128" "192.168.1.128/25"
|
||||||
|
check "192.168.1.192/255.255.255.192" "192.168.1.192/26"
|
||||||
|
check "0.0.0.0/0.0.0.0" "0.0.0.0/0"
|
||||||
|
check "192.168.1.1/255.255.255.255" "192.168.1.1/32"
|
||||||
|
|
||||||
|
# ---- normalize_route: already-CIDR IPv4 passed through ---------------------
|
||||||
|
|
||||||
|
check "10.0.0.0/8" "10.0.0.0/8"
|
||||||
|
check "192.168.1.0/24" "192.168.1.0/24"
|
||||||
|
check "0.0.0.0/0" "0.0.0.0/0"
|
||||||
|
check "192.168.1.1/32" "192.168.1.1/32"
|
||||||
|
|
||||||
|
# ---- normalize_route: IPv6 passed through unchanged ------------------------
|
||||||
|
|
||||||
|
check "fd00::/48" "fd00::/48"
|
||||||
|
check "2001:db8::/32" "2001:db8::/32"
|
||||||
|
check "fd91:6d14:7241:dc6a::/112" "fd91:6d14:7241:dc6a::/112"
|
||||||
|
check "fc7d:b139:f7ba:5f53:c634::/80" "fc7d:b139:f7ba:5f53:c634::/80"
|
||||||
|
check "::/0" "::/0"
|
||||||
|
check "::1/128" "::1/128"
|
||||||
|
|
||||||
|
# ---- mask_to_prefix: invalid octet values -----------------------------------
|
||||||
|
|
||||||
|
check_mask_invalid "255.300.0.0"
|
||||||
|
check_mask_invalid "255.255.127.0"
|
||||||
|
check_mask_invalid "255.1.0.0"
|
||||||
|
|
||||||
|
# ---- mask_to_prefix: non-contiguous masks -----------------------------------
|
||||||
|
|
||||||
|
check_mask_invalid "254.192.128.0"
|
||||||
|
check_mask_invalid "255.0.255.0"
|
||||||
|
check_mask_invalid "0.255.0.0"
|
||||||
|
check_mask_invalid "255.255.0.255"
|
||||||
|
|
||||||
|
# ---- mask_to_prefix: wrong number of octets ---------------------------------
|
||||||
|
|
||||||
|
check_mask_invalid "255.255.0"
|
||||||
|
check_mask_invalid "255.255.255.0.0"
|
||||||
|
|
||||||
|
# ---- normalize_route: propagates mask_to_prefix failure ---------------------
|
||||||
|
|
||||||
|
check_route_invalid "10.0.0.0/255.0.255.0"
|
||||||
|
check_route_invalid "10.0.0.0/255.255.255.0.0"
|
||||||
|
|
||||||
|
# ---- result -----------------------------------------------------------------
|
||||||
|
|
||||||
|
if test "$FAIL" = "0"; then
|
||||||
|
echo "All tests passed."
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user