summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/completion/list_container_sysctl_parameters.sh20
-rwxr-xr-xsrc/conf_mode/container.py57
-rwxr-xr-xsrc/conf_mode/interfaces_macsec.py10
-rwxr-xr-xsrc/conf_mode/interfaces_openvpn.py4
-rwxr-xr-xsrc/conf_mode/nat_cgnat.py30
-rwxr-xr-xsrc/conf_mode/pki.py2
-rwxr-xr-xsrc/conf_mode/service_snmp.py28
-rwxr-xr-xsrc/conf_mode/system_login.py50
-rwxr-xr-xsrc/conf_mode/system_option.py18
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py43
-rwxr-xr-xsrc/migration-scripts/firewall/15-to-165
-rwxr-xr-xsrc/migration-scripts/interfaces/20-to-217
-rwxr-xr-xsrc/migration-scripts/openvpn/1-to-274
-rwxr-xr-xsrc/op_mode/conntrack_sync.py25
-rwxr-xr-xsrc/op_mode/cpu.py12
-rwxr-xr-xsrc/op_mode/ikev2_profile_generator.py57
-rwxr-xr-xsrc/op_mode/lldp.py23
-rwxr-xr-xsrc/op_mode/pki.py22
-rwxr-xr-xsrc/op_mode/powerctrl.py2
-rw-r--r--src/op_mode/tcpdump.py165
-rwxr-xr-xsrc/op_mode/uptime.py4
-rwxr-xr-xsrc/services/vyos-configd10
-rw-r--r--src/shim/vyshim.c11
23 files changed, 539 insertions, 140 deletions
diff --git a/src/completion/list_container_sysctl_parameters.sh b/src/completion/list_container_sysctl_parameters.sh
new file mode 100755
index 000000000..cf8d006e5
--- /dev/null
+++ b/src/completion/list_container_sysctl_parameters.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# 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/>.
+
+declare -a vals
+eval "vals=($(/sbin/sysctl -N -a|grep -E '^(fs.mqueue|net)\.|^(kernel.msgmax|kernel.msgmnb|kernel.msgmni|kernel.sem|kernel.shmall|kernel.shmmax|kernel.shmmni|kernel.shm_rmid_forced)$'))"
+echo ${vals[@]}
+exit 0
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 3efeb9b40..ded370a7a 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -29,7 +29,7 @@ from vyos.configdict import node_changed
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.ifconfig import Interface
-from vyos.cpu import get_core_count
+from vyos.utils.cpu import get_core_count
from vyos.utils.file import write_file
from vyos.utils.process import call
from vyos.utils.process import cmd
@@ -43,6 +43,7 @@ from vyos.template import render
from vyos.xml_ref import default_value
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
config_containers = '/etc/containers/containers.conf'
@@ -50,16 +51,19 @@ config_registry = '/etc/containers/registries.conf'
config_storage = '/etc/containers/storage.conf'
systemd_unit_path = '/run/systemd/system'
+
def _cmd(command):
if os.path.exists('/tmp/vyos.container.debug'):
print(command)
return cmd(command)
+
def network_exists(name):
# Check explicit name for network, returns True if network exists
c = _cmd(f'podman network ls --quiet --filter name=^{name}$')
return bool(c)
+
# Common functions
def get_config(config=None):
if config:
@@ -86,21 +90,22 @@ def get_config(config=None):
# registry is a tagNode with default values - merge the list from
# default_values['registry'] into the tagNode variables
if 'registry' not in container:
- container.update({'registry' : {}})
+ container.update({'registry': {}})
default_values = default_value(base + ['registry'])
for registry in default_values:
- tmp = {registry : {}}
+ tmp = {registry: {}}
container['registry'] = dict_merge(tmp, container['registry'])
# Delete container network, delete containers
tmp = node_changed(conf, base + ['network'])
- if tmp: container.update({'network_remove' : tmp})
+ if tmp: container.update({'network_remove': tmp})
tmp = node_changed(conf, base + ['name'])
- if tmp: container.update({'container_remove' : tmp})
+ if tmp: container.update({'container_remove': tmp})
return container
+
def verify(container):
# bail out early - looks like removal from running config
if not container:
@@ -125,8 +130,8 @@ def verify(container):
# of image upgrade and deletion.
image = container_config['image']
if run(f'podman image exists {image}') != 0:
- Warning(f'Image "{image}" used in container "{name}" does not exist '\
- f'locally. Please use "add container image {image}" to add it '\
+ Warning(f'Image "{image}" used in container "{name}" does not exist ' \
+ f'locally. Please use "add container image {image}" to add it ' \
f'to the system! Container "{name}" will not be started!')
if 'cpu_quota' in container_config:
@@ -167,11 +172,11 @@ def verify(container):
# We can not use the first IP address of a network prefix as this is used by podman
if ip_address(address) == ip_network(network)[1]:
- raise ConfigError(f'IP address "{address}" can not be used for a container, '\
+ raise ConfigError(f'IP address "{address}" can not be used for a container, ' \
'reserved for the container engine!')
if cnt_ipv4 > 1 or cnt_ipv6 > 1:
- raise ConfigError(f'Only one IP address per address family can be used for '\
+ raise ConfigError(f'Only one IP address per address family can be used for ' \
f'container "{name}". {cnt_ipv4} IPv4 and {cnt_ipv6} IPv6 address(es)!')
if 'device' in container_config:
@@ -186,6 +191,13 @@ def verify(container):
if not os.path.exists(source):
raise ConfigError(f'Device "{dev}" source path "{source}" does not exist!')
+ if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
+ for var, cfg in container_config['sysctl']['parameter'].items():
+ if 'value' not in cfg:
+ raise ConfigError(f'sysctl parameter {var} has no value assigned!')
+ if var.startswith('net.') and 'allow_host_networks' in container_config:
+ raise ConfigError(f'sysctl parameter {var} cannot be set when using host networking!')
+
if 'environment' in container_config:
for var, cfg in container_config['environment'].items():
if 'value' not in cfg:
@@ -219,7 +231,8 @@ def verify(container):
# Can not set both allow-host-networks and network at the same time
if {'allow_host_networks', 'network'} <= set(container_config):
- raise ConfigError(f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
+ raise ConfigError(
+ f'"allow-host-networks" and "network" for "{name}" cannot be both configured at the same time!')
# gid cannot be set without uid
if 'gid' in container_config and 'uid' not in container_config:
@@ -235,8 +248,10 @@ def verify(container):
raise ConfigError(f'prefix for network "{network}" must be defined!')
for prefix in network_config['prefix']:
- if is_ipv4(prefix): v4_prefix += 1
- elif is_ipv6(prefix): v6_prefix += 1
+ if is_ipv4(prefix):
+ v4_prefix += 1
+ elif is_ipv6(prefix):
+ v6_prefix += 1
if v4_prefix > 1:
raise ConfigError(f'Only one IPv4 prefix can be defined for network "{network}"!')
@@ -262,6 +277,7 @@ def verify(container):
return None
+
def generate_run_arguments(name, container_config):
image = container_config['image']
cpu_quota = container_config['cpu_quota']
@@ -269,6 +285,12 @@ def generate_run_arguments(name, container_config):
shared_memory = container_config['shared_memory']
restart = container_config['restart']
+ # Add sysctl options
+ sysctl_opt = ''
+ if 'sysctl' in container_config and 'parameter' in container_config['sysctl']:
+ for k, v in container_config['sysctl']['parameter'].items():
+ sysctl_opt += f" --sysctl {k}={v['value']}"
+
# Add capability options. Should be in uppercase
capabilities = ''
if 'capability' in container_config:
@@ -341,7 +363,7 @@ def generate_run_arguments(name, container_config):
if 'allow_host_pid' in container_config:
host_pid = '--pid host'
- container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} ' \
+ container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \
f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid} {host_pid}'
@@ -375,6 +397,7 @@ def generate_run_arguments(name, container_config):
return f'{container_base_cmd} --no-healthcheck --net {networks} {ip_param} {entrypoint} {image} {command} {command_arguments}'.strip()
+
def generate(container):
# bail out early - looks like removal from running config
if not container:
@@ -387,7 +410,7 @@ def generate(container):
for network, network_config in container['network'].items():
tmp = {
'name': network,
- 'id' : sha256(f'{network}'.encode()).hexdigest(),
+ 'id': sha256(f'{network}'.encode()).hexdigest(),
'driver': 'bridge',
'network_interface': f'pod-{network}',
'subnets': [],
@@ -399,7 +422,7 @@ def generate(container):
}
}
for prefix in network_config['prefix']:
- net = {'subnet' : prefix, 'gateway' : inc_ip(prefix, 1)}
+ net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
tmp['subnets'].append(net)
if is_ipv6(prefix):
@@ -418,11 +441,12 @@ def generate(container):
file_path = os.path.join(systemd_unit_path, f'vyos-container-{name}.service')
run_args = generate_run_arguments(name, container_config)
- render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args,},
+ render(file_path, 'container/systemd-unit.j2', {'name': name, 'run_args': run_args, },
formater=lambda _: _.replace("&quot;", '"').replace("&apos;", "'"))
return None
+
def apply(container):
# Delete old containers if needed. We can't delete running container
# Option "--force" allows to delete containers with any status
@@ -485,6 +509,7 @@ def apply(container):
return None
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/interfaces_macsec.py b/src/conf_mode/interfaces_macsec.py
index eb0ca9a8b..3ede4377a 100755
--- a/src/conf_mode/interfaces_macsec.py
+++ b/src/conf_mode/interfaces_macsec.py
@@ -103,9 +103,9 @@ def verify(macsec):
# Logic to check static configuration
if dict_search('security.static', macsec) != None:
- # tx-key must be defined
+ # key must be defined
if dict_search('security.static.key', macsec) == None:
- raise ConfigError('Static MACsec tx-key must be defined.')
+ raise ConfigError('Static MACsec key must be defined.')
tx_len = len(dict_search('security.static.key', macsec))
@@ -119,12 +119,12 @@ def verify(macsec):
if 'peer' not in macsec['security']['static']:
raise ConfigError('Must have at least one peer defined for static MACsec')
- # For every enabled peer, make sure a MAC and rx-key is defined
+ # For every enabled peer, make sure a MAC and key is defined
for peer, peer_config in macsec['security']['static']['peer'].items():
if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config):
- raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.')
+ raise ConfigError('Every enabled MACsec static peer must have a MAC address and key defined!')
- # check rx-key length against cipher suite
+ # check key length against cipher suite
rx_len = len(peer_config['key'])
if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN:
diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py
index 627cc90ba..017010a61 100755
--- a/src/conf_mode/interfaces_openvpn.py
+++ b/src/conf_mode/interfaces_openvpn.py
@@ -515,6 +515,10 @@ def verify(openvpn):
print('Warning: using dh-params and EC keys simultaneously will ' \
'lead to DH ciphers being used instead of ECDH')
+ if dict_search('encryption.cipher', openvpn):
+ raise ConfigError('"encryption cipher" option is deprecated for TLS mode. '
+ 'Use "encryption ncp-ciphers" instead')
+
if dict_search('encryption.cipher', openvpn) == 'none':
print('Warning: "encryption none" was specified!')
print('No encryption will be performed and data is transmitted in ' \
diff --git a/src/conf_mode/nat_cgnat.py b/src/conf_mode/nat_cgnat.py
index d429f6e21..cb336a35c 100755
--- a/src/conf_mode/nat_cgnat.py
+++ b/src/conf_mode/nat_cgnat.py
@@ -16,9 +16,11 @@
import ipaddress
import jmespath
+import logging
import os
from sys import exit
+from logging.handlers import SysLogHandler
from vyos.config import Config
from vyos.template import render
@@ -32,6 +34,18 @@ airbag.enable()
nftables_cgnat_config = '/run/nftables-cgnat.nft'
+# Logging
+logger = logging.getLogger('cgnat')
+logger.setLevel(logging.DEBUG)
+
+syslog_handler = SysLogHandler(address="/dev/log")
+syslog_handler.setLevel(logging.INFO)
+
+formatter = logging.Formatter('%(name)s: %(message)s')
+syslog_handler.setFormatter(formatter)
+
+logger.addHandler(syslog_handler)
+
class IPOperations:
def __init__(self, ip_prefix: str):
@@ -356,6 +370,22 @@ def apply(config):
return None
cmd(f'nft --file {nftables_cgnat_config}')
+ # Logging allocations
+ if 'log_allocation' in config:
+ allocations = config['proto_map_elements']
+ allocations = allocations.split(',')
+ for allocation in allocations:
+ try:
+ # Split based on the delimiters used in the nft data format
+ internal_host, rest = allocation.split(' : ')
+ 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}")
+ except ValueError as e:
+ # Log error message
+ logger.error(f"Error processing line '{allocation}': {e}")
+
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index f37cac524..4a0e86f32 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -232,7 +232,7 @@ def get_config(config=None):
path = search['path']
path_str = ' '.join(path + found_path)
- print(f'PKI: Updating config: {path_str} {item_name}')
+ #print(f'PKI: Updating config: {path_str} {item_name}')
if path[0] == 'interfaces':
ifname = found_path[0]
diff --git a/src/conf_mode/service_snmp.py b/src/conf_mode/service_snmp.py
index 6565ffd60..6f025cc23 100755
--- a/src/conf_mode/service_snmp.py
+++ b/src/conf_mode/service_snmp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2023 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -26,10 +26,12 @@ from vyos.snmpv3_hashgen import plaintext_to_md5
from vyos.snmpv3_hashgen import plaintext_to_sha1
from vyos.snmpv3_hashgen import random
from vyos.template import render
-from vyos.utils.process import call
-from vyos.utils.permission import chmod_755
+from vyos.utils.configfs import delete_cli_node
+from vyos.utils.configfs import add_cli_node
from vyos.utils.dict import dict_search
from vyos.utils.network import is_addr_assigned
+from vyos.utils.process import call
+from vyos.utils.permission import chmod_755
from vyos.version import get_version_data
from vyos import ConfigError
from vyos import airbag
@@ -192,12 +194,8 @@ def generate(snmp):
return None
if 'v3' in snmp:
- # net-snmp is now regenerating the configuration file in the background
- # thus we need to re-open and re-read the file as the content changed.
- # After that we can no read the encrypted password from the config and
- # replace the CLI plaintext password with its encrypted version.
- os.environ['vyos_libexec_dir'] = '/usr/libexec/vyos'
-
+ # SNMPv3 uses a hashed password. If CLI defines a plaintext password,
+ # we will hash it in the background and replace the CLI node!
if 'user' in snmp['v3']:
for user, user_config in snmp['v3']['user'].items():
if dict_search('auth.type', user_config) == 'sha':
@@ -212,8 +210,9 @@ def generate(snmp):
snmp['v3']['user'][user]['auth']['encrypted_password'] = tmp
del snmp['v3']['user'][user]['auth']['plaintext_password']
- call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" auth encrypted-password "{tmp}" > /dev/null')
- call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" auth plaintext-password > /dev/null')
+ cli_base = ['service', 'snmp', 'v3', 'user', user, 'auth']
+ delete_cli_node(cli_base + ['plaintext-password'])
+ add_cli_node(cli_base + ['encrypted-password'], value=tmp)
if dict_search('privacy.plaintext_password', user_config) is not None:
tmp = hash(dict_search('privacy.plaintext_password', user_config),
@@ -222,8 +221,9 @@ def generate(snmp):
snmp['v3']['user'][user]['privacy']['encrypted_password'] = tmp
del snmp['v3']['user'][user]['privacy']['plaintext_password']
- call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" privacy encrypted-password "{tmp}" > /dev/null')
- call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" privacy plaintext-password > /dev/null')
+ cli_base = ['service', 'snmp', 'v3', 'user', user, 'privacy']
+ delete_cli_node(cli_base + ['plaintext-password'])
+ add_cli_node(cli_base + ['encrypted-password'], value=tmp)
# Write client config file
render(config_file_client, 'snmp/etc.snmp.conf.j2', snmp)
@@ -246,7 +246,7 @@ def apply(snmp):
return None
# start SNMP daemon
- call(f'systemctl restart {systemd_service}')
+ call(f'systemctl reload-or-restart {systemd_service}')
# Enable AgentX in FRR
# This should be done for each daemon individually because common command
diff --git a/src/conf_mode/system_login.py b/src/conf_mode/system_login.py
index 20121f170..439fa645b 100755
--- a/src/conf_mode/system_login.py
+++ b/src/conf_mode/system_login.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2023 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -26,14 +26,15 @@ from time import sleep
from vyos.config import Config
from vyos.configverify import verify_vrf
-from vyos.defaults import directories
from vyos.template import render
from vyos.template import is_ipv4
+from vyos.utils.auth import get_current_user
+from vyos.utils.configfs import delete_cli_node
+from vyos.utils.configfs import add_cli_node
from vyos.utils.dict import dict_search
from vyos.utils.file import chown
from vyos.utils.process import cmd
from vyos.utils.process import call
-from vyos.utils.process import rc_cmd
from vyos.utils.process import run
from vyos.utils.process import DEVNULL
from vyos import ConfigError
@@ -125,10 +126,9 @@ def verify(login):
# This check is required as the script is also executed from vyos-router
# init script and there is no SUDO_USER environment variable available
# during system boot.
- if 'SUDO_USER' in os.environ:
- cur_user = os.environ['SUDO_USER']
- if cur_user in login['rm_users']:
- raise ConfigError(f'Attempting to delete current user: {cur_user}')
+ tmp = get_current_user()
+ if tmp in login['rm_users']:
+ raise ConfigError(f'Attempting to delete current user: {tmp}')
if 'user' in login:
system_users = getpwall()
@@ -221,35 +221,13 @@ def generate(login):
login['user'][user]['authentication']['encrypted_password'] = encrypted_password
del login['user'][user]['authentication']['plaintext_password']
- # remove old plaintext password and set new encrypted password
- env = os.environ.copy()
- env['vyos_libexec_dir'] = directories['base']
-
# Set default commands for re-adding user with encrypted password
- del_user_plain = f"system login user {user} authentication plaintext-password"
- add_user_encrypt = f"system login user {user} authentication encrypted-password '{encrypted_password}'"
-
- lvl = env['VYATTA_EDIT_LEVEL']
- # We're in config edit level, for example "edit system login"
- # Change default commands for re-adding user with encrypted password
- if lvl != '/':
- # Replace '/system/login' to 'system login'
- lvl = lvl.strip('/').split('/')
- # Convert command str to list
- del_user_plain = del_user_plain.split()
- # New command exclude level, for example "edit system login"
- del_user_plain = del_user_plain[len(lvl):]
- # Convert string to list
- del_user_plain = " ".join(del_user_plain)
-
- add_user_encrypt = add_user_encrypt.split()
- add_user_encrypt = add_user_encrypt[len(lvl):]
- add_user_encrypt = " ".join(add_user_encrypt)
-
- ret, out = rc_cmd(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env)
- if ret: raise ConfigError(out)
- ret, out = rc_cmd(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env)
- if ret: raise ConfigError(out)
+ del_user_plain = ['system', 'login', 'user', user, 'authentication', 'plaintext-password']
+ add_user_encrypt = ['system', 'login', 'user', user, 'authentication', 'encrypted-password']
+
+ delete_cli_node(del_user_plain)
+ add_cli_node(add_user_encrypt, value=encrypted_password)
+
else:
try:
if get_shadow_password(user) == dict_search('authentication.encrypted_password', user_config):
@@ -283,8 +261,6 @@ def generate(login):
if os.path.isfile(tacacs_nss_config_file):
os.unlink(tacacs_nss_config_file)
-
-
# NSS must always be present on the system
render(nss_config_file, 'login/nsswitch.conf.j2', login,
permission=0o644, user='root', group='root')
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index a2e5db575..ad4c0deae 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -24,6 +24,9 @@ from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
from vyos.system import grub_util
from vyos.template import render
+from vyos.utils.dict import dict_search
+from vyos.utils.file import write_file
+from vyos.utils.kernel import check_kmod
from vyos.utils.process import cmd
from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_addr_assigned
@@ -35,6 +38,8 @@ airbag.enable()
curlrc_config = r'/etc/curlrc'
ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf'
systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target'
+usb_autosuspend = r'/etc/udev/rules.d/40-usb-autosuspend.rules'
+kernel_dynamic_debug = r'/sys/kernel/debug/dynamic_debug/control'
time_format_to_locale = {
'12-hour': 'en_US.UTF-8',
'24-hour': 'en_GB.UTF-8'
@@ -85,6 +90,7 @@ def verify(options):
def generate(options):
render(curlrc_config, 'system/curlrc.j2', options)
render(ssh_config, 'system/ssh_config.j2', options)
+ render(usb_autosuspend, 'system/40_usb_autosuspend.j2', options)
cmdline_options = []
if 'kernel' in options:
@@ -155,6 +161,18 @@ def apply(options):
time_format = time_format_to_locale.get(options['time_format'])
cmd(f'localectl set-locale LC_TIME={time_format}')
+ cmd('udevadm control --reload-rules')
+
+ # Enable/disable dynamic debugging for kernel modules
+ modules = ['wireguard']
+ modules_enabled = dict_search('kernel.debug', options) or []
+ for module in modules:
+ if module in modules_enabled:
+ check_kmod(module)
+ write_file(kernel_dynamic_debug, f'module {module} +p')
+ else:
+ write_file(kernel_dynamic_debug, f'module {module} -p')
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 8159fedea..42785134f 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -21,14 +21,17 @@ from vyos.base import Warning
from vyos.config import Config
from vyos.configverify import verify_pki_certificate
from vyos.configverify import verify_pki_ca_certificate
-from vyos.pki import wrap_certificate
+from vyos.pki import find_chain
+from vyos.pki import encode_certificate
+from vyos.pki import load_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
-from vyos.utils.process import call
+from vyos.utils.dict import dict_search
+from vyos.utils.file import write_file
from vyos.utils.network import check_port_availability
-from vyos.utils.process import is_systemd_service_running
from vyos.utils.network import is_listen_port_bind_service
-from vyos.utils.dict import dict_search
+from vyos.utils.process import call
+from vyos.utils.process import is_systemd_service_running
from vyos import ConfigError
from passlib.hash import sha512_crypt
from time import sleep
@@ -142,7 +145,8 @@ def verify(ocserv):
verify_pki_certificate(ocserv, ocserv['ssl']['certificate'])
if 'ca_certificate' in ocserv['ssl']:
- verify_pki_ca_certificate(ocserv, ocserv['ssl']['ca_certificate'])
+ for ca_cert in ocserv['ssl']['ca_certificate']:
+ verify_pki_ca_certificate(ocserv, ca_cert)
# Check network settings
if "network_settings" in ocserv:
@@ -219,25 +223,36 @@ def generate(ocserv):
if "ssl" in ocserv:
cert_file_path = os.path.join(cfg_dir, 'cert.pem')
cert_key_path = os.path.join(cfg_dir, 'cert.key')
- ca_cert_file_path = os.path.join(cfg_dir, 'ca.pem')
+
if 'certificate' in ocserv['ssl']:
cert_name = ocserv['ssl']['certificate']
pki_cert = ocserv['pki']['certificate'][cert_name]
- with open(cert_file_path, 'w') as f:
- f.write(wrap_certificate(pki_cert['certificate']))
+ loaded_pki_cert = load_certificate(pki_cert['certificate'])
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in ocserv['pki']['ca'].values()} if 'ca' in ocserv['pki'] else {}
+
+ cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
+
+ write_file(cert_file_path,
+ '\n'.join(encode_certificate(c) for c in cert_full_chain))
if 'private' in pki_cert and 'key' in pki_cert['private']:
- with open(cert_key_path, 'w') as f:
- f.write(wrap_private_key(pki_cert['private']['key']))
+ write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
if 'ca_certificate' in ocserv['ssl']:
- ca_name = ocserv['ssl']['ca_certificate']
- pki_ca_cert = ocserv['pki']['ca'][ca_name]
+ ca_cert_file_path = os.path.join(cfg_dir, 'ca.pem')
+ ca_chains = []
+
+ for ca_name in ocserv['ssl']['ca_certificate']:
+ pki_ca_cert = ocserv['pki']['ca'][ca_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))
- with open(ca_cert_file_path, 'w') as f:
- f.write(wrap_certificate(pki_ca_cert['certificate']))
+ write_file(ca_cert_file_path, '\n'.join(ca_chains))
# Render config
render(ocserv_conf, 'ocserv/ocserv_config.j2', ocserv)
diff --git a/src/migration-scripts/firewall/15-to-16 b/src/migration-scripts/firewall/15-to-16
index 7c8d38fe6..28df1256e 100755
--- a/src/migration-scripts/firewall/15-to-16
+++ b/src/migration-scripts/firewall/15-to-16
@@ -42,8 +42,9 @@ if not config.exists(conntrack_base):
for protocol in ['icmp', 'tcp', 'udp', 'other']:
if config.exists(conntrack_base + [protocol]):
- if not config.exists(firewall_base):
+ if not config.exists(firewall_base + ['timeout']):
config.set(firewall_base + ['timeout'])
+
config.copy(conntrack_base + [protocol], firewall_base + ['timeout', protocol])
config.delete(conntrack_base + [protocol])
@@ -52,4 +53,4 @@ try:
f.write(config.to_string())
except OSError as e:
print("Failed to save the modified config: {}".format(e))
- exit(1) \ No newline at end of file
+ exit(1)
diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21
index 14ad0fe4d..05a0c7237 100755
--- a/src/migration-scripts/interfaces/20-to-21
+++ b/src/migration-scripts/interfaces/20-to-21
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 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
@@ -22,6 +22,7 @@ from sys import argv
from vyos.ethtool import Ethtool
from vyos.configtree import ConfigTree
+from vyos.utils.network import interface_exists
if len(argv) < 2:
print("Must specify file name!")
@@ -38,6 +39,10 @@ if not config.exists(base):
exit(0)
for ifname in config.list_nodes(base):
+ # Bail out early if interface vanished from system
+ if not interface_exists(ifname):
+ continue
+
eth = Ethtool(ifname)
# If GRO is enabled by the Kernel - we reflect this on the CLI. If GRO is
diff --git a/src/migration-scripts/openvpn/1-to-2 b/src/migration-scripts/openvpn/1-to-2
new file mode 100755
index 000000000..1f82a2128
--- /dev/null
+++ b/src/migration-scripts/openvpn/1-to-2
@@ -0,0 +1,74 @@
+#!/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/>.
+#
+# Removes --cipher option (deprecated) from OpenVPN configs
+# and moves it to --data-ciphers for server and client modes
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['interfaces', 'openvpn']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ ovpn_intfs = config.list_nodes(['interfaces', 'openvpn'])
+ for i in ovpn_intfs:
+ # Remove 'encryption cipher' and add this value to 'encryption ncp-ciphers'
+ # for server and client mode.
+ # Site-to-site mode still can use --cipher option
+ cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'cipher']
+ ncp_cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers']
+ if config.exists(cipher_path):
+ if config.exists(['interfaces', 'openvpn', i, 'shared-secret-key']):
+ continue
+ cipher = config.return_value(cipher_path)
+ config.delete(cipher_path)
+ if cipher == 'none':
+ if not config.exists(ncp_cipher_path):
+ config.delete(['interfaces', 'openvpn', i, 'encryption'])
+ continue
+
+ ncp_ciphers = []
+ if config.exists(ncp_cipher_path):
+ ncp_ciphers = config.return_values(ncp_cipher_path)
+ config.delete(ncp_cipher_path)
+
+ # need to add the deleted cipher at the first place in the list
+ if cipher in ncp_ciphers:
+ ncp_ciphers.remove(cipher)
+ ncp_ciphers.insert(0, cipher)
+
+ for c in ncp_ciphers:
+ config.set(ncp_cipher_path, value=c, replace=False)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py
index 6c86ff492..f3b09b452 100755
--- a/src/op_mode/conntrack_sync.py
+++ b/src/op_mode/conntrack_sync.py
@@ -19,6 +19,8 @@ import sys
import syslog
import xmltodict
+from tabulate import tabulate
+
import vyos.opmode
from vyos.configquery import CliShellApiConfigQuery
@@ -27,7 +29,6 @@ from vyos.utils.commit import commit_in_progress
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import run
-from vyos.template import render_to_string
conntrackd_bin = '/usr/sbin/conntrackd'
conntrackd_config = '/run/conntrackd/conntrackd.conf'
@@ -59,6 +60,26 @@ def flush_cache(direction):
if tmp > 0:
raise vyos.opmode.Error('Failed to clear {direction} cache')
+def get_formatted_output(data):
+ data_entries = []
+ for parsed in data:
+ for meta in parsed.get('flow', {}).get('meta', []):
+ direction = meta['@direction']
+ if direction == 'original':
+ src = meta['layer3']['src']
+ dst = meta['layer3']['dst']
+ sport = meta['layer4'].get('sport')
+ dport = meta['layer4'].get('dport')
+ protocol = meta['layer4'].get('@protoname')
+ orig_src = f'{src}:{sport}' if sport else src
+ orig_dst = f'{dst}:{dport}' if dport else dst
+
+ data_entries.append([orig_src, orig_dst, protocol])
+
+ headers = ["Source", "Destination", "Protocol"]
+ output = tabulate(data_entries, headers, tablefmt="simple")
+ return output
+
def from_xml(raw, xml):
out = []
for line in xml.splitlines():
@@ -70,7 +91,7 @@ def from_xml(raw, xml):
if raw:
return out
else:
- return render_to_string('conntrackd/conntrackd.op-mode.j2', {'data' : out})
+ return get_formatted_output(out)
def restart():
is_configured()
diff --git a/src/op_mode/cpu.py b/src/op_mode/cpu.py
index d53663c17..1a0f7392f 100755
--- a/src/op_mode/cpu.py
+++ b/src/op_mode/cpu.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2016-2022 VyOS maintainers and contributors
+# Copyright (C) 2016-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
@@ -16,8 +16,9 @@
import sys
-import vyos.cpu
import vyos.opmode
+from vyos.utils.cpu import get_cpus
+from vyos.utils.cpu import get_core_count
from jinja2 import Template
@@ -37,15 +38,15 @@ CPU model(s): {{models | join(", ")}}
""")
def _get_raw_data():
- return vyos.cpu.get_cpus()
+ return get_cpus()
def _format_cpus(cpu_data):
env = {'cpus': cpu_data}
return cpu_template.render(env).strip()
def _get_summary_data():
- count = vyos.cpu.get_core_count()
- cpu_data = vyos.cpu.get_cpus()
+ count = get_core_count()
+ cpu_data = get_cpus()
models = [c['model name'] for c in cpu_data]
env = {'count': count, "models": models}
@@ -79,4 +80,3 @@ if __name__ == '__main__':
except (ValueError, vyos.opmode.Error) as e:
print(e)
sys.exit(1)
-
diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py
index 169a15840..b193d8109 100755
--- a/src/op_mode/ikev2_profile_generator.py
+++ b/src/op_mode/ikev2_profile_generator.py
@@ -21,6 +21,7 @@ from socket import getfqdn
from cryptography.x509.oid import NameOID
from vyos.configquery import ConfigTreeQuery
+from vyos.config import config_dict_mangle_acme
from vyos.pki import CERT_BEGIN
from vyos.pki import CERT_END
from vyos.pki import find_chain
@@ -123,6 +124,8 @@ pki_base = ['pki']
conf = ConfigTreeQuery()
if not conf.exists(config_base):
exit('IPsec remote-access is not configured!')
+if not conf.exists(pki_base):
+ exit('PKI is not configured!')
profile_name = 'VyOS IKEv2 Profile'
if args.profile:
@@ -147,30 +150,36 @@ tmp = getfqdn().split('.')
tmp = reversed(tmp)
data['rfqdn'] = '.'.join(tmp)
-pki = conf.get_config_dict(pki_base, get_first_key=True)
-cert_name = data['authentication']['x509']['certificate']
-
-cert_data = load_certificate(pki['certificate'][cert_name]['certificate'])
-data['cert_common_name'] = cert_data.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
-data['ca_common_name'] = cert_data.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
-data['ca_certificates'] = []
-
-loaded_ca_certs = {load_certificate(c['certificate'])
- for c in pki['ca'].values()} if 'ca' in pki else {}
-
-for ca_name in data['authentication']['x509']['ca_certificate']:
- loaded_ca_cert = load_certificate(pki['ca'][ca_name]['certificate'])
- ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
- for ca in ca_full_chain:
- tmp = {
- 'ca_name' : ca.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value,
- 'ca_chain' : encode_certificate(ca).replace(CERT_BEGIN, '').replace(CERT_END, '').replace('\n', ''),
- }
- data['ca_certificates'].append(tmp)
-
-# Remove duplicate list entries for CA certificates, as they are added by their common name
-# https://stackoverflow.com/a/9427216
-data['ca_certificates'] = [dict(t) for t in {tuple(d.items()) for d in data['ca_certificates']}]
+if args.os == 'ios':
+ pki = conf.get_config_dict(pki_base, get_first_key=True)
+ if 'certificate' in pki:
+ for certificate in pki['certificate']:
+ pki['certificate'][certificate] = config_dict_mangle_acme(certificate, pki['certificate'][certificate])
+
+ cert_name = data['authentication']['x509']['certificate']
+
+
+ cert_data = load_certificate(pki['certificate'][cert_name]['certificate'])
+ data['cert_common_name'] = cert_data.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
+ data['ca_common_name'] = cert_data.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
+ data['ca_certificates'] = []
+
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in pki['ca'].values()} if 'ca' in pki else {}
+
+ for ca_name in data['authentication']['x509']['ca_certificate']:
+ loaded_ca_cert = load_certificate(pki['ca'][ca_name]['certificate'])
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ for ca in ca_full_chain:
+ tmp = {
+ 'ca_name' : ca.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value,
+ 'ca_chain' : encode_certificate(ca).replace(CERT_BEGIN, '').replace(CERT_END, '').replace('\n', ''),
+ }
+ data['ca_certificates'].append(tmp)
+
+ # Remove duplicate list entries for CA certificates, as they are added by their common name
+ # https://stackoverflow.com/a/9427216
+ data['ca_certificates'] = [dict(t) for t in {tuple(d.items()) for d in data['ca_certificates']}]
esp_proposals = conf.get_config_dict(ipsec_base + ['esp-group', data['esp_group'], 'proposal'],
key_mangling=('-', '_'), get_first_key=True)
diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py
index 58cfce443..fac622b81 100755
--- a/src/op_mode/lldp.py
+++ b/src/op_mode/lldp.py
@@ -120,7 +120,12 @@ def _get_formatted_output(raw_data):
tmp.append('')
# Remote interface
- interface = jmespath.search('port.descr', values)
+ interface = None
+ if jmespath.search('port.id.type', values) == 'ifname':
+ # Remote peer has explicitly returned the interface name as the PortID
+ interface = jmespath.search('port.id.value', values)
+ if not interface:
+ interface = jmespath.search('port.descr', values)
if not interface:
interface = jmespath.search('port.id.value', values)
if not interface:
@@ -136,11 +141,17 @@ def _get_formatted_output(raw_data):
@_verify
def show_neighbors(raw: bool, interface: typing.Optional[str], detail: typing.Optional[bool]):
- lldp_data = _get_raw_data(interface=interface, detail=detail)
- if raw:
- return lldp_data
- else:
- return _get_formatted_output(lldp_data)
+ if raw or not detail:
+ lldp_data = _get_raw_data(interface=interface, detail=detail)
+ if raw:
+ return lldp_data
+ else:
+ return _get_formatted_output(lldp_data)
+ else: # non-raw, detail
+ tmp = 'lldpcli -f text show neighbors details'
+ if interface:
+ tmp += f' ports {interface}'
+ return cmd(tmp)
if __name__ == "__main__":
try:
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index 361b60e0e..9ce166c7d 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -426,11 +426,15 @@ def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
return None
cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=True, is_sub_ca=True)
- passphrase = ask_passphrase()
+
+ passphrase = None
+ if private_key is not None:
+ passphrase = ask_passphrase()
if not install and not file:
print(encode_certificate(cert))
- print(encode_private_key(private_key, passphrase=passphrase))
+ if private_key is not None:
+ print(encode_private_key(private_key, passphrase=passphrase))
return None
if install:
@@ -438,7 +442,8 @@ def generate_ca_certificate_sign(name, ca_name, install=False, file=False):
if file:
write_file(f'{name}.pem', encode_certificate(cert))
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+ if private_key is not None:
+ write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
def generate_certificate_sign(name, ca_name, install=False, file=False):
ca_dict = get_config_ca_certificate(ca_name)
@@ -492,11 +497,15 @@ def generate_certificate_sign(name, ca_name, install=False, file=False):
return None
cert = generate_certificate(cert_req, ca_cert, ca_private_key, is_ca=False)
- passphrase = ask_passphrase()
+
+ passphrase = None
+ if private_key is not None:
+ passphrase = ask_passphrase()
if not install and not file:
print(encode_certificate(cert))
- print(encode_private_key(private_key, passphrase=passphrase))
+ if private_key is not None:
+ print(encode_private_key(private_key, passphrase=passphrase))
return None
if install:
@@ -504,7 +513,8 @@ def generate_certificate_sign(name, ca_name, install=False, file=False):
if file:
write_file(f'{name}.pem', encode_certificate(cert))
- write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
+ if private_key is not None:
+ write_file(f'{name}.key', encode_private_key(private_key, passphrase=passphrase))
def generate_certificate_selfsign(name, install=False, file=False):
private_key, key_type = generate_private_key()
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index 6c8f802b5..cb4a175dd 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -110,7 +110,7 @@ def check_unsaved_config():
from vyos.config_mgmt import unsaved_commits
from vyos.utils.boot import boot_configuration_success
- if unsaved_commits() and boot_configuration_success():
+ if unsaved_commits(allow_missing_config=True) and boot_configuration_success():
print("Warning: there are unsaved configuration changes!")
print("Run 'save' command if you do not want to lose those changes after reboot/shutdown.")
else:
diff --git a/src/op_mode/tcpdump.py b/src/op_mode/tcpdump.py
new file mode 100644
index 000000000..607b59603
--- /dev/null
+++ b/src/op_mode/tcpdump.py
@@ -0,0 +1,165 @@
+#! /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/>.
+
+import sys
+
+from vyos.utils.process import call
+
+options = {
+ 'dump': {
+ 'cmd': '{command} -A',
+ 'type': 'noarg',
+ 'help': 'Print each packet (minus its link level header) in ASCII.'
+ },
+ 'hexdump': {
+ 'cmd': '{command} -X',
+ 'type': 'noarg',
+ 'help': 'Print each packet (minus its link level header) in both hex and ASCII.'
+ },
+ 'filter': {
+ 'cmd': '{command} \'{value}\'',
+ 'type': '<pcap-filter>',
+ 'help': 'Match traffic for capture and display with a pcap-filter expression.'
+ },
+ 'numeric': {
+ 'cmd': '{command} -nn',
+ 'type': 'noarg',
+ 'help': 'Do not attempt to resolve addresses, protocols or services to names.'
+ },
+ 'save': {
+ 'cmd': '{command} -w {value}',
+ 'type': '<file>',
+ 'help': 'Write captured raw packets to <file> rather than parsing or printing them out.'
+ },
+ 'verbose': {
+ 'cmd': '{command} -vvv -ne',
+ 'type': 'noarg',
+ 'help': 'Parse packets with increased detail output, including link-level headers and extended decoding protocol sanity checks.'
+ },
+}
+
+tcpdump = 'sudo /usr/bin/tcpdump'
+
+class List(list):
+ def first(self):
+ return self.pop(0) if self else ''
+
+ def last(self):
+ return self.pop() if self else ''
+
+ def prepend(self, value):
+ self.insert(0, value)
+
+
+def completion_failure(option: str) -> None:
+ """
+ Shows failure message after TAB when option is wrong
+ :param option: failure option
+ :type str:
+ """
+ sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option))
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
+
+
+def expansion_failure(option, completions):
+ reason = 'Ambiguous' if completions else 'Invalid'
+ sys.stderr.write(
+ '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv),
+ option))
+ if completions:
+ sys.stderr.write(' Possible completions:\n ')
+ sys.stderr.write('\n '.join(completions))
+ sys.stderr.write('\n')
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
+
+
+def complete(prefix):
+ return [o for o in options if o.startswith(prefix)]
+
+
+def convert(command, args):
+ while args:
+ shortname = args.first()
+ longnames = complete(shortname)
+ if len(longnames) != 1:
+ expansion_failure(shortname, longnames)
+ longname = longnames[0]
+ if options[longname]['type'] == 'noarg':
+ command = options[longname]['cmd'].format(
+ command=command, value='')
+ elif not args:
+ sys.exit(f'monitor traffic: missing argument for {longname} option')
+ else:
+ command = options[longname]['cmd'].format(
+ command=command, value=args.first())
+ return command
+
+
+if __name__ == '__main__':
+ args = List(sys.argv[1:])
+ ifname = args.first()
+
+ # Slightly simplified & tweaked version of the code from mtr.py - it may be
+ # worthwhile to combine and centralise this in a common module.
+ if ifname == '--get-options-nested':
+ args.first() # pop monitor
+ args.first() # pop traffic
+ args.first() # pop interface
+ args.first() # pop <ifname>
+ usedoptionslist = []
+ while args:
+ option = args.first() # pop option
+ matched = complete(option) # get option parameters
+ usedoptionslist.append(option) # list of used options
+ # Select options
+ if not args:
+ # remove from Possible completions used options
+ for o in usedoptionslist:
+ if o in matched:
+ matched.remove(o)
+ if not matched:
+ sys.stdout.write('<nocomps>')
+ else:
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+
+ if len(matched) > 1:
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+ # If option doesn't have value
+ if matched:
+ if options[matched[0]]['type'] == 'noarg':
+ continue
+ else:
+ # Unexpected option
+ completion_failure(option)
+
+ value = args.first() # pop option's value
+ if not args:
+ matched = complete(option)
+ helplines = options[matched[0]]['type']
+ # Run helpfunction to get list of possible values
+ if 'helpfunction' in options[matched[0]]:
+ result = options[matched[0]]['helpfunction']()
+ if result:
+ helplines = '\n' + ' '.join(result)
+ sys.stdout.write(helplines)
+ sys.exit(0)
+
+ command = convert(tcpdump, args)
+ call(f'{command} -i {ifname}')
diff --git a/src/op_mode/uptime.py b/src/op_mode/uptime.py
index 059a4c3f6..559eed24c 100755
--- a/src/op_mode/uptime.py
+++ b/src/op_mode/uptime.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 as
@@ -29,8 +29,8 @@ def _get_uptime_seconds():
def _get_load_averages():
from re import search
+ from vyos.utils.cpu import get_core_count
from vyos.utils.process import cmd
- from vyos.cpu import get_core_count
data = cmd("uptime")
matches = search(r"load average:\s*(?P<one>[0-9\.]+)\s*,\s*(?P<five>[0-9\.]+)\s*,\s*(?P<fifteen>[0-9\.]+)\s*", data)
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index c89c486e5..d92b539c8 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -179,8 +179,13 @@ def initialization(socket):
pid_string = socket.recv().decode("utf-8", "ignore")
resp = "pid"
socket.send(resp.encode())
+ sudo_user_string = socket.recv().decode("utf-8", "ignore")
+ resp = "sudo_user"
+ socket.send(resp.encode())
logger.debug(f"config session pid is {pid_string}")
+ logger.debug(f"config session sudo_user is {sudo_user_string}")
+
try:
session_out = os.readlink(f"/proc/{pid_string}/fd/1")
session_mode = 'w'
@@ -192,6 +197,8 @@ def initialization(socket):
session_out = script_stdout_log
session_mode = 'a'
+ os.environ['SUDO_USER'] = sudo_user_string
+
try:
configsource = ConfigSourceString(running_config_text=active_string,
session_config_text=session_string)
@@ -266,9 +273,6 @@ if __name__ == '__main__':
cfg_group = grp.getgrnam(CFG_GROUP)
os.setgid(cfg_group.gr_gid)
- os.environ['SUDO_USER'] = 'vyos'
- os.environ['SUDO_GID'] = str(cfg_group.gr_gid)
-
def sig_handler(signum, frame):
shutdown()
diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c
index 41723e7a4..4d836127d 100644
--- a/src/shim/vyshim.c
+++ b/src/shim/vyshim.c
@@ -178,6 +178,13 @@ int initialization(void* Requester)
strsep(&pid_val, "_");
debug_print("config session pid: %s\n", pid_val);
+ char *sudo_user = getenv("SUDO_USER");
+ if (!sudo_user) {
+ char nobody[] = "nobody";
+ sudo_user = nobody;
+ }
+ debug_print("sudo_user is %s\n", sudo_user);
+
debug_print("Sending init announcement\n");
char *init_announce = mkjson(MKJSON_OBJ, 1,
MKJSON_STRING, "type", "init");
@@ -240,6 +247,10 @@ int initialization(void* Requester)
zmq_recv(Requester, buffer, 16, 0);
debug_print("Received pid receipt\n");
+ debug_print("Sending config session sudo_user\n");
+ zmq_send(Requester, sudo_user, strlen(sudo_user), 0);
+ zmq_recv(Requester, buffer, 16, 0);
+ debug_print("Received sudo_user receipt\n");
return 0;
}