Skip to content

Commit

Permalink
T4930: Move dns resolution to vyos-domain-resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
sskaje committed Nov 29, 2024
1 parent a6bd1c3 commit 9e67192
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 13 deletions.
46 changes: 39 additions & 7 deletions python/vyos/ifconfig/wireguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from vyos.ifconfig import Interface
from vyos.ifconfig import Operational
from vyos.template import is_ipv6
from vyos.template import is_ipv6, is_ipv4


class WireGuardOperational(Operational):
Expand Down Expand Up @@ -156,16 +156,39 @@ def show_interface(self):
answer += '\n'
return answer

def reset_peer(self, interface, peer_name=None):
def get_latest_handshakes(self):

"""Get latest handshake time for each peer"""
output = {}

# Dump wireguard last handshake
_f = self._cmd(f'wg show {self.config["ifname"]} latest-handshakes')
# Output:
# xxxw= 1732812147
# xxx= 0
for line in _f.split('\n'):
if not line:
# Skip empty lines and last line
continue
items = line.split('\t')

if len(items) != 2:
continue

output[items[0]] = int(items[1])

return output

def reset_peer(self, peer_name=None, public_key=None):
from vyos.config import Config

c = Config()
c.set_level(['interfaces', 'wireguard', self.config['ifname']])
max_dns_retry = c.return_effective_value(['max-dns-retry'], 3)

for peer in c.list_effective_nodes(['peer']):
if peer_name is None or peer == peer_name:
public_key = c.return_effective_value(['peer', peer, 'public-key'])
peer_public_key = c.return_effective_value(['peer', peer, 'public-key'])
if peer_name is None or peer == peer_name or public_key == peer_public_key:
address = c.return_effective_value(['peer', peer, 'address'])
port = c.return_effective_value(['peer', peer, 'port'])

Expand All @@ -174,10 +197,13 @@ def reset_peer(self, interface, peer_name=None):
print(f'Peer {peer_name} endpoint not set')
continue

cmd = f"wg set {self.config['ifname']} peer {public_key} endpoint {address}:{port}"
if c.exists_effective(['peer', peer, 'disable']):
continue

cmd = f"wg set {self.config['ifname']} peer {peer_public_key} endpoint {address}:{port}"
try:
print(
f'Resetting {self.config["ifname"]} peer {public_key} endpoint to {address}:{port} ... ',
f'Resetting {self.config["ifname"]} peer {peer_public_key} endpoint to {address}:{port} ... ',
end='',
)
self._cmd(cmd, env={'WG_ENDPOINT_RESOLUTION_RETRIES': str(max_dns_retry)})
Expand Down Expand Up @@ -240,6 +266,9 @@ def update(self, config):
# marked as disabled - also active sessions are terminated as
# the public key was already removed when entering this method!
if 'disable' in peer_config:
# remove peer if disabled, no error report even if peer not exists
cmd = base_cmd + ' peer {public_key} remove'
self._cmd(cmd.format(**peer_config))
continue

psk_file = no_psk_file
Expand Down Expand Up @@ -274,8 +303,11 @@ def update(self, config):
if {'address', 'port'} <= set(peer_config):
if is_ipv6(peer_config['address']):
cmd += ' endpoint [{address}]:{port}'
else:
elif is_ipv4(peer_config['address']):
cmd += ' endpoint {address}:{port}'
else:
# don't set endpoint if address uses domain name
continue

self._cmd(
cmd.format(**peer_config),
Expand Down
1 change: 0 additions & 1 deletion src/conf_mode/firewall.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@

nftables_conf = '/run/nftables.conf'
domain_resolver_usage = '/run/use-vyos-domain-resolver-firewall'
domain_resolver_usage_nat = '/run/use-vyos-domain-resolver-nat'

sysctl_file = r'/run/sysctl/10-vyos-firewall.conf'

Expand Down
16 changes: 16 additions & 0 deletions src/conf_mode/interfaces_wireguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@
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()

domain_resolver_usage = '/run/use-vyos-domain-resolver-interfaces-wireguard'


def get_config(config=None):
"""
Expand Down Expand Up @@ -122,6 +126,18 @@ def apply(wireguard):
wg = WireGuardIf(**wireguard)
wg.update(wireguard)

## DOMAIN RESOLVER
domain_action = 'restart'
if True:
text = f'# Automatically generated by interfaces_wireguard.py\nThis file indicates that vyos-domain-resolver service is used by the interfaces_wireguard.\n'
Path(domain_resolver_usage).write_text(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__':
Expand Down
8 changes: 4 additions & 4 deletions src/conf_mode/nat.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import os

from sys import exit
from pathlib import Path

from vyos.base import Warning
from vyos.config import Config
Expand All @@ -43,7 +44,6 @@
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',
Expand Down Expand Up @@ -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')

Expand Down
37 changes: 37 additions & 0 deletions src/helpers/vyos-domain-resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
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 = {}

Expand Down Expand Up @@ -165,6 +167,39 @@ def update_fqdn(config, node):

print(f'Updated {count} sets in {node} - result: {code}')

def update_interfaces(config, node):
if node == 'interfaces':
wireguard_interfaces = dict_search_args(config, 'wireguard')

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 address is not ipv4 and not ipv6
if 'address' in peer_config and not is_ip(peer_config['address']):
# 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__':
print(f'VyOS domain resolver')

Expand All @@ -178,10 +213,12 @@ def update_fqdn(config, node):
conf = ConfigTreeQuery()
firewall = get_config(conf, base_firewall)
nat = get_config(conf, base_nat)
interfaces = get_config(conf, base_interfaces)

print(f'interval: {timeout}s - cache: {cache}')

while True:
update_fqdn(firewall, 'firewall')
update_fqdn(nat, 'nat')
update_interfaces(interfaces, 'interfaces')
time.sleep(timeout)
2 changes: 1 addition & 1 deletion src/op_mode/reset_wireguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def _wrapper(*args, **kwargs):
@_verify
def reset_peer(interface: str, peer: typing.Optional[str] = None):
intf = WireGuardIf(interface, create=False, debug=False)
return intf.operational.reset_peer(interface, peer)
return intf.operational.reset_peer(peer)


if __name__ == '__main__':
Expand Down

0 comments on commit 9e67192

Please sign in to comment.