Skip to content

Commit

Permalink
wireless: T6318: move country-code to a system wide configuration
Browse files Browse the repository at this point in the history
Wireless devices are subject to regulations issued by authorities. For any
given AP or router, there will most likely be no case where one wireless NIC is
located in one country and another wireless NIC in the same device is located
in another country, resulting in different regulatory domains to apply to the
same box.

Currently, wireless regulatory domains in VyOS need to be configured per-NIC:
  set interfaces wireless wlan0 country-code us

This leads to several side-effects:
* When operating multiple WiFi NICs, they all can have different regulatory
  domains configured which might offend legislation.
* Some NICs need additional entries to /etc/modprobe.d/cfg80211.conf to apply
  regulatory domain settings, such as: "options cfg80211 ieee80211_regdom=US"
  This is true for the Compex WLE600VX. This setting cannot be done
  per-interface.

Migrate the first found wireless module country-code from the wireless
interface CLI to: "system wireless country-code"
  • Loading branch information
c-po committed Jun 15, 2024
1 parent f3d3b0f commit 6fe2217
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 43 deletions.
3 changes: 3 additions & 0 deletions data/config-mode-dependencies/vyos-1x.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,8 @@
"wireguard": ["interfaces_wireguard"],
"wireless": ["interfaces_wireless"],
"wwan": ["interfaces_wwan"]
},
"system_wireless": {
"wireless": ["interfaces_wireless"]
}
}
1 change: 1 addition & 0 deletions data/configd-include.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 -->
20 changes: 0 additions & 20 deletions interface-definitions/interfaces_wireless.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
36 changes: 36 additions & 0 deletions interface-definitions/system_wireless.xml.in
Original file line number Diff line number Diff line change
@@ -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>
24 changes: 24 additions & 0 deletions smoketest/config-tests/wireless-basic
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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 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'
61 changes: 61 additions & 0 deletions smoketest/configs/wireless-basic
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
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"
}
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
50 changes: 30 additions & 20 deletions smoketest/scripts/cli/test_interfaces_wireless.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -54,6 +66,8 @@ def setUpClass(cls):
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()
Expand All @@ -74,7 +88,6 @@ def test_wireless_hostapd_config(self):
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
Expand Down Expand Up @@ -150,7 +163,7 @@ def test_wireless_hostapd_wpa_config(self):
# Only set the hostapd (access-point) options
interface = 'wlan0'
phy = 'phy0'
ssid = 'ssid'
ssid = 'VyOS-SMOKETEST'
channel = '1'
wpa_key = 'VyOSVyOSVyOS'
mode = 'n'
Expand All @@ -160,21 +173,20 @@ def test_wireless_hostapd_wpa_config(self):
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])

Expand Down Expand Up @@ -222,7 +234,6 @@ def test_wireless_access_point_bridge(self):
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()
Expand Down Expand Up @@ -260,7 +271,6 @@ def test_wireless_security_station_address(self):
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'])

Expand Down Expand Up @@ -295,4 +305,4 @@ def test_wireless_security_station_address(self):

if __name__ == '__main__':
check_kmod('mac80211_hwsim')
unittest.main(verbosity=2)
unittest.main(verbosity=2, failfast=True)
11 changes: 9 additions & 2 deletions src/conf_mode/interfaces_wireless.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
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 -
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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!')
Expand Down
64 changes: 64 additions & 0 deletions src/conf_mode/system_wireless.py
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit 6fe2217

Please sign in to comment.