summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/interfaces_wireless.py29
-rwxr-xr-xsrc/conf_mode/nat_cgnat.py110
-rwxr-xr-xsrc/conf_mode/service_monitoring_telegraf.py18
3 files changed, 113 insertions, 44 deletions
diff --git a/src/conf_mode/interfaces_wireless.py b/src/conf_mode/interfaces_wireless.py
index 998ff9dba..73944dc8b 100755
--- a/src/conf_mode/interfaces_wireless.py
+++ b/src/conf_mode/interfaces_wireless.py
@@ -104,6 +104,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)
@@ -143,6 +152,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!')
@@ -176,7 +202,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':
diff --git a/src/conf_mode/nat_cgnat.py b/src/conf_mode/nat_cgnat.py
index 34ec64fce..3484e5873 100755
--- a/src/conf_mode/nat_cgnat.py
+++ b/src/conf_mode/nat_cgnat.py
@@ -119,37 +119,34 @@ class IPOperations:
+ [self.ip_network.broadcast_address]
]
- def get_prefix_by_ip_range(self):
+ def get_prefix_by_ip_range(self) -> list[ipaddress.IPv4Network]:
"""Return the common prefix for the address range
Example:
% ip = IPOperations('100.64.0.1-100.64.0.5')
% ip.get_prefix_by_ip_range()
- 100.64.0.0/29
+ [IPv4Network('100.64.0.1/32'), IPv4Network('100.64.0.2/31'), IPv4Network('100.64.0.4/31')]
"""
- if '-' in self.ip_prefix:
- ip_start, ip_end = self.ip_prefix.split('-')
- start_ip = ipaddress.IPv4Address(ip_start.strip())
- end_ip = ipaddress.IPv4Address(ip_end.strip())
-
- start_int = int(start_ip)
- end_int = int(end_ip)
-
- # XOR to find differing bits
- xor = start_int ^ end_int
-
- # Count the number of leading zeros in the XOR result to find the prefix length
- prefix_length = 32 - xor.bit_length()
-
- # Calculate the network address
- network_int = start_int & (0xFFFFFFFF << (32 - prefix_length))
- network_address = ipaddress.IPv4Address(network_int)
+ # We do not need to convert the IP range to network
+ # if it is already in network format
+ if self.ip_network:
+ return [self.ip_network]
+
+ # Raise an error if the IP range is not in the correct format
+ if '-' not in self.ip_prefix:
+ raise ValueError(
+ 'Invalid IP range format. Please provide the IP range in CIDR format or with "-" separator.'
+ )
+ # Split the IP range and convert it to IP address objects
+ range_start, range_end = self.ip_prefix.split('-')
+ range_start = ipaddress.IPv4Address(range_start)
+ range_end = ipaddress.IPv4Address(range_end)
- return f"{network_address}/{prefix_length}"
- return self.ip_prefix
+ # Return the summarized IP networks list
+ return list(ipaddress.summarize_address_range(range_start, range_end))
-def _delete_conntrack_entries(source_prefixes: list) -> None:
+def _delete_conntrack_entries(source_prefixes: list[ipaddress.IPv4Network]) -> None:
"""Delete all conntrack entries for the list of prefixes"""
for source_prefix in source_prefixes:
run(f'conntrack -D -s {source_prefix}')
@@ -224,15 +221,31 @@ def get_config(config=None):
with_recursive_defaults=True,
)
- if conf.exists(base) and is_node_changed(conf, base + ['pool']):
- config.update({'delete_conntrack_entries': {}})
+ effective_config = conf.get_config_dict(
+ base,
+ get_first_key=True,
+ key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ effective=True,
+ )
+
+ # Check if the pool configuration has changed
+ if not conf.exists(base) or is_node_changed(conf, base + ['pool']):
+ config['delete_conntrack_entries'] = {}
+
+ # add running config
+ if effective_config:
+ config['effective'] = effective_config
+
+ if not conf.exists(base):
+ config['deleted'] = {}
return config
def verify(config):
# bail out early - looks like removal from running config
- if not config:
+ if 'deleted' in config:
return None
if 'pool' not in config:
@@ -336,7 +349,7 @@ def verify(config):
def generate(config):
- if not config:
+ if 'deleted' in config:
return None
proto_maps = []
@@ -401,13 +414,38 @@ def generate(config):
def apply(config):
- if not config:
+ if 'deleted' in config:
# Cleanup cgnat
cmd('nft delete table ip cgnat')
if os.path.isfile(nftables_cgnat_config):
os.unlink(nftables_cgnat_config)
- return None
- cmd(f'nft --file {nftables_cgnat_config}')
+ else:
+ cmd(f'nft --file {nftables_cgnat_config}')
+
+ # Delete conntrack entries
+ # if the pool configuration has changed
+ if 'delete_conntrack_entries' in config and 'effective' in config:
+ # Prepare the list of internal pool prefixes
+ internal_pool_prefix_list: list[ipaddress.IPv4Network] = []
+
+ # Get effective rules configurations
+ for rule_config in config['effective'].get('rule', {}).values():
+ # Get effective internal pool configuration
+ internal_pool = rule_config['source']['pool']
+ # Find the internal IP ranges for the internal pool
+ internal_ip_ranges: list[str] = config['effective']['pool']['internal'][
+ internal_pool
+ ]['range']
+ # Get the IP prefixes for the internal IP range
+ for internal_range in internal_ip_ranges:
+ ip_prefix: list[ipaddress.IPv4Network] = IPOperations(
+ internal_range
+ ).get_prefix_by_ip_range()
+ # Add the IP prefixes to the list of all internal pool prefixes
+ internal_pool_prefix_list += ip_prefix
+
+ # Delete required sources for conntrack
+ _delete_conntrack_entries(internal_pool_prefix_list)
# Logging allocations
if 'log_allocation' in config:
@@ -420,23 +458,11 @@ def apply(config):
external_host, port_range = rest.split(' . ')
# Log the parsed data
logger.info(
- f"Internal host: {internal_host.lstrip()}, external host: {external_host}, Port range: {port_range}")
+ f'Internal host: {internal_host.lstrip()}, external host: {external_host}, Port range: {port_range}')
except ValueError as e:
# Log error message
logger.error(f"Error processing line '{allocation}': {e}")
- # Delete conntrack entries
- if 'delete_conntrack_entries' in config:
- internal_pool_prefix_list = []
- for rule, rule_config in config['rule'].items():
- internal_pool = rule_config['source']['pool']
- internal_ip_ranges: list = config['pool']['internal'][internal_pool]['range']
- for internal_range in internal_ip_ranges:
- ip_prefix = IPOperations(internal_range).get_prefix_by_ip_range()
- internal_pool_prefix_list.append(ip_prefix)
- # Deleta required sources for conntrack
- _delete_conntrack_entries(internal_pool_prefix_list)
-
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py
index 40eb13e23..9455b6109 100755
--- a/src/conf_mode/service_monitoring_telegraf.py
+++ b/src/conf_mode/service_monitoring_telegraf.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-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
@@ -113,6 +113,9 @@ def get_config(config=None):
if not conf.exists(base + ['azure-data-explorer']):
del monitoring['azure_data_explorer']
+ if not conf.exists(base + ['loki']):
+ del monitoring['loki']
+
return monitoring
def verify(monitoring):
@@ -159,6 +162,19 @@ def verify(monitoring):
if 'url' not in monitoring['splunk']:
raise ConfigError(f'Monitoring splunk "url" is mandatory!')
+ # Verify Loki
+ if 'loki' in monitoring:
+ if 'url' not in monitoring['loki']:
+ raise ConfigError(f'Monitoring loki "url" is mandatory!')
+ if 'authentication' in monitoring['loki']:
+ if (
+ 'username' not in monitoring['loki']['authentication']
+ or 'password' not in monitoring['loki']['authentication']
+ ):
+ raise ConfigError(
+ f'Authentication "username" and "password" are mandatory!'
+ )
+
return None
def generate(monitoring):