summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAndrew Guschin <guschin@altlinux.org>2026-10-30 13:20:18 +0400
committerAndrew Guschin <guschin@altlinux.org>2026-10-30 13:20:18 +0400
commit0b898d982549eab1ba12bf743d001aee6c2e27f9 (patch)
treeca2689cd63ce7f89584e95c5ce88bbb22b817f47 /tests
parent3c8b130be89fbb0e58e2ee23f07ad56a3b9de2e7 (diff)
Validate real TUN forwarding and fix firewall routingmain
- Move the default-drop guard to POSTROUTING so it sees final TUN egress - Treat a missing TUN interface as absent and add acceptance coverage - Document successful real-subscription validation on ALT Xray 26.10.29
Diffstat (limited to 'tests')
-rw-r--r--tests/routes_acceptance.rs23
-rw-r--r--tests/vm_tun_acceptance.py46
2 files changed, 69 insertions, 0 deletions
diff --git a/tests/routes_acceptance.rs b/tests/routes_acceptance.rs
new file mode 100644
index 0000000..608e309
--- /dev/null
+++ b/tests/routes_acceptance.rs
@@ -0,0 +1,23 @@
+#![cfg(feature = "linux-runtime")]
+
+use futures_util::TryStreamExt;
+use rtnetlink::packet_route::link::LinkAttribute;
+
+// Run on the disposable ALT VM, including with no active roentgen0. A dump
+// omits missing interfaces, whereas the production named lookup returns ENODEV.
+#[tokio::test]
+async fn tun_lookup_agrees_with_kernel_link_dump() -> anyhow::Result<()> {
+ let (connection, handle, _) = rtnetlink::new_connection()?;
+ tokio::spawn(connection);
+ let mut links = handle.link().get().execute();
+ let mut expected = None;
+ while let Some(link) = links.try_next().await? {
+ if link.attributes.iter().any(|attribute| {
+ matches!(attribute, LinkAttribute::IfName(name) if name == "roentgen0")
+ }) {
+ expected = Some(link.header.index);
+ }
+ }
+ assert_eq!(roentgen::linux::routes::tun_index().await?, expected);
+ Ok(())
+}
diff --git a/tests/vm_tun_acceptance.py b/tests/vm_tun_acceptance.py
new file mode 100644
index 0000000..619459e
--- /dev/null
+++ b/tests/vm_tun_acceptance.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+"""Verify a selected real configuration on the authorized disposable ALT VM."""
+import argparse
+import ipaddress
+import json
+import os
+import socket
+import subprocess
+
+
+def command(*args):
+ return subprocess.run(args, check=True, capture_output=True, text=True, timeout=35).stdout.strip()
+
+
+def public_ipv4():
+ value = command('curl', '-4', '--noproxy', '*', '--fail', '--silent', '--show-error',
+ '--connect-timeout', '5', '--max-time', '15', 'https://ifconfig.me')
+ return str(ipaddress.IPv4Address(value))
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--expected-ip', required=True, type=ipaddress.IPv4Address)
+ args = parser.parse_args()
+ assert os.geteuid() == 0 and socket.gethostname() == 'altctlrs'
+ binary = '/usr/local/bin/roentgen'
+ status = json.loads(command(binary, 'status', '--json'))
+ assert not status['requested_enabled'], 'Start with proxying disabled'
+ assert status['selected_configuration'], 'Select the real configuration before running'
+ direct = public_ipv4()
+ try:
+ command(binary, 'enable')
+ status = json.loads(command(binary, 'status', '--json'))
+ assert status['requested_enabled'] and status['routing'] == 'tun', status
+ proxied = public_ipv4()
+ assert proxied == str(args.expected_ip), (proxied, str(args.expected_ip))
+ assert proxied != direct, 'Proxy IPv4 must differ from direct IPv4'
+ finally:
+ command(binary, 'disable')
+ restored = public_ipv4()
+ assert restored == direct, (restored, direct)
+ print(json.dumps({'direct_ipv4': direct, 'tun_ipv4': proxied, 'restored_ipv4': restored}))
+
+
+if __name__ == '__main__':
+ main()