summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsskaje <sskaje@gmail.com>2024-12-31 10:44:01 +0800
committerChristian Breunig <christian@breunig.cc>2025-01-19 00:17:12 +0100
commit2212a438b234f34f32e08efef2f841ba55a3b6a0 (patch)
tree47528dafb6733efb134a5ceee51e52118be7896f /src
parent4d3e976271e30d70c8b2660d869a220de98d8c59 (diff)
downloadvyos-1x-2212a438b234f34f32e08efef2f841ba55a3b6a0.tar.gz
vyos-1x-2212a438b234f34f32e08efef2f841ba55a3b6a0.zip
wireguard: T4930: allow peers via FQDN
* set interfaces wireguard wgXX peer YY hostname <fqdn>
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/interfaces_wireguard.py40
-rwxr-xr-xsrc/conf_mode/nat.py8
-rwxr-xr-xsrc/op_mode/reset_wireguard.py55
-rwxr-xr-xsrc/services/vyos-domain-resolver48
4 files changed, 142 insertions, 9 deletions
diff --git a/src/conf_mode/interfaces_wireguard.py b/src/conf_mode/interfaces_wireguard.py
index b6fd6b0b2..1dbaa9d4e 100755
--- a/src/conf_mode/interfaces_wireguard.py
+++ b/src/conf_mode/interfaces_wireguard.py
@@ -29,11 +29,12 @@ from vyos.ifconfig import WireGuardIf
from vyos.utils.kernel import check_kmod
from vyos.utils.network import check_port_availability
from vyos.utils.network import is_wireguard_key_pair
+from vyos.utils.process import call
from vyos import ConfigError
from vyos import airbag
+from pathlib import Path
airbag.enable()
-
def get_config(config=None):
"""
Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
@@ -54,6 +55,12 @@ def get_config(config=None):
if is_node_changed(conf, base + [ifname, 'peer']):
wireguard.update({'rebuild_required': {}})
+ wireguard['peers_need_resolve'] = []
+ if 'peer' in wireguard:
+ for peer, peer_config in wireguard['peer'].items():
+ if 'disable' not in peer_config and 'host_name' in peer_config:
+ wireguard['peers_need_resolve'].append(peer)
+
return wireguard
def verify(wireguard):
@@ -82,16 +89,15 @@ def verify(wireguard):
for tmp in wireguard['peer']:
peer = wireguard['peer'][tmp]
+ if 'host_name' in peer and 'address' in peer:
+ raise ConfigError('"host-name" and "address" are mutually exclusive')
+
if 'allowed_ips' not in peer:
raise ConfigError(f'Wireguard allowed-ips required for peer "{tmp}"!')
if 'public_key' not in peer:
raise ConfigError(f'Wireguard public-key required for peer "{tmp}"!')
- if ('address' in peer and 'port' not in peer) or ('port' in peer and 'address' not in peer):
- raise ConfigError('Both Wireguard port and address must be defined '
- f'for peer "{tmp}" if either one of them is set!')
-
if peer['public_key'] in public_keys:
raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"')
@@ -99,6 +105,13 @@ def verify(wireguard):
if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']):
raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"')
+ if 'port' not in peer:
+ if 'host_name' in peer or 'address' in peer:
+ raise ConfigError(f'Missing "host-name" or "address" on peer "{tmp}"')
+ else:
+ if 'host_name' not in peer and 'address' not in peer:
+ raise ConfigError(f'Missing "host-name" and "address" on peer "{tmp}"')
+
public_keys.append(peer['public_key'])
def generate(wireguard):
@@ -122,6 +135,23 @@ def apply(wireguard):
wg = WireGuardIf(**wireguard)
wg.update(wireguard)
+ domain_resolver_usage = '/run/use-vyos-domain-resolver-interfaces-wireguard-' + wireguard['ifname']
+
+ ## DOMAIN RESOLVER
+ domain_action = 'restart'
+ if 'peers_need_resolve' in wireguard and len(wireguard['peers_need_resolve']) > 0 and 'disable' not in wireguard:
+ from vyos.utils.file import write_file
+
+ text = f'# Automatically generated by interfaces_wireguard.py\nThis file indicates that vyos-domain-resolver service is used by the interfaces_wireguard.\n'
+ text += "intefaces:\n" + "".join([f" - {peer}\n" for peer in wireguard['peers_need_resolve']])
+ Path(domain_resolver_usage).write_text(text)
+ write_file(domain_resolver_usage, text)
+ else:
+ Path(domain_resolver_usage).unlink(missing_ok=True)
+ if not Path('/run').glob('use-vyos-domain-resolver*'):
+ domain_action = 'stop'
+ call(f'systemctl {domain_action} vyos-domain-resolver.service')
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 98b2f3f29..504b3e82a 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -17,6 +17,7 @@
import os
from sys import exit
+from pathlib import Path
from vyos.base import Warning
from vyos.config import Config
@@ -43,7 +44,6 @@ k_mod = ['nft_nat', 'nft_chain_nat']
nftables_nat_config = '/run/nftables_nat.conf'
nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'
domain_resolver_usage = '/run/use-vyos-domain-resolver-nat'
-domain_resolver_usage_firewall = '/run/use-vyos-domain-resolver-firewall'
valid_groups = [
'address_group',
@@ -265,9 +265,9 @@ def apply(nat):
text = f'# Automatically generated by nat.py\nThis file indicates that vyos-domain-resolver service is used by nat.\n'
write_file(domain_resolver_usage, text)
elif os.path.exists(domain_resolver_usage):
- os.unlink(domain_resolver_usage)
- if not os.path.exists(domain_resolver_usage_firewall):
- # Firewall not using domain resolver
+ Path(domain_resolver_usage).unlink(missing_ok=True)
+
+ if not Path('/run').glob('use-vyos-domain-resolver*'):
domain_action = 'stop'
call(f'systemctl {domain_action} vyos-domain-resolver.service')
diff --git a/src/op_mode/reset_wireguard.py b/src/op_mode/reset_wireguard.py
new file mode 100755
index 000000000..1fcfb31b5
--- /dev/null
+++ b/src/op_mode/reset_wireguard.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2025 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import typing
+
+import vyos.opmode
+
+from vyos.ifconfig import WireGuardIf
+from vyos.configquery import ConfigTreeQuery
+
+
+def _verify(func):
+ """Decorator checks if WireGuard interface config exists"""
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ interface = kwargs.get('interface')
+ if not config.exists(['interfaces', 'wireguard', interface]):
+ unconf_message = f'WireGuard interface {interface} is not configured'
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+
+ return _wrapper
+
+
+@_verify
+def reset_peer(interface: str, peer: typing.Optional[str] = None):
+ intf = WireGuardIf(interface, create=False, debug=False)
+ return intf.operational.reset_peer(peer)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/services/vyos-domain-resolver b/src/services/vyos-domain-resolver
index bc74a05d1..6eab7e7e5 100755
--- a/src/services/vyos-domain-resolver
+++ b/src/services/vyos-domain-resolver
@@ -27,12 +27,14 @@ from vyos.utils.dict import dict_search_args
from vyos.utils.process import cmd
from vyos.utils.process import run
from vyos.xml_ref import get_defaults
+from vyos.template import is_ip
base = ['firewall']
timeout = 300
cache = False
base_firewall = ['firewall']
base_nat = ['nat']
+base_interfaces = ['interfaces']
domain_state = {}
@@ -171,6 +173,50 @@ def update_fqdn(config, node):
logger.info(f'Updated {count} sets in {node} - result: {code}')
+def update_interfaces(config, node):
+ if node == 'interfaces':
+ wireguard_interfaces = dict_search_args(config, 'wireguard')
+
+ # WireGuard redo handshake usually every 180 seconds, but not documented officially.
+ # If peer with domain name in its endpoint didn't get handshake for over 300 seconds,
+ # we do re-resolv and reset its endpoint from config tree.
+ handshake_threshold = 300
+
+ from vyos.ifconfig import WireGuardIf
+
+ check_wireguard_peer_public_keys = {}
+ # for each wireguard interfaces
+ for interface, wireguard in wireguard_interfaces.items():
+ check_wireguard_peer_public_keys[interface] = []
+ for peer, peer_config in wireguard['peer'].items():
+ # check peer if peer host-name or address is set
+ if 'host-name' in peer_config or 'address' in peer_config:
+ # check latest handshake
+ check_wireguard_peer_public_keys[interface].append(
+ peer_config['public_key']
+ )
+
+ now_time = time.time()
+ for (
+ interface,
+ check_peer_public_keys
+ ) in check_wireguard_peer_public_keys.items():
+ if len(check_peer_public_keys) == 0:
+ continue
+
+ intf = WireGuardIf(interface, create=False, debug=False)
+ handshakes = intf.operational.get_latest_handshakes()
+
+ for public_key, handshake_time in handshakes.items():
+ if public_key in check_peer_public_keys and (
+ handshake_time == 0
+ or now_time - handshake_time > handshake_threshold
+ ):
+ intf.operational.reset_peer(public_key=public_key)
+
+ print(f'Wireguard: reset {interface} peer {public_key}')
+
+
if __name__ == '__main__':
logger.info(f'VyOS domain resolver')
@@ -184,10 +230,12 @@ if __name__ == '__main__':
conf = ConfigTreeQuery()
firewall = get_config(conf, base_firewall)
nat = get_config(conf, base_nat)
+ interfaces = get_config(conf, base_interfaces)
logger.info(f'interval: {timeout}s - cache: {cache}')
while True:
update_fqdn(firewall, 'firewall')
update_fqdn(nat, 'nat')
+ update_interfaces(interfaces, 'interfaces')
time.sleep(timeout)