From f8a4314503072af90d8a4ab443898be7c73f3895 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Tue, 3 Nov 2020 17:19:04 +0100
Subject: wireless: T3043: rename "wpa mode both" to "wpa+wpa2"

---
 src/migration-scripts/interfaces/13-to-14 | 51 +++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)
 create mode 100755 src/migration-scripts/interfaces/13-to-14

(limited to 'src')

diff --git a/src/migration-scripts/interfaces/13-to-14 b/src/migration-scripts/interfaces/13-to-14
new file mode 100755
index 000000000..545b57ab2
--- /dev/null
+++ b/src/migration-scripts/interfaces/13-to-14
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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/>.
+
+# Rename Wireless interface security mode 'both' to 'wpa+wpa2'
+
+import os
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if __name__ == '__main__':
+    if (len(argv) < 1):
+        print("Must specify file name!")
+        exit(1)
+
+    file_name = argv[1]
+    with open(file_name, 'r') as f:
+        config_file = f.read()
+
+    config = ConfigTree(config_file)
+    base = ['interfaces', 'wireless']
+    if not config.exists(base):
+        # Nothing to do
+        exit(0)
+
+    for wifi in config.list_nodes(base):
+        sec_mode = base + [wifi, 'security', 'wpa', 'mode']
+        if config.exists(sec_mode):
+            mode = config.return_value(sec_mode)
+            if mode == 'both':
+                config.set(sec_mode, value='wpa+wpa2', replace=True)
+
+    try:
+        with open(file_name, 'w') as f:
+            f.write(config.to_string())
+    except OSError as e:
+        print("Failed to save the modified config: {}".format(e))
+        exit(1)
-- 
cgit v1.2.3


From 01e3d935a34f38e48364724da1cad7cbbb18576e Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Tue, 3 Nov 2020 18:22:55 +0100
Subject: wireless: T3042: move wifi-regulatory-domain into individual
 interface definition

---
 data/configd-include.json                         |  3 +-
 data/templates/wifi/hostapd.conf.tmpl             | 12 +--
 interface-definitions/interfaces-wireless.xml.in  | 59 +++++++--------
 smoketest/scripts/cli/test_interfaces_wireless.py | 19 ++++-
 src/conf_mode/interfaces-wireless.py              |  8 +-
 src/conf_mode/system-wifi-regdom.py               | 90 -----------------------
 src/migration-scripts/interfaces/13-to-14         | 12 ++-
 7 files changed, 61 insertions(+), 142 deletions(-)
 delete mode 100755 src/conf_mode/system-wifi-regdom.py

(limited to 'src')

diff --git a/data/configd-include.json b/data/configd-include.json
index 95aef65ad..da6fb915f 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -48,7 +48,6 @@
 "system-options.py",
 "system-syslog.py",
 "system-timezone.py",
-"system-wifi-regdom.py",
 "system_console.py",
 "system_lcd.py",
 "task_scheduler.py",
@@ -59,4 +58,4 @@
 "vrf.py",
 "vrrp.py",
 "vyos_cert.py"
-]
\ No newline at end of file
+]
diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl
index 95837da95..16d9f7c98 100644
--- a/data/templates/wifi/hostapd.conf.tmpl
+++ b/data/templates/wifi/hostapd.conf.tmpl
@@ -72,18 +72,18 @@ ssid={{ ssid }}
 channel={{ channel }}
 {% endif %}
 
-{% if mode %}
+{% if mode is defined and mode is not none %}
 # Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz),
 # g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used
 # with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this
-# needs to be set to hw_mode=a. For IEEE 802.11ax (HE) on 6 GHz this needs
-# to be set to hw_mode=a. When using ACS (see channel parameter), a
+# needs to be set to hw_mode a. For IEEE 802.11ax (HE) on 6 GHz this needs
+# to be set to hw_mode a. When using ACS (see channel parameter), a
 # special value "any" can be used to indicate that any support band can be used.
 # This special case is currently supported only with drivers with which
 # offloaded ACS is used.
-{%   if 'n' in mode %}
+{%   if mode == 'n' %}
 hw_mode=g
-{%   elif 'ac' in mode %}
+{%   elif mode == 'ac' %}
 hw_mode=a
 ieee80211h=1
 ieee80211ac=1
@@ -533,7 +533,7 @@ wep_key{{ loop.index -1 }}={{ security.wep.key }}
 # bit0 = WPA
 # bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled)
 # Note that WPA3 is also configured with bit1 since it uses RSN just like WPA2.
