AM_CONDITIONAL(VDE, test "$vde" = true)
AM_CONDITIONAL(TUNEMU, test "$tunemu" = true)
AM_CONDITIONAL(WITH_SYSTEMD, test "$systemd" = true)
+AM_CONDITIONAL(WITH_LEGACY_PROTOCOL, test "x$enable_legacy_protocol" != "xno")
AC_CACHE_SAVE
TESTS = \
basic.test \
- commandline.test \
executables.test \
+ commandline.test \
import-export.test \
invite-join.test \
invite-offline.test \
invite-tinc-up.test \
- legacy-protocol.test \
- ns-ping.test \
+ variables.test \
scripts.test \
security.test \
- sptps-basic.test \
- variables.test
+ sptps-basic.test
+
+if WITH_LEGACY_PROTOCOL
+TESTS += \
+ legacy-protocol.test \
+ algorithms.test
+endif
+
+if LINUX
+TESTS += ns-ping.test
+endif
dist_check_SCRIPTS = $(TESTS)
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Initialize two nodes
+echo [STEP] Initialize two nodes
-$tinc $c1 <<EOF
+tinc foo <<EOF
init foo
set DeviceType dummy
-set Port 32755
+set Port 30070
set Address localhost
set ExperimentalProtocol no
EOF
-$tinc $c2 <<EOF
+tinc bar <<EOF
init bar
set DeviceType dummy
set Port 0
set ExperimentalProtocol no
EOF
-# Exchange configuration
+create_script foo hosts/bar-up
+create_script bar hosts/foo-up
-$tinc $c1 export | $tinc $c2 exchange | $tinc $c1 import
-$tinc $c2 add ConnectTo foo
-$tinc $c1 start $r1
+echo [STEP] Exchange configuration
-# Test various ciphers and digests
+tinc foo export | tinc bar exchange | tinc foo import
+tinc bar add ConnectTo foo
+start_tinc foo
-for digest in none md5 sha1 sha256 sha512; do
- for cipher in none bf-cbc aes-128-cbc aes-256-cbc camellia-128-cbc camellia-256-cbc; do
- echo Testing $cipher $digest
- $tinc $c2 <<EOF
+echo [STEP] Test various ciphers and digests
+
+# The full suite results in a large test matrix and it takes a lot of time to run.
+# The list was reduced to the strongest available algorithms (and "none").
+digests="none sha256 sha512"
+ciphers="none aes-256-cbc camellia-256-cbc"
+
+for digest in $digests; do
+ for cipher in $ciphers; do
+ echo "Testing $cipher $digest"
+
+ tinc bar <<EOF
set Digest $digest
set Cipher $cipher
EOF
- $tinc $c2 start $r2
- sleep 2;
- $tinc $c1 info bar
- $tinc $c1 info bar | grep -q 'directly with UDP'
- $tinc $c2 stop
- done
-done
+ start_tinc bar
+ wait_script foo hosts/bar-up
+ wait_script bar hosts/foo-up
+
+ tinc foo info bar | grep reachable
-$tinc $c1 stop
+ tinc bar stop
+ done
+done
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Initialize and test one node
+echo [STEP] Initialize and test one node
-$tinc $c1 init foo
-$tinc $c1 set DeviceType dummy
-$tinc $c1 set Port 0
+tinc foo <<EOF
+init foo
+set DeviceType dummy
+set Port 0
+EOF
-# Test running in the foreground
+echo [STEP] Test running in the foreground
-(sleep 1; $tinc $c1 stop) &
-$tinc $c1 start $r1 -D
+create_script foo tinc-up '
+ tinc foo stop &
+'
+start_tinc foo -D
-# Test running tinc in the background
+echo [STEP] Test running tinc in the background
-$tinc $c1 start $r1
-sleep 1
-$tinc $c1 stop
+create_script foo tinc-up '
+ tinc foo stop &
+'
+start_tinc foo
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Initialize one node
+echo [STEP] Initialize one node
-$tinc $c1 <<EOF
+tinc foo <<EOF
init foo
set DeviceType dummy
set Port 0
EOF
-cat >$d1/tinc-up <<EOF
-#!/bin/sh
-read pid rest <$d1/pid
-(sleep 0.1; kill \$pid) &
-EOF
-
-cat >$d1/tinc-up.cmd <<EOF
-start /min ../$tinc $c1 stop
-EOF
+create_script foo tinc-up '
+ tinc foo stop &
+'
-# Test tincd command line options that should work
+echo [STEP] Test tincd command line options that should work
-$tincd $c1 $r1 -D
-$tincd $c1 $r1 --no-detach
-$tincd $c1 $r1 -D -d
-$tincd $c1 $r1 -D -d2
-$tincd $c1 $r1 -D -d 2
-$tincd $c1 $r1 -D -n foo
-$tincd $c1 $r1 -D -nfoo
-$tincd $c1 $r1 -D --net=foo
-$tincd $c1 $r1 -D --net foo
+tincd foo -D
+tincd foo --no-detach
+tincd foo -D -d
+tincd foo -D -d2
+tincd foo -D -d 2
+tincd foo -D -n foo
+tincd foo -D -nfoo
+tincd foo -D --net=foo
+tincd foo -D --net foo
-# Test tincd command line options that should not work
+echo [STEP] Test tincd command line options that should not work
-$tincd $c1 $r1 foo && exit 1 || true
-$tincd $c1 $r1 --pidfile && exit 1 || true
-$tincd $c1 $r1 --foo && exit 1 || true
+must_fail tincd foo foo
+must_fail tincd foo --pidfile
+must_fail tincd foo --foo
-# Test tinc command line options that should work
+echo [STEP] Test tinc command line options that should work
-$tinc $c1 get name
-$tinc $c1 -n foo get name
-$tinc $c1 -nfoo get name
-$tinc $c1 --net=foo get name
-$tinc $c1 --net foo get name
+tinc foo get name
+tinc foo -n foo get name
+tinc foo -nfoo get name
+tinc foo --net=foo get name
+tinc foo --net foo get name
-# Test tinc command line options that should not work
+echo [STEP] Test tinc command line options that should not work
-$tinc $c1 -n foo get somethingreallyunknown && exit 1 || true
-$tinc $c1 --net && exit 1 || true
-$tinc $c1 --net get name && exit 1 || true
-$tinc $c1 foo && exit 1 || true
+must_fail tinc foo -n foo get somethingreallyunknown
+must_fail tinc foo --net
+must_fail tinc foo --net get name
+must_fail tinc foo foo
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Just test whether the executables work
-$tincd --help
-$tinc --help
-if [ -e $sptps_test ]; then
- $sptps_test --help
+echo [STEP] Just test whether the executables work
+
+tinc foo --help
+
+tincd foo --help
+
+if [ -e "$SPTPS_TEST" ]; then
+ $SPTPS_TEST --help
fi
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Initialize three nodes
+echo [STEP] Initialize three nodes
-$tinc $c1 <<EOF
+tinc foo <<EOF
init foo
set DeviceType dummy
-set Port 32752
+set Port 30000
set Address localhost
EOF
-$tinc $c2 <<EOF
+tinc bar <<EOF
init bar
set DeviceType dummy
set Port 0
EOF
-$tinc $c3 <<EOF
+tinc baz <<EOF
init baz
set DeviceType dummy
set Port 0
EOF
-# Test import, export and exchange commands
+echo [STEP] Test import, export and exchange commands
-$tinc $c1 export | $tinc $c2 exchange | $tinc $c1 import
+tinc foo export | tinc bar exchange | tinc foo import
-# Test export-all and exchange-all
+echo [STEP] Test export-all and exchange-all
-$tinc $c1 export-all | $tinc $c3 exchange | $tinc $c1 import
-$tinc $c1 exchange-all </dev/null | $tinc $c2 import
+tinc foo export-all | tinc baz exchange | tinc foo import
+tinc foo exchange-all </dev/null | tinc bar import
-# Test equivalence of host config files
+echo [STEP] Test equivalence of host config files
-cmp $d1/hosts/foo $d2/hosts/foo
-cmp $d1/hosts/foo $d3/hosts/foo
-cmp $d1/hosts/bar $d2/hosts/bar
-cmp $d1/hosts/bar $d3/hosts/bar
-cmp $d1/hosts/baz $d2/hosts/baz
-cmp $d1/hosts/baz $d3/hosts/baz
+diff -w "$DIR_FOO/hosts/foo" "$DIR_BAR/hosts/foo"
+diff -w "$DIR_FOO/hosts/foo" "$DIR_BAZ/hosts/foo"
+diff -w "$DIR_FOO/hosts/bar" "$DIR_BAR/hosts/bar"
+diff -w "$DIR_FOO/hosts/bar" "$DIR_BAZ/hosts/bar"
+diff -w "$DIR_FOO/hosts/baz" "$DIR_BAR/hosts/baz"
+diff -w "$DIR_FOO/hosts/baz" "$DIR_BAZ/hosts/baz"
-# Check whether the nodes can connect to each other
+echo [STEP] Check whether the nodes can connect to each other
-$tinc $c1 start $r1
+create_script foo tinc-up '
+ tinc bar add ConnectTo foo
+ tinc baz add ConnectTo foo
+'
-$tinc $c2 add ConnectTo foo
-$tinc $c3 add ConnectTo foo
+create_script foo hosts/bar-up
+create_script foo hosts/baz-up
-sleep 1
+start_tinc foo
-$tinc $c2 start $r2
-$tinc $c3 start $r3
+wait_script foo tinc-up
-sleep 1
+start_tinc bar
+start_tinc baz
-test `$tinc $c1 dump reachable nodes | wc -l` = 3
-test `$tinc $c2 dump reachable nodes | wc -l` = 3
-test `$tinc $c3 dump reachable nodes | wc -l` = 3
+wait_script foo hosts/bar-up
+wait_script foo hosts/baz-up
-$tinc $c3 stop
-$tinc $c2 stop
-$tinc $c1 stop
+require_nodes foo 3
+require_nodes bar 3
+require_nodes baz 3
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Initialize one node
+echo [STEP] Initialize one node
-$tinc $c1 <<EOF
+tinc foo <<EOF
init foo
set DeviceType dummy
set Mode switch
set Broadcast no
set Address localhost
-set Port 32751
-start $r1
+set Port 30010
EOF
-# Generate an invitation and let another node join the VPN
+start_tinc foo
-$tinc $c1 invite bar | $tinc $c2 join
+echo [STEP] Generate an invitation and let another node join the VPN
-# Test equivalence of host config files
+tinc foo invite bar | tinc bar join
-cmp $d1/hosts/foo $d2/hosts/foo
-test "`grep ^Ed25519PublicKey $d1/hosts/bar`" = "`grep ^Ed25519PublicKey $d2/hosts/bar`"
+echo [STEP] Test equivalence of host config files
-# Test Mode, Broadcast and ConnectTo statements
+diff -w "$DIR_FOO/hosts/foo" "$DIR_BAR/hosts/foo"
+test "$(grep ^Ed25519PublicKey "$DIR_FOO/hosts/bar")" = "$(grep ^Ed25519PublicKey "$DIR_BAR/hosts/bar")"
-test `$tinc $c2 get Mode` = switch
-test `$tinc $c2 get Broadcast` = no
-test `$tinc $c2 get ConnectTo` = foo
+echo [STEP] Test Mode, Broadcast and ConnectTo statements
-# Check whether the new node can join the VPN
+test "$(tinc bar get Mode)" = switch
+test "$(tinc bar get Broadcast)" = no
+test "$(tinc bar get ConnectTo)" = foo
-$tinc $c2 << EOF
+echo [STEP] Check whether the new node can join the VPN
+
+tinc bar <<EOF
set DeviceType dummy
set Port 0
-start $r2
EOF
-sleep 1
+create_script foo hosts/bar-up
+create_script bar hosts/foo-up
+
+start_tinc bar
-test `$tinc $c1 dump reachable nodes | wc -l` = 2
-test `$tinc $c2 dump reachable nodes | wc -l` = 2
+wait_script foo hosts/bar-up
+wait_script bar hosts/foo-up
-$tinc $c2 stop
-$tinc $c1 stop
+require_nodes foo 2
+require_nodes bar 2
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Initialize one node
+echo [STEP] Initialize one node
-$tinc $c1 <<EOF
+tinc foo <<EOF
init foo
set DeviceType dummy
set Mode switch
set Broadcast no
set Address localhost
-set Port 32758
+set Port 30020
EOF
-# Generate an invitation offline and let another node join the VPN
+echo [STEP] Generate an invitation offline and let another node join the VPN
-invitation=`$tinc $c1 invite bar | tr -d '\r'`
+invitation=$(tinc foo invite bar)
-$tinc $c1 start $r1
+start_tinc foo
+tinc bar join "$invitation"
-$tinc $c2 join $invitation
+echo [STEP] Test equivalence of host config files
-# Test equivalence of host config files
+diff -w "$DIR_FOO/hosts/foo" "$DIR_BAR/hosts/foo"
+test "$(grep ^Ed25519PublicKey "$DIR_FOO/hosts/bar")" = "$(grep ^Ed25519PublicKey "$DIR_BAR/hosts/bar")"
-cmp $d1/hosts/foo $d2/hosts/foo
-test "`grep ^Ed25519PublicKey $d1/hosts/bar`" = "`grep ^Ed25519PublicKey $d2/hosts/bar`"
+echo [STEP] Test Mode, Broadcast and ConnectTo statements
-# Test Mode, Broadcast and ConnectTo statements
+test "$(tinc bar get Mode)" = switch
+test "$(tinc bar get Broadcast)" = no
+test "$(tinc bar get ConnectTo)" = foo
-test `$tinc $c2 get Mode` = switch
-test `$tinc $c2 get Broadcast` = no
-test `$tinc $c2 get ConnectTo` = foo
+echo [STEP] Check whether the new node can join the VPN
-# Check whether the new node can join the VPN
-
-$tinc $c2 << EOF
+tinc bar <<EOF
set DeviceType dummy
set Port 0
-start $r2
EOF
-sleep 1
+create_script foo hosts/bar-up
+create_script bar hosts/foo-up
+
+start_tinc bar
-test `$tinc $c1 dump reachable nodes | wc -l` = 2
-test `$tinc $c2 dump reachable nodes | wc -l` = 2
+wait_script foo hosts/bar-up
+wait_script bar hosts/foo-up
-$tinc $c2 stop
-$tinc $c1 stop
+require_nodes foo 2
+require_nodes bar 2
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Initialize one node
+echo [STEP] Initialize one node
-$tinc $c1 <<EOF
+tinc foo <<EOF
init foo
set DeviceType dummy
set Address localhost
-set Port 32756
-start $r1
+set Port 30030
EOF
-# Generate an invitation and let another node join the VPN
+create_script foo tinc-up
+start_tinc foo
+wait_script foo tinc-up
-sleep 1
+echo [STEP] Generate an invitation and let another node join the VPN
-cat >$d1/invitation-created <<EOF
-#!/bin/sh
-echo Name = \$NODE >\$INVITATION_FILE
-echo Ifconfig = 93.184.216.34/24 >>\$INVITATION_FILE
-echo Route = 2606:2800:220:1::/64 2606:2800:220:1:248:1893:25c8:1946 >>\$INVITATION_FILE
-echo Route = 1.2.3.4 1234:: >>\$INVITATION_FILE
-$tinc $c1 export >>\$INVITATION_FILE
-EOF
-
-cat >$d1/invitation-created.cmd <<EOF
-echo Name = %NODE% >%INVITATION_FILE%
-echo Ifconfig = 93.184.216.34/24 >>%INVITATION_FILE%
-echo Route = 2606:2800:220:1::/64 2606:2800:220:1:248:1893:25c8:1946 >>%INVITATION_FILE%
-echo Route = 1.2.3.4 1234:: >>%INVITATION_FILE%
-$tinc $c1 export >>%INVITATION_FILE%
-EOF
-
-chmod u+x $d1/invitation-created
+# shellcheck disable=SC2016
+create_script foo invitation-created '
+cat >"$INVITATION_FILE" <<INVITE
+Name = $NODE
+Ifconfig = 93.184.216.34/24
+Route = 2606:2800:220:1::/64 2606:2800:220:1:248:1893:25c8:1946
+Route = 1.2.3.4 1234::
-$tinc $c1 invite bar | tail -1 | $tinc $c2 --batch join
+$(tinc foo export)
+INVITE
+'
-# Test equivalence of host config files
+tinc foo invite bar | tail -1 | tinc bar --batch join
-cmp $d1/hosts/foo $d2/hosts/foo
-test "`grep ^Ed25519PublicKey $d1/hosts/bar`" = "`grep ^Ed25519PublicKey $d2/hosts/bar`"
+echo [STEP] Test equivalence of host config files
-# Check if the tinc-up.invitation file is created and contains the right commands
+diff -w "$DIR_FOO/hosts/foo" "$DIR_BAR/hosts/foo"
+test "$(grep ^Ed25519PublicKey "$DIR_FOO/hosts/bar")" = "$(grep ^Ed25519PublicKey "$DIR_BAR/hosts/bar")"
-test -f $d2/tinc-up.invitation
+echo [STEP] Check if the tinc-up.invitation file is created and contains the right commands
-fgrep -q "93.184.216.34/24" $d2/tinc-up.invitation
-fgrep -q "2606:2800:220:1::/64" $d2/tinc-up.invitation
-fgrep -q "2606:2800:220:1:248:1893:25c8:1946" $d2/tinc-up.invitation
-fgrep -q "1234::" $d2/tinc-up.invitation && exit 1
+bar_tinc_up="$DIR_BAR/tinc-up.invitation"
+test -f "$bar_tinc_up"
-# Check that no tinc-up is created and that tinc-up.invitation is not executable
+grep -F -q "93.184.216.34/24" "$bar_tinc_up"
+grep -F -q "2606:2800:220:1::/64" "$bar_tinc_up"
+grep -F -q "2606:2800:220:1:248:1893:25c8:1946" "$bar_tinc_up"
+must_fail grep -F -q "1234::" "$bar_tinc_up"
-test -x $d2/tinc-up.invitation && exit 1
-test -f $d2/tinc-up && exit 1
+echo [STEP] Check that no tinc-up is created and that tinc-up.invitation is not executable
-$tinc $c1 stop
+must_fail test -x "$bar_tinc_up"
+must_fail test -f "$DIR_BAR/tinc-up"
#!/bin/sh
-# Skip this test if the legacy protocol is disabled
-if grep -q "define DISABLE_LEGACY 1" "${0%/*}/../config.h"; then
- exit 77
-fi
-
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Initialize two nodes
+echo [STEP] Initialize two nodes
-$tinc $c1 <<EOF
+tinc foo <<EOF
init foo
set DeviceType dummy
-set Port 32753
+set Port 30060
set Address localhost
-set PingTimeout 1
+add Subnet 10.98.98.1
+set PingTimeout 2
EOF
-$tinc $c2 <<EOF
+tinc bar <<EOF
init bar
set DeviceType dummy
set Port 0
-set PingTimeout 1
-set MaxTimeout 1
+add Subnet 10.98.98.2
+set PingTimeout 2
+set MaxTimeout 2
EOF
-# Exchange host config files
+echo [STEP] Exchange host config files
+
+tinc foo export | tinc bar exchange | tinc foo import
+tinc bar add ConnectTo foo
-$tinc $c1 export | $tinc $c2 exchange | $tinc $c1 import
-$tinc $c2 add ConnectTo foo
+echo [STEP] Foo 1.1, bar 1.0
-# Foo 1.1, bar 1.0
+tinc bar set ExperimentalProtocol no
+tinc foo del bar.Ed25519PublicKey
+tinc bar del foo.Ed25519PublicKey
-$tinc $c2 set ExperimentalProtocol no
-$tinc $c1 del bar.Ed25519PublicKey
-$tinc $c2 del foo.Ed25519PublicKey
+create_script foo hosts/bar-up
+create_script bar hosts/foo-up
-$tinc $c1 start $r1
-$tinc $c2 start $r2
+start_tinc foo
+start_tinc bar
-sleep 1
+wait_script foo hosts/bar-up
+wait_script bar hosts/foo-up
-test `$tinc $c1 dump reachable nodes | wc -l` = 2
-test `$tinc $c2 dump reachable nodes | wc -l` = 2
+require_nodes foo 2
+require_nodes bar 2
-$tinc $c2 stop
-$tinc $c1 stop
+tinc bar stop
+tinc foo stop
-test -z "`$tinc $c1 get bar.Ed25519PublicKey`"
-test -z "`$tinc $c2 get foo.Ed25519PublicKey`"
+test -z "$(tinc foo get bar.Ed25519PublicKey)"
+test -z "$(tinc bar get foo.Ed25519PublicKey)"
-# Foo 1.1, bar upgrades to 1.1
+echo [STEP] Foo 1.1, bar upgrades to 1.1
-$tinc $c2 del ExperimentalProtocol
+tinc bar del ExperimentalProtocol
-$tinc $c1 start $r1
-$tinc $c2 start $r2
+start_tinc foo
+start_tinc bar
-sleep 5
+wait_script foo hosts/bar-up
+wait_script bar hosts/foo-up
-test `$tinc $c1 dump reachable nodes | wc -l` = 2
-test `$tinc $c2 dump reachable nodes | wc -l` = 2
+require_nodes foo 2
+require_nodes bar 2
-$tinc $c2 stop
-$tinc $c1 stop
+tinc bar stop
+tinc foo stop
-test -n "`$tinc $c1 get bar.Ed25519PublicKey`"
-test -n "`$tinc $c2 get foo.Ed25519PublicKey`"
+test -n "$(tinc foo get bar.Ed25519PublicKey)"
+test -n "$(tinc bar get foo.Ed25519PublicKey)"
-# Bar downgrades, must no longer be allowed to connect
+echo [STEP] Bar downgrades, must no longer be allowed to connect
-$tinc $c2 set ExperimentalProtocol no
+tinc bar set ExperimentalProtocol no
-$tinc $c1 start $r1
-$tinc $c2 start $r2
+create_script foo subnet-up
+start_tinc foo
+wait_script foo subnet-up
-sleep 5
+create_script bar subnet-up
+start_tinc bar
+wait_script bar subnet-up
-test `$tinc $c1 dump reachable nodes | wc -l` = 1
-test `$tinc $c2 dump reachable nodes | wc -l` = 1
+# There is no reliable way to wait for 'not connecting'.
+sleep 10
-$tinc $c2 stop
-$tinc $c1 stop
+require_nodes foo 1
+require_nodes bar 1
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Skip this test if we aren't root or if "ip netns" does not exist
+echo "[STEP] Skip this test if we aren't root or if 'ip netns' does not exist"
-test "`id -u`" = "0" || exit 77
-ip netns list || exit 77
+test "$(id -u)" = "0" || exit $EXIT_SKIP_TEST
+test -e /dev/net/tun || exit $EXIT_SKIP_TEST
+ip netns list || exit $EXIT_SKIP_TEST
-# Initialize two nodes
+ip_foo=192.168.1.1
+ip_bar=192.168.1.2
+mask=24
-$tinc $c1 <<EOF
+echo [STEP] Initialize two nodes
+
+tinc foo <<EOF
init foo
-set Subnet 192.168.1.1
+set Subnet $ip_foo
set Interface ping.test1
-set Port 32577
+set Port 30090
set Address localhost
set AutoConnect no
EOF
-cat >$d1/tinc-up <<EOF
-#!/bin/sh
-ip netns add ping.test1
-ip link set dev \$INTERFACE netns ping.test1
-ip netns exec ping.test1 ip addr add 192.168.1.1/24 dev \$INTERFACE
-ip netns exec ping.test1 ip link set \$INTERFACE up
-EOF
+# shellcheck disable=SC2016
+create_script foo tinc-up "
+ ip netns add ping.test1
+ ip link set dev \$INTERFACE netns ping.test1
+ ip netns exec ping.test1 ip addr add $ip_foo/$mask dev \$INTERFACE
+ ip netns exec ping.test1 ip link set \$INTERFACE up
+"
-$tinc $c2 <<EOF
+tinc bar <<EOF
init bar
-set Subnet 192.168.1.2
+set Subnet $ip_bar
set Interface ping.test2
-set Port 32574
+set Port 30091
set AutoConnect no
EOF
-cat >$d2/tinc-up <<EOF
-#!/bin/sh
-ip netns add ping.test2
-ip link set dev \$INTERFACE netns ping.test2
-ip netns exec ping.test2 ip addr add 192.168.1.2/24 dev \$INTERFACE
-ip netns exec ping.test2 ip link set \$INTERFACE up
-EOF
+# shellcheck disable=SC2016
+create_script bar tinc-up "
+ ip netns add ping.test2
+ ip link set dev \$INTERFACE netns ping.test2
+ ip netns exec ping.test2 ip addr add $ip_bar/$mask dev \$INTERFACE
+ ip netns exec ping.test2 ip link set \$INTERFACE up
+"
+
+echo [STEP] Exchange configuration files
-# Exchange configuration files
+tinc foo export | tinc bar exchange | tinc foo import
-$tinc $c1 export | $tinc $c2 exchange | $tinc $c1 import
+echo [STEP] Start tinc
-# Start tinc
+start_tinc foo
+start_tinc bar
-$tinc $c1 start $r1
-$tinc $c2 start $r2
+wait_script foo tinc-up
+wait_script bar tinc-up
-sleep 1
+echo [STEP] The nodes should not be able to ping each other if there is no connection
-# The nodes should not be able to ping each other if there is no connection
+must_fail ip netns exec ping.test1 ping -W1 -c3 $ip_bar
-ip netns exec ping.test1 ping -W1 -c3 192.168.1.2 && exit 1
+echo [STEP] After connecting they should be
-# After connecting they should be
+create_script bar hosts/foo-up
-$tinc $c2 add ConnectTo foo
-sleep 1
+tinc bar add ConnectTo foo
+wait_script bar hosts/foo-up
-ip netns exec ping.test1 ping -W1 -c3 192.168.1.2
+ip netns exec ping.test1 ping -W1 -c3 $ip_bar
-# Clean up
+echo [STEP] Clean up
-$tinc $c2 stop
-$tinc $c1 stop
ip netns del ping.test2
ip netns del ping.test1
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-echo Initializing node...
+echo [STEP] Initializing server node
-# Initialize server node
+port_foo=30040
+port_bar=30041
-$tinc $c1 <<EOF
+tinc foo <<EOF
init foo
set DeviceType dummy
-set Port 32759
+set Port $port_foo
set Address 127.0.0.1
add Subnet 10.0.0.1
add Subnet fec0::/64
EOF
-# Set up scripts
-
-echo Setting up scripts...
-
-OUT=$d1/scripts.out
-rm -f $OUT
-
-for script in tinc-up tinc-down host-up host-down subnet-up subnet-down hosts/foo-up hosts/foo-down hosts/bar-up hosts/bar-down invitation-created invitation-accepted; do
-cat >$d1/$script << EOF
-#!/bin/sh
-echo $script \$NETNAME,\$NAME,\$DEVICE,\$IFACE,\$NODE,\$REMOTEADDRESS,\$REMOTEPORT,\$SUBNET,\$WEIGHT,\$INVITATION_FILE,\$INVITATION_URL,\$DEBUG >>$OUT
+echo [STEP] Setting up scripts
+
+OUT=$DIR_FOO/scripts.out
+rm -f "$OUT"
+
+for script in \
+ tinc-up tinc-down \
+ host-up host-down \
+ subnet-up subnet-down \
+ hosts/foo-up hosts/foo-down \
+ hosts/bar-up hosts/bar-down \
+ invitation-created invitation-accepted; do
+
+ commands=$(
+ cat <<EOF
+ if is_windows && [ -n "\$INVITATION_FILE" ]; then
+ INVITATION_FILE=\$(cygpath --unix -- "\$INVITATION_FILE")
+ fi
+ echo >>'$OUT' "$script" "$TINC_SCRIPT_VARS"
EOF
-chmod u+x $d1/$script
+ )
-cat >$d1/$script.cmd << EOF
-echo $script %NETNAME%,%NAME%,%DEVICE%,%IFACE%,%NODE%,%REMOTEADDRESS%,%REMOTEPORT%,%SUBNET%,%WEIGHT%,%INVITATION_FILE%,%INVITATION_URL%,%DEBUG% >>$OUT
-EOF
+ create_script foo "$script" "$commands"
done
-# Start server node
-
-echo Starting server node...
-
-$tinc $c1 -n netname start $r1
+echo [STEP] Starting server node
-echo foo-started >>$OUT
+start_tinc foo -n netname
+wait_script foo subnet-up 2
+echo foo-started >>"$OUT"
-# Invite client node
+echo [STEP] Inviting client node
-echo Inviting client node...
+url=$(tinc foo -n netname2 invite bar)
+file=$(basename "$(find "$DIR_FOO/invitations" -type f ! -name ed25519_key.priv)")
-url=`$tinc $c1 -n netname2 invite bar | tr -d '\r'`
-file=`cd $d1/invitations; ls | grep -v ed25519_key.priv`
-echo bar-invited >>$OUT
+if is_windows; then
+ file=$(cygpath --unix -- "$file")
+fi
-echo Joining client node...
+wait_script foo invitation-created
+echo bar-invited >>"$OUT"
-$tinc $c2 -n netname3 join $url
-echo bar-joined >>$OUT
+echo [STEP] Joining client node
-# Start and stop client node
+tinc bar -n netname3 join "$url"
+wait_script foo invitation-accepted
+echo bar-joined >>"$OUT"
-echo Starting client node...
+echo [STEP] Starting client node
-$tinc $c2 << EOF
+tinc bar <<EOF
set DeviceType dummy
-set Port 32760
+set Port $port_bar
add Subnet 10.0.0.2
add Subnet fec0::/64#5
-start $r2
EOF
-sleep 1
+start_tinc bar
+wait_script foo subnet-up 2
+echo bar-started-1 >>"$OUT"
-echo bar-started >>$OUT
+tinc foo debug 4
+tinc bar stop
+wait_script foo subnet-down 2
+echo bar-stopped >>"$OUT"
-$tinc $c1 debug 4
-$tinc $c2 stop
+tinc foo debug 5
+start_tinc bar
+wait_script foo subnet-up 2
+echo bar-started-2 >>"$OUT"
-sleep 1
+echo [STEP] Stop server node
-echo bar-stopped >>$OUT
+tinc foo stop
+tinc bar stop
+wait_script foo tinc-down
-$tinc $c1 debug 5
-$tinc $c2 start $r2
+echo [STEP] Check if the script output is what is expected
-sleep 1
-
-echo bar-started >>$OUT
-
-# Stop server node
-
-$tinc $c1 stop
-sleep 1
-$tinc $c2 stop
-
-# Check if the script output is what is expected
-
-cat >$OUT.expected << EOF
+cat >"$OUT.expected" <<EOF
tinc-up netname,foo,dummy,,,,,,,,,5
subnet-up netname,foo,dummy,,foo,,,10.0.0.1,,,,5
subnet-up netname,foo,dummy,,foo,,,fec0::/64,,,,5
foo-started
-invitation-created netname2,foo,,,bar,,,,,$d1/invitations/$file,$url,
+invitation-created netname2,foo,,,bar,,,,,$DIR_FOO/invitations/$file,$url,
bar-invited
invitation-accepted netname,foo,dummy,,bar,127.0.0.1,,,,,,5
bar-joined
-host-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5
-hosts/bar-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5
-subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,5
-subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,5
-bar-started
-host-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,4
-hosts/bar-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,4
-subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,4
-subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,4
+host-up netname,foo,dummy,,bar,127.0.0.1,$port_bar,,,,,5
+hosts/bar-up netname,foo,dummy,,bar,127.0.0.1,$port_bar,,,,,5
+subnet-up netname,foo,dummy,,bar,127.0.0.1,$port_bar,10.0.0.2,,,,5
+subnet-up netname,foo,dummy,,bar,127.0.0.1,$port_bar,fec0::/64,5,,,5
+bar-started-1
+host-down netname,foo,dummy,,bar,127.0.0.1,$port_bar,,,,,4
+hosts/bar-down netname,foo,dummy,,bar,127.0.0.1,$port_bar,,,,,4
+subnet-down netname,foo,dummy,,bar,127.0.0.1,$port_bar,10.0.0.2,,,,4
+subnet-down netname,foo,dummy,,bar,127.0.0.1,$port_bar,fec0::/64,5,,,4
bar-stopped
-host-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5
-hosts/bar-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5
-subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,5
-subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,5
-bar-started
-host-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5
-hosts/bar-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5
-subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,5
-subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,5
+host-up netname,foo,dummy,,bar,127.0.0.1,$port_bar,,,,,5
+hosts/bar-up netname,foo,dummy,,bar,127.0.0.1,$port_bar,,,,,5
+subnet-up netname,foo,dummy,,bar,127.0.0.1,$port_bar,10.0.0.2,,,,5
+subnet-up netname,foo,dummy,,bar,127.0.0.1,$port_bar,fec0::/64,5,,,5
+bar-started-2
+host-down netname,foo,dummy,,bar,127.0.0.1,$port_bar,,,,,5
+hosts/bar-down netname,foo,dummy,,bar,127.0.0.1,$port_bar,,,,,5
+subnet-down netname,foo,dummy,,bar,127.0.0.1,$port_bar,10.0.0.2,,,,5
+subnet-down netname,foo,dummy,,bar,127.0.0.1,$port_bar,fec0::/64,5,,,5
subnet-down netname,foo,dummy,,foo,,,10.0.0.1,,,,5
subnet-down netname,foo,dummy,,foo,,,fec0::/64,,,,5
tinc-down netname,foo,dummy,,,,,,,,,5
EOF
-tr -d '\r' <$OUT >$OUT.actual
-
-cmp $OUT.actual $OUT.expected
+diff -w "$OUT" "$OUT.expected"
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Skip this test if tools are missing
+echo [STEP] Skip this test if tools are missing
-which nc >/dev/null || exit 77
+which nc >/dev/null || exit $EXIT_SKIP_TEST
+which timeout >/dev/null || exit $EXIT_SKIP_TEST
-if [ "$(uname)" = "Darwin" ]; then
- alias timeout=gtimeout
-fi
+foo_port=30050
+bar_port=30051
-which timeout >/dev/null || exit 77
+# usage: splice protocol_version
+splice() {
+ ./splice foo localhost $foo_port bar localhost $bar_port "$1" &
+ sleep 10
+}
-# Initialize two nodes
+# usage: send_with_timeout "data to send" "data expected to receive"
+send_with_timeout() {
+ data=$1
+ expected=$3
-$tinc $c1 <<EOF
+ result=$(
+ (
+ sleep 6
+ printf "%s\n" "$data"
+ ) | timeout 10 nc localhost $foo_port
+ ) && exit 1
+
+ test $? = $EXIT_TIMEOUT
+
+ if [ -z "$expected" ]; then
+ test -z "$result"
+ else
+ echo "$result" | grep -q "^$expected"
+ fi
+}
+
+echo [STEP] Initialize two nodes
+
+tinc foo <<EOF
init foo
set DeviceType dummy
-set Port 32754
+set Port $foo_port
set Address localhost
-set PingTimeout 1
+set PingTimeout 3
set AutoConnect no
+set Subnet 10.96.96.1
EOF
-$tinc $c2 <<EOF
+tinc bar <<EOF
init bar
set DeviceType dummy
-set Port 32755
-set PingTimeout 1
-set MaxTimeout 1
+set Port $bar_port
+set PingTimeout 3
+set MaxTimeout 3
set ExperimentalProtocol no
set AutoConnect no
+set Subnet 10.96.96.2
EOF
-# Exchange host config files
+echo [STEP] Exchange host config files
-$tinc $c1 export | $tinc $c2 exchange | $tinc $c1 import
+tinc foo export | tinc bar exchange | tinc foo import
-$tinc $c1 start $r1
-$tinc $c2 start $r2
+create_script foo subnet-up
+start_tinc foo
+wait_script foo subnet-up
-# No ID sent by responding node if we don't send an ID first, before the timeout
+create_script bar subnet-up
+start_tinc bar
+wait_script bar subnet-up
-result=`(sleep 2; echo "0 bar 17.7") | timeout 3 nc localhost 32754` && exit 1
-test $? = 124
-test -z "$result"
+echo "[STEP] No ID sent by responding node if we don't send an ID first, before the timeout"
+send_with_timeout "0 bar 17.7" ""
-# ID sent if initiator sends first, but still tarpitted
+echo [STEP] ID sent if initiator sends first, but still tarpitted
+send_with_timeout "0 bar 17.7" "0 foo 17.7"
-result=`echo "0 bar 17.7" | timeout 3 nc localhost 32754` && exit 1
-test $? = 124
-test "`echo "$result" | head -c 10`" = "0 foo 17.7"
+echo [STEP] No invalid IDs allowed
+send_with_timeout "0 foo 17.7" ""
+send_with_timeout "0 baz 17.7" ""
-# No invalid IDs allowed
+echo [STEP] No NULL METAKEYs allowed
+data="0 foo 17.0\n1 0 672 0 0 834188619F4D943FD0F4B1336F428BD4AC06171FEABA66BD2356BC9593F0ECD643F0E4B748C670D7750DFDE75DC9F1D8F65AB1026F5ED2A176466FBA4167CC567A2085ABD070C1545B180BDA86020E275EA9335F509C57786F4ED2378EFFF331869B856DDE1C05C461E4EECAF0E2FB97AF77B7BC2AD1B34C12992E45F5D1254BBF0C3FB224ABB3E8859594A83B6CA393ED81ECAC9221CE6BC71A727BCAD87DD80FC0834B87BADB5CB8FD3F08BEF90115A8DF1923D7CD9529729F27E1B8ABD83C4CF8818AE10257162E0057A658E265610B71F9BA4B365A20C70578FAC65B51B91100392171BA12A440A5E93C4AA62E0C9B6FC9B68F953514AAA7831B4B2C31C4\n"
+send_with_timeout "$data" "" # Not even the ID should be sent when the first packet contains illegal data
-result=`echo "0 foo 17.7" | timeout 1 nc localhost 32754` && exit 1
-test $? = 124
-test -z "$result"
+echo [STEP] No splicing allowed
-result=`echo "0 baz 17.7" | timeout 1 nc localhost 32754` && exit 1
-test $? = 124
-test -z "$result"
+tinc bar stop
+tinc bar del ExperimentalProtocol
-# No NULL METAKEYs allowed
+create_script bar subnet-up
+start_tinc bar
+wait_script bar subnet-up
-result=`printf "0 foo 17.0\n1 0 672 0 0 834188619F4D943FD0F4B1336F428BD4AC06171FEABA66BD2356BC9593F0ECD643F0E4B748C670D7750DFDE75DC9F1D8F65AB1026F5ED2A176466FBA4167CC567A2085ABD070C1545B180BDA86020E275EA9335F509C57786F4ED2378EFFF331869B856DDE1C05C461E4EECAF0E2FB97AF77B7BC2AD1B34C12992E45F5D1254BBF0C3FB224ABB3E8859594A83B6CA393ED81ECAC9221CE6BC71A727BCAD87DD80FC0834B87BADB5CB8FD3F08BEF90115A8DF1923D7CD9529729F27E1B8ABD83C4CF8818AE10257162E0057A658E265610B71F9BA4B365A20C70578FAC65B51B91100392171BA12A440A5E93C4AA62E0C9B6FC9B68F953514AAA7831B4B2C31C4\n" | timeout 3 nc localhost 32755` && exit 1
-test $? = 124
-test -z "$result" # Not even the ID should be sent when the first packet contains illegal data
+splice 17.7
+pid=$!
-# No splicing allowed
+require_nodes foo 1
+require_nodes bar 1
-$tinc $c2 stop
-$tinc $c2 del ExperimentalProtocol
-$tinc $c2 start $r2
+kill $pid
-./splice foo localhost 32754 bar localhost 32755 17.7 &
-sleep 3
-test `$tinc $c1 dump reachable nodes | wc -l` = 1
-test `$tinc $c2 dump reachable nodes | wc -l` = 1
-kill $!
+tinc bar stop
+tinc foo stop
-$tinc $c2 stop
-$tinc $c1 stop
+echo [STEP] Test splicing again with legacy protocol
-# Test splicing again with legacy protocol
+tinc foo set ExperimentalProtocol no
+tinc bar set ExperimentalProtocol no
-$tinc $c1 set ExperimentalProtocol no
-$tinc $c2 set ExperimentalProtocol no
+create_script foo subnet-up
+start_tinc foo
+wait_script foo subnet-up
-$tinc $c1 start $r1
-$tinc $c2 start $r2
+create_script bar subnet-up
+start_tinc bar
+wait_script bar subnet-up
-./splice foo localhost 32754 bar localhost 32755 17.0 &
-sleep 3
-test `$tinc $c1 dump reachable nodes | wc -l` = 1
-test `$tinc $c2 dump reachable nodes | wc -l` = 1
-kill $!
+splice 17.0
+pid=$!
-# Clean up
+require_nodes foo 1
+require_nodes bar 1
-$tinc $c2 stop
-$tinc $c1 stop
+kill $pid
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Skip this test if we did not compile sptps_test
+echo [STEP] Skip this test if we did not compile sptps_test
-test -e $sptps_test -a -e $sptps_keypair || exit 77
+test -e "$SPTPS_TEST" -a -e "$SPTPS_KEYPAIR" || exit $EXIT_SKIP_TEST
-# Generate keys
+port=30080
-mkdir -p $d1
+server_priv="$DIR_FOO/server.priv"
+client_priv="$DIR_FOO/client.priv"
+server_pub="$DIR_FOO/server.pub"
+client_pub="$DIR_FOO/client.pub"
-$sptps_keypair $d1/server.priv $d1/server.pub
-$sptps_keypair $d1/client.priv $d1/client.pub
+echo [STEP] Generate keys
-# Test transfer of a simple file.
+mkdir -p "$DIR_FOO"
+$SPTPS_KEYPAIR "$server_priv" "$server_pub"
+$SPTPS_KEYPAIR "$client_priv" "$client_pub"
-(sleep 1; $sptps_test -4 -q $d1/client.priv $d1/server.pub localhost 32750 <Makefile) &
-$sptps_test -4 $d1/server.priv $d1/client.pub 32750 >$d1/out1
-cmp $d1/out1 Makefile
+echo [STEP] Test transfer of a simple file
-$sptps_test -4 -q $d1/server.priv $d1/client.pub 32750 <Makefile &
-sleep 1
-$sptps_test -4 $d1/client.priv $d1/server.pub localhost 32750 > $d1/out2
-cmp $d1/out2 Makefile
+reference=sptps-basic.test
-# Datagram mode
+(
+ sleep 3
+ $SPTPS_TEST -4 -q "$client_priv" "$server_pub" localhost $port <"$reference"
+) &
-$sptps_test -4 -dq $d1/server.priv $d1/client.pub 32750 <Makefile &
-sleep 1
-sleep 1 | $sptps_test -4 -dq $d1/client.priv $d1/server.pub localhost 32750 >$d1/out3
-cmp $d1/out3 Makefile
+$SPTPS_TEST -4 "$server_priv" "$client_pub" $port >"$DIR_FOO/out1"
+diff -w "$DIR_FOO/out1" "$reference"
+
+$SPTPS_TEST -4 -q "$server_priv" "$client_pub" $port <"$reference" &
+sleep 3
+$SPTPS_TEST -4 "$client_priv" "$server_pub" localhost $port >"$DIR_FOO/out2"
+diff -w "$DIR_FOO/out2" "$reference"
+
+echo [STEP] Datagram mode
+
+$SPTPS_TEST -4 -dq "$server_priv" "$client_pub" $port <"$reference" &
+sleep 3
+sleep 3 | $SPTPS_TEST -4 -dq "$client_priv" "$server_pub" localhost $port >"$DIR_FOO/out3"
+diff -w "$DIR_FOO/out3" "$reference"
#!/bin/sh
-# Paths to executables
+set -ex
+
+echo [STEP] Initialize test library
+
+# Paths to compiled executables
+
+# realpath on FreeBSD fails if the path does not exist.
+realdir() {
+ [ -e "$1" ] || mkdir -p "$1"
+ if type realpath >/dev/null; then
+ realpath "$1"
+ else
+ readlink -f "$1"
+ fi
+}
+
+tincd_path=$(realdir "../src/tincd@EXEEXT@")
+tinc_path=$(realdir "../src/tinc@EXEEXT@")
+
+SPTPS_TEST=$(realdir "../src/sptps_test@EXEEXT@")
+SPTPS_KEYPAIR=$(realdir "../src/sptps_keypair@EXEEXT@")
+
+# Exit status list
+EXIT_SKIP_TEST=77
-tincd=../src/tincd@EXEEXT@
-tinc=../src/tinc@EXEEXT@
-sptps_test=../src/sptps_test@EXEEXT@
-sptps_keypair=../src/sptps_keypair@EXEEXT@
+# The list of the environment variables that tinc injects into the scripts it calls.
+# shellcheck disable=SC2016
+TINC_SCRIPT_VARS='$NETNAME,$NAME,$DEVICE,$IFACE,$NODE,$REMOTEADDRESS,$REMOTEPORT,$SUBNET,$WEIGHT,$INVITATION_FILE,$INVITATION_URL,$DEBUG'
# Test directories
-scriptname=`basename $0`
+# Reuse script name if it was passed in an env var (when imported from tinc scripts).
+if [ -z "$SCRIPTNAME" ]; then
+ SCRIPTNAME=$(basename "$0")
+fi
-n1=$scriptname.1
-n2=$scriptname.2
-n3=$scriptname.3
+# Network names for tincd daemons.
+net1=$SCRIPTNAME.1
+net2=$SCRIPTNAME.2
+net3=$SCRIPTNAME.3
-d1=$PWD/$n1
-d2=$PWD/$n2
-d3=$PWD/$n3
+# Configuration/pidfile directories for tincd daemons.
+DIR_FOO=$(realdir "$PWD/$net1")
+DIR_BAR=$(realdir "$PWD/$net2")
+DIR_BAZ=$(realdir "$PWD/$net3")
-# Default arguments for both tinc and tincd
+# Register helper functions
-c1="-n $n1 --config=$d1 --pidfile=$d1/pid"
-c2="-n $n2 --config=$d2 --pidfile=$d2/pid"
-c3="-n $n3 --config=$d3 --pidfile=$d3/pid"
+# Alias gtimeout to timeout if it exists.
+if type gtimeout >/dev/null; then
+ timeout() { gtimeout "$@"; }
+fi
-# Arguments when running tincd
+# Are the shell tools provided by busybox?
+is_busybox() {
+ timeout --help 2>&1 | grep -q -i busybox
+}
-r1="--logfile=$d1/log -d5"
-r2="--logfile=$d2/log -d5"
-r3="--logfile=$d3/log -d5"
+# busybox timeout returns 128 + signal number (which is TERM by default)
+if is_busybox; then
+ EXIT_TIMEOUT=$((128 + 15))
+else
+ EXIT_TIMEOUT=124
+fi
-# Check for leftover tinc daemons
+# Is this msys2?
+is_windows() {
+ test "$(uname -o)" = Msys
+}
-[ -f $d1/pid ] && $tinc $c1 stop
-[ -f $d2/pid ] && $tinc $c2 stop
-[ -f $d3/pid ] && $tinc $c3 stop
+# Are we running on a CI server?
+is_ci() {
+ test "$CI"
+}
-# Remove test directories
+# Dump error message and exit with an error.
+bail() {
+ echo >&2 "$@"
+ exit 1
+}
-rm -rf $d1 $d2 $d3
+# Remove carriage returns to normalize strings on Windows for easier comparisons.
+rm_cr() {
+ tr -d '\r'
+}
-# Exit on errors, log all commands being executed
+# Executes whatever is passed to it, checking that the resulting exit code is non-zero.
+must_fail() {
+ if "$@"; then
+ bail "expected a non-zero exit code"
+ fi
+}
-set -ex
+# Runs its arguments with timeout(1) or gtimeout(1) if either are installed.
+# Usage: try_limit_time 10 command --with --args
+if type timeout >/dev/null; then
+ if is_busybox; then
+ # busybox does not support --foreground
+ try_limit_time() {
+ time=$1
+ shift
+ timeout "$time" "$@"
+ }
+ else
+ # BSD and GNU timeout do not require special handling
+ try_limit_time() {
+ time=$1
+ shift
+ timeout --foreground "$time" "$@"
+ }
+ fi
+else
+ try_limit_time() {
+ echo >&2 "timeout was not found, running without time limits!"
+ shift
+ "$@"
+ }
+fi
+
+# wc -l on mac prints whitespace before the actual number.
+# This is simplest cross-platform alternative without that behavior.
+count_lines() {
+ awk 'END{ print NR }'
+}
+
+# Calls compiled tinc, passing any supplied arguments.
+# Usage: tinc { foo | bar | baz } --arg1 val1 "$args"
+tinc() {
+ peer=$1
+ shift
+
+ case "$peer" in
+ foo) try_limit_time 30 "$tinc_path" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" "$@" ;;
+ bar) try_limit_time 30 "$tinc_path" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" "$@" ;;
+ baz) try_limit_time 30 "$tinc_path" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" "$@" ;;
+ *) bail "invalid command [[$peer $*]]" ;;
+ esac
+}
+
+# Calls compiled tincd, passing any supplied arguments.
+# Usage: tincd { foo | bar | baz } --arg1 val1 "$args"
+tincd() {
+ peer=$1
+ shift
+
+ case "$peer" in
+ foo) try_limit_time 30 "$tincd_path" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" --logfile="$DIR_FOO/log" -d5 "$@" ;;
+ bar) try_limit_time 30 "$tincd_path" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" --logfile="$DIR_BAR/log" -d5 "$@" ;;
+ baz) try_limit_time 30 "$tincd_path" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" --logfile="$DIR_BAZ/log" -d5 "$@" ;;
+ *) bail "invalid command [[$peer $*]]" ;;
+ esac
+}
+
+# Start the specified tinc daemon.
+# usage: start_tinc { foo | bar | baz }
+start_tinc() {
+ peer=$1
+ shift
+
+ case "$peer" in
+ foo) tinc "$peer" start --logfile="$DIR_FOO/log" -d5 "$@" ;;
+ bar) tinc "$peer" start --logfile="$DIR_BAR/log" -d5 "$@" ;;
+ baz) tinc "$peer" start --logfile="$DIR_BAZ/log" -d5 "$@" ;;
+ *) bail "invalid peer $peer" ;;
+ esac
+}
+
+# Stop all tinc clients.
+stop_all_tincs() {
+ (
+ # In case these pid files are mangled.
+ set +e
+ [ -f "$DIR_FOO/pid" ] && tinc foo stop
+ [ -f "$DIR_BAR/pid" ] && tinc bar stop
+ [ -f "$DIR_BAZ/pid" ] && tinc baz stop
+ true
+ )
+}
+
+# Checks that the number of reachable nodes matches what is expected.
+# usage: require_nodes node_name expected_number
+require_nodes() {
+ echo >&2 "Check that we're able to reach tincd"
+ test "$(tinc "$1" pid | count_lines)" = 1
+
+ echo >&2 "Check the number of reachable nodes for $1 (expecting $2)"
+ actual="$(tinc "$1" dump reachable nodes | count_lines)"
+
+ if [ "$actual" != "$2" ]; then
+ echo >&2 "tinc $1: expected $2 reachable nodes, got $actual"
+ exit 1
+ fi
+}
+
+peer_directory() {
+ case "$peer" in
+ foo) echo "$DIR_FOO" ;;
+ bar) echo "$DIR_BAR" ;;
+ baz) echo "$DIR_BAZ" ;;
+ *) bail "invalid peer $peer" ;;
+ esac
+}
+
+# This is an append-only log of all scripts executed by all peers.
+script_runs_log() {
+ echo "$(peer_directory "$1")/script-runs.log"
+}
+
+# Create tincd script. If it fails, it kills the test script with SIGTERM.
+# usage: create_script { foo | bar | baz } { tinc-up | host-down | ... } 'script content'
+create_script() {
+ peer=$1
+ script=$2
+ shift 2
+
+ # This is the line that we should start from when reading the script execution log while waiting
+ # for $script from $peer. It is a poor man's hash map to avoid polluting tinc's home directory with
+ # "last seen" files. There seem to be no good solutions to this that are compatible with all shells.
+ line_var=$(next_line_var "$peer" "$script")
+
+ # We must reassign it here in case the script is recreated.
+ # shellcheck disable=SC2229
+ read -r "$line_var" <<EOF
+1
+EOF
+
+ # Full path to the script.
+ script_path=$(peer_directory "$peer")/$script
+
+ # Full path to the script execution log (one for each peer).
+ script_log=$(script_runs_log "$peer")
+ printf '' >"$script_log"
+
+ # Script output is redirected into /dev/null. Otherwise, it ends up
+ # in tinc's output and breaks things like 'tinc invite'.
+ cat >"$script_path" <<EOF
+#!/bin/sh
+(
+ cd "$PWD" || exit 1
+ SCRIPTNAME="$SCRIPTNAME" . ./testlib.sh
+ $@
+ echo "$script,\$$,$TINC_SCRIPT_VARS" >>"$script_log"
+) >/dev/null 2>&1 || kill -TERM $$
+EOF
+
+ chmod u+x "$script_path"
+
+ if is_windows; then
+ echo "@$MINGW_SHELL '$script_path'" >"$script_path.cmd"
+ fi
+}
+
+# Returns the name of the variable that contains the line number
+# we should read next when waiting on $script from $peer.
+# usage: next_line_var foo host-up
+next_line_var() {
+ peer=$1
+ script=$(echo "$2" | sed 's/[^a-zA-Z0-9]/_/g')
+ printf "%s" "next_line_${peer}_${script}"
+}
+
+# Waits for `peer`'s script `script` to finish `count` number of times.
+# usage: wait_script { foo | bar | baz } { tinc-up | host-up | ... } [count=1]
+wait_script() {
+ peer=$1
+ script=$2
+ count=$3
+
+ if [ -z "$count" ] || [ "$count" -lt 1 ]; then
+ count=1
+ fi
+
+ # Find out the location of the log and how many lines we should skip
+ # (because we've already seen them in previous invocations of wait_script
+ # for current $peer and $script).
+ line_var=$(next_line_var "$peer" "$script")
+
+ # eval is the only solution supported by POSIX shells.
+ # https://github.com/koalaman/shellcheck/wiki/SC3053
+ # 1. $line_var expands into 'next_line_foo_hosts_bar_up'
+ # 2. the name is substituted and the command becomes 'echo "$next_line_foo_hosts_bar_up"'
+ # 3. the command is evaluated and the line number is assigned to $line
+ line=$(eval "echo \"\$$line_var\"")
+
+ # This is the file that we monitor for script execution records.
+ script_log=$(script_runs_log "$peer")
+
+ # Starting from $line, read until $count matches are found.
+ # Print the number of the last matching line and exit.
+ # GNU tail 2.82 and newer terminates by itself when the pipe breaks.
+ # To support other tails we do an explicit `kill`.
+ # FIFO is useful here because otherwise it's difficult to determine
+ # which tail process should be killed. We could stick them in a process
+ # group by enabling job control, but this results in weird behavior when
+ # running tests in parallel on some interactive shells
+ # (e.g. when /bin/sh is symlinked to dash).
+ new_line=$(
+ try_limit_time 60 sh -c "
+ fifo=\$$.fifo
+ cleanup() { rm -f \$fifo; }
+ cleanup && trap cleanup EXIT
+
+ mkfifo \$$.fifo
+ tail -n '+$line' -f '$script_log' >\$fifo &
+ grep -n -m '$count' '^$script,' <\$fifo
+ kill \$!
+ " | awk -F: 'END { print $1 }'
+ )
+
+ # Remember the next line number for future reference. We'll use it if
+ # wait_script is called again with same $peer and $script.
+ read -r "${line_var?}" <<EOF
+$((line + new_line))
+EOF
+}
+
+# Are we running tests in parallel?
+is_parallel() {
+ # Grep the make flags for any of: '-j', '-j5', '-j 42', but not 'n-j', '-junk'.
+ echo "$MAKEFLAGS" | grep -E -q '(^|[[:space:]])-j[[:digit:]]*([[:space:]]|$)'
+}
+
+# Cleanup after running each script.
+cleanup() {
+ (
+ set +ex
+
+ stop_all_tincs
+
+ # Ask nicely, then kill anything that's left.
+ if is_ci && ! is_parallel; then
+ kill_processes() {
+ signal=$1
+ shift
+ for process in "$@"; do
+ pkill -"SIG$signal" -x -u "$(id -u)" "$process"
+ done
+ }
+ echo >&2 "CI server detected, performing aggressive cleanup"
+ kill_processes TERM tinc tincd
+ kill_processes KILL tinc tincd
+ fi
+ ) || true
+}
+
+# Generate path to current shell which can be used from Windows applications.
+if is_windows; then
+ MINGW_SHELL=$(cygpath --mixed -- "$SHELL")
+fi
+
+# This was called from a tincd script. Skip executing commands with side effects.
+[ -n "$NAME" ] && return
+
+echo [STEP] Check for leftover tinc daemons and test directories
+
+# Cleanup leftovers from previous runs.
+stop_all_tincs
+
+# On Windows this can actually fail. We don't want to suppress possible failure with -f.
+if [ -d "$DIR_FOO" ]; then rm -r "$DIR_FOO"; fi
+if [ -d "$DIR_BAR" ]; then rm -r "$DIR_BAR"; fi
+if [ -d "$DIR_BAZ" ]; then rm -r "$DIR_BAZ"; fi
+
+# Register cleanup function so we don't have to call it everywhere
+# (and failed scripts do not leave stray tincd running).
+trap cleanup EXIT INT TERM
#!/bin/sh
+# shellcheck source=testlib.sh
. "${0%/*}/testlib.sh"
-# Initialize one node
+echo [STEP] Initialize one node
-$tinc $c1 init foo
-test "`$tinc $c1 get Name | tr -d '\r'`" = "foo"
+tinc foo init foo
+test "$(tinc foo get Name)" = "foo"
-# Test case sensitivity
+echo [STEP] Test case sensitivity
-$tinc $c1 set Mode switch
-test "`$tinc $c1 get Mode | tr -d '\r'`" = "switch"
-test "`$tinc $c1 get mode | tr -d '\r'`" = "switch"
-$tinc $c1 set mode router
-test "`$tinc $c1 get Mode | tr -d '\r'`" = "router"
-test "`$tinc $c1 get mode | tr -d '\r'`" = "router"
-$tinc $c1 set Mode Switch
-test "`$tinc $c1 get Mode | tr -d '\r'`" = "Switch"
+tinc foo set Mode switch
+test "$(tinc foo get Mode)" = "switch"
+test "$(tinc foo get mode)" = "switch"
-# Test deletion
+tinc foo set mode router
+test "$(tinc foo get Mode)" = "router"
+test "$(tinc foo get mode)" = "router"
-$tinc $c1 del Mode hub && exit 1 || true
-$tinc $c1 del Mode switch
-test -z "`$tinc $c1 get Mode`"
+tinc foo set Mode Switch
+test "$(tinc foo get Mode)" = "Switch"
-# There can only be one Mode variable
+echo [STEP] Test deletion
-$tinc $c1 add Mode switch
-$tinc $c1 add Mode hub
-test "`$tinc $c1 get Mode | tr -d '\r'`" = "hub"
+must_fail tinc foo del Mode hub
+tinc foo del Mode switch
+test -z "$(tinc foo get Mode)"
-# Test addition/deletion of multivalued variables
+echo [STEP] There can only be one Mode variable
-$tinc $c1 add Subnet 1
-$tinc $c1 add Subnet 2
-$tinc $c1 add Subnet 2
-$tinc $c1 add Subnet 3
-test "`$tinc $c1 get Subnet | tr -d '\r'`" = "1
+tinc foo add Mode switch
+tinc foo add Mode hub
+test "$(tinc foo get Mode)" = "hub"
+
+echo [STEP] Test addition/deletion of multivalued variables
+
+tinc foo add Subnet 1
+tinc foo add Subnet 2
+tinc foo add Subnet 2
+tinc foo add Subnet 3
+test "$(tinc foo get Subnet | rm_cr)" = "1
2
3"
-$tinc $c1 del Subnet 2
-test "`$tinc $c1 get Subnet | tr -d '\r'`" = "1
+
+tinc foo del Subnet 2
+test "$(tinc foo get Subnet | rm_cr)" = "1
3"
-$tinc $c1 del Subnet
-test -z "`$tinc $c1 get Subnet`"
-# We should not be able to get/set server variables using node.variable syntax
+tinc foo del Subnet
+test -z "$(tinc foo get Subnet)"
-test -z "`$tinc $c1 get foo.Name`"
-$tinc $c1 set foo.Name bar && exit 1 || true
+echo [STEP] We should not be able to get/set server variables using node.variable syntax
-# Test getting/setting host variables for other nodes
+test -z "$(tinc foo get foo.Name)"
+must_fail tinc foo set foo.Name bar
-touch $d1/hosts/bar
+echo [STEP] Test getting/setting host variables for other nodes
-$tinc $c1 add bar.PMTU 1
-$tinc $c1 add bar.PMTU 2
-test "`$tinc $c1 get bar.PMTU | tr -d '\r'`" = "2"
+touch "$DIR_FOO/hosts/bar"
-$tinc $c1 add bar.Subnet 1
-$tinc $c1 add bar.Subnet 2
-$tinc $c1 add bar.Subnet 2
-$tinc $c1 add bar.Subnet 3
-test "`$tinc $c1 get bar.Subnet | tr -d '\r'`" = "1
+tinc foo add bar.PMTU 1
+tinc foo add bar.PMTU 2
+test "$(tinc foo get bar.PMTU)" = "2"
+
+tinc foo add bar.Subnet 1
+tinc foo add bar.Subnet 2
+tinc foo add bar.Subnet 2
+tinc foo add bar.Subnet 3
+test "$(tinc foo get bar.Subnet | rm_cr)" = "1
2
3"
-$tinc $c1 del bar.Subnet 2
-test "`$tinc $c1 get bar.Subnet | tr -d '\r'`" = "1
+
+tinc foo del bar.Subnet 2
+test "$(tinc foo get bar.Subnet | rm_cr)" = "1
3"
-$tinc $c1 del bar.Subnet
-test -z "`$tinc $c1 get bar.Subnet`"
-# We should not be able to get/set for nodes with invalid names
+tinc foo del bar.Subnet
+test -z "$(tinc foo get bar.Subnet)"
+
+echo [STEP] We should not be able to get/set for nodes with invalid names
-touch $d1/hosts/qu-ux
+touch "$DIR_FOO/hosts/qu-ux"
+must_fail tinc foo set qu-ux.Subnet 1
-$tinc $c1 set qu-ux.Subnet 1 && exit 1 || true
+echo [STEP] We should not be able to set obsolete variables unless forced
-# We should not be able to set obsolete variables unless forced
+must_fail tinc foo set PrivateKey 12345
+tinc foo --force set PrivateKey 12345
+test "$(tinc foo get PrivateKey)" = "12345"
-$tinc $c1 set PrivateKey 12345 && exit 1 || true
-$tinc $c1 --force set PrivateKey 12345
-test "`$tinc $c1 get PrivateKey | tr -d '\r'`" = "12345"
-$tinc $c1 del PrivateKey
-test -z "`$tinc $c1 get PrivateKey`"
+tinc foo del PrivateKey
+test -z "$(tinc foo get PrivateKey)"