Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5402256ef6 | ||
|
|
64da0cf97f |
No files matched your search
@@ -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` |
|
| `general` | `mode` | string | `manual` | How much you supply: `manual`, `interfaces` or `auto` |
|
||||||
| `interfaces` | `external` | string | | Name of the NIC facing the untrusted network |
|
| `interfaces` | `external` | string | | Name of the NIC facing the untrusted network |
|
||||||
| `interfaces` | `internal` | string | | Name of the NIC facing the protected 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` |
|
| `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` | `cidrs` | list | | Local subnets advertised into the tunnel |
|
||||||
| `local` | `int_addr` | string | | This host's address on the internal network |
|
| `local` | `int_addr` | string | | This host's address on the internal network |
|
||||||
| `local` | `int_gateway_ip` | string | | Next hop on the internal side |
|
| `local` | `int_gateway_ip` | string | | Next hop on the internal side |
|
||||||
| `remote` | `addrs` | list | | Remote gateway addresses or FQDNs |
|
| `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` | `cidrs` | list | | Remote subnets reachable through the tunnel |
|
||||||
| `remote` | `psk` | string | | Pre-shared key |
|
| `remote` | `psk` | string | | Pre-shared key |
|
||||||
| `remote` | `psk_file` | string | | Path to a file holding the key; wins over `psk` |
|
| `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
|
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
|
`[Interface]` section of that file and rewrites it when the settings above change, but it
|
||||||
carries every `[Peer]` section across untouched.
|
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 |
|
| `manual` | both interface names, `int_addr`, `int_gateway_ip` | nothing |
|
||||||
| `interfaces` | both interface names | `int_addr` and `int_gateway_ip`, from those interfaces |
|
| `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
|
`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
|
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.
|
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
|
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
|
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
|
interface does not exist the service stops and says so. In `interfaces` and `auto` mode
|
||||||
|
|||||||
@@ -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
|
vpn-router (0.3.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
* Secure the ports Docker publishes. Rules in a chain of their own,
|
* Secure the ports Docker publishes. Rules in a chain of their own,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ Rules-Requires-Root: no
|
|||||||
Package: vpn-router
|
Package: vpn-router
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: ${misc:Depends},
|
Depends: ${misc:Depends},
|
||||||
strongswan-swanctl,
|
strongswan-swanctl (>= 5.0),
|
||||||
charon-systemd,
|
charon-systemd (>= 5.0),
|
||||||
libstrongswan-extra-plugins,
|
libstrongswan-extra-plugins,
|
||||||
libcharon-extra-plugins,
|
libcharon-extra-plugins,
|
||||||
wireguard-tools,
|
wireguard-tools,
|
||||||
|
|||||||
@@ -190,6 +190,12 @@ def validate(cfg):
|
|||||||
raise ConfigError(
|
raise ConfigError(
|
||||||
f'{section}.{option} is required in mode {mode or "manual"}')
|
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')
|
id_mode = _get(cfg, 'wan', 'local_id_mode')
|
||||||
if id_mode and id_mode not in ID_MODES:
|
if id_mode and id_mode not in ID_MODES:
|
||||||
raise ConfigError(
|
raise ConfigError(
|
||||||
@@ -309,12 +315,22 @@ def detect_interfaces():
|
|||||||
return external, candidates[0]
|
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():
|
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 = []
|
names = []
|
||||||
for line in _ip_route('-o', '-4', 'addr', 'show').splitlines():
|
for line in _ip_route('-o', '-4', 'addr', 'show').splitlines():
|
||||||
fields = line.split()
|
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])
|
names.append(fields[1])
|
||||||
return names
|
return names
|
||||||
|
|
||||||
@@ -414,7 +430,7 @@ def build_context(cfg, local_id_override=None):
|
|||||||
if local_id_override:
|
if local_id_override:
|
||||||
local_id = local_id_override
|
local_id = local_id_override
|
||||||
elif id_mode == 'fqdn':
|
elif id_mode == 'fqdn':
|
||||||
local_id = f'@{local_fqdn}' if local_fqdn else ''
|
local_id = local_fqdn
|
||||||
elif id_mode == 'internal_ip':
|
elif id_mode == 'internal_ip':
|
||||||
local_id = int_addr
|
local_id = int_addr
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ connections {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remote {
|
remote {
|
||||||
id = @{{ remote_id }}
|
id = {{ remote_id }}
|
||||||
auth = psk
|
auth = psk
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ connections {
|
|||||||
secrets {
|
secrets {
|
||||||
ike-psk {
|
ike-psk {
|
||||||
id-1 = {{ local_id }}
|
id-1 = {{ local_id }}
|
||||||
id-2 = @{{ remote_id }}
|
id-2 = {{ remote_id }}
|
||||||
secret = "{{ psk }}"
|
secret = "{{ psk }}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user