-# In other words, for WPA3, wpa=2 is used the configuration (and
+# In other words, for WPA3, wpa 2 is used the configuration (and
 # wpa_key_mgmt=SAE for WPA3-Personal instead of wpa_key_mgmt=WPA-PSK).
 {%   if security.wpa.mode is defined %}
 {%     if security.wpa.mode == 'wpa+wpa2' %}
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index c805e488a..78c40d876 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -58,7 +58,7 @@
                         <description>Supported channel set width both 20 MHz and 40 MHz with secondary channel below primary channel</description>
                       </valueHelp>
                       <constraint>
-                        <regex>(ht20|ht40\+|ht40-)</regex>
+                        <regex>^(ht20|ht40\+|ht40-)$</regex>
                       </constraint>
                       <multi/>
                     </properties>
@@ -108,7 +108,7 @@
                         <description>Set maximum A-MSDU length to 7935 octets</description>
                       </valueHelp>
                       <constraint>
-                        <regex>(3839|7935)</regex>
+                        <regex>^(3839|7935)$</regex>
                       </constraint>
                     </properties>
                   </leafNode>
@@ -127,7 +127,7 @@
                         <description>Short GI for 40 MHz</description>
                       </valueHelp>
                       <constraint>
-                        <regex>(20|40)</regex>
+                        <regex>^(20|40)$</regex>
                       </constraint>
                       <multi/>
                     </properties>
@@ -147,7 +147,7 @@
                         <description>DYNAMIC Spatial Multiplexing (SM) Power Save</description>
                       </valueHelp>
                       <constraint>
-                        <regex>(static|dynamic)</regex>
+                        <regex>^(static|dynamic)$</regex>
                       </constraint>
                     </properties>
                   </leafNode>
@@ -164,7 +164,7 @@
                             <description>Number of spacial streams that can use RX STBC</description>
                           </valueHelp>
                           <constraint>
-                            <regex>[1-3]+</regex>
+                            <regex>^[1-3]+$</regex>
                           </constraint>
                           <constraintErrorMessage>Invalid capability item</constraintErrorMessage>
                         </properties>
@@ -243,7 +243,7 @@
                         <description>Support for operation as multi user beamformee</description>
                       </valueHelp>
                       <constraint>
-                        <regex>(single-user-beamformer|single-user-beamformee|multi-user-beamformer|multi-user-beamformee)</regex>
+                        <regex>^(single-user-beamformer|single-user-beamformee|multi-user-beamformer|multi-user-beamformee)$</regex>
                       </constraint>
                       <multi/>
                     </properties>
@@ -329,7 +329,7 @@
                         <description>Station can provide VHT MFB in response to VHT MRQ and unsolicited VHT MFB</description>
                       </valueHelp>
                       <constraint>
-                        <regex>(unsolicited|both)</regex>
+                        <regex>^(unsolicited|both)$</regex>
                       </constraint>
                       <constraintErrorMessage>Invalid capability item</constraintErrorMessage>
                     </properties>
@@ -361,7 +361,7 @@
                         <description>ncrease Maximum MPDU length to 11454 octets</description>
                       </valueHelp>
                       <constraint>
-                        <regex>(7991|11454)</regex>
+                        <regex>^(7991|11454)$</regex>
                       </constraint>
                     </properties>
                   </leafNode>
@@ -380,7 +380,7 @@
                         <description>Short GI for 160 MHz</description>
                       </valueHelp>
                       <constraint>
-                        <regex>(80|160)</regex>
+                        <regex>^(80|160)$</regex>
                       </constraint>
                       <multi/>
                     </properties>
@@ -398,7 +398,7 @@
                             <description>Number of spacial streams that can use RX STBC</description>
                           </valueHelp>
                           <constraint>
-                            <regex>[1-4]+</regex>
+                            <regex>^[1-4]+$</regex>
                           </constraint>
                           <constraintErrorMessage>Invalid capability item</constraintErrorMessage>
                         </properties>
@@ -443,6 +443,22 @@
               </constraint>
             </properties>
           </leafNode>
+          <leafNode name="country-code">
+            <properties>
+              <help>Indicate country in which device is operating</help>
+              <completionHelp>
+                <list>US EU JP DE UK CN ES FR RU</list>
+              </completionHelp>
+              <valueHelp>
+                <format>&lt;code%gt;</format>
+                <description>ISO/IEC 3166-1 Country Code</description>
+              </valueHelp>
+              <constraint>
+                <regex>^[A-Z][A-Z]$</regex>
+              </constraint>
+              <constraintErrorMessage>Invalid ISO/IEC 3166-1 Country Code</constraintErrorMessage>
+            </properties>
+          </leafNode>
           #include <include/interface-description.xml.i>
           #include <include/dhcp-options.xml.i>
           #include <include/dhcpv6-options.xml.i>
