summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/accel-ppp/ipoe.config.j24
-rw-r--r--data/templates/frr/igmp.frr.j26
-rw-r--r--data/templates/wifi/hostapd.conf.j214
-rw-r--r--interface-definitions/container.xml.in34
-rw-r--r--interface-definitions/include/interface/eapol.xml.i2
-rw-r--r--op-mode-definitions/monitor-command.xml.in28
-rw-r--r--op-mode-definitions/show-hardware.xml.in2
-rw-r--r--python/vyos/configverify.py13
-rw-r--r--python/vyos/utils/network.py31
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py9
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py3
-rwxr-xr-xsrc/conf_mode/container.py11
-rwxr-xr-xsrc/conf_mode/high-availability.py16
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py13
-rwxr-xr-xsrc/conf_mode/protocols_igmp.py2
-rwxr-xr-xsrc/op_mode/dhcp.py4
-rwxr-xr-xsrc/op_mode/vyos-op-cmd-wrapper.sh6
17 files changed, 162 insertions, 36 deletions
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2
index add3dc7e4..f59428509 100644
--- a/data/templates/accel-ppp/ipoe.config.j2
+++ b/data/templates/accel-ppp/ipoe.config.j2
@@ -36,7 +36,9 @@ verbose=1
{% set shared = 'shared=0,' %}
{% endif %}
{% set range = 'range=' ~ iface_config.client_subnet ~ ',' if iface_config.client_subnet is vyos_defined else '' %}
-{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1
+{% set relay = ',' ~ 'relay=' ~ iface_config.external_dhcp.dhcp_relay if iface_config.external_dhcp.dhcp_relay is vyos_defined else '' %}
+{% set giaddr = ',' ~ 'giaddr=' ~ iface_config.external_dhcp.giaddr if iface_config.external_dhcp.giaddr is vyos_defined else '' %}
+{{ tmp }},{{ shared }}mode={{ iface_config.mode | upper }},ifcfg=1,{{ range }}start=dhcpv4,ipv6=1{{ relay }}{{ giaddr }}
{% if iface_config.vlan is vyos_defined %}
vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }}
{% endif %}
diff --git a/data/templates/frr/igmp.frr.j2 b/data/templates/frr/igmp.frr.j2
index ce1f8fdda..b75884484 100644
--- a/data/templates/frr/igmp.frr.j2
+++ b/data/templates/frr/igmp.frr.j2
@@ -27,9 +27,9 @@ interface {{ interface }}
{% if interface_config.query_max_resp_time %}
ip igmp query-max-response-time {{ interface_config.query_max_resp_time }}
{% endif %}
-{% for group in interface_config.gr_join %}
-{% if ifaces[iface].gr_join[group] %}
-{% for source in ifaces[iface].gr_join[group] %}
+{% for group, sources in interface_config.gr_join.items() %}
+{% if sources is vyos_defined %}
+{% for source in sources %}
ip igmp join {{ group }} {{ source }}
{% endfor %}
{% else %}
diff --git a/data/templates/wifi/hostapd.conf.j2 b/data/templates/wifi/hostapd.conf.j2
index 613038597..c3f32da72 100644
--- a/data/templates/wifi/hostapd.conf.j2
+++ b/data/templates/wifi/hostapd.conf.j2
@@ -340,6 +340,11 @@ vht_oper_chwidth={{ capabilities.vht.channel_set_width }}
{% endif %}
{% set output = namespace(value='') %}
+{% if capabilities.vht.channel_set_width is vyos_defined('2') %}
+{% set output.value = output.value ~ '[VHT160]' %}
+{% elif capabilities.vht.channel_set_width is vyos_defined('3') %}
+{% set output.value = output.value ~ '[VHT160-80PLUS80]' %}
+{% endif %}
{% if capabilities.vht.stbc.tx is vyos_defined %}
{% set output.value = output.value ~ '[TX-STBC-2BY1]' %}
{% endif %}
@@ -363,30 +368,21 @@ vht_oper_chwidth={{ capabilities.vht.channel_set_width }}
{% endif %}
{% if capabilities.vht.max_mpdu_exp is vyos_defined %}
{% set output.value = output.value ~ '[MAX-A-MPDU-LEN-EXP-' ~ capabilities.vht.max_mpdu_exp ~ ']' %}
-{% if capabilities.vht.max_mpdu_exp is vyos_defined('2') %}
-{% set output.value = output.value ~ '[VHT160]' %}
-{% endif %}
-{% if capabilities.vht.max_mpdu_exp is vyos_defined('3') %}
-{% set output.value = output.value ~ '[VHT160-80PLUS80]' %}
-{% endif %}
{% endif %}
{% if capabilities.vht.link_adaptation is vyos_defined('unsolicited') %}
{% set output.value = output.value ~ '[VHT-LINK-ADAPT2]' %}
{% elif capabilities.vht.link_adaptation is vyos_defined('both') %}
{% set output.value = output.value ~ '[VHT-LINK-ADAPT3]' %}
{% endif %}
-
{% for short_gi in capabilities.vht.short_gi if capabilities.vht.short_gi is vyos_defined %}
{% set output.value = output.value ~ '[SHORT-GI-' ~ short_gi | upper ~ ']' %}
{% endfor %}
-
{% for beamform in capabilities.vht.beamform if capabilities.vht.beamform is vyos_defined %}
{% set output.value = output.value ~ '[SU-BEAMFORMER]' if beamform is vyos_defined('single-user-beamformer') else '' %}
{% set output.value = output.value ~ '[SU-BEAMFORMEE]' if beamform is vyos_defined('single-user-beamformee') else '' %}
{% set output.value = output.value ~ '[MU-BEAMFORMER]' if beamform is vyos_defined('multi-user-beamformer') else '' %}
{% set output.value = output.value ~ '[MU-BEAMFORMEE]' if beamform is vyos_defined('multi-user-beamformee') else '' %}
{% endfor %}
-
{% if capabilities.vht.antenna_count is vyos_defined and capabilities.vht.antenna_count | int > 1 %}
{% if capabilities.vht.beamform is vyos_defined %}
{% if capabilities.vht.beamform == 'single-user-beamformer' %}
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index baab6104f..b35ba8d1c 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -25,7 +25,7 @@
<properties>
<help>Container capabilities/permissions</help>
<completionHelp>
- <list>net-admin net-bind-service net-raw setpcap sys-admin sys-time</list>
+ <list>net-admin net-bind-service net-raw setpcap sys-admin sys-module sys-time</list>
</completionHelp>
<valueHelp>
<format>net-admin</format>
@@ -48,11 +48,15 @@
<description>Administation operations (quotactl, mount, sethostname, setdomainame)</description>
</valueHelp>
<valueHelp>
+ <format>sys-module</format>
+ <description>Load, unload and delete kernel modules</description>
+ </valueHelp>
+ <valueHelp>
<format>sys-time</format>
<description>Permission to set system clock</description>
</valueHelp>
<constraint>
- <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-time)</regex>
+ <regex>(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-module|sys-time)</regex>
</constraint>
<multi/>
</properties>
@@ -110,7 +114,7 @@
<constraint>
<regex>[ !#-%&amp;(-~]+</regex>
</constraint>
- <constraintErrorMessage>Entrypoint must be ascii characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
+ <constraintErrorMessage>Entrypoint must be ASCII characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="host-name">
@@ -133,7 +137,7 @@
<constraint>
<regex>[ !#-%&amp;(-~]+</regex>
</constraint>
- <constraintErrorMessage>Command must be ascii characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
+ <constraintErrorMessage>Command must be ASCII characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="arguments">
@@ -142,9 +146,29 @@
<constraint>
<regex>[ !#-%&amp;(-~]+</regex>
</constraint>
- <constraintErrorMessage>The command's arguments must be ascii characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
+ <constraintErrorMessage>The command's arguments must be ASCII characters, use &amp;quot; and &amp;apos for double and single quotes respectively</constraintErrorMessage>
</properties>
</leafNode>
+ <tagNode name="label">
+ <properties>
+ <help>Add label variables</help>
+ <constraint>
+ <regex>[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?</regex>
+ </constraint>
+ <constraintErrorMessage>Label variable name must be alphanumeric and can contain hyphen, dots and underscores</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="value">
+ <properties>
+ <help>Set label option value</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Set label option value</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
<leafNode name="memory">
<properties>
<help>Memory (RAM) available to this container</help>
diff --git a/interface-definitions/include/interface/eapol.xml.i b/interface-definitions/include/interface/eapol.xml.i
index c4cdeae0c..a3206f2c7 100644
--- a/interface-definitions/include/interface/eapol.xml.i
+++ b/interface-definitions/include/interface/eapol.xml.i
@@ -4,7 +4,7 @@
<help>Extensible Authentication Protocol over Local Area Network</help>
</properties>
<children>
- #include <include/pki/ca-certificate.xml.i>
+ #include <include/pki/ca-certificate-multi.xml.i>
#include <include/pki/certificate-key.xml.i>
</children>
</node>
diff --git a/op-mode-definitions/monitor-command.xml.in b/op-mode-definitions/monitor-command.xml.in
new file mode 100644
index 000000000..31c68f029
--- /dev/null
+++ b/op-mode-definitions/monitor-command.xml.in
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="monitor">
+ <children>
+ <tagNode name="command">
+ <properties>
+ <help>Monitor operational mode command (refreshes every 2 seconds)</help>
+ </properties>
+ <command>watch --no-title ${vyos_op_scripts_dir}/vyos-op-cmd-wrapper.sh ${@:3}</command>
+ </tagNode>
+ <node name="command">
+ <children>
+ <node name="diff">
+ <properties>
+ <help>Show differences during each run</help>
+ </properties>
+ </node>
+ <tagNode name="diff">
+ <properties>
+ <help>Monitor operational mode command (refreshes every 2 seconds)</help>
+ </properties>
+ <command>watch --no-title --differences ${vyos_op_scripts_dir}/vyos-op-cmd-wrapper.sh ${@:4}</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-hardware.xml.in b/op-mode-definitions/show-hardware.xml.in
index ebd806ba5..21079765a 100644
--- a/op-mode-definitions/show-hardware.xml.in
+++ b/op-mode-definitions/show-hardware.xml.in
@@ -31,7 +31,7 @@
<properties>
<help>Show system DMI details</help>
</properties>
- <command>${vyatta_bindir}/vyatta-show-dmi</command>
+ <command>sudo dmidecode</command>
</node>
<node name="mem">
<properties>
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 5b94bd98b..52f9238b8 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -187,15 +187,14 @@ def verify_eapol(config):
if 'ca' not in config['pki']:
raise ConfigError('Invalid CA certificate specified for EAPoL')
- ca_cert_name = config['eapol']['ca_certificate']
+ for ca_cert_name in config['eapol']['ca_certificate']:
+ if ca_cert_name not in config['pki']['ca']:
+ raise ConfigError('Invalid CA certificate specified for EAPoL')
- if ca_cert_name not in config['pki']['ca']:
- raise ConfigError('Invalid CA certificate specified for EAPoL')
-
- ca_cert = config['pki']['ca'][ca_cert_name]
+ ca_cert = config['pki']['ca'][ca_cert_name]
- if 'certificate' not in ca_cert:
- raise ConfigError('Invalid CA certificate specified for EAPoL')
+ if 'certificate' not in ca_cert:
+ raise ConfigError('Invalid CA certificate specified for EAPoL')
def verify_mirror_redirect(config):
"""
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py
index 2f181d8d9..bc6899e45 100644
--- a/python/vyos/utils/network.py
+++ b/python/vyos/utils/network.py
@@ -117,6 +117,37 @@ def get_interface_namespace(iface):
if iface == tmp["ifname"]:
return netns
+def is_ipv6_tentative(iface: str, ipv6_address: str) -> bool:
+ """Check if IPv6 address is in tentative state.
+
+ This function checks if an IPv6 address on a specific network interface is
+ in the tentative state. IPv6 tentative addresses are not fully configured
+ and are undergoing Duplicate Address Detection (DAD) to ensure they are
+ unique on the network.
+
+ Args:
+ iface (str): The name of the network interface.
+ ipv6_address (str): The IPv6 address to check.
+
+ Returns:
+ bool: True if the IPv6 address is tentative, False otherwise.
+ """
+ import json
+ from vyos.utils.process import rc_cmd
+
+ rc, out = rc_cmd(f'ip -6 --json address show dev {iface} scope global')
+ if rc:
+ return False
+
+ data = json.loads(out)
+ for addr_info in data[0]['addr_info']:
+ if (
+ addr_info.get('local') == ipv6_address and
+ addr_info.get('tentative', False)
+ ):
+ return True
+ return False
+
def is_wwan_connected(interface):
""" Determine if a given WWAN interface, e.g. wwan0 is connected to the
carrier network or not """
diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index 5ea21fea8..a39b81348 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.py
@@ -250,10 +250,19 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
for interface in self._interfaces:
# Enable EAPoL
self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-server-ca-intermediate'])
+ self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate'])
self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name])
self.cli_commit()
+ # Test multiple CA chains
+ self.assertEqual(get_certificate_count(interface, 'ca'), 4)
+
+ for interface in self._interfaces:
+ self.cli_delete(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate'])
+
+ self.cli_commit()
+
# Check for running process
self.assertTrue(process_named_running('wpa_supplicant'))
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index f8686edd8..95246a7b9 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -97,6 +97,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
vht_opt = {
# VyOS CLI option hostapd - ht_capab setting
+ 'channel-set-width 3' : '[VHT160-80PLUS80]',
'stbc tx' : '[TX-STBC-2BY1]',
'stbc rx 12' : '[RX-STBC-12]',
'ldpc' : '[RXLDPC]',
@@ -104,7 +105,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
'vht-cf' : '[HTC-VHT]',
'antenna-pattern-fixed' : '[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]',
'max-mpdu 11454' : '[MAX-MPDU-11454]',
- 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2][VHT160]',
+ 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2]',
'link-adaptation both' : '[VHT-LINK-ADAPT3]',
'short-gi 80' : '[SHORT-GI-80]',
'short-gi 160' : '[SHORT-GI-160]',
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 79b605ffb..46eb10714 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -178,6 +178,11 @@ def verify(container):
if 'value' not in cfg:
raise ConfigError(f'Environment variable {var} has no value assigned!')
+ if 'label' in container_config:
+ for var, cfg in container_config['label'].items():
+ if 'value' not in cfg:
+ raise ConfigError(f'Label variable {var} has no value assigned!')
+
if 'volume' in container_config:
for volume, volume_config in container_config['volume'].items():
if 'source' not in volume_config:
@@ -268,6 +273,12 @@ def generate_run_arguments(name, container_config):
for k, v in container_config['environment'].items():
env_opt += f" --env \"{k}={v['value']}\""
+ # Check/set label options "--label foo=bar"
+ env_opt = ''
+ if 'label' in container_config:
+ for k, v in container_config['label'].items():
+ env_opt += f" --label \"{k}={v['value']}\""
+
hostname = ''
if 'host_name' in container_config:
hostname = container_config['host_name']
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index 626a3757e..0121df11c 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -15,6 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import time
+
from sys import exit
from ipaddress import ip_interface
from ipaddress import IPv4Interface
@@ -26,11 +28,13 @@ from vyos.ifconfig.vrrp import VRRP
from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
+from vyos.utils.network import is_ipv6_tentative
from vyos.utils.process import call
from vyos import ConfigError
from vyos import airbag
airbag.enable()
+
def get_config(config=None):
if config:
conf = config
@@ -171,6 +175,18 @@ def apply(ha):
call(f'systemctl stop {service_name}')
return None
+ # Check if IPv6 address is tentative T5533
+ for group, group_config in ha['vrrp']['group'].items():
+ if 'hello_source_address' in group_config:
+ if is_ipv6(group_config['hello_source_address']):
+ ipv6_address = group_config['hello_source_address']
+ interface = group_config['interface']
+ checks = 20
+ interval = 0.1
+ for _ in range(checks):
+ if is_ipv6_tentative(interface, ipv6_address):
+ time.sleep(interval)
+
call(f'systemctl reload-or-restart {service_name}')
return None
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index b015bba88..f3e65ad5e 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -186,14 +186,15 @@ def generate(ethernet):
if 'ca_certificate' in ethernet['eapol']:
ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem')
- ca_cert_name = ethernet['eapol']['ca_certificate']
- pki_ca_cert = ethernet['pki']['ca'][ca_cert_name]
+ ca_chains = []
- loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
- ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ for ca_cert_name in ethernet['eapol']['ca_certificate']:
+ pki_ca_cert = ethernet['pki']['ca'][ca_cert_name]
+ loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ ca_chains.append('\n'.join(encode_certificate(c) for c in ca_full_chain))
- write_file(ca_cert_file_path,
- '\n'.join(encode_certificate(c) for c in ca_full_chain))
+ write_file(ca_cert_file_path, '\n'.join(ca_chains))
return None
diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py
index f6097e282..435189025 100755
--- a/src/conf_mode/protocols_igmp.py
+++ b/src/conf_mode/protocols_igmp.py
@@ -102,7 +102,7 @@ def verify(igmp):
# Check, is this multicast group
for intfc in igmp['ifaces']:
for gr_addr in igmp['ifaces'][intfc]['gr_join']:
- if IPv4Address(gr_addr) < IPv4Address('224.0.0.0'):
+ if not IPv4Address(gr_addr).is_multicast:
raise ConfigError(gr_addr + " not a multicast group")
def generate(igmp):
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index f558c18b7..77f38992b 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -338,10 +338,12 @@ def _get_formatted_client_leases(lease_data, family):
from time import localtime
from time import strftime
- from vyos.validate import is_intf_addr_assigned
+ from vyos.utils.network import is_intf_addr_assigned
data_entries = []
for lease in lease_data:
+ if not lease.get('new_ip_address'):
+ continue
data_entries.append(["Interface", lease['interface']])
if 'new_ip_address' in lease:
tmp = '[Active]' if is_intf_addr_assigned(lease['interface'], lease['new_ip_address']) else '[Inactive]'
diff --git a/src/op_mode/vyos-op-cmd-wrapper.sh b/src/op_mode/vyos-op-cmd-wrapper.sh
new file mode 100755
index 000000000..a89211b2b
--- /dev/null
+++ b/src/op_mode/vyos-op-cmd-wrapper.sh
@@ -0,0 +1,6 @@
+#!/bin/vbash
+shopt -s expand_aliases
+source /etc/default/vyatta
+source /etc/bash_completion.d/vyatta-op
+_vyatta_op_init
+_vyatta_op_run "$@"