From 64da0cf97fc7c32487c2867a75d2f610ba1ad89b Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Fri, 23 Oct 2026 13:43:46 +0200 Subject: [PATCH] fix: update interface detection code to support additional interfaces Docker adds. --- README.md | 7 ++++++- debian-package/debian/changelog | 9 +++++++++ debian-package/src/usr/lib/vpn-router/vpnrouter.py | 14 ++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bafa899..c5dd302 100644 --- a/README.md +++ b/README.md @@ -148,12 +148,17 @@ much the package reads from the system: |---|---|---| | `manual` | both interface names, `int_addr`, `int_gateway_ip` | nothing | | `interfaces` | both interface names | `int_addr` and `int_gateway_ip`, from those interfaces | -| `auto` | nothing | the interface names too, from the routing table | +| `auto` | nothing | the interface names too: the external one from the routing table, the internal one as the only other NIC with an address | `manual` is the only mode in which no detection code runs at all. `auto` is the convenient one and can pick the wrong interface, which is a trade you make deliberately by choosing it rather than something the package does behind your back. +Detection ignores virtual interfaces, those the kernel lists under `/sys/devices/virtual`: +loopback, bridges such as `docker0`, veth pairs and tunnels. Only the NICs of the machine +count, so Docker does not change the result. If the host has more than two NICs with an +address, `auto` stops and says so; use mode `interfaces` and name them. + The addresses strongSwan binds on always come from the external NIC, in every mode, so they are never configured twice and cannot drift out of step with the host. If a named interface does not exist the service stops and says so. In `interfaces` and `auto` mode diff --git a/debian-package/debian/changelog b/debian-package/debian/changelog index 2c10fd7..caa963f 100644 --- a/debian-package/debian/changelog +++ b/debian-package/debian/changelog @@ -1,3 +1,12 @@ +vpn-router (0.3.1-1) unstable; urgency=medium + + * Ignore virtual interfaces when mode auto looks for the internal + interface. The Docker bridge docker0, which has an address, was counted + as a second internal interface, so vpn-router-setup and + vpn-router-docker failed on a host with Docker installed. + + -- Sławomir Koszewski Fri, 23 Oct 2026 13:44:00 +0200 + vpn-router (0.3.0-1) unstable; urgency=medium * Secure the ports Docker publishes. Rules in a chain of their own, diff --git a/debian-package/src/usr/lib/vpn-router/vpnrouter.py b/debian-package/src/usr/lib/vpn-router/vpnrouter.py index 6d8ecb8..6dc9cf4 100644 --- a/debian-package/src/usr/lib/vpn-router/vpnrouter.py +++ b/debian-package/src/usr/lib/vpn-router/vpnrouter.py @@ -309,12 +309,22 @@ def detect_interfaces(): return external, candidates[0] +def _is_virtual(name): + """True for an interface the kernel creates without a device behind it. + + Loopback, bridges, veth pairs and tunnels live under /sys/devices/virtual, + which is where the Docker bridge is; a NIC of the virtual machine does not. + """ + link = pathlib.Path('/sys/class/net') / name + return '/devices/virtual/' in str(link.resolve()) + + def _addressed_interfaces(): - """Interface names holding at least one IPv4 address, excluding loopback.""" + """Names of the NICs holding at least one IPv4 address; virtual interfaces are left out.""" names = [] for line in _ip_route('-o', '-4', 'addr', 'show').splitlines(): fields = line.split() - if len(fields) >= 4 and fields[1] != 'lo' and fields[1] not in names: + if len(fields) >= 4 and fields[1] not in names and not _is_virtual(fields[1]): names.append(fields[1]) return names