[3/3] tests: Add tests for OpenVPN::n2n_status()

Message ID 20261027222110.2642078-4-antonio@mandelbit.com
State New
Headers
Series OpenVPN: Fix N2N connections shown as DISCONNECTED while up |

Commit Message

Antonio Quartulli 27 Oct 2026, 10:21 p.m. UTC
Run n2n_status() against a fake management interface, including a
new session that never completes on an established tunnel (#13897).

Signed-off-by: Antonio Quartulli <antonio@mandelbit.com>
---
 .../ovpn-functions.pl/data/auth_pending       |  6 ++
 .../cfgroot/ovpn-functions.pl/data/connected  |  7 ++
 .../data/connected_stray_session              |  8 ++
 .../cfgroot/ovpn-functions.pl/data/empty      |  1 +
 .../cfgroot/ovpn-functions.pl/data/exiting    |  7 ++
 .../ovpn-functions.pl/data/never_connected    |  6 ++
 .../ovpn-functions.pl/data/notifications      | 11 +++
 .../ovpn-functions.pl/data/reconnected        | 11 +++
 .../ovpn-functions.pl/data/reconnecting       |  9 +++
 .../cfgroot/ovpn-functions.pl/data/truncated  |  3 +
 .../cfgroot/ovpn-functions.pl/n2n_status.pl   | 73 +++++++++++++++++++
 .../config/cfgroot/ovpn-functions.pl/test.sh  | 70 ++++++++++++++++++
 12 files changed, 212 insertions(+)
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/auth_pending
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/connected
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/connected_stray_session
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/empty
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/exiting
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/never_connected
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/notifications
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/reconnected
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/reconnecting
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/data/truncated
 create mode 100644 tests/config/cfgroot/ovpn-functions.pl/n2n_status.pl
 create mode 100755 tests/config/cfgroot/ovpn-functions.pl/test.sh
  

Patch

diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/auth_pending b/tests/config/cfgroot/ovpn-functions.pl/data/auth_pending
new file mode 100644
index 000000000..aca1f1b57
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/auth_pending
@@ -0,0 +1,6 @@ 
+1793050400,CONNECTING,,,,,,
+1793050417,CONNECTED,SUCCESS,10.8.0.1,127.0.0.1,21195,,
+1793050448,AUTH_PENDING,,,,,,
+1793054017,RECONNECTING,ping-restart,,,,,
+1793054022,WAIT,,,,,,
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/connected b/tests/config/cfgroot/ovpn-functions.pl/data/connected
new file mode 100644
index 000000000..afd358d07
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/connected
@@ -0,0 +1,7 @@ 
+1793050400,CONNECTING,,,,,,
+1793050401,WAIT,,,,,,
+1793050402,AUTH,,,,,,
+1793050403,GET_CONFIG,,,,,,
+1793050404,ASSIGN_IP,,10.8.0.1,,,,
+1793050417,CONNECTED,SUCCESS,10.8.0.1,127.0.0.1,21195,,
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/connected_stray_session b/tests/config/cfgroot/ovpn-functions.pl/data/connected_stray_session
new file mode 100644
index 000000000..78a3a5bd5
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/connected_stray_session
@@ -0,0 +1,8 @@ 
+1793050400,CONNECTING,,,,,,
+1793050401,WAIT,,,,,,
+1793050402,AUTH,,,,,,
+1793050404,ASSIGN_IP,,10.8.0.1,,,,
+1793050417,CONNECTED,SUCCESS,10.8.0.1,127.0.0.1,21195,,
+1793054017,AUTH,,,,,,
+1793054018,WAIT,,,,,,
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/empty b/tests/config/cfgroot/ovpn-functions.pl/data/empty
new file mode 100644
index 000000000..dce2c1d51
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/empty
@@ -0,0 +1 @@ 
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/exiting b/tests/config/cfgroot/ovpn-functions.pl/data/exiting
new file mode 100644
index 000000000..cc3fd5300
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/exiting
@@ -0,0 +1,7 @@ 
+1793050400,CONNECTING,,,,,,
+1793050401,WAIT,,,,,,
+1793050402,AUTH,,,,,,
+1793050404,ASSIGN_IP,,10.8.0.1,,,,
+1793050417,CONNECTED,SUCCESS,10.8.0.1,127.0.0.1,21195,,
+1793054017,EXITING,SIGTERM,,,,,
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/never_connected b/tests/config/cfgroot/ovpn-functions.pl/data/never_connected
new file mode 100644
index 000000000..d40357abd
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/never_connected
@@ -0,0 +1,6 @@ 
+1793050400,CONNECTING,,,,,,
+1793050401,WAIT,,,,,,
+1793050402,AUTH,,,,,,
+1793050462,WAIT,,,,,,
+1793050463,AUTH,,,,,,
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/notifications b/tests/config/cfgroot/ovpn-functions.pl/data/notifications
new file mode 100644
index 000000000..a2db8e956
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/notifications
@@ -0,0 +1,11 @@ 
+>HOLD:Waiting for hold release:0
+1793050400,CONNECTING,,,,,,
+>STATE:1793050401,WAIT,,,,,,
+1793050401,WAIT,,,,,,
+1793050402,AUTH,,,,,,
+>LOG:1793050403,I,Peer Connection Initiated
+1793050417,CONNECTED,SUCCESS,10.8.0.1,127.0.0.1,21195,,
+>STATE:1793054017,AUTH,,,,,,
+1793054017,AUTH,,,,,,
+1793054018,WAIT,,,,,,
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/reconnected b/tests/config/cfgroot/ovpn-functions.pl/data/reconnected
new file mode 100644
index 000000000..3b1530458
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/reconnected
@@ -0,0 +1,11 @@ 
+1793050400,CONNECTING,,,,,,
+1793050401,WAIT,,,,,,
+1793050402,AUTH,,,,,,
+1793050404,ASSIGN_IP,,10.8.0.1,,,,
+1793050417,CONNECTED,SUCCESS,10.8.0.1,127.0.0.1,21195,,
+1793054017,RECONNECTING,ping-restart,,,,,
+1793054022,WAIT,,,,,,
+1793054023,AUTH,,,,,,
+1793054024,GET_CONFIG,,,,,,
+1793054025,CONNECTED,SUCCESS,10.8.0.1,127.0.0.1,21195,,
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/reconnecting b/tests/config/cfgroot/ovpn-functions.pl/data/reconnecting
new file mode 100644
index 000000000..97c0d32d6
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/reconnecting
@@ -0,0 +1,9 @@ 
+1793050400,CONNECTING,,,,,,
+1793050401,WAIT,,,,,,
+1793050402,AUTH,,,,,,
+1793050404,ASSIGN_IP,,10.8.0.1,,,,
+1793050417,CONNECTED,SUCCESS,10.8.0.1,127.0.0.1,21195,,
+1793054017,RECONNECTING,ping-restart,,,,,
+1793054022,WAIT,,,,,,
+1793054023,AUTH,,,,,,
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/data/truncated b/tests/config/cfgroot/ovpn-functions.pl/data/truncated
new file mode 100644
index 000000000..a4137f117
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/data/truncated
@@ -0,0 +1,3 @@ 
+1793050417,AUTH,,,,,,
+1793050418,WAIT,,,,,,
+END
diff --git a/tests/config/cfgroot/ovpn-functions.pl/n2n_status.pl b/tests/config/cfgroot/ovpn-functions.pl/n2n_status.pl
new file mode 100644
index 000000000..5876ef2f0
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/n2n_status.pl
@@ -0,0 +1,73 @@ 
+#!/usr/bin/perl
+#
+# Runs OpenVPN::n2n_status() against a fake OpenVPN management interface
+# and prints the result.
+#
+# Usage: n2n_status.pl <ovpn-functions.pl> <data file|--no-server|--close>
+#
+# The fake management interface sends the banner, waits for "state all"
+# and replies with the content of the data file.
+
+use strict;
+use warnings;
+use IO::Socket::INET;
+
+my ($functions, $mode) = @ARGV;
+
+require $functions;
+
+my $server = IO::Socket::INET->new(
+	LocalAddr => "127.0.0.1",
+	LocalPort => 0,
+	Proto     => "tcp",
+	Listen    => 1,
+	ReuseAddr => 1,
+) or die "Could not create socket: $!\n";
+
+my $port = $server->sockport();
+
+# Nothing listening on the port
+if ($mode eq "--no-server") {
+	$server->close();
+	print OpenVPN::n2n_status($port) . "\n";
+	exit 0;
+}
+
+my $pid = fork() // die "Could not fork: $!\n";
+
+if ($pid == 0) {
+	my $client = $server->accept() or exit 1;
+
+	# Close the connection right away
+	if ($mode eq "--close") {
+		$client->close();
+		exit 0;
+	}
+
+	open(my $fh, "<", $mode) or die "Could not open $mode: $!\n";
+	my @reply = <$fh>;
+	close($fh);
+
+	$client->autoflush(1);
+	print $client ">INFO:OpenVPN Management Interface Version 5 -- type 'help' for more info\r\n";
+
+	while (my $line = <$client>) {
+		last if ($line =~ m/^state all\r?\n/);
+	}
+
+	foreach my $line (@reply) {
+		chomp($line);
+		print $client "$line\r\n";
+	}
+
+	# Wait for the client to disconnect
+	while (<$client>) {}
+
+	exit 0;
+}
+
+$server->close();
+
+print OpenVPN::n2n_status($port) . "\n";
+
+waitpid($pid, 0);
diff --git a/tests/config/cfgroot/ovpn-functions.pl/test.sh b/tests/config/cfgroot/ovpn-functions.pl/test.sh
new file mode 100755
index 000000000..2110cf4f7
--- /dev/null
+++ b/tests/config/cfgroot/ovpn-functions.pl/test.sh
@@ -0,0 +1,70 @@ 
+#!/bin/bash
+
+SCRIPT_PATH="$(dirname "$(readlink -f "$0")")"
+
+ROOT="$(readlink -f "${SCRIPT_PATH}/../../../..")"
+
+. ${ROOT}/tests/lib.sh
+
+n2n_status() {
+	perl "${SCRIPT_PATH}/n2n_status.pl" "${ROOT}/config/cfgroot/ovpn-functions.pl" "$@"
+}
+
+# Run n2n_status() against a fake management interface replying with the given
+# state history and check the result
+test_n2n_status() {
+	local data="${1}"
+	local expected="${2}"
+	local status
+
+	status="$(n2n_status "${data}")"
+
+	if [[ "${status}" == "${expected}" ]]; then
+		log_test_succeded "n2n_status() returns '${status}' for '${data##*/}'"
+		return 0
+	else
+		log_test_failed "n2n_status() returns '${status}' for '${data##*/}' and not '${expected}'"
+		return 1
+	fi
+}
+
+if ! perl -MNet::Telnet -e 1 &>/dev/null; then
+	log_test_failed "The perl module Net::Telnet is not installed"
+	exit 1
+fi
+
+# The tunnel came up
+test_n2n_status "${SCRIPT_PATH}/data/connected" "CONNECTED"
+
+# A new session that never completes does not affect the tunnel (#13897)
+test_n2n_status "${SCRIPT_PATH}/data/connected_stray_session" "CONNECTED"
+
+# Notifications are ignored
+test_n2n_status "${SCRIPT_PATH}/data/notifications" "CONNECTED"
+
+# The tunnel went down and came back up
+test_n2n_status "${SCRIPT_PATH}/data/reconnected" "CONNECTED"
+
+# The process has been running for a long time
+test_n2n_status "${SCRIPT_PATH}/data/truncated" "CONNECTED"
+
+# The tunnel went down
+test_n2n_status "${SCRIPT_PATH}/data/reconnecting" "DISCONNECTED"
+
+# The tunnel never came up
+test_n2n_status "${SCRIPT_PATH}/data/never_connected" "DISCONNECTED"
+
+# A state containing "END" does not end the history early
+test_n2n_status "${SCRIPT_PATH}/data/auth_pending" "DISCONNECTED"
+
+# The process is exiting
+test_n2n_status "${SCRIPT_PATH}/data/exiting" "DISCONNECTED"
+
+# No states at all
+test_n2n_status "${SCRIPT_PATH}/data/empty" "DISCONNECTED"
+
+# The management interface closes the connection
+test_n2n_status --close "DISCONNECTED"
+
+# The management interface is not running
+test_n2n_status --no-server "DISCONNECTED"