Skip to content

Commit

Permalink
Improve IPv4 and IPv6 address handling
Browse files Browse the repository at this point in the history
by:

    1. Farming out address expansion and validation to battle-tested
       tools, when possible (sipcalc, Python's "ipaddress" module),
    2. Making native IPv6 expansion and validation handle a wider
       variety of address representations (not just what is allowed by
       RFC 5952),
    3. Fixing various bugs in the native IPv6 address expansion
       routines,
    4. Making the native IP expansion routines log _why_ address
       expansion failed, when it failed, and
    5. Adding a variety of test cases to show that addresses are
       expanded properly and that the different expansion
       implementations (Python, sipcalc, and native) agree with one
       another.
  • Loading branch information
tomeon committed May 13, 2022
1 parent 4842280 commit 9506ce7
Show file tree
Hide file tree
Showing 10 changed files with 1,054 additions and 157 deletions.
12 changes: 9 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
---
# Don't need sudo access or to install anything
arch:
- amd64
- arm64
- ppc64le
sudo: false
install: true

sudo: true

language: python
python:
- '3.9'

install:
- sudo apt-get -y install sipcalc

script:
- ./run-tests
85 changes: 74 additions & 11 deletions run-tests
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ RUNTEST_CALLED=0

AUTOMATED_TESTING=1

if ! toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"; then
if ! toplevel="$(readlink -f "${BASH_SOURCE[0]%/*}/.." 2>/dev/null)"; then
toplevel="${PWD:-$(pwd)}"
fi
fi

# Ensure that things like "source update-systemd-resolved" pick up the script
# in this repository's toplevel rather than, say,
# /usr/bin/update-systemd-resolved.
export PATH="${toplevel}${PATH:+:${PATH}}"
unset toplevel

function busctl {
shift 4
_log "busctl called with: ${@}"
Expand All @@ -43,6 +55,8 @@ function busctl {
if [[ "${TEST_BUSCTL_DNS}" == "" ]]; then
[[ "${ip_ifindex} ${TEST_BUSCTL_DNS}" == "${@}" ]] || \
_fail "SetLinkDNS was called and should not be: '${@}'"
elif [[ "${TEST_BUSCTL_DNS}" == SKIP ]]; then
:
else
[[ "${ip_ifindex} ${TEST_BUSCTL_DNS}" == "${@}" ]] && \
_pass "SetLinkDNS was called correctly" || \
Expand Down Expand Up @@ -97,13 +111,36 @@ function resolvectl {

function logger {
# Remove standard options
local message="$*"
_log "-- ${message##* -- }"
if [[ "$*" == *' --' ]]; then
set --
else
while (( "$#" > 0 )); do
case "$1" in
--)
shift
break
;;
*)
shift
;;
esac
done
fi

if (( "$#" == 0 )) && ! [[ -t 0 ]]; then
local message
while read -r message; do
_log "-- ${message}"
done
else
_log "-- $*"
fi
}

function exit {
# Override
_log "exit called with status ${1}"
# Override "exit" builtin. Note that "exit" is equivalent to "exit $?", so
# handle that case.
_log "exit called with status ${1:-$?}"
}

function _log {
Expand All @@ -120,27 +157,35 @@ function _fail {
( >&2 echo -e " ${RED}${FAIL} ${@}${RESET}" )
}

function runtest {
function checktest() {
# Increment counter so that we don't double-execute if a test script calls
# this function.
: ${RUNTEST_CALLED:=0}
(( RUNTEST_CALLED += 1 ))

echo -e "${GREEN}- Testing ${TEST_TITLE:-a nameless test}${RESET}"

if (( $# < 1 )); then
set -- source update-systemd-resolved
fi

# Source, don't run, so we don't need to export and internal functions override
# external calls out to system commands
source update-systemd-resolved
exit_status="$?"
exit_status=0
"$@" || exit_status="$?"
exit_message="script exited with a ${exit_status} exit status"

if [[ "$(( exit_status > 0 ))" == "${EXPECT_FAILURE:-0}" ]]; then
_pass "$exit_message"
else
_fail "$exit_message"
fi
}

if [[ ${TEST_BUSCTL_CALLED} -eq 0 ]]; then
function runtest {
checktest

if [[ "${TEST_BUSCTL_CALLED:-}" = 0 ]]; then
[[ ${busctl_called} -eq 0 ]] && \
_pass "busctl was not called, as expected" || \
_fail "busctl was called, not expected"
Expand All @@ -152,10 +197,9 @@ function runtest {
echo
}

echo "update-systemd-resolved Test Suite"
echo
evaltest() {
TEST="${1?}"

for TEST in tests/*.sh; do
# Set/Reset loop variables
RUNTEST_CALLED=0
EXPECT_FAILURE=0
Expand All @@ -178,6 +222,25 @@ for TEST in tests/*.sh; do
source "${TEST}"

(( RUNTEST_CALLED > 0 )) || runtest
}

echo "update-systemd-resolved Test Suite"
echo

if (( "$#" < 1 )); then
set -- ./tests
fi

for path in "$@"; do
if [[ -d "$path" ]]; then
for test in "$path"/*.sh; do
if [[ -f "$test" ]]; then
evaltest "$test"
fi
done
else
evaltest "$path"
fi
done

echo -e " ${GREEN}${PASS} ${COUNT_PASS} Passed${RESET}"
Expand Down
23 changes: 0 additions & 23 deletions tests/19_dns_invalid_ipv6.sh

This file was deleted.

80 changes: 80 additions & 0 deletions tests/19a_dns_invalid_ipv6.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
source "${BASH_SOURCE[0]%/*}/helpers/ipv6.sh"

script_type="up"
dev="tun19"

# busctl should not be called for any test in here
TEST_BUSCTL_CALLED=0

cases=(
# More than one \`::'
1234::567::89:ab
1:::8

# Too long
1234:567:89:a:b:c:d:e:f

# Also too long
1234:567:89:0::c:d:e:f

# Leading colon
:1234:567:89:a:b:c:d:e

# Trailing colon
1234:567:89:a:b:c:d:e:

# Not hexadecimal
::zzzz

# Bad embedded IPv4
::ffff:999.999.999.999

# Embedded IPv4 in wrong location
1.2.3.4::ffff

# Leading garbage
$'\n'::1

# Plain garbage
:
:::
)

all_ipv6_addresses_invalid() {
local -a improperly_accepted_ipv6=()
local -a wrongly_parsed_ipv6=()
local ipv6 bad status

for ipv6 in "${cases[@]}"; do
foreign_option_1="dhcp-option DNS ${ipv6}"

if source update-systemd-resolved; then
improperly_accepted_ipv6+=("$ipv6")
fi

if ! bad="$(all_ipv6_expansion_implementations "$ipv6")"; then
wrongly_parsed_ipv6+=("$bad")
fi
done

if (( ${#improperly_accepted_ipv6[@]} > 0 )); then
printf 1>&2 -- 'improperly accepted the following IPv6 addresses:\n'
printf 1>&2 -- ' %s\n' "${improperly_accepted_ipv6[@]}"
status=1
fi

if (( ${#wrongly_parsed_ipv6[@]} > 0 )); then
printf 1>&2 -- 'parse for the following IPv6 addresses wrongly succeeded:\n'

for bad in "${wrongly_parsed_ipv6[@]}"; do
printf 1>&2 -- ' %s\n' "$bad"
done

status=1
fi

return "${status:-0}"
}

TEST_TITLE="Known-bad IPv6 addresses are all rejected"
checktest all_ipv6_addresses_invalid
Loading

0 comments on commit 9506ce7

Please sign in to comment.