Browse code

full "VPN client connect" test framework for OpenVPN

Run from "make check" if "t_client.rc" is found in workdir or srcdir
(copy t_client.rc-sample, fill in specifics for your test server)

How does it work?

- you run "sudo make check" (needs root access to configure tun if!)

- t_client.sh reads t_client.rc from current dir or ${srcdir}

- t_client.rc defines a number of "test suffixes" to run (could be
"1" "2" "3" or "p2m", "p2p", "special" or whatever you like), and
for each suffix, there's config variables to specify

- how to call OpenVPN
- which hosts to ping for IPv4 and IPv6 when OpenVPN is up
(and actually before starting OpenVPN - to make the test more
meaningful, I have decided that the test hosts must not ping
before the tests starts)
- which addresses must show up in the output of "ifconfig" after
OpenVPN has started
- all variables except OPENVPN_CONF_<x> are optional

(this should all be fairly obvious from looking at t_client.rc-sample)

- the script wants to connect to a well-defined OpenVPN server that
will assign well-known IPv4 (and IPv6) addresses, have well-defined
pingable addresse, etc. - so you need to setup the test server before
the script is useful for you. (Whether you use certificates or
username/password is up to you, you could even mix and match - run
one test with certs, and one with user/pass against different target
ports... :-) )

[we *could* run a "reference server" somewhere and ship a sample
t_client.rc + cert so that users could use this right away, but I
do not currently have the resources to run such a public server]

- whatever the script does is logged to a newly created directory
below the current directory (openvpn output, ifconfig+route before
starting OpenVPN, while running it, after ending it)

- important: at least on NetBSD and OpenBSD, the script will print
one failure, because the tun0 interface created is not destroyed
after openvpn ends. For OpenBSD, I have changed close_tun() to
do so ("ifconfig tun0 destroy"), for NetBSD I have not yet changed
anything - but I strongly believe that the output of "ifconfig+route"
should be reverted to exactly how it looked like before OpenVPN
was started, so I consider this a bug in the NetBSD-specific bits
of OpenVPN (and will look into this).

- the test framework has been tested on Linux, NetBSD and OpenBSD.
It *should* work fine on FreeBSD and Solaris.
It works on MacOS X (but the output looks funny, because /bin/sh
does not implement "echo -e" - need to add configure trickery)

It will *not* work on Windows yet - I haven't looked into what's
needed to make it work (background processes and signals in mingw
bash?), maybe it's as easy as adding the necessary "ipconfig" and
"netsh" commands to print interface + routing config...

- I have only tested "connect via IPv4 transport, use IPv4+IPv6 payload",
but the framework is generic enough that "connect via IPv6 transport"
should work just fine (just setup OPENVPN_CONF_x accordingly in the
t_client.rc).

- this is neither finished nor pretty, but it helps me a *lot* in
quickly testing whether I broke anything when fiddling system-dependent
code (tun.c, route.c) across multiple build hosts - so I hope this
is going to be fairly useful to Samuli and the buildbot :-)

Signed-off-by: Gert Doering <gert@greenie.muc.de>
Acked-by: David Sommerseth <dazo@users.sourceforge.net>
Signed-off-by: David Sommerseth <dazo@users.sourceforge.net>

Gert Doering authored on 2010/08/09 04:24:30
Showing 3 changed files
... ...
@@ -56,7 +56,7 @@ SUBDIRS = \
56 56
 	service-win32 \
57 57
 	install-win32
58 58
 
59
-TESTS = t_lpback.sh t_cltsrv.sh
59
+TESTS = t_client.sh t_lpback.sh t_cltsrv.sh
60 60
 sbin_PROGRAMS = openvpn
61 61
 
62 62
 dist_noinst_HEADERS =
