summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2024-06-17 17:31:40 +0200
committerGitHub <noreply@github.com>2024-06-17 17:31:40 +0200
commit444728ba6256fa7d39bedf9c4be83c098a9cc1ae (patch)
treef7e3168df466976acfdea9467711899b39c6907d
parent2224890a5be141daf9dd5613de0ebb81bb706db0 (diff)
parent9e22ab6b2aee48029d3455f65880e45c558cf1da (diff)
downloadvyos-1x-444728ba6256fa7d39bedf9c4be83c098a9cc1ae.tar.gz
vyos-1x-444728ba6256fa7d39bedf9c4be83c098a9cc1ae.zip
Merge pull request #3656 from c-po/wireless-regdomain
wireless: T6318: move country-code to a system wide configuration
-rw-r--r--data/config-mode-dependencies/vyos-1x.json3
-rw-r--r--data/configd-include.json1
-rw-r--r--interface-definitions/include/version/interfaces-version.xml.i2
-rw-r--r--interface-definitions/interfaces_wireless.xml.in20
-rw-r--r--interface-definitions/system_wireless.xml.in36
-rw-r--r--smoketest/config-tests/wireless-basic25
-rw-r--r--smoketest/configs/wireless-basic66
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py50
-rwxr-xr-xsrc/conf_mode/interfaces_wireless.py11
-rw-r--r--src/conf_mode/system_wireless.py64
-rwxr-xr-xsrc/migration-scripts/interfaces/32-to-3357
11 files changed, 292 insertions, 43 deletions
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index 3f381169b..9623948c2 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -59,5 +59,8 @@
"wireguard": ["interfaces_wireguard"],
"wireless": ["interfaces_wireless"],
"wwan": ["interfaces_wwan"]
+ },
+ "system_wireless": {
+ "wireless": ["interfaces_wireless"]
}
}
diff --git a/data/configd-include.json b/data/configd-include.json
index dcee50306..a00eb4bcb 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -102,6 +102,7 @@
"system_task-scheduler.py",
"system_timezone.py",
"system_update-check.py",
+"system_wireless.py",
"vpn_ipsec.py",
"vpn_l2tp.py",
"vpn_openconnect.py",
diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i
index 854e60f4e..2915b318e 100644
--- a/interface-definitions/include/version/interfaces-version.xml.i
+++ b/interface-definitions/include/version/interfaces-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/interfaces-version.xml.i -->
-<syntaxVersion component='interfaces' version='32'></syntaxVersion>
+<syntaxVersion component='interfaces' version='33'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces_wireless.xml.in b/interface-definitions/interfaces_wireless.xml.in
index 458f7ebb3..b3fc2302d 100644
--- a/interface-definitions/interfaces_wireless.xml.in
+++ b/interface-definitions/interfaces_wireless.xml.in
@@ -451,26 +451,6 @@
</properties>
<defaultValue>0</defaultValue>
</leafNode>
- <leafNode name="country-code">
- <properties>
- <help>Indicate country in which device is operating</help>
- <completionHelp>
- <list>00 ad ae af ai al am an ar as at au aw az ba bb bd be bf bg bh bl bm bn bo br bs bt by bz ca cf ch ci cl cn co cr cu cx cy cz de dk dm do dz ec ee eg es et fi fm fr gb gd ge gf gh gl gp gr gt gu gy hk hn hr ht hu id ie il in ir is it jm jo jp ke kh kn kp kr kw ky kz lb lc li lk ls lt lu lv ma mc md me mf mh mk mn mo mp mq mr mt mu mv mw mx my ng ni nl no np nz om pa pe pf pg ph pk pl pm pr pt pw py qa re ro rs ru rw sa se sg si sk sn sr sv sy tc td tg th tn tr tt tw tz ua ug us uy uz vc ve vi vn vu wf ws ye yt za zw</list>
- </completionHelp>
- <valueHelp>
- <format>00</format>
- <description>World regulatory domain</description>
- </valueHelp>
- <valueHelp>
- <format>txt</format>
- <description>ISO/IEC 3166-1 Country Code</description>
- </valueHelp>
- <constraint>
- <regex>(00|ad|ae|af|ai|al|am|an|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bl|bm|bn|bo|br|bs|bt|by|bz|ca|cf|ch|ci|cl|cn|co|cr|cu|cx|cy|cz|de|dk|dm|do|dz|ec|ee|eg|es|et|fi|fm|fr|gb|gd|ge|gf|gh|gl|gp|gr|gt|gu|gy|hk|hn|hr|ht|hu|id|ie|il|in|ir|is|it|jm|jo|jp|ke|kh|kn|kp|kr|kw|ky|kz|lb|lc|li|lk|ls|lt|lu|lv|ma|mc|md|me|mf|mh|mk|mn|mo|mp|mq|mr|mt|mu|mv|mw|mx|my|ng|ni|nl|no|np|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pr|pt|pw|py|qa|re|ro|rs|ru|rw|sa|se|sg|si|sk|sn|sr|sv|sy|tc|td|tg|th|tn|tr|tt|tw|tz|ua|ug|us|uy|uz|vc|ve|vi|vn|vu|wf|ws|ye|yt|za|zw)</regex>
- </constraint>
- <constraintErrorMessage>Invalid ISO/IEC 3166-1 Country Code</constraintErrorMessage>
- </properties>
- </leafNode>
#include <include/generic-description.xml.i>
#include <include/interface/dhcp-options.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
diff --git a/interface-definitions/system_wireless.xml.in b/interface-definitions/system_wireless.xml.in
new file mode 100644
index 000000000..834f8b624
--- /dev/null
+++ b/interface-definitions/system_wireless.xml.in
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="system">
+ <children>
+ <node name="wireless" owner="${vyos_conf_scripts_dir}/system_wireless.py">
+ <properties>
+ <help>Wireless (IEEE-802.11) subsystem settings</help>
+ <!-- must be before interface wireless, check /opt/vyatta/sbin/priority.pl -->
+ <priority>317</priority>
+ </properties>
+ <children>
+ <leafNode name="country-code">
+ <properties>
+ <help>Indicate country in which device is operating</help>
+ <completionHelp>
+ <list>00 ad ae af ai al am an ar as at au aw az ba bb bd be bf bg bh bl bm bn bo br bs bt by bz ca cf ch ci cl cn co cr cu cx cy cz de dk dm do dz ec ee eg es et fi fm fr gb gd ge gf gh gl gp gr gt gu gy hk hn hr ht hu id ie il in ir is it jm jo jp ke kh kn kp kr kw ky kz lb lc li lk ls lt lu lv ma mc md me mf mh mk mn mo mp mq mr mt mu mv mw mx my ng ni nl no np nz om pa pe pf pg ph pk pl pm pr pt pw py qa re ro rs ru rw sa se sg si sk sn sr sv sy tc td tg th tn tr tt tw tz ua ug us uy uz vc ve vi vn vu wf ws ye yt za zw</list>
+ </completionHelp>
+ <valueHelp>
+ <format>00</format>
+ <description>World regulatory domain</description>
+ </valueHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>ISO/IEC 3166-1 Country Code</description>
+ </valueHelp>
+ <constraint>
+ <regex>(00|ad|ae|af|ai|al|am|an|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bl|bm|bn|bo|br|bs|bt|by|bz|ca|cf|ch|ci|cl|cn|co|cr|cu|cx|cy|cz|de|dk|dm|do|dz|ec|ee|eg|es|et|fi|fm|fr|gb|gd|ge|gf|gh|gl|gp|gr|gt|gu|gy|hk|hn|hr|ht|hu|id|ie|il|in|ir|is|it|jm|jo|jp|ke|kh|kn|kp|kr|kw|ky|kz|lb|lc|li|lk|ls|lt|lu|lv|ma|mc|md|me|mf|mh|mk|mn|mo|mp|mq|mr|mt|mu|mv|mw|mx|my|ng|ni|nl|no|np|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pr|pt|pw|py|qa|re|ro|rs|ru|rw|sa|se|sg|si|sk|sn|sr|sv|sy|tc|td|tg|th|tn|tr|tt|tw|tz|ua|ug|us|uy|uz|vc|ve|vi|vn|vu|wf|ws|ye|yt|za|zw)</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid ISO/IEC 3166-1 Country Code</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/smoketest/config-tests/wireless-basic b/smoketest/config-tests/wireless-basic
new file mode 100644
index 000000000..5d7bc8aaa
--- /dev/null
+++ b/smoketest/config-tests/wireless-basic
@@ -0,0 +1,25 @@
+set interfaces ethernet eth0 duplex 'auto'
+set interfaces ethernet eth0 speed 'auto'
+set interfaces ethernet eth1 duplex 'auto'
+set interfaces ethernet eth1 speed 'auto'
+set interfaces wireless wlan0 address '192.168.0.1/24'
+set interfaces wireless wlan0 channel '1'
+set interfaces wireless wlan0 mode 'n'
+set interfaces wireless wlan0 security wpa cipher 'CCMP'
+set interfaces wireless wlan0 security wpa mode 'wpa2'
+set interfaces wireless wlan0 security wpa passphrase '12345678'
+set interfaces wireless wlan0 ssid 'VyOS'
+set interfaces wireless wlan0 type 'access-point'
+set interfaces wireless wlan1 address '192.168.1.1/24'
+set interfaces wireless wlan1 channel '2'
+set interfaces wireless wlan1 mode 'n'
+set interfaces wireless wlan1 ssid 'VyOS-PUBLIC'
+set interfaces wireless wlan1 type 'access-point'
+set system config-management commit-revisions '200'
+set system console device ttyS0 speed 115200
+set system domain-name 'dev.vyos.net'
+set system host-name 'WR1'
+set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
+set system wireless country-code 'es'
+set system syslog global facility all level 'info'
+set system syslog global facility local7 level 'debug'
diff --git a/smoketest/configs/wireless-basic b/smoketest/configs/wireless-basic
new file mode 100644
index 000000000..9cc34f5bc
--- /dev/null
+++ b/smoketest/configs/wireless-basic
@@ -0,0 +1,66 @@
+interfaces {
+ ethernet eth0 {
+ duplex "auto"
+ speed "auto"
+ }
+ ethernet eth1 {
+ duplex "auto"
+ speed "auto"
+ }
+ wireless wlan0 {
+ address 192.168.0.1/24
+ channel 1
+ country-code es
+ mode n
+ security {
+ wpa {
+ cipher CCMP
+ mode wpa2
+ passphrase 12345678
+ }
+ }
+ ssid VyOS
+ type access-point
+ }
+ wireless wlan1 {
+ address 192.168.1.1/24
+ channel 2
+ country-code de
+ mode n
+ ssid VyOS-PUBLIC
+ type access-point
+ }
+}
+system {
+ config-management {
+ commit-revisions "200"
+ }
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ domain-name "dev.vyos.net"
+ host-name "WR1"
+ login {
+ user vyos {
+ authentication {
+ encrypted-password "$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0"
+ }
+ }
+ }
+ syslog {
+ global {
+ facility all {
+ level "info"
+ }
+ facility local7 {
+ level "debug"
+ }
+ }
+ }
+}
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@8:dhcpv6-server@1:dns-dynamic@4:dns-forwarding@4:firewall@15:flow-accounting@1:https@6:ids@1:interfaces@32:ipoe-server@3:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@1:nat@8:nat66@3:ntp@3:openconnect@3:ospf@2:pim@1:policy@8:pppoe-server@10:pptp@5:qos@2:quagga@11:reverse-proxy@1:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@27:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2"
+// Release version: 1.4.0
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index b45754cae..2806b7201 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -32,19 +32,31 @@ def get_config_value(interface, key):
tmp = re.findall(f'{key}=+(.*)', tmp)
return tmp[0]
+wifi_cc_path = ['system', 'wireless', 'country-code']
+
class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
def setUpClass(cls):
cls._base_path = ['interfaces', 'wireless']
cls._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', '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', 'country-code se',
- 'type access-point', 'address 192.0.2.13/30', 'channel 0'],
+ 'wlan0': ['physical-device phy0',
+ 'ssid VyOS-WIFI-0',
+ 'type station',
+ 'address 192.0.2.1/30'],
+ 'wlan1': ['physical-device phy0',
+ 'ssid VyOS-WIFI-1',
+ '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',
+ 'type access-point',
+ 'address 192.0.2.13/30',
+ 'channel 0'],
}
cls._interfaces = list(cls._options)
# call base-classes classmethod
@@ -54,6 +66,8 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
cls._test_ipv6 = False
cls._test_vlan = False
+ cls.cli_set(cls, wifi_cc_path + ['es'])
+
def test_wireless_add_single_ip_address(self):
# derived method to check if member interfaces are enslaved properly
super().test_add_single_ip_address()
@@ -74,7 +88,6 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
ssid = 'ssid'
self.cli_set(self._base_path + [interface, 'ssid', ssid])
- self.cli_set(self._base_path + [interface, 'country-code', 'se'])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
# auto-powersave is special
@@ -150,7 +163,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
# Only set the hostapd (access-point) options
interface = 'wlan0'
phy = 'phy0'
- ssid = 'ssid'
+ ssid = 'VyOS-SMOKETEST'
channel = '1'
wpa_key = 'VyOSVyOSVyOS'
mode = 'n'
@@ -160,21 +173,20 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_set(self._base_path + [interface, 'mode', mode])
+ # Country-Code must be set
+ self.cli_delete(wifi_cc_path)
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(wifi_cc_path + [country])
+
# SSID must be set
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(self._base_path + [interface, 'ssid', ssid])
# Channel must be set
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
self.cli_set(self._base_path + [interface, 'channel', channel])
- # Country-Code must be set
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
- self.cli_set(self._base_path + [interface, 'country-code', country])
-
self.cli_set(self._base_path + [interface, 'security', 'wpa', 'mode', 'wpa2'])
self.cli_set(self._base_path + [interface, 'security', 'wpa', 'passphrase', wpa_key])
@@ -222,7 +234,6 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_set(bridge_path + ['member', 'interface', interface])
self.cli_set(self._base_path + [interface, 'ssid', ssid])
- self.cli_set(self._base_path + [interface, 'country-code', 'se'])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_commit()
@@ -260,7 +271,6 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
deny_mac = ['00:00:00:00:de:01', '00:00:00:00:de:02', '00:00:00:00:de:03', '00:00:00:00:de:04']
self.cli_set(self._base_path + [interface, 'ssid', ssid])
- self.cli_set(self._base_path + [interface, 'country-code', 'se'])
self.cli_set(self._base_path + [interface, 'type', 'access-point'])
self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'accept'])
@@ -295,4 +305,4 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
if __name__ == '__main__':
check_kmod('mac80211_hwsim')
- unittest.main(verbosity=2)
+ unittest.main(verbosity=2, failfast=True)
diff --git a/src/conf_mode/interfaces_wireless.py b/src/conf_mode/interfaces_wireless.py
index c0a17c0bc..998ff9dba 100755
--- a/src/conf_mode/interfaces_wireless.py
+++ b/src/conf_mode/interfaces_wireless.py
@@ -44,6 +44,8 @@ hostapd_conf = '/run/hostapd/{ifname}.conf'
hostapd_accept_station_conf = '/run/hostapd/{ifname}_station_accept.conf'
hostapd_deny_station_conf = '/run/hostapd/{ifname}_station_deny.conf'
+country_code_path = ['system', 'wireless', 'country-code']
+
def find_other_stations(conf, base, ifname):
"""
Only one wireless interface per phy can be in station mode -
@@ -78,7 +80,11 @@ def get_config(config=None):
conf = Config()
base = ['interfaces', 'wireless']
- ifname, wifi = get_interface_dict(conf, base)
+ _, wifi = get_interface_dict(conf, base)
+
+ # retrieve global Wireless regulatory domain setting
+ if conf.exists(country_code_path):
+ wifi['country_code'] = conf.return_value(country_code_path)
if 'deleted' not in wifi:
# then get_interface_dict provides default keys
@@ -131,7 +137,8 @@ def verify(wifi):
if wifi['type'] == 'access-point':
if 'country_code' not in wifi:
- raise ConfigError('Wireless country-code is mandatory')
+ raise ConfigError(f'Wireless country-code is mandatory, use: '\
+ f'"set {" ".join(country_code_path)}"!')
if 'channel' not in wifi:
raise ConfigError('Wireless channel must be configured!')
diff --git a/src/conf_mode/system_wireless.py b/src/conf_mode/system_wireless.py
new file mode 100644
index 000000000..e0ca0ab8e
--- /dev/null
+++ b/src/conf_mode/system_wireless.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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/>.
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['system', 'wireless']
+ interface_base = ['interfaces', 'wireless']
+
+ wireless = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True)
+
+
+ if conf.exists(interface_base):
+ wireless['interfaces'] = conf.list_nodes(interface_base)
+ for interface in wireless['interfaces']:
+ set_dependents('wireless', conf, interface)
+
+ return wireless
+
+def verify(wireless):
+ pass
+
+def generate(wireless):
+ pass
+
+def apply(wireless):
+ if 'interfaces' in wireless:
+ call_dependents()
+ pass
+
+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/32-to-33 b/src/migration-scripts/interfaces/32-to-33
new file mode 100755
index 000000000..caf588474
--- /dev/null
+++ b/src/migration-scripts/interfaces/32-to-33
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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/>.
+#
+# T6318: WiFi country-code should be set system-wide instead of per-device
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['interfaces', 'wireless']
+
+config = ConfigTree(config_file)
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+installed = False
+for interface in config.list_nodes(base):
+ cc_path = base + [interface, 'country-code']
+ if config.exists(cc_path):
+ tmp = config.return_value(cc_path)
+ config.delete(cc_path)
+
+ # There can be only ONE wireless country-code per device, everything
+ # else makes no sense as a WIFI router can not operate in two
+ # different countries
+ if not installed:
+ config.set(['system', 'wireless', 'country-code'], value=tmp)
+ installed = True
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)