Compare commits

...
2 Commits
Author SHA1 Message Date
slawek 5402256ef6 fix: removed unnecressary peer ID handling that is no longer required for >=5.0 StrongSwan.
Release / release (push) Successful in 17s
2026-10-23 17:31:22 +02:00
slawek 64da0cf97f fix: update interface detection code to support additional interfaces Docker adds.
Release / release (push) Successful in 17s
2026-10-23 13:43:46 +02:00
5 changed files with 57 additions and 10 deletions

No files matched your search

+13 -3
View File
@@ -97,13 +97,13 @@ must have the type shown below; an unknown section or setting is an error.
| `general` | `mode` | string | `manual` | How much you supply: `manual`, `interfaces` or `auto` |
| `interfaces` | `external` | string | | Name of the NIC facing the untrusted network |
| `interfaces` | `internal` | string | | Name of the NIC facing the protected network |
| `wan` | `local_fqdn` | string | | This router's FQDN |
| `wan` | `local_fqdn` | string | | This router's name or IP address, used as its IKE identity in `fqdn` mode |
| `wan` | `local_id_mode` | string | `fqdn` | IKE identity source: `fqdn`, `public_ip` or `internal_ip` |
| `local` | `cidrs` | list | | Local subnets advertised into the tunnel |
| `local` | `int_addr` | string | | This host's address on the internal network |
| `local` | `int_gateway_ip` | string | | Next hop on the internal side |
| `remote` | `addrs` | list | | Remote gateway addresses or FQDNs |
| `remote` | `id` | string | | Remote IKE identity, without a leading `@` |
| `remote` | `id` | string | | Remote IKE identity, an FQDN or an IPv4 or IPv6 address |
| `remote` | `cidrs` | list | | Remote subnets reachable through the tunnel |
| `remote` | `psk` | string | | Pre-shared key |
| `remote` | `psk_file` | string | | Path to a file holding the key; wins over `psk` |
@@ -132,6 +132,11 @@ A minimal site-to-site configuration in `auto` mode:
}
```
The router's IKE identity (`wan.local_fqdn` in `fqdn` mode) and the remote one (`remote.id`) are
an FQDN or an IPv4 or IPv6 address. They are written to the strongSwan configuration as given;
strongSwan reads an address as an IP identity and a name as an FQDN, so a leading `@` is not needed
and is refused. Road-warrior access needs a name in `wan.local_fqdn`.
WireGuard peers are added by hand in `/etc/wireguard/wg0.conf`. The package owns the
`[Interface]` section of that file and rewrites it when the settings above change, but it
carries every `[Peer]` section across untouched.
@@ -148,12 +153,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
+21
View File
@@ -1,3 +1,24 @@
vpn-router (0.3.2-1) unstable; urgency=medium
* Accept an IPv4 or IPv6 address as remote.id and as wan.local_fqdn. The
identities are written to the strongSwan configuration as given, without
an @ added, and strongSwan reads an address as an IP identity and a name
as an FQDN. A value that starts with @ is refused with an error, since
strongSwan 5.0 and later need no @ to avoid resolving a name.
Road-warrior access still needs a name in wan.local_fqdn.
* Depend on strongswan-swanctl and charon-systemd 5.0 or later.
-- Slawomir Koszewski <slawek@koszewscy.waw.pl> Fri, 23 Oct 2026 18:00:00 +0200
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 <slawek@koszewscy.waw.pl> 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,
+2 -2
View File
@@ -9,8 +9,8 @@ Rules-Requires-Root: no
Package: vpn-router
Architecture: all
Depends: ${misc:Depends},
strongswan-swanctl,
charon-systemd,
strongswan-swanctl (>= 5.0),
charon-systemd (>= 5.0),
libstrongswan-extra-plugins,
libcharon-extra-plugins,
wireguard-tools,
@@ -190,6 +190,12 @@ def validate(cfg):
raise ConfigError(
f'{section}.{option} is required in mode {mode or "manual"}')
for section, option in (('wan', 'local_fqdn'), ('remote', 'id')):
if str(_get(cfg, section, option)).startswith('@'):
raise ConfigError(
f'{section}.{option}: remove the leading @; strongSwan reads an '
'address as an IP identity and a name as an FQDN')
id_mode = _get(cfg, 'wan', 'local_id_mode')
if id_mode and id_mode not in ID_MODES:
raise ConfigError(
@@ -309,12 +315,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
@@ -414,7 +430,7 @@ def build_context(cfg, local_id_override=None):
if local_id_override:
local_id = local_id_override
elif id_mode == 'fqdn':
local_id = f'@{local_fqdn}' if local_fqdn else ''
local_id = local_fqdn
elif id_mode == 'internal_ip':
local_id = int_addr
else:
@@ -14,7 +14,7 @@ connections {
}
remote {
id = @{{ remote_id }}
id = {{ remote_id }}
auth = psk
}
@@ -40,7 +40,7 @@ connections {
secrets {
ike-psk {
id-1 = {{ local_id }}
id-2 = @{{ remote_id }}
id-2 = {{ remote_id }}
secret = "{{ psk }}"
}
}