63 63
new file mode 100644
... ...
@@ -0,0 +1,83 @@
0
+#
1
+# this is sourced from t_client.sh and defines which openvpn client tests
2
+# to run
3
+#
4
+# (sample config, copy to t_client.rc and adapt to your environment)
5
+#
6
+#
7
+# define these - if empty, no tests will run
8
+#
9
+CA_CERT="/home/openvpn-test-ca/keys/ca.crt"
10
+CLIENT_KEY="/home/openvpn-test-ca/keys/client-test.key"
11
+CLIENT_CERT="/home/openvpn-test-ca/keys/client-test.crt"
12
+#
13
+# remote host (used as macro below)
14
+#
15
+REMOTE=mytestserver
16
+#
17
+# tests to run (list suffixes for config stanzas below)
18
+#
19
+TEST_RUN_LIST="1 2"
20
+
21
+#
22
+# base confic that is the same for all the p2mp test runs
23
+#
24
+OPENVPN_BASE_P2MP="--client --ca $CA_CERT \
25
+	--cert $CLIENT_CERT --key $CLIENT_KEY \
26
+	--ns-cert-type server --nobind --comp-lzo --verb 3"
27
+
28
+# base config for p2p tests
29
+#
30
+OPENVPN_BASE_P2P="..."
31
+
32
+#
33
+#
34
+# now define the individual tests - all variables suffixed with _1, _2 etc
35
+# will be used in test run "1", "2", etc.
36
+#
37
+# if something is not defined here, the corresponding test is not run
38
+#
39
+# possible test options:
40
+#
41
+# OPENVPN_CONF_x = "how to call ./openvpn" [mandatory]
42
+# EXPECT_IFCONFIG4_x = "this IPv4 address needs to show up in ifconfig"
43
+# EXPECT_IFCONFIG6_x = "this IPv6 address needs to show up in ifconfig"
44
+# PING4_HOSTS_x = "these hosts musts ping when openvpn is up (IPv4 fping)"
45
+# PING6_HOSTS_x = "these hosts musts ping when openvpn is up (IPv6 fping6)"
46
+#
47
+# Test 1: UDP / p2mp tun
48
+#   specify IPv4+IPv6 addresses expected from server and ping targets
49
+#
50
+OPENVPN_CONF_1="$OPENVPN_BASE_P2MP --dev tun --proto udp --remote $REMOTE --port 51194"
51
+EXPECT_IFCONFIG4_1="10.100.50.6"
52
+EXPECT_IFCONFIG6_1="2001:dba:a050::1:0"
53
+PING4_HOSTS_1="10.100.50.1 10.100.0.1"
54
+PING6_HOSTS_1="2001:dba::1 2001:dba:a050::1"
55
+
56
+# Test 2: TCP / p2mp tun
57
+#
58
+OPENVPN_CONF_2="$OPENVPN_BASE_P2MP --dev tun --proto tcp --remote $REMOTE --port 51194"
59
+EXPECT_IFCONFIG4_2="10.100.51.6"
60
+EXPECT_IFCONFIG6_2="2001:dba:a051::1:0"
61
+PING4_HOSTS_2="10.100.51.1 10.100.0.1"
62
+PING6_HOSTS_1="2001:dba::1 2001:dba:a051::1"
63
+
64
+# Test 3: UDP / p2p tun
65
+# ...
66
+
67
+# Test 4: TCP / p2p tun
68
+# ...
69
+
70
+# Test 5: UDP / p2mp tap
71
+# ...
72
+
73
+# Test 6: TCP / p2mp tun
74
+# ...
75
+
76
+# Test 7: UDP / p2p tap
77
+# ...
78
+
79
+# Test 8: TCP / p2p tap
80
+# ...
81
+
82
+# Test 9: whatever you want to test... :-)
0 83
new file mode 100644
... ...
@@ -0,0 +1,298 @@
0
+#!/bin/sh
1
+#
2
+# run OpenVPN client against ``test reference'' server
3
+# - check that ping, http, ... via tunnel works
4
+# - check that interface config / routes are properly cleaned after test end
5
+#
6
+# prerequisites:
7
+# - openvpn binary in current directory
8
+# - writable current directory to create subdir for logs
9
+# - t_client.rc in current directory OR source dir that specifies tests
10
+# - for "ping4" checks: fping binary in $PATH
11
+# - for "ping6" checks: fping6 binary in $PATH
12
+#
13
+
14
+if [ ! -x ./openvpn ]
15
+then
16
+    echo "no (executable) openvpn binary in current directory. FAIL." >&2
17
+    exit 1
18
+fi
19
+
20
+if [ ! -w . ]
21
+then
22
+    echo "current directory is not writable (required for logging). FAIL." >&2
23
+    exit 1
24
+fi
25
+
26
+if [ -r ./t_client.rc ] ; then
27
+    . ./t_client.rc
28
+elif [ -r "${srcdir}"/t_client.rc ] ; then
29
+    . "${srcdir}"/t_client.rc
30
+else
31
+    echo "cannot find 't_client.rc' in current directory or" >&2
32
+    echo "source dir ('${srcdir}').  FAIL." >&2
33
+    exit 1
34
+fi
35
+
36
+if [ -z "$CA_CERT" ] ; then
37
+    echo "CA_CERT not defined in 't_client.rc'. SKIP test." >&2
38
+    exit 0
39
+fi
40
+
41
+if [ -z "$TEST_RUN_LIST" ] ; then
42
+    echo "TEST_RUN_LIST empty, no tests defined.  SKIP test." >&2
43
+    exit 0
44
+fi
45
+
46
+# make sure we have permissions to run ifconfig/route from OpenVPN
47
+# can't use "id -u" here - doesn't work on Solaris
48
+ID=`id`
49
+if expr "$ID" : "uid=0" >/dev/null
50
+then :
51
+else
52
+    echo "$0: this test must run be as root. SKIP." >&2
53
+    exit 0
54
+fi
55
+
56
+LOGDIR=t_client-`hostname`-`date +%Y%m%d-%H%M%S`
57
+if mkdir $LOGDIR
58
+then :
59
+else
60
+    echo "can't create log directory '$LOGDIR'. FAIL." >&2
61
+    exit 1
62
+fi
63
+
64
+exit_code=0
65
+
66
+# ----------------------------------------------------------
67
+# helper functions
68
+# ----------------------------------------------------------
69
+# print failure message, increase FAIL counter
70
+fail()
71
+{
72
+    echo ""
73
+    echo "FAIL: $@" >&2
74
+    fail_count=$(( $fail_count + 1 ))
75
+}
76
+
77
+# print "all interface IP addresses" + "all routes"
78
+# this is higly system dependent...
79
+get_ifconfig_route()
80
+{
81
+    # linux / iproute2?
82
+    if [ -x /sbin/ip -o -x /usr/sbin/ip ]
83
+    then
84
+	echo "-- linux iproute2 --"
85
+	ip addr show     | grep -v valid_lft
86
+	ip route show
87
+	ip -6 route show | sed -e 's/expires [0-9]*sec //'
88
+	return
89
+    fi
90
+
91
+    # try uname
92
+    case `uname -s` in
93
+	Linux)
94
+	   echo "-- linux / ifconfig --"
95
+	   LANG=C ifconfig -a |egrep  "( addr:|encap:)"
96
+	   LANG=C netstat -rn -4 -6
97
+	   return
98
+	   ;;
99
+	FreeBSD|NetBSD|Darwin)
100
+	   echo "-- FreeBSD/NetBSD/Darwin [MacOS X] --"
101
+	   ifconfig -a | egrep "(flags=|inet)"
102
+	   netstat -rn | awk '$3 !~ /^UHL/ { print $1,$2,$3,$NF }'
103
+	   return
104
+	   ;;
105
+	OpenBSD)
106
+	   echo "-- OpenBSD --"
107
+	   ifconfig -a | egrep "(flags=|inet)" | \
108
+		sed -e 's/pltime [0-9]*//' -e 's/vltime [0-9]*//'
109
+	   netstat -rn | awk '$3 !~ /^UHL/ { print $1,$2,$3,$NF }'
110
+	   return
111
+	   ;;
112
+	SunOS)
113
+	   echo "-- Solaris --"
114
+	   ifconfig -a | egrep "(flags=|inet)"
115
+	   netstat -rn
116
+	   return
117
+	   ;;
118
+    esac
119
+
120
+    echo "get_ifconfig_route(): no idea how to get info on your OS.  FAIL." >&2
121
+    exit 20
122
+}
123
+
124
+# ----------------------------------------------------------
125
+# check ifconfig
126
+#  arg1: "4" or "6" -> for message
127
+#  arg2: IPv4/IPv6 address that must show up in out of "get_ifconfig_route"
128
+check_ifconfig()
129
+{
130
+    proto=$1 ; shift
131
+    expect_list="$@"
132
+
133
+    if [ -z "$expect_list" ] ; then return ; fi
134
+
135
+    for expect in $expect_list
136
+    do
137
+	if get_ifconfig_route | fgrep "$expect" >/dev/null
138
+	then :
139
+	else
140
+	    fail "check_ifconfig(): expected IPv$proto address '$expect' not found in ifconfig output."
141
+	fi
142
+    done
143
+}
144
+
145
+# ----------------------------------------------------------
146
+# run pings
147
+#  arg1: "4" or "6" -> fping/fing6
148
+#  arg2: "want_ok" or "want_fail" (expected ping result)
149
+#  arg3... -> fping arguments (host list)
150
+run_ping_tests()
151
+{
152
+    proto=$1 ; want=$2 ; shift ; shift
153
+    targetlist="$@"
154
+
155
+    # "no targets" is fine
156
+    if [ -z "$targetlist" ] ; then return ; fi
157
+
158
+    case $proto in
159
+	4) cmd=fping ;;
160
+	6) cmd=fping6 ;;
161
+	*) echo "internal error in run_ping_tests arg 1: '$proto'" >&2
162
+	   exit 1 ;;
163
+    esac
164
+
165
+    case $want in
166
+	want_ok)   sizes_list="64 1440 3000" ;;
167
+	want_fail) sizes_list="64" ;;
168
+    esac
169
+
170
+    for bytes in $sizes_list
171
+    do
172
+	echo "run IPv$proto ping tests ($want), $bytes byte packets..."
173
+
174
+	echo "$cmd -b $bytes -C 20 -p 250 -q $targetlist" >>$LOGDIR/$SUF:fping.out
175
+	$cmd -b $bytes -C 20 -p 250 -q $targetlist >>$LOGDIR/$SUF:fping.out 2>&1
176
+
177
+	# while OpenVPN is running, pings must succeed (want='want_ok')
178
+	# before OpenVPN is up, pings must NOT succeed (want='want_fail')
179
+
180
+	rc=$?
181
+	if [ $rc = 0 ] 				# all ping OK
182
+	then
183
+	    if [ $want = "want_fail" ]		# not what we want
184
+	    then
185
+		fail "IPv$proto ping test succeeded, but needs to *fail*."
186
+	    fi
187
+	else					# ping failed
188
+	    if [ $want = "want_ok" ]		# not what we wanted
189
+	    then
190
+		fail "IPv$proto ping test ($bytes bytes) failed, but should succeed."
191
+	    fi
192
+	fi
193
+    done
194
+}
195
+
196
+# ----------------------------------------------------------
197
+# main test loop
198
+# ----------------------------------------------------------
199
+for SUF in $TEST_RUN_LIST
200
+do
201
+    echo -e "\n### test run $SUF ###\n"
202
+    fail_count=0
203
+
204
+    echo "save pre-openvpn ifconfig + route"
205
+    get_ifconfig_route >$LOGDIR/$SUF:ifconfig_route_pre.txt
206
+
207
+    # get config variables
208
+    eval openvpn_conf=\"\$OPENVPN_CONF_$SUF\"
209
+    eval expect_ifconfig4=\"\$EXPECT_IFCONFIG4_$SUF\"
210
+    eval expect_ifconfig6=\"\$EXPECT_IFCONFIG6_$SUF\"
211
+    eval ping4_hosts=\"\$PING4_HOSTS_$SUF\"
212
+    eval ping6_hosts=\"\$PING6_HOSTS_$SUF\"
213
+
214
+    echo -e "\nrun pre-openvpn ping tests - targets must not be reachable..."
215
+    run_ping_tests 4 want_fail "$ping4_hosts"
216
+    run_ping_tests 6 want_fail "$ping6_hosts"
217
+    if [ "$fail_count" = 0 ] ; then
218
+        echo -e "OK.\n"
219
+    else
220
+	echo -e "FAIL: make sure that ping hosts are ONLY reachable via VPN, SKIP test $SUF".
221
+	exit_code=31
222
+	continue
223
+    fi
224
+
225
+    echo " run ./openvpn $openvpn_conf"
226
+    ./openvpn $openvpn_conf >$LOGDIR/$SUF:openvpn.log &
227
+    opid=$!
228
+
229
+    # make sure openvpn client is terminated in case shell exits
230
+    trap "kill $opid" 0
231
+    trap "kill $opid ; trap - 0 ; exit 1" 1 2 3 15
232
+
233
+    echo "wait for connection to establish..."
234
+    sleep 10
235
+
236
+    # test whether OpenVPN process is still there
237
+    if kill -0 $opid
238
+    then :
239
+    else
240
+	echo -e "OpenVPN process has failed to start up, check log ($LOGDIR/$SUF:openvpn.log).  FAIL.\ntail of logfile follows:\n..." >&2
241
+	tail $LOGDIR/$SUF:openvpn.log >&2
242
+	trap - 0 1 2 3 15
243
+	exit 10
244
+    fi
245
+
246
+    # compare whether anything changed in ifconfig/route setup?
247
+    echo "save ifconfig+route"
248
+    get_ifconfig_route >$LOGDIR/$SUF:ifconfig_route.txt
249
+
250
+    echo -n "compare pre-openvpn ifconfig+route with current values..."
251
+    if diff $LOGDIR/$SUF:ifconfig_route_pre.txt \
252
+	    $LOGDIR/$SUF:ifconfig_route.txt >/dev/null
253
+    then
254
+	fail "no differences between ifconfig/route before OpenVPN start and now."
255
+    else
256
+	echo -e " OK!\n"
257
+    fi
258
+
259
+    # expected ifconfig values in there?
260
+    check_ifconfig 4 "$expect_ifconfig4"
261
+    check_ifconfig 6 "$expect_ifconfig6"
262
+
263
+    run_ping_tests 4 want_ok "$ping4_hosts"
264
+    run_ping_tests 6 want_ok "$ping6_hosts"
265
+    echo -e "ping tests done.\n"
266
+
267
+    echo "stopping OpenVPN"
268
+    kill $opid
269
+    wait $!
270
+    rc=$?
271
+    if [ $rc != 0 ] ; then
272
+	fail "OpenVPN return code $rc, expect 0"
273
+    fi
274
+
275
+    echo -e "\nsave post-openvpn ifconfig + route..."
276
+    get_ifconfig_route >$LOGDIR/$SUF:ifconfig_route_post.txt
277
+
278
+    echo -n "compare pre- and post-openvpn ifconfig + route..."
279
+    if diff $LOGDIR/$SUF:ifconfig_route_pre.txt \
280
+	    $LOGDIR/$SUF:ifconfig_route_post.txt >$LOGDIR/$SUF:ifconfig_route_diff.txt
281
+    then
282
+	echo -e " OK.\n"
283
+    else
284
+	cat $LOGDIR/$SUF:ifconfig_route_diff.txt >&2
285
+	fail "differences between pre- and post-ifconfig/route"
286
+    fi
287
+    if [ "$fail_count" = 0 ] ; then
288
+        echo -e "test run $SUF: all tests OK.\n"
289
+    else
290
+	echo -e "test run $SUF: $fail_count test failures. FAIL.\n";
291
+	exit_code=30
292
+    fi
293
+done
294
+
295
+# remove trap handler
296
+trap - 0 1 2 3 15
297
+exit $exit_code