summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/interfaces_geneve.py2
-rwxr-xr-xsrc/conf_mode/interfaces_l2tpv3.py2
-rwxr-xr-xsrc/conf_mode/interfaces_openvpn.py17
-rwxr-xr-xsrc/conf_mode/interfaces_vti.py2
-rwxr-xr-xsrc/conf_mode/interfaces_vxlan.py2
-rwxr-xr-xsrc/conf_mode/interfaces_wireless.py104
-rwxr-xr-xsrc/conf_mode/vrf.py6
7 files changed, 97 insertions, 38 deletions
diff --git a/src/conf_mode/interfaces_geneve.py b/src/conf_mode/interfaces_geneve.py
index 769139e0f..007708d4a 100755
--- a/src/conf_mode/interfaces_geneve.py
+++ b/src/conf_mode/interfaces_geneve.py
@@ -24,6 +24,7 @@ from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_bond_bridge_member
+from vyos.configverify import verify_vrf
from vyos.ifconfig import GeneveIf
from vyos.utils.network import interface_exists
from vyos import ConfigError
@@ -59,6 +60,7 @@ def verify(geneve):
verify_mtu_ipv6(geneve)
verify_address(geneve)
+ verify_vrf(geneve)
verify_bond_bridge_member(geneve)
verify_mirror_redirect(geneve)
diff --git a/src/conf_mode/interfaces_l2tpv3.py b/src/conf_mode/interfaces_l2tpv3.py
index e25793543..b9f827bee 100755
--- a/src/conf_mode/interfaces_l2tpv3.py
+++ b/src/conf_mode/interfaces_l2tpv3.py
@@ -24,6 +24,7 @@ from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_bond_bridge_member
+from vyos.configverify import verify_vrf
from vyos.ifconfig import L2TPv3If
from vyos.utils.kernel import check_kmod
from vyos.utils.network import is_addr_assigned
@@ -76,6 +77,7 @@ def verify(l2tpv3):
verify_mtu_ipv6(l2tpv3)
verify_address(l2tpv3)
+ verify_vrf(l2tpv3)
verify_bond_bridge_member(l2tpv3)
verify_mirror_redirect(l2tpv3)
return None
diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py
index 627cc90ba..5bb663a9b 100755
--- a/src/conf_mode/interfaces_openvpn.py
+++ b/src/conf_mode/interfaces_openvpn.py
@@ -235,10 +235,6 @@ def verify_pki(openvpn):
def verify(openvpn):
if 'deleted' in openvpn:
- # remove totp secrets file if totp is not configured
- if os.path.isfile(otp_file.format(**openvpn)):
- os.remove(otp_file.format(**openvpn))
-
verify_bridge_delete(openvpn)
return None
@@ -624,9 +620,19 @@ def generate_pki_files(openvpn):
def generate(openvpn):
+ if 'deleted' in openvpn:
+ # remove totp secrets file if totp is not configured
+ if os.path.isfile(otp_file.format(**openvpn)):
+ os.remove(otp_file.format(**openvpn))
+ return None
+
+ if 'disable' in openvpn:
+ return None
+
interface = openvpn['ifname']
directory = os.path.dirname(cfg_file.format(**openvpn))
openvpn['plugin_dir'] = '/usr/lib/openvpn'
+
# create base config directory on demand
makedir(directory, user, group)
# enforce proper permissions on /run/openvpn
@@ -643,9 +649,6 @@ def generate(openvpn):
if os.path.isdir(service_dir):
rmtree(service_dir, ignore_errors=True)
- if 'deleted' in openvpn or 'disable' in openvpn:
- return None
-
# create client config directory on demand
makedir(ccd_dir, user, group)
diff --git a/src/conf_mode/interfaces_vti.py b/src/conf_mode/interfaces_vti.py
index e6a833df7..20629c6c1 100755
--- a/src/conf_mode/interfaces_vti.py
+++ b/src/conf_mode/interfaces_vti.py
@@ -19,6 +19,7 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_mirror_redirect
+from vyos.configverify import verify_vrf
from vyos.ifconfig import VTIIf
from vyos import ConfigError
from vyos import airbag
@@ -38,6 +39,7 @@ def get_config(config=None):
return vti
def verify(vti):
+ verify_vrf(vti)
verify_mirror_redirect(vti)
return None
diff --git a/src/conf_mode/interfaces_vxlan.py b/src/conf_mode/interfaces_vxlan.py
index 39365968a..bc4918a52 100755
--- a/src/conf_mode/interfaces_vxlan.py
+++ b/src/conf_mode/interfaces_vxlan.py
@@ -28,6 +28,7 @@ from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_bond_bridge_member
+from vyos.configverify import verify_vrf
from vyos.ifconfig import Interface
from vyos.ifconfig import VXLANIf
from vyos.template import is_ipv6
@@ -193,6 +194,7 @@ def verify(vxlan):
verify_mtu_ipv6(vxlan)
verify_address(vxlan)
+ verify_vrf(vxlan)
verify_bond_bridge_member(vxlan)
verify_mirror_redirect(vxlan)
diff --git a/src/conf_mode/interfaces_wireless.py b/src/conf_mode/interfaces_wireless.py
index c0a17c0bc..ff38c979c 100755
--- a/src/conf_mode/interfaces_wireless.py
+++ b/src/conf_mode/interfaces_wireless.py
@@ -19,6 +19,7 @@ import os
from sys import exit
from re import findall
from netaddr import EUI, mac_unix_expanded
+from time import sleep
from vyos.config import Config
from vyos.configdict import get_interface_dict
@@ -34,6 +35,9 @@ from vyos.template import render
from vyos.utils.dict import dict_search
from vyos.utils.kernel import check_kmod
from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_active
+from vyos.utils.process import is_systemd_service_running
+from vyos.utils.network import interface_exists
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -87,6 +91,11 @@ def get_config(config=None):
if wifi.from_defaults(['security', 'wpa']): # if not set by user
del wifi['security']['wpa']
+ # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number
+ if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []):
+ wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable']
+ del wifi['capabilities']['ht']['40mhz_incapable']
+
if dict_search('security.wpa', wifi) != None:
wpa_cipher = wifi['security']['wpa'].get('cipher')
wpa_mode = wifi['security']['wpa'].get('mode')
@@ -98,6 +107,15 @@ def get_config(config=None):
tmp = {'security': {'wpa': {'cipher' : ['CCMP']}}}
elif wpa_mode == 'both':
tmp = {'security': {'wpa': {'cipher' : ['CCMP', 'TKIP']}}}
+ elif wpa_mode == 'wpa3':
+ # According to WiFi specs (https://www.wi-fi.org/file/wpa3-specification)
+ # section 3.5: WPA3-Enterprise 192-bit mode
+ # WiFi NICs which would be able to connect to WPA3-Enterprise managed
+ # networks MUST support GCMP-256.
+ # Reasoning: Provided that chipsets would most likely _not_ be
+ # "private user only", they all would come with built-in support
+ # for GCMP-256.
+ tmp = {'security': {'wpa': {'cipher' : ['CCMP', 'CCMP-256', 'GCMP', 'GCMP-256']}}}
if tmp: wifi = dict_merge(tmp, wifi)
@@ -105,7 +123,7 @@ def get_config(config=None):
tmp = find_other_stations(conf, base, wifi['ifname'])
if tmp: wifi['station_interfaces'] = tmp
- # used in hostapt.conf.j2
+ # used in hostapd.conf.j2
wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi)
wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi)
@@ -136,6 +154,23 @@ def verify(wifi):
if 'channel' not in wifi:
raise ConfigError('Wireless channel must be configured!')
+ if 'capabilities' in wifi and 'he' in wifi['capabilities']:
+ if 'channel_set_width' not in wifi['capabilities']['he']:
+ raise ConfigError('Channel width must be configured!')
+
+ # op_modes drawn from:
+ # https://w1.fi/cgit/hostap/tree/src/common/ieee802_11_common.c?id=195cc3d919503fb0d699d9a56a58a72602b25f51#n1525
+ # 802.11ax (WiFi-6e - HE) can use up to 160MHz bandwidth channels
+ six_ghz_op_modes_he = ['131', '132', '133', '134', '135']
+ # 802.11be (WiFi-7 - EHT) can use up to 320MHz bandwidth channels
+ six_ghz_op_modes_eht = six_ghz_op_modes_he.append('137')
+ if 'security' in wifi and 'wpa' in wifi['security'] and 'mode' in wifi['security']['wpa']:
+ if wifi['security']['wpa']['mode'] == 'wpa3':
+ if 'he' in wifi['capabilities']:
+ if wifi['capabilities']['he']['channel_set_width'] in six_ghz_op_modes_he:
+ if 'mgmt_frame_protection' not in wifi or wifi['mgmt_frame_protection'] != 'required':
+ raise ConfigError('Management Frame Protection (MFP) is required with WPA3 at 6GHz! Consider also enabling Beacon Frame Protection (BFP) if your device supports it.')
+
if 'security' in wifi:
if {'wep', 'wpa'} <= set(wifi.get('security', {})):
raise ConfigError('Must either use WEP or WPA security!')
@@ -169,7 +204,8 @@ def verify(wifi):
if capabilities['vht']['beamform'] == 'single-user-beamformer':
if int(capabilities['vht']['antenna_count']) < 3:
- # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705
+ # Nasty Gotcha: see lines 708-721 in:
+ # https://w1.fi/cgit/hostap/tree/hostapd/hostapd.conf?h=hostap_2_10&id=cff80b4f7d3c0a47c052e8187d671710f48939e4#n708
raise ConfigError('Single-user beam former requires at least 3 antennas!')
if 'station_interfaces' in wifi and wifi['type'] == 'station':
@@ -191,11 +227,6 @@ def verify(wifi):
def generate(wifi):
interface = wifi['ifname']
- # always stop hostapd service first before reconfiguring it
- call(f'systemctl stop hostapd@{interface}.service')
- # always stop wpa_supplicant service first before reconfiguring it
- call(f'systemctl stop wpa_supplicant@{interface}.service')
-
# Delete config files if interface is removed
if 'deleted' in wifi:
if os.path.isfile(hostapd_conf.format(**wifi)):
@@ -231,11 +262,6 @@ def generate(wifi):
mac.dialect = mac_unix_expanded
wifi['mac'] = str(mac)
- # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number
- if '40mhz_incapable' in (dict_search('capabilities.ht', wifi) or []):
- wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable']
- del wifi['capabilities']['ht']['40mhz_incapable']
-
# render appropriate new config files depending on access-point or station mode
if wifi['type'] == 'access-point':
render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', wifi)
@@ -249,23 +275,45 @@ def generate(wifi):
def apply(wifi):
interface = wifi['ifname']
+ # From systemd source code:
+ # If there's a stop job queued before we enter the DEAD state, we shouldn't act on Restart=,
+ # in order to not undo what has already been enqueued. */
+ #
+ # It was found that calling restart on hostapd will (4 out of 10 cases) deactivate
+ # the service instead of restarting it, when it was not yet properly stopped
+ # systemd[1]: hostapd@wlan1.service: Deactivated successfully.
+ # Thus kill all WIFI service and start them again after it's ensured nothing lives
+ call(f'systemctl stop hostapd@{interface}.service')
+ call(f'systemctl stop wpa_supplicant@{interface}.service')
+
if 'deleted' in wifi:
- WiFiIf(interface).remove()
- else:
- # Finally create the new interface
- w = WiFiIf(**wifi)
- w.update(wifi)
-
- # Enable/Disable interface - interface is always placed in
- # administrative down state in WiFiIf class
- if 'disable' not in wifi:
- # Physical interface is now configured. Proceed by starting hostapd or
- # wpa_supplicant daemon. When type is monitor we can just skip this.
- if wifi['type'] == 'access-point':
- call(f'systemctl start hostapd@{interface}.service')
-
- elif wifi['type'] == 'station':
- call(f'systemctl start wpa_supplicant@{interface}.service')
+ WiFiIf(**wifi).remove()
+ return None
+
+ while (is_systemd_service_running(f'hostapd@{interface}.service') or \
+ is_systemd_service_active(f'hostapd@{interface}.service')):
+ sleep(0.250) # wait 250ms
+
+ # Finally create the new interface
+ w = WiFiIf(**wifi)
+ w.update(wifi)
+
+ # Enable/Disable interface - interface is always placed in
+ # administrative down state in WiFiIf class
+ if 'disable' not in wifi:
+ # Wait until interface was properly added to the Kernel
+ ii = 0
+ while not (interface_exists(interface) and ii < 20):
+ sleep(0.250) # wait 250ms
+ ii += 1
+
+ # Physical interface is now configured. Proceed by starting hostapd or
+ # wpa_supplicant daemon. When type is monitor we can just skip this.
+ if wifi['type'] == 'access-point':
+ call(f'systemctl start hostapd@{interface}.service')
+
+ elif wifi['type'] == 'station':
+ call(f'systemctl start wpa_supplicant@{interface}.service')
return None
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 8d8c234c0..184725573 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -26,7 +26,7 @@ from vyos.ifconfig import Interface
from vyos.template import render
from vyos.template import render_to_string
from vyos.utils.dict import dict_search
-from vyos.utils.network import get_interface_config
+from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import get_vrf_members
from vyos.utils.network import interface_exists
from vyos.utils.process import call
@@ -160,8 +160,8 @@ def verify(vrf):
# routing table id can't be changed - OS restriction
if interface_exists(name):
- tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name)))
- if tmp and tmp != vrf_config['table']:
+ tmp = get_vrf_tableid(name)
+ if tmp and tmp != int(vrf_config['table']):
raise ConfigError(f'VRF "{name}" table id modification not possible!')
# VRF routing table ID must be unique on the system