From 993f6873c02f3f79013acedfe61ce705bdb3a4d0 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
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')

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 <christian@poessinger.com>
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')

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 <christian@poessinger.com>
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')

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 <christian@poessinger.com>
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')

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 <marcus.hoff@ring2.dk>
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')

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 @@
                 <properties>
                   <help>Standard Data Encryption Algorithm</help>
                   <completionHelp>
-                    <list>des 3des bf128 bf256 aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list>
+                    <list>none des 3des bf128 bf256 aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list>
                   </completionHelp>
+                  <valueHelp>
+                    <format>none</format>
+                    <description>Disable encryption</description>
+                  </valueHelp>
                   <valueHelp>
                     <format>des</format>
                     <description>DES algorithm</description>
@@ -106,7 +110,7 @@
                     <description>AES algorithm with 256-bit key GCM</description>
                   </valueHelp>
                   <constraint>
-                    <regex>(des|3des|bf128|bf256|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
+                    <regex>(none|des|3des|bf128|bf256|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
                   </constraint>
                 </properties>
               </leafNode>
@@ -114,8 +118,12 @@
                 <properties>
                   <help>Cipher negotiation list for use in server or client mode</help>
                   <completionHelp>
-                    <list>des 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list>
+                    <list>none des 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list>
                   </completionHelp>
+                  <valueHelp>
+                    <format>none</format>
+                    <description>Disable encryption</description>
+                  </valueHelp>
                   <valueHelp>
                     <format>des</format>
                     <description>DES algorithm</description>
@@ -149,7 +157,7 @@
                     <description>AES algorithm with 256-bit key GCM</description>
                   </valueHelp>
                   <constraint>
-                    <regex>(des|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
+                    <regex>(none|des|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex>
                   </constraint>
                   <multi/>
                 </properties>
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 <marcus.hoff@ring2.dk>
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')

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 <christian@poessinger.com>
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')

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 <christian@poessinger.com>
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')

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 @@
           </leafNode>
           #include <include/vif.xml.i>
           #include <include/vif-s.xml.i>
+          <leafNode name="wds">
+            <properties>
+              <help>Enable WDS (Wireless Distribution System)</help>
+              <valueless/>
+            </properties>
+          </leafNode>
         </children>
       </tagNode>
     </children>
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 <christian@poessinger.com>
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')

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 @@
             <children>
               <leafNode name="cache-size">
                 <properties>
-                  <help>DNS forwarding cache size</help>
+                  <help>DNS forwarding cache size (default: 10000)</help>
                   <valueHelp>
                     <format>0-10000</format>
                     <description>DNS forwarding cache size</description>
@@ -25,6 +25,7 @@
                     <validator name="numeric" argument="--range 0-10000"/>
                   </constraint>
                 </properties>
+                <defaultValue>10000</defaultValue>
               </leafNode>
               <leafNode name="dhcp">
                 <properties>
@@ -37,7 +38,7 @@
               </leafNode>
               <leafNode name="dnssec">
                 <properties>
-                  <help>DNSSEC mode</help>
+                  <help>DNSSEC mode (default: process-no-validate)</help>
                   <completionHelp>
                     <list>off process-no-validate process log-fail validate</list>
                   </completionHelp>
@@ -62,9 +63,10 @@
                     <description>Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses.</description>
                   </valueHelp>
                   <constraint>
-                    <regex>(off|process-no-validate|process|log-fail|validate)</regex>
+                    <regex>^(off|process-no-validate|process|log-fail|validate)$</regex>
                   </constraint>
                 </properties>
+                <defaultValue>process-no-validate</defaultValue>
               </leafNode>
               <tagNode name="domain">
                 <properties>
@@ -146,7 +148,7 @@
               </leafNode>
               <leafNode name="negative-ttl">
                 <properties>
-                  <help>Maximum amount of time negative entries are cached</help>
+                  <help>Maximum amount of time negative entries are cached (default: 3600)</help>
                   <valueHelp>
                     <format>0-7200</format>
                     <description>Seconds to cache NXDOMAIN entries</description>
@@ -155,6 +157,7 @@
                     <validator name="numeric" argument="--range 0-7200"/>
                   </constraint>
                 </properties>
+                <defaultValue>3600</defaultValue>
               </leafNode>
               <leafNode name="name-server">
                 <properties>
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': {
 #       '<str zone>': {
-#           'nslist': ['<str nameserver>', ...],
-#           'addNTA': <bool>,
-#           'recursion-desired': <bool>
+#           'server': ['<str nameserver>', ...],
+#           'addnta': <bool>,
+#           'recursion_desired': <bool>
 #         }
 #       ...
 #     }
@@ -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