Attention is currently required from: flichtenheld, plaisthos.

Hello flichtenheld, plaisthos,

I'd like you to reexamine a change. Please visit

    http://gerrit.openvpn.net/c/openvpn/+/1949?usp=email

to look at the new patch set (#3).


Change subject: tests: add an end-to-end test for --route gateway keywords
......................................................................

tests: add an end-to-end test for --route gateway keywords

Nothing self-contained checks that a route openvpn says it installed
actually reached the kernel with the gateway it reported. t_client.sh does
compare routes, but it needs a t_client.rc and reachable servers, so it
skips for contributors and in CI.

Run a TLS loopback pair inside a throwaway network namespace, ask the
client for a route via net_gateway, and check where it lands. route.c
tracks two gateways, the system default route and the route towards the
peer, and net_gateway is the former. The peer here is on loopback, so the
two differ and the assertion can tell them apart: resolving from the wrong
one leaves the route with no gateway and the install fails outright.

The namespace also means the fixed loopback ports cannot collide, so this
needs none of the retry-on-address-in-use handling t_cltsrv.sh has.

The part that does not depend on how the environment is isolated lives in
t_route_common.sh: starting the openvpn pair, waiting for the tunnel and
then the route, and comparing the gateway.  t_route.sh supplies the hooks
that do depend on the platform, running a command inside the namespace and
the routing table queries.  The comparison is against the gateway the
platform reports rather than the text of the routing table entry, because
every routing tool spells that entry differently.  A front end for a
system without network namespaces can then reuse the whole driver.

Nothing can be concluded about a route until the tunnel carrying it
exists, so the tun device is waited for separately and /dev/net/tun is
checked up front.  An environment that cannot open a tunnel skips; only a
tunnel that comes up without the route, or with the wrong gateway, is a
failure.  Both paths print the client and server logs together with the
addresses and routes, so a failing run says why.

Both files are POSIX sh.  Nothing here needed bash, and with a /bin/sh
shebang the runtime skips below can do their job on a platform without
bash rather than the script failing to start.

Skips rather than fails when it cannot run: not Linux, no iproute2, no
tun device, or no way to get the privileges a namespace needs. Containers
commonly disallow namespace creation, so this will skip there and run on
VMs and bare metal. RUN_SUDO is picked up from t_client.rc the same way
t_net.sh does.

Change-Id: I2e8f1449fe54e7f628e2bdb042ee2a567f25e02d
Signed-off-by: Charlie Vigue <[email protected]>
---
M tests/Makefile.am
A tests/t_route.sh
A tests/t_route_common.sh
3 files changed, 261 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/49/1949/3

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3907965..5cfbf67e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -18,7 +18,7 @@
 SH_LOG_DRIVER = $(SHELL) $(top_srcdir)/forked-test-driver

 if !WIN32
-test_scripts = t_client.sh t_lpback.sh t_cltsrv.sh t_server_null.sh
+test_scripts = t_client.sh t_lpback.sh t_cltsrv.sh t_server_null.sh t_route.sh

 check_PROGRAMS = ntlm_support
 if HAVE_SITNL
@@ -35,6 +35,8 @@
        t_cltsrv-down.sh \
        t_lpback.sh \
        t_net.sh \
+       t_route.sh \
+       t_route_common.sh \
        t_server_null.sh \
        t_server_null_client.sh \
        t_server_null_server.sh \
diff --git a/tests/t_route.sh b/tests/t_route.sh
new file mode 100755
index 0000000..dcab81d
--- /dev/null
+++ b/tests/t_route.sh
@@ -0,0 +1,161 @@
+#!/bin/sh
+#
+# t_route.sh - check that --route installs a route where it says it will
+#
+# The special gateway keywords (net_gateway, vpn_gateway, remote_host) are
+# resolved in route.c from data gathered off the system routing table. The
+# unit tests cover the resolution itself; this checks that the answer
+# reaches the kernel.
+#
+# route.c keeps two gateways: the system default route, and the route
+# towards the peer. net_gateway is the former. Here the peer is on
+# loopback, so the two are necessarily different and an assertion on the
+# installed gateway tells them apart.
+#
+# This is the network namespace front end, so the host routing table is not
+# touched and the fixed loopback ports cannot collide with anything. The
+# part that drives openvpn and checks the result lives in
+# t_route_common.sh, so a front end for a platform without namespaces can
+# reuse it.
+
+srcdir="${srcdir:-.}"
+top_builddir="${top_builddir:-..}"
+top_srcdir="${top_srcdir:-${srcdir}/..}"
+openvpn="${openvpn:-${top_builddir}/src/openvpn/openvpn}"
+
+# Namespace topology: one interface carrying a default route, which is
+# what net_gateway has to resolve to.
+NS="ovpnroute$$"
+TUN="ovpnt$$"
+LAN_GW="10.71.1.1"
+TARGET="10.71.250.1" # what we ask to be routed via net_gateway
+VPN_LOCAL="10.71.8.2"
+VPN_REMOTE="10.71.8.1"
+
+# Namespaces and dummy interfaces are Linux-only. A front end for another
+# platform would set its own environment up and reuse t_route_common.sh.
+if [ "$(uname -s)" != "Linux" ]; then
+    echo "$0: this front end runs only on Linux. SKIPPING TEST."
+    exit 77
+fi
+
+if [ ! -x "${openvpn}" ]; then
+    echo "$0: no (executable) openvpn binary in current build tree. FAIL." >&2
+    exit 1
+fi
+
+if ! command -v ip >/dev/null 2>&1; then
+    echo "$0: no iproute2 'ip' command in \$PATH. SKIPPING TEST." >&2
+    exit 77
+fi
+
+# /dev is not namespaced, so this is the same device the client will open.
+if [ ! -c /dev/net/tun ]; then
+    echo "$0: no /dev/net/tun, cannot open a tunnel. SKIPPING TEST." >&2
+    exit 77
+fi
+
+# t_client.rc is read only for a RUN_SUDO definition, as t_net.sh does
+if [ -r "${top_builddir}"/t_client.rc ]; then
+    . "${top_builddir}"/t_client.rc
+elif [ -r "${srcdir}"/t_client.rc ]; then
+    . "${srcdir}"/t_client.rc
+fi
+
+if [ "$(id -u)" -ne 0 ]; then
+    if [ -z "$RUN_SUDO" ]; then
+        RUN_SUDO="sudo"
+    fi
+else
+    RUN_SUDO=""
+fi
+
+# A namespace needs the same privileges the rest of the test does, so use
+# it as the probe.
+if ! $RUN_SUDO ip netns add "$NS"; then
+    echo "$0: cannot create a network namespace with '$RUN_SUDO'." >&2
+    echo "$0: containers commonly disallow this. SKIPPING TEST." >&2
+    exit 77
+fi
+
+srv_pid=""
+clt_pid=""
+logdir=$(mktemp -d) || exit 1
+
+cleanup()
+{
+    # kill -0 is a liveness probe, not an action: its failure is the answer
+    # rather than an error worth printing. The kill itself stays unguarded.
+    [ -n "$clt_pid" ] && kill -0 "$clt_pid" 2>/dev/null && kill "$clt_pid"
+    [ -n "$srv_pid" ] && kill -0 "$srv_pid" 2>/dev/null && kill "$srv_pid"
+    wait
+    # deleting the namespace takes every interface and route in it with it
+    $RUN_SUDO ip netns del "$NS"
+    [ -n "$logdir" ] && rm -rf "$logdir"
+    return 0
+}
+trap cleanup EXIT
+
+plat_exec()
+{
+    $RUN_SUDO ip netns exec "$NS" "$@"
+}
+
+plat_have_tun()
+{
+    plat_exec ip link show "$1" >/dev/null 2>&1
+}
+
+plat_dump_state()
+{
+    plat_exec ip -o addr show
+    plat_exec ip route show
+}
+
+plat_route_show()
+{
+    # absence is the expected state while polling, so the error iproute2
+    # prints for an unreachable address is noise rather than a diagnostic
+    plat_exec ip route show "$1" 2>/dev/null
+}
+
+plat_route_gateway()
+{
+    plat_route_show "$1" | sed -n 
's/.*[[:space:]]via[[:space:]]\([^[:space:]]*\).*/\1/p'
+}
+
+. "${srcdir}"/t_route_common.sh
+
+set -e
+plat_exec ip link set lo up
+plat_exec ip link add lan0 type dummy
+plat_exec ip link set lan0 up
+plat_exec ip addr add 10.71.1.2/24 dev lan0
+plat_exec ip route add default via "$LAN_GW" dev lan0
+set +e
+
+route_start_pair
+
+# A tunnel that never comes up means this environment cannot run the test,
+# which is a skip. Only once it is up does the routing assertion mean
+# anything, and a failure from there on is a real one.
+if ! route_wait_tun "$TUN"; then
+    echo "$0: tunnel device $TUN never appeared." >&2
+    route_dump_diag
+    echo "$0: cannot establish a tunnel here. SKIPPING TEST." >&2
+    exit 77
+fi
+
+if ! route_wait_for "$TARGET"; then
+    echo "$0: $TUN is up but the route to $TARGET was never installed." >&2
+    route_dump_diag
+    echo "$0: FAIL." >&2
+    exit 1
+fi
+
+if ! route_assert_gateway "$LAN_GW"; then
+    route_dump_diag
+    exit 1
+fi
+
+exit 0
diff --git a/tests/t_route_common.sh b/tests/t_route_common.sh
new file mode 100755
index 0000000..19c92ae
--- /dev/null
+++ b/tests/t_route_common.sh
@@ -0,0 +1,97 @@
+#!/bin/sh
+#
+# t_route_common.sh - platform-independent part of the --route gateway test
+#
+# Sourced by a front end that supplies the environment the test runs in and
+# the routing-table queries for the platform:
+#
+#   plat_exec CMD...          run CMD inside the test environment
+#   plat_have_tun IFACE       succeed if IFACE exists in that environment
+#   plat_route_show ADDR      print the routing table entry for ADDR, if any
+#   plat_route_gateway ADDR   print just the gateway of that entry, if any
+#   plat_dump_state           print addresses and routes, for diagnostics
+#
+# The front end also sets openvpn, top_srcdir, logdir, TUN, TARGET,
+# VPN_LOCAL and VPN_REMOTE before sourcing this file.
+#
+# Nothing here knows how the environment is isolated or which tools report
+# the routing table, so a front end for a platform without network
+# namespaces can reuse all of it.
+
+# A TLS loopback pair, as t_cltsrv.sh uses. Options after --config override
+# the config file, which is how the client gets a tun and a route.
+route_start_pair()
+{
+    plat_exec "${openvpn}" --cd "${top_srcdir}/sample" \
+        --config sample-config-files/loopback-server \
+        --verb 3 --ping-exit 60 >"$logdir"/srv.log 2>&1 &
+    srv_pid=$!
+
+    plat_exec "${openvpn}" --cd "${top_srcdir}/sample" \
+        --config sample-config-files/loopback-client \
+        --dev "$TUN" --dev-type tun \
+        --ifconfig "$VPN_LOCAL" "$VPN_REMOTE" \
+        --route "$TARGET" 255.255.255.255 net_gateway \
+        --verb 4 --ping-exit 60 >"$logdir"/clt.log 2>&1 &
+    clt_pid=$!
+}
+
+# Wait for the tunnel itself.  Nothing can be concluded about routes until
+# the tun interface exists, so this is checked separately: an environment
+# that cannot open a tunnel is a skip, whereas a tunnel that is up without
+# the route is a real failure.  Gives up early if the client has died.
+route_wait_tun()
+{
+    i=0
+    while [ "$i" -lt 60 ]; do
+        plat_have_tun "$1" && return 0
+        kill -0 "$clt_pid" 2>/dev/null || return 1
+        i=$((i + 1))
+        sleep 1
+    done
+    return 1
+}
+
+# Everything a failing run needs, printed to stderr so it reaches the CI log.
+route_dump_diag()
+{
+    echo "--- client log ---" >&2
+    cat "$logdir"/clt.log >&2 2>/dev/null
+    echo "--- server log ---" >&2
+    cat "$logdir"/srv.log >&2 2>/dev/null
+    echo "--- addresses and routes ---" >&2
+    plat_dump_state >&2 2>/dev/null
+}
+
+# Wait for the route to show up rather than guessing at a sleep.  Sets
+# route_out to the entry that appeared.
+route_wait_for()
+{
+    i=0
+    route_out=""
+    while [ "$i" -lt 30 ]; do
+        route_out=$(plat_route_show "$1")
+        [ -n "$route_out" ] && return 0
+        i=$((i + 1))
+        sleep 1
+    done
+    return 1
+}
+
+# net_gateway is the system default gateway, not the route to the peer.
+# Compare the gateway the platform reports rather than the text of the
+# entry, which is spelled differently by every routing tool.
+route_assert_gateway()
+{
+    _want=$1
+    _got=$(plat_route_gateway "$TARGET")
+
+    if [ "$_got" = "$_want" ]; then
+        echo "$0: net_gateway resolved to $_want as expected"
+        return 0
+    fi
+
+    echo "$0: expected the route via the default gateway $_want," >&2
+    echo "     got gateway '$_got' in: $route_out. FAIL." >&2
+    return 1
+}

--
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1949?usp=email
To unsubscribe, or for help writing mail filters, visit 
http://gerrit.openvpn.net/settings?usp=email

Gerrit-MessageType: newpatchset
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I2e8f1449fe54e7f628e2bdb042ee2a567f25e02d
Gerrit-Change-Number: 1949
Gerrit-PatchSet: 3
Gerrit-Owner: chugly <[email protected]>
Gerrit-Reviewer: flichtenheld <[email protected]>
Gerrit-Reviewer: plaisthos <[email protected]>
Gerrit-CC: openvpn-devel <[email protected]>
Gerrit-Attention: plaisthos <[email protected]>
Gerrit-Attention: flichtenheld <[email protected]>
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to