@@ -520,7 +536,7 @@
                 <description>MFP enforced</description>
               </valueHelp>
               <constraint>
-                <regex>(disabled|optional|required)</regex>
+                <regex>^(disabled|optional|required)$</regex>
               </constraint>
             </properties>
           </leafNode>
@@ -782,25 +798,4 @@
       </tagNode>
     </children>
   </node>
-  <node name="system">
-    <children>
-      <leafNode name="wifi-regulatory-domain" owner="${vyos_conf_scripts_dir}/system-wifi-regdom.py">
-        <properties>
-          <help>Wireless regulatory domain (mandatory)</help>
-          <priority>305</priority>
-          <completionHelp>
-            <list>US EU JP DE UK CN</list>
-          </completionHelp>
-          <valueHelp>
-            <format>&lt;code%gt;</format>
-            <description>Country code (ISO/IEC 3166-1)</description>
-          </valueHelp>
-          <constraint>
-            <regex>[A-Z][A-Z]$</regex>
-          </constraint>
-          <constraintErrorMessage>invalid country code</constraintErrorMessage>
-        </properties>
-      </leafNode>
-    </children>
-  </node>
 </interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index a62b44ee5..65cf127ce 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -38,15 +38,14 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
         self._options = {
             'wlan0':  ['physical-device phy0', 'ssid VyOS-WIFI-0',
                        'type station', 'address 192.0.2.1/30'],
-            'wlan1':  ['physical-device phy0', 'ssid VyOS-WIFI-1',
+            'wlan1':  ['physical-device phy0', 'ssid VyOS-WIFI-1', 'country-code SE',
                        'type access-point', 'address 192.0.2.5/30', 'channel 0'],
             'wlan10': ['physical-device phy1', 'ssid VyOS-WIFI-2',
                        'type station', 'address 192.0.2.9/30'],
-            'wlan11': ['physical-device phy1', 'ssid VyOS-WIFI-3',
+            'wlan11': ['physical-device phy1', 'ssid VyOS-WIFI-3', 'country-code SE',
                        'type access-point', 'address 192.0.2.13/30', 'channel 0'],
         }
         self._interfaces = list(self._options)
-        self.session.set(['system', 'wifi-regulatory-domain', 'SE'])
 
     def test_add_address_single(self):
         """ derived method to check if member interfaces are enslaved properly """
@@ -75,6 +74,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
         self.session.set(self._base_path + [interface, 'ssid', ssid])
         self.session.set(self._base_path + [interface, 'type', 'access-point'])
         self.session.set(self._base_path + [interface, 'channel', channel])
+        self.session.set(self._base_path + [interface, 'country-code', 'SE'])
         # auto-powersave is special
         self.session.set(self._base_path + [interface, 'capabilities', 'ht', 'auto-powersave'])
 
@@ -152,10 +152,11 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
         channel = '0'
         wpa_key = 'VyOSVyOSVyOS'
         mode = 'n'
+        country = 'DE'
 
         self.session.set(self._base_path + [interface, 'physical-device', phy])
         self.session.set(self._base_path + [interface, 'type', 'access-point'])
-        self.session.set(self._base_path + [interface, 'mode', 'mode'])
+        self.session.set(self._base_path + [interface, 'mode', mode])
 
         # SSID must be set
         with self.assertRaises(ConfigSessionError):
@@ -167,6 +168,10 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
             self.session.commit()
         self.session.set(self._base_path + [interface, 'channel', channel])
 
+        # Country-Code must be set
+        with self.assertRaises(ConfigSessionError):
+            self.session.commit()
+        self.session.set(self._base_path + [interface, 'country-code', country])
 
         self.session.set(self._base_path + [interface, 'security', 'wpa', 'mode', 'wpa2'])
         self.session.set(self._base_path + [interface, 'security', 'wpa', 'passphrase', wpa_key])
@@ -180,6 +185,8 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
         self.assertEqual(interface, tmp)
 
         tmp = get_config_value(interface, 'hw_mode')
+        # rewrite special mode
+        if mode == 'n': mode = 'g'
         self.assertEqual(mode, tmp)
 
         # WPA key
@@ -196,6 +203,10 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
         tmp = get_config_value(interface, 'channel')
         self.assertEqual(channel, tmp)
 
+        # Country code
+        tmp = get_config_value(interface, 'country_code')
+        self.assertEqual(country, tmp)
+
         # Check for running process
         self.assertTrue(process_named_running('hostapd'))
 
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index a18a21b83..5d723bbfd 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -109,11 +109,6 @@ def get_config(config=None):
 
             if tmp: wifi = dict_merge(tmp, wifi)
 
-    # retrieve configured regulatory domain
-    conf.set_level(['system'])
-    if conf.exists(['wifi-regulatory-domain']):
-        wifi['country_code'] = conf.return_value(['wifi-regulatory-domain'])
-
     # Only one wireless interface per phy can be in station mode
     tmp = find_other_stations(conf, base, wifi['ifname'])
     if tmp: wifi['station_interfaces'] = tmp
@@ -144,8 +139,7 @@ def verify(wifi):
 
     if wifi['type'] == 'access-point':
         if 'country_code' not in wifi:
-            raise ConfigError('Wireless regulatory domain is mandatory,\n' \
-                              'use "set system wifi-regulatory-domain" for configuration.')
+            raise ConfigError('Wireless country-code is mandatory')
 
         if 'channel' not in wifi:
             raise ConfigError('Wireless channel must be configured!')
diff --git a/src/conf_mode/system-wifi-regdom.py b/src/conf_mode/system-wifi-regdom.py
deleted file mode 100755
index 874f93923..000000000
--- a/src/conf_mode/system-wifi-regdom.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019-2020 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 os
-
-from copy import deepcopy
-from sys import exit
-
-from vyos.config import Config
-from vyos import ConfigError
-from vyos.template import render
-
-from vyos import airbag
-airbag.enable()
-
-config_80211_file='/etc/modprobe.d/cfg80211.conf'
-config_crda_file='/etc/default/crda'
-
-default_config_data = {
-    'regdom' : '',
-    'deleted' : False
-}
-
-def get_config(config=None):
-    regdom = deepcopy(default_config_data)
-    if config:
-        conf = config
-    else:
-        conf = Config()
-    base = ['system', 'wifi-regulatory-domain']
-
-    # Check if interface has been removed
-    if not conf.exists(base):
-        regdom['deleted'] = True
-        return regdom
-    else:
-        regdom['regdom'] = conf.return_value(base)
-
-    return regdom
-
-def verify(regdom):
-    if regdom['deleted']:
-        return None
-
-    if not regdom['regdom']:
-        raise ConfigError("Wireless regulatory domain is mandatory.")
-
-    return None
-
-def generate(regdom):
-    print("Changing the wireless regulatory domain requires a system reboot.")
-
-    if regdom['deleted']:
-        if os.path.isfile(config_80211_file):
-            os.unlink(config_80211_file)
-
-        if os.path.isfile(config_crda_file):
-            os.unlink(config_crda_file)
-
-        return None
-
-    render(config_80211_file, 'wifi/cfg80211.conf.tmpl', regdom)
-    render(config_crda_file, 'wifi/crda.tmpl', regdom)
-    return None
-
-def apply(regdom):
-    return None
-
-if __name__ == '__main__':
-    try:
-        c = get_config()
-        verify(c)
-        generate(c)
-        apply(c)
-    except ConfigError as e:
-        print(e)
-        exit(1)
diff --git a/src/migration-scripts/interfaces/13-to-14 b/src/migration-scripts/interfaces/13-to-14
index 545b57ab2..fc6d7f443 100755
--- a/src/migration-scripts/interfaces/13-to-14
+++ b/src/migration-scripts/interfaces/13-to-14
@@ -14,7 +14,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-# Rename Wireless interface security mode 'both' to 'wpa+wpa2'
+# T3043: rename Wireless interface security mode 'both' to 'wpa+wpa2'
+# T3043: move "system wifi-regulatory-domain" to indicidual wireless interface
 
 import os
 
@@ -36,6 +37,12 @@ if __name__ == '__main__':
         # Nothing to do
         exit(0)
 
+    country_code = ''
+    cc_cli = ['system', 'wifi-regulatory-domain']
+    if config.exists(cc_cli):
+        country_code = config.return_value(cc_cli)
+        config.delete(cc_cli)
+
     for wifi in config.list_nodes(base):
         sec_mode = base + [wifi, 'security', 'wpa', 'mode']
         if config.exists(sec_mode):
@@ -43,6 +50,9 @@ if __name__ == '__main__':
             if mode == 'both':
                 config.set(sec_mode, value='wpa+wpa2', replace=True)
 
+        if country_code:
+            config.set(base + [wifi, 'country-code'], value=country_code)
+
     try:
         with open(file_name, 'w') as f:
             f.write(config.to_string())
-- 
cgit v1.2.3