From 993f6873c02f3f79013acedfe61ce705bdb3a4d0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 20 Sep 2020 13:53:55 +0200 Subject: wwan: ifconfig: T2905: sync CLI nodes in dialup interfaces Both PPPoE and WWAN interfaces are dialer interfaces handled by ppp, but use different CLI nodes for the same functionality. PPPoE has "connect-on-demand" to initiate an "on-demand" dialing and WWAN uses "ondemand" for this purpose. Rename WWAN "ondemand" node to "connect-on-demand". --- data/templates/wwan/peer.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'data/templates') diff --git a/data/templates/wwan/peer.tmpl b/data/templates/wwan/peer.tmpl index aa759f741..e23881bf8 100644 --- a/data/templates/wwan/peer.tmpl +++ b/data/templates/wwan/peer.tmpl @@ -21,7 +21,7 @@ noauth crtscts lock persist -{{ "demand" if ondemand is defined }} +{{ "demand" if connect_on_demand is defined }} connect '/usr/sbin/chat -v -t6 -f /etc/ppp/peers/chat.{{ ifname }}' -- cgit v1.2.3 From 7ea3802aa3de99ec78fbe0cf24a8527b80c927db Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Sep 2020 17:30:27 +0200 Subject: smoketest: macsec: T2023: test MTU setting --- data/templates/macsec/wpa_supplicant.conf.tmpl | 2 +- smoketest/scripts/cli/test_interfaces_macsec.py | 39 +++++++++++++++---------- 2 files changed, 25 insertions(+), 16 deletions(-) (limited to 'data/templates') diff --git a/data/templates/macsec/wpa_supplicant.conf.tmpl b/data/templates/macsec/wpa_supplicant.conf.tmpl index 1731bf160..5b353def8 100644 --- a/data/templates/macsec/wpa_supplicant.conf.tmpl +++ b/data/templates/macsec/wpa_supplicant.conf.tmpl @@ -1,4 +1,4 @@ -# autogenerated by interfaces-macsec.py +### Autogenerated by interfaces-macsec.py ### # see full documentation: # https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py index 0f1b6486d..30b040b97 100755 --- a/smoketest/scripts/cli/test_interfaces_macsec.py +++ b/smoketest/scripts/cli/test_interfaces_macsec.py @@ -23,8 +23,8 @@ from base_interfaces_test import BasicInterfaceTest from vyos.configsession import ConfigSessionError from vyos.util import read_file -def get_config_value(intf, key): - tmp = read_file(f'/run/wpa_supplicant/{intf}.conf') +def get_config_value(interface, key): + tmp = read_file(f'/run/wpa_supplicant/{interface}.conf') tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp) return tmp[0] @@ -49,52 +49,61 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): mode - both using different mandatory settings, lets test encryption as the basic authentication test has been performed using the base class tests """ - intf = 'macsec0' - src_intf = 'eth0' + interface = 'macsec0' + src_interface = 'eth0' mak_cak = '232e44b7fda6f8e2d88a07bf78a7aff4' mak_ckn = '40916f4b23e3d548ad27eedd2d10c6f98c2d21684699647d63d41b500dfe8836' replay_window = '64' - self.session.set(self._base_path + [intf, 'security', 'encrypt']) + self.session.set(self._base_path + [interface, 'security', 'encrypt']) # check validate() - Cipher suite must be set for MACsec with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(self._base_path + [intf, 'security', 'cipher', 'gcm-aes-128']) + self.session.set(self._base_path + [interface, 'security', 'cipher', 'gcm-aes-128']) # check validate() - Physical source interface must be set for MACsec with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(self._base_path + [intf, 'source-interface', src_intf]) + self.session.set(self._base_path + [interface, 'source-interface', src_interface]) + + # check validate() - Physical source interface MTU must be higher then our MTU + self.session.set(self._base_path + [interface, 'mtu', '1500']) + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.delete(self._base_path + [interface, 'mtu']) # check validate() - MACsec security keys mandartory when encryption is enabled with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(self._base_path + [intf, 'security', 'mka', 'cak', mak_cak]) + self.session.set(self._base_path + [interface, 'security', 'mka', 'cak', mak_cak]) # check validate() - MACsec security keys mandartory when encryption is enabled with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(self._base_path + [intf, 'security', 'mka', 'ckn', mak_ckn]) + self.session.set(self._base_path + [interface, 'security', 'mka', 'ckn', mak_ckn]) - self.session.set(self._base_path + [intf, 'security', 'replay-window', replay_window]) + self.session.set(self._base_path + [interface, 'security', 'replay-window', replay_window]) self.session.commit() - tmp = get_config_value(src_intf, 'macsec_integ_only') + tmp = get_config_value(src_interface, 'macsec_integ_only') self.assertTrue("0" in tmp) - tmp = get_config_value(src_intf, 'mka_cak') + tmp = get_config_value(src_interface, 'mka_cak') self.assertTrue(mak_cak in tmp) - tmp = get_config_value(src_intf, 'mka_ckn') + tmp = get_config_value(src_interface, 'mka_ckn') self.assertTrue(mak_ckn in tmp) # check that the default priority of 255 is programmed - tmp = get_config_value(src_intf, 'mka_priority') + tmp = get_config_value(src_interface, 'mka_priority') self.assertTrue("255" in tmp) - tmp = get_config_value(src_intf, 'macsec_replay_window') + tmp = get_config_value(src_interface, 'macsec_replay_window') self.assertTrue(replay_window in tmp) + tmp = read_file(f'/sys/class/net/{interface}/mtu') + self.assertEqual(tmp, '1460') + # Check for running process self.assertTrue("wpa_supplicant" in (p.name() for p in process_iter())) -- cgit v1.2.3 From 6635d8822a42cffcb4df9a44d87a0c4fb79ef698 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Sep 2020 17:31:26 +0200 Subject: wireless: T2887: hostapd: add bridge option --- data/templates/wifi/hostapd.conf.tmpl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'data/templates') diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index 132c4ce40..3980fb896 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -11,6 +11,21 @@ device_name={{ description | truncate(32, True) }} # command line parameter. interface={{ ifname }} +{% if is_bridge_member is defined %} +# In case of atheros and nl80211 driver interfaces, an additional +# configuration parameter, bridge, may be used to notify hostapd if the +# interface is included in a bridge. This parameter is not used with Host AP +# driver. If the bridge parameter is not set, the drivers will automatically +# figure out the bridge interface (assuming sysfs is enabled and mounted to +# /sys) and this parameter may not be needed. +# +# For nl80211, this parameter can be used to request the AP interface to be +# added to the bridge automatically (brctl may refuse to do this before hostapd +# has been started to change the interface mode). If needed, the bridge +# interface is also created. +bridge={{ is_bridge_member }} +{% endif %} + # Driver interface type (hostap/wired/none/nl80211/bsd); # default: hostap). nl80211 is used with all Linux mac80211 drivers. # Use driver=none if building hostapd as a standalone RADIUS server that does -- cgit v1.2.3 From d22b476e0e1ca2a173ecf9c85596b4f02646e772 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 21 Sep 2020 17:32:55 +0200 Subject: wireless: T2887: help when searching hidden SSIDs in station mode --- data/templates/wifi/wpa_supplicant.conf.tmpl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'data/templates') diff --git a/data/templates/wifi/wpa_supplicant.conf.tmpl b/data/templates/wifi/wpa_supplicant.conf.tmpl index 9ddad35fd..f84892dc0 100644 --- a/data/templates/wifi/wpa_supplicant.conf.tmpl +++ b/data/templates/wifi/wpa_supplicant.conf.tmpl @@ -1,7 +1,13 @@ -# WPA supplicant config +### Autogenerated by interfaces-macsec.py ### + +# see full documentation: +# https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf + network={ ssid="{{ ssid }}" + scan_ssid=1 {% if security is defined and security.wpa is defined and security.wpa.passphrase is defined %} + key_mgmt=WPA-PSK psk="{{ security.wpa.passphrase }}" {% else %} key_mgmt=NONE -- cgit v1.2.3 From b2c61e2127d83cc0a0e27092462b62c2e8e7eaa1 Mon Sep 17 00:00:00 2001 From: Marcus Hoff Date: Tue, 22 Sep 2020 19:44:24 +0200 Subject: openvpn: T2907: add 'none' encryption option to not encrypt any data --- data/templates/openvpn/server.conf.tmpl | 4 +++- interface-definitions/interfaces-openvpn.xml.in | 16 ++++++++++++---- src/conf_mode/interfaces-openvpn.py | 8 +++++++- 3 files changed, 22 insertions(+), 6 deletions(-) (limited to 'data/templates') diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index 401f8e04b..8a1ac6bd8 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -196,7 +196,9 @@ tls-server # Encryption options {%- if encryption %} -{% if encryption == 'des' -%} +{% if encryption == 'none' -%} +cipher none +{%- elif encryption == 'des' -%} cipher des-cbc {%- elif encryption == '3des' -%} cipher des-ede3-cbc diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 905c76507..5675379d5 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -63,8 +63,12 @@ Standard Data Encryption Algorithm - des 3des bf128 bf256 aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm + none des 3des bf128 bf256 aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm + + none + Disable encryption + des DES algorithm @@ -106,7 +110,7 @@ AES algorithm with 256-bit key GCM - (des|3des|bf128|bf256|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm) + (none|des|3des|bf128|bf256|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm) @@ -114,8 +118,12 @@ Cipher negotiation list for use in server or client mode - des 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm + none des 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm + + none + Disable encryption + des DES algorithm @@ -149,7 +157,7 @@ AES algorithm with 256-bit key GCM - (des|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm) + (none|des|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm) diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index f83590209..518dbdc0e 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -257,7 +257,10 @@ def get_config(config=None): if conf.exists('encryption ncp-ciphers'): _ncp_ciphers = [] for enc in conf.return_values('encryption ncp-ciphers'): - if enc == 'des': + if enc == 'none': + _ncp_ciphers.append('none') + _ncp_ciphers.append('NONE') + elif enc == 'des': _ncp_ciphers.append('des-cbc') _ncp_ciphers.append('DES-CBC') elif enc == '3des': @@ -944,6 +947,9 @@ def verify(openvpn): else: print('Diffie-Hellman prime file is unspecified, assuming ECDH') + if openvpn['encryption'] == 'none': + print('Warning: "encryption none" was specified. NO encryption will be performed and tunnelled data WILL be transmitted in clear text over the network!') + # # Auth user/pass # -- cgit v1.2.3 From 92edd930c49b63247dbbcc370c9f93b3456cb855 Mon Sep 17 00:00:00 2001 From: Marcus Hoff Date: Tue, 22 Sep 2020 19:44:55 +0200 Subject: openvpn: T2906: tls-auth missing key direction --- data/templates/openvpn/server.conf.tmpl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'data/templates') diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index 8a1ac6bd8..fea310236 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -181,7 +181,11 @@ dh {{ tls_dh }} {%- endif %} {%- if tls_auth %} -tls-auth {{tls_auth}} +{%- if mode == 'client' %} +tls-auth {{tls_auth}} 1 +{%- elif mode == 'server' %} +tls-auth {{tls_auth}} 0 +{%- endif %} {%- endif %} {%- if tls_role %} -- cgit v1.2.3 From 2b06653a824f21bf5b3a843f109f99096e7500ff Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Sep 2020 18:22:49 +0200 Subject: dns: forwarding: T2921: template cleanup --- .../recursor.forward-zones.conf.tmpl | 34 +++++++++++----------- .../recursor.vyos-hostsd.conf.lua.tmpl | 28 +++++++++--------- src/conf_mode/dns_forwarding.py | 4 +-- 3 files changed, 33 insertions(+), 33 deletions(-) (limited to 'data/templates') diff --git a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl index de5eaee00..e62b9bb81 100644 --- a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl @@ -3,26 +3,26 @@ # dot zone (catch-all): '+' indicates recursion is desired # (same as forward-zones-recurse) -{#- the code below ensures the order of nameservers is determined first by #} -{#- the order of tags, then by the order of nameservers within that tag #} -{%- set n = namespace(dot_zone_ns='') %} -{%- for tag in name_server_tags_recursor %} -{%- set ns = '' %} -{%- if tag in name_servers %} -{%- set ns = ns + name_servers[tag]|join(', ') %} -{%- set n.dot_zone_ns = (n.dot_zone_ns, ns)|join(', ') if n.dot_zone_ns != '' else ns %} -{%- endif %} +{# the code below ensures the order of nameservers is determined first by #} +{# the order of tags, then by the order of nameservers within that tag #} +{% set n = namespace(dot_zone_ns='') %} +{% for tag in name_server_tags_recursor %} +{% set ns = '' %} +{% if tag in name_servers %} +{% set ns = ns + name_servers[tag]|join(', ') %} +{% set n.dot_zone_ns = (n.dot_zone_ns, ns)|join(', ') if n.dot_zone_ns != '' else ns %} +{% endif %} # {{ tag }}: {{ ns }} -{%- endfor %} +{% endfor %} -{%- if n.dot_zone_ns %} +{% if n.dot_zone_ns %} +.={{ n.dot_zone_ns }} -{%- endif %} +{% endif %} -{% if forward_zones -%} +{% if forward_zones %} # zones added via 'service dns forwarding domain' -{%- for zone, zonedata in forward_zones.items() %} -{% if zonedata['recursion-desired'] %}+{% endif %}{{ zone }}={{ zonedata['nslist']|join(', ') }} -{%- endfor %} -{%- endif %} +{% for zone, zonedata in forward_zones.items() %} +{% if zonedata['recursion-desired'] %}+{% endif %}{{ zone }}={{ zonedata['nslist']|join(', ') }} +{% endfor %} +{% endif %} diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl index b0d99d9ae..8fefae0b2 100644 --- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl +++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl @@ -1,24 +1,24 @@ -- Autogenerated by VyOS (vyos-hostsd) -- -- Do not edit, your changes will get overwritten -- -{% if hosts -%} +{% if hosts %} -- from 'system static-host-mapping' and DHCP server -{%- for tag, taghosts in hosts.items() %} -{%- for host, hostprops in taghosts.items() %} +{% for tag, taghosts in hosts.items() %} +{% for host, hostprops in taghosts.items() %} addNTA("{{ host }}.", "{{ tag }}") -{%- for a in hostprops['aliases'] %} +{% for a in hostprops['aliases'] %} addNTA("{{ a }}.", "{{ tag }} alias") -{%- endfor %} -{%- endfor %} -{%- endfor %} -{%- endif %} +{% endfor %} +{% endfor %} +{% endfor %} +{% endif %} -{% if forward_zones -%} +{% if forward_zones %} -- from 'service dns forwarding domain' -{%- for zone, zonedata in forward_zones.items() %} -{%- if zonedata['addNTA'] %} +{% for zone, zonedata in forward_zones.items() %} +{% if zonedata['addNTA'] %} addNTA("{{ zone }}", "static") -{%- endif %} -{%- endfor %} -{%- endif %} +{% endif %} +{% endfor %} +{% endif %} diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 53bc37882..d6eb76d91 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -148,10 +148,10 @@ def generate(dns): return None render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl', - dns, user=pdns_rec_user, group=pdns_rec_group) + dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl', - dns, user=pdns_rec_user, group=pdns_rec_group) + dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) # if vyos-hostsd didn't create its files yet, create them (empty) for f in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: -- cgit v1.2.3 From 806f35b5856c3f8dae634718a6a9e82cc90bb63a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Sep 2020 19:55:54 +0200 Subject: wireless: T2241: add "wds" CLI option --- data/templates/wifi/hostapd.conf.tmpl | 8 +++++++ interface-definitions/interfaces-wireless.xml.in | 6 +++++ python/vyos/ifconfig/wireless.py | 29 ++++++++++++------------ src/conf_mode/interfaces-wireless.py | 1 + 4 files changed, 30 insertions(+), 14 deletions(-) (limited to 'data/templates') diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index 3980fb896..c5e4240d1 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -448,6 +448,14 @@ macaddr_acl=0 max_num_sta={{ max_stations }} {% endif %} +{% if wds is defined %} +# WDS (4-address frame) mode with per-station virtual interfaces +# (only supported with driver=nl80211) +# This mode allows associated stations to use 4-address frames to allow layer 2 +# bridging to be used. +wds_sta=1 +{% endif %} + {% if isolate_stations is defined %} # Client isolation can be used to prevent low-level bridging of frames between # associated stations in the BSS. By default, this bridging is allowed. diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index a0caf810f..8c594e758 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -770,6 +770,12 @@ #include #include + + + Enable WDS (Wireless Distribution System) + + + diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py index 37703d242..deca68bf0 100644 --- a/python/vyos/ifconfig/wireless.py +++ b/python/vyos/ifconfig/wireless.py @@ -23,8 +23,10 @@ class WiFiIf(Interface): default = { 'type': 'wifi', - 'phy': 'phy0' + 'phy': '', + 'wds': 'off', } + definition = { **Interface.definition, **{ @@ -33,12 +35,19 @@ class WiFiIf(Interface): 'bridgeable': True, } } + options = Interface.options + \ ['phy', 'op_mode'] + _command_set = {**Interface._command_set, **{ + '4addr': { + 'shellcmd': 'iw dev {ifname} set 4addr {value}', + }, + }} + def _create(self): # all interfaces will be added in monitor mode - cmd = 'iw phy {phy} interface add {ifname} type monitor' \ + cmd = 'iw phy {phy} interface add {ifname} type monitor 4addr {wds}' \ .format(**self.config) self._cmd(cmd) @@ -50,28 +59,20 @@ class WiFiIf(Interface): .format(**self.config) self._cmd(cmd) + def set_4aadr_mode(self, state): + return self.set_interface('4addr', state) + def update(self, config): """ General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered interface setup code and provide a single point of entry when workin on any interface. """ - # We can not call add_to_bridge() until wpa_supplicant is running, thus - # we will remove the key from the config dict and react to this specal - # case in thie derived class. - # re-add ourselves to any bridge we might have fallen out of - bridge_member = '' - if 'is_bridge_member' in config: - bridge_member = config['is_bridge_member'] - del config['is_bridge_member'] + self.set_4aadr_mode('on' if 'wds' in config else 'off') # call base class first super().update(config) - # re-add ourselves to any bridge we might have fallen out of - if bridge_member: - self.add_to_bridge(bridge_member) - # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() # function. We will only enable the interface if 'up' was called as diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index be59b72b5..f8520aecf 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -240,6 +240,7 @@ def apply(wifi): # Assign WiFi instance configuration parameters to config dict conf['phy'] = wifi['physical_device'] + conf['wds'] = 'on' if 'wds' in wifi else 'off' # Finally create the new interface w = WiFiIf(interface, **conf) -- cgit v1.2.3 From f39f5dde342aa5e14d1fb4155920c61ac5fd11b1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Sep 2020 19:21:36 +0200 Subject: dns: forwarding: T2921: migrate to get_config_dict() --- data/configd-include.json | 1 + data/templates/dns-forwarding/recursor.conf.tmpl | 2 +- .../recursor.forward-zones.conf.tmpl | 4 +- .../recursor.vyos-hostsd.conf.lua.tmpl | 4 +- interface-definitions/dns-forwarding.xml.in | 11 +- .../scripts/cli/test_service_dns_forwarding.py | 35 ++++- src/conf_mode/dns_forwarding.py | 172 ++++++++------------- src/services/vyos-hostsd | 21 +-- src/utils/vyos-hostsd-client | 6 +- 9 files changed, 122 insertions(+), 134 deletions(-) (limited to 'data/templates') diff --git a/data/configd-include.json b/data/configd-include.json index 0c75657e0..2711a29b8 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -2,6 +2,7 @@ "bcast_relay.py", "dhcp_relay.py", "dhcpv6_relay.py", +"dns_forwarding.py", "dynamic_dns.py", "firewall_options.py", "host_name.py", diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index d233b8abc..b0ae3cc61 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -21,7 +21,7 @@ max-cache-entries={{ cache_size }} max-negative-ttl={{ negative_ttl }} # ignore-hosts-file -export-etc-hosts={{ export_hosts_file }} +export-etc-hosts={{ 'no' if ignore_hosts_file is defined else 'yes' }} # listen-address local-address={{ listen_address | join(',') }} diff --git a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl index e62b9bb81..90f35ae1c 100644 --- a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl @@ -19,10 +19,10 @@ +.={{ n.dot_zone_ns }} {% endif %} -{% if forward_zones %} +{% if forward_zones is defined %} # zones added via 'service dns forwarding domain' {% for zone, zonedata in forward_zones.items() %} -{% if zonedata['recursion-desired'] %}+{% endif %}{{ zone }}={{ zonedata['nslist']|join(', ') }} +{{ "+" if zonedata['recursion_desired'] is defined }}{{ zone }}={{ zonedata['server']|join(', ') }} {% endfor %} {% endif %} diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl index 8fefae0b2..784d5c360 100644 --- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl +++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl @@ -13,10 +13,10 @@ addNTA("{{ a }}.", "{{ tag }} alias") {% endfor %} {% endif %} -{% if forward_zones %} +{% if forward_zones is defined %} -- from 'service dns forwarding domain' {% for zone, zonedata in forward_zones.items() %} -{% if zonedata['addNTA'] %} +{% if zonedata['addnta'] is defined %} addNTA("{{ zone }}", "static") {% endif %} {% endfor %} diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index aaf8bb27d..07e63d54a 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -16,7 +16,7 @@ - DNS forwarding cache size + DNS forwarding cache size (default: 10000) 0-10000 DNS forwarding cache size @@ -25,6 +25,7 @@ + 10000 @@ -37,7 +38,7 @@ - DNSSEC mode + DNSSEC mode (default: process-no-validate) off process-no-validate process log-fail validate @@ -62,9 +63,10 @@ Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses. - (off|process-no-validate|process|log-fail|validate) + ^(off|process-no-validate|process|log-fail|validate)$ + process-no-validate @@ -146,7 +148,7 @@ - Maximum amount of time negative entries are cached + Maximum amount of time negative entries are cached (default: 3600) 0-7200 Seconds to cache NXDOMAIN entries @@ -155,6 +157,7 @@ + 3600 diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py index 717b5b56d..5e2f3dfbd 100755 --- a/smoketest/scripts/cli/test_service_dns_forwarding.py +++ b/smoketest/scripts/cli/test_service_dns_forwarding.py @@ -24,6 +24,7 @@ from vyos.util import process_named_running CONFIG_FILE = '/run/powerdns/recursor.conf' FORWARD_FILE = '/run/powerdns/recursor.forward-zones.conf' +HOSTSD_FILE = '/run/powerdns/recursor.vyos-hostsd.conf.lua' PROCESS_NAME= 'pdns-r/worker' base_path = ['service', 'dns', 'forwarding'] @@ -69,6 +70,9 @@ class TestServicePowerDNS(unittest.TestCase): # configure DNSSEC self.session.set(base_path + ['dnssec', 'validate']) + # Do not use local /etc/hosts file in name resolution + self.session.set(base_path + ['ignore-hosts-file']) + # commit changes self.session.commit() @@ -88,6 +92,10 @@ class TestServicePowerDNS(unittest.TestCase): tmp = get_config_value('max-negative-ttl') self.assertEqual(tmp, negative_ttl) + # Do not use local /etc/hosts file in name resolution + tmp = get_config_value('export-etc-hosts') + self.assertEqual(tmp, 'no') + # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -130,6 +138,11 @@ class TestServicePowerDNS(unittest.TestCase): tmp = get_config_value(r'\+.', file=FORWARD_FILE) self.assertEqual(tmp, ', '.join(nameservers)) + # Do not use local /etc/hosts file in name resolution + # default: yes + tmp = get_config_value('export-etc-hosts') + self.assertEqual(tmp, 'yes') + # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -141,21 +154,39 @@ class TestServicePowerDNS(unittest.TestCase): for address in listen_adress: self.session.set(base_path + ['listen-address', address]) - domains = ['vyos.io', 'vyos.net'] + domains = ['vyos.io', 'vyos.net', 'vyos.com'] nameservers = ['192.0.2.1', '192.0.2.2'] for domain in domains: for nameserver in nameservers: self.session.set(base_path + ['domain', domain, 'server', nameserver]) + # Test 'recursion-desired' flag for only one domain + if domain == domains[0]: + self.session.set(base_path + ['domain', domain, 'recursion-desired']) + + # Test 'negative trust anchor' flag for the second domain only + if domain == domains[1]: + self.session.set(base_path + ['domain', domain, 'addnta']) + # commit changes self.session.commit() + # Test configured name-servers + hosts_conf = read_file(HOSTSD_FILE) for domain in domains: - tmp = get_config_value(domain, file=FORWARD_FILE) + # Test 'recursion-desired' flag for the first domain only + if domain == domains[0]: key =f'\+{domain}' + else: key =f'{domain}' + tmp = get_config_value(key, file=FORWARD_FILE) self.assertEqual(tmp, ', '.join(nameservers)) + # Test 'negative trust anchor' flag for the second domain only + if domain == domains[1]: + self.assertIn(f'addNTA("{domain}", "static")', hosts_conf) + # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) if __name__ == '__main__': unittest.main() + diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index d6eb76d91..5101c1e79 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -17,14 +17,17 @@ import os from sys import exit -from copy import deepcopy from vyos.config import Config +from vyos.configdict import dict_merge from vyos.hostsd_client import Client as hostsd_client -from vyos import ConfigError -from vyos.util import call, chown +from vyos.util import call +from vyos.util import chown +from vyos.util import vyos_dict_search from vyos.template import render +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() @@ -35,116 +38,63 @@ pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.l pdns_rec_hostsd_zones_file = f'{pdns_rec_run_dir}/recursor.forward-zones.conf' pdns_rec_config_file = f'{pdns_rec_run_dir}/recursor.conf' -default_config_data = { - 'allow_from': [], - 'cache_size': 10000, - 'export_hosts_file': 'yes', - 'listen_address': [], - 'name_servers': [], - 'negative_ttl': 3600, - 'system': False, - 'domains': {}, - 'dnssec': 'process-no-validate', - 'dhcp_interfaces': [] -} - hostsd_tag = 'static' -def get_config(conf): - dns = deepcopy(default_config_data) +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'dns', 'forwarding'] - if not conf.exists(base): return None - conf.set_level(base) - - if conf.exists(['allow-from']): - dns['allow_from'] = conf.return_values(['allow-from']) - - if conf.exists(['cache-size']): - cache_size = conf.return_value(['cache-size']) - dns['cache_size'] = cache_size - - if conf.exists('negative-ttl'): - negative_ttl = conf.return_value(['negative-ttl']) - dns['negative_ttl'] = negative_ttl - - if conf.exists(['domain']): - for domain in conf.list_nodes(['domain']): - conf.set_level(base + ['domain', domain]) - entry = { - 'nslist': bracketize_ipv6_addrs(conf.return_values(['server'])), - 'addNTA': conf.exists(['addnta']), - 'recursion-desired': conf.exists(['recursion-desired']) - } - dns['domains'][domain] = entry - - conf.set_level(base) + dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base) + dns = dict_merge(default_values, dns) - if conf.exists(['ignore-hosts-file']): - dns['export_hosts_file'] = "no" + # some additions to the default dictionary + if 'system' in dns: + base_nameservers = ['system', 'name-server'] + if conf.exists(base_nameservers): + dns.update({'system_name_server': conf.return_values(base_nameservers)}) - if conf.exists(['name-server']): - dns['name_servers'] = bracketize_ipv6_addrs( - conf.return_values(['name-server'])) - - if conf.exists(['system']): - dns['system'] = True - - if conf.exists(['listen-address']): - dns['listen_address'] = conf.return_values(['listen-address']) - - if conf.exists(['dnssec']): - dns['dnssec'] = conf.return_value(['dnssec']) - - if conf.exists(['dhcp']): - dns['dhcp_interfaces'] = conf.return_values(['dhcp']) + base_nameservers_dhcp = ['system', 'name-servers-dhcp'] + if conf.exists(base_nameservers_dhcp): + dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) return dns -def bracketize_ipv6_addrs(addrs): - """Wraps each IPv6 addr in addrs in [], leaving IPv4 addrs untouched.""" - return ['[{0}]'.format(a) if a.count(':') > 1 else a for a in addrs] - -def verify(conf, dns): +def verify(dns): # bail out early - looks like removal from running config - if dns is None: + if not dns: return None - if not dns['listen_address']: - raise ConfigError( - "Error: DNS forwarding requires a listen-address") - - if not dns['allow_from']: - raise ConfigError( - "Error: DNS forwarding requires an allow-from network") - - if dns['domains']: - for domain in dns['domains']: - if not dns['domains'][domain]['nslist']: - raise ConfigError(( - f'Error: No server configured for domain {domain}')) - - no_system_nameservers = False - conf.set_level([]) - if dns['system'] and not ( - conf.exists(['system', 'name-server']) or - conf.exists(['system', 'name-servers-dhcp']) ): - no_system_nameservers = True - print(("DNS forwarding warning: No 'system name-server' or " - "'system name-servers-dhcp' set\n")) - - if (no_system_nameservers or not dns['system']) and not ( - dns['name_servers'] or dns['dhcp_interfaces']): - print(("DNS forwarding warning: No 'dhcp', 'name-server' or 'system' " - "nameservers set. Forwarding will operate as a recursor.\n")) + if 'listen_address' not in dns: + raise ConfigError('DNS forwarding requires a listen-address') + + if 'allow_from' not in dns: + raise ConfigError('DNS forwarding requires an allow-from network') + + # we can not use vyos_dict_search() when testing for domain servers + # as a domain will contains dot's which is out dictionary delimiter. + if 'domain' in dns: + for domain in dns['domain']: + if 'server' not in dns['domain'][domain]: + raise ConfigError(f'No server configured for domain {domain}!') + + if 'system' in dns: + if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns): + print("Warning: No 'system name-server' or 'system " \ + "name-servers-dhcp' configured") return None def generate(dns): # bail out early - looks like removal from running config - if dns is None: + if not dns: return None render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl', @@ -154,17 +104,18 @@ def generate(dns): dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) # if vyos-hostsd didn't create its files yet, create them (empty) - for f in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: - with open(f, 'a'): + for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: + with open(file, 'a'): pass - chown(f, user=pdns_rec_user, group=pdns_rec_group) + chown(file, user=pdns_rec_user, group=pdns_rec_group) return None def apply(dns): - if dns is None: + if not dns: # DNS forwarding is removed in the commit - call("systemctl stop pdns-recursor.service") + call('systemctl stop pdns-recursor.service') + if os.path.isfile(pdns_rec_config_file): os.unlink(pdns_rec_config_file) else: @@ -174,8 +125,8 @@ def apply(dns): # add static nameservers to hostsd so they can be joined with other # sources hc.delete_name_servers([hostsd_tag]) - if dns['name_servers']: - hc.add_name_servers({hostsd_tag: dns['name_servers']}) + if 'name_server' in dns: + hc.add_name_servers({hostsd_tag: dns['name_server']}) # delete all nameserver tags hc.delete_name_server_tags_recursor(hc.get_name_server_tags_recursor()) @@ -184,32 +135,33 @@ def apply(dns): # our own tag (static) hc.add_name_server_tags_recursor([hostsd_tag]) - if dns['system']: + if 'system' in dns: hc.add_name_server_tags_recursor(['system']) else: hc.delete_name_server_tags_recursor(['system']) # add dhcp nameserver tags for configured interfaces - for intf in dns['dhcp_interfaces']: - hc.add_name_server_tags_recursor(['dhcp-' + intf, 'dhcpv6-' + intf ]) + if 'system_name_server_dhcp' in dns: + for interface in dns['system_name_server_dhcp']: + hc.add_name_server_tags_recursor(['dhcp-' + interface, + 'dhcpv6-' + interface ]) # hostsd will generate the forward-zones file # the list and keys() are required as get returns a dict, not list hc.delete_forward_zones(list(hc.get_forward_zones().keys())) - if dns['domains']: - hc.add_forward_zones(dns['domains']) + if 'domain' in dns: + hc.add_forward_zones(dns['domain']) # call hostsd to generate forward-zones and its lua-config-file hc.apply() ### finally (re)start pdns-recursor - call("systemctl restart pdns-recursor.service") + call('systemctl restart pdns-recursor.service') if __name__ == '__main__': try: - conf = Config() - c = get_config(conf) - verify(conf, c) + c = get_config() + verify(c) generate(c) apply(c) except ConfigError as e: diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 0079f7e5c..59dbeda17 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -107,16 +107,17 @@ # ### forward_zones ## Additional zones added to pdns-recursor forward-zones-file. -## If recursion-desired is true, '+' will be prepended to the zone line. -## If addNTA is true, a NTA will be added via lua-config-file. +## If recursion_desired is true, '+' will be prepended to the zone line. +## If addnta is true, a NTA (Negative Trust Anchor) will be added via +## lua-config-file. # # { 'type': 'forward_zones', # 'op': 'add', # 'data': { # '': { -# 'nslist': ['', ...], -# 'addNTA': , -# 'recursion-desired': +# 'server': ['', ...], +# 'addnta': , +# 'recursion_desired': # } # ... # } @@ -305,12 +306,12 @@ tag_regex_schema = op_type_schema.extend({ forward_zone_add_schema = op_type_schema.extend({ 'data': { str: { - 'nslist': [str], - 'addNTA': bool, - 'recursion-desired': bool + 'server': [str], + 'addnta': Any({}, None), + 'recursion_desired': Any({}, None), } } - }, required=True) + }, required=False) hosts_add_schema = op_type_schema.extend({ 'data': { @@ -586,7 +587,7 @@ if __name__ == '__main__': context = zmq.Context() socket = context.socket(zmq.REP) - + # Set the right permissions on the socket, then change it back o_mask = os.umask(0o007) socket.bind(SOCKET_PATH) diff --git a/src/utils/vyos-hostsd-client b/src/utils/vyos-hostsd-client index 48ebc83f7..d4d38315a 100755 --- a/src/utils/vyos-hostsd-client +++ b/src/utils/vyos-hostsd-client @@ -99,9 +99,9 @@ try: raise ValueError("--nameservers is required for this operation") client.add_forward_zones( { args.add_forward_zone: { - 'nslist': args.nameservers, - 'addNTA': args.addnta, - 'recursion-desired': args.recursion_desired + 'server': args.nameservers, + 'addnta': args.addnta, + 'recursion_desired': args.recursion_desired } }) elif args.delete_forward_zones: -- cgit v1.2.3