summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/completion/list_login_ttys.py25
-rwxr-xr-xsrc/conf_mode/system_console.py15
-rwxr-xr-xsrc/conf_mode/vrf.py17
-rwxr-xr-xsrc/op_mode/generate_ovpn_client_file.py113
-rwxr-xr-xsrc/op_mode/ipsec.py1
-rw-r--r--src/op_mode/serial.py38
6 files changed, 150 insertions, 59 deletions
diff --git a/src/completion/list_login_ttys.py b/src/completion/list_login_ttys.py
new file mode 100644
index 000000000..4d77a1b8b
--- /dev/null
+++ b/src/completion/list_login_ttys.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from vyos.utils.serial import get_serial_units
+
+if __name__ == '__main__':
+ # Autocomplete uses runtime state rather than the config tree, as a manual
+ # restart/cleanup may be needed for deleted devices.
+ tty_completions = [ '<text>' ] + [ x['device'] for x in get_serial_units() if 'device' in x ]
+ print(' '.join(tty_completions))
+
+
diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py
index 19bbb8875..27bf92e0b 100755
--- a/src/conf_mode/system_console.py
+++ b/src/conf_mode/system_console.py
@@ -19,8 +19,10 @@ from pathlib import Path
from vyos.config import Config
from vyos.utils.process import call
+from vyos.utils.serial import restart_login_consoles
from vyos.system import grub_util
from vyos.template import render
+from vyos.defaults import directories
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -74,7 +76,6 @@ def generate(console):
for root, dirs, files in os.walk(base_dir):
for basename in files:
if 'serial-getty' in basename:
- call(f'systemctl stop {basename}')
os.unlink(os.path.join(root, basename))
if not console or 'device' not in console:
@@ -122,6 +123,11 @@ def apply(console):
# Reload systemd manager configuration
call('systemctl daemon-reload')
+ # Service control moved to vyos.utils.serial to unify checks and prompts.
+ # If users are connected, we want to show an informational message on completing
+ # the process, but not halt configuration processing with an interactive prompt.
+ restart_login_consoles(prompt_user=False, quiet=False)
+
if not console:
return None
@@ -129,13 +135,6 @@ def apply(console):
# Configure screen blank powersaving on VGA console
call('/usr/bin/setterm -blank 15 -powersave powerdown -powerdown 60 -term linux </dev/tty1 >/dev/tty1 2>&1')
- # Start getty process on configured serial interfaces
- for device in console['device']:
- # Only start console if it exists on the running system. If a user
- # detaches a USB serial console and reboots - it should not fail!
- if os.path.exists(f'/dev/{device}'):
- call(f'systemctl restart serial-getty@{device}.service')
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 184725573..72b178c89 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sys import exit
+from jmespath import search
from json import loads
from vyos.config import Config
@@ -70,6 +71,14 @@ def has_rule(af : str, priority : int, table : str=None):
return True
return False
+def is_nft_vrf_zone_rule_setup() -> bool:
+ """
+ Check if an nftables connection tracking rule already exists
+ """
+ tmp = loads(cmd('sudo nft -j list table inet vrf_zones'))
+ num_rules = len(search("nftables[].rule[].chain", tmp))
+ return bool(num_rules)
+
def vrf_interfaces(c, match):
matched = []
old_level = c.get_level()
@@ -264,6 +273,7 @@ def apply(vrf):
if not has_rule(afi, 2000, 'l3mdev'):
call(f'ip {afi} rule add pref 2000 l3mdev unreachable')
+ nft_vrf_zone_rule_setup = False
for name, config in vrf['name'].items():
table = config['table']
if not interface_exists(name):
@@ -302,7 +312,12 @@ def apply(vrf):
nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}'
cmd(f'nft {nft_add_element}')
- if vrf['conntrack']:
+ # Only call into nftables as long as there is nothing setup to avoid wasting
+ # CPU time and thus lenghten the commit process
+ if not nft_vrf_zone_rule_setup:
+ nft_vrf_zone_rule_setup = is_nft_vrf_zone_rule_setup()
+ # Install nftables conntrack rules only once
+ if vrf['conntrack'] and not nft_vrf_zone_rule_setup:
for chain, rule in nftables_rules.items():
cmd(f'nft add rule inet vrf_zones {chain} {rule}')
diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py
index 2d96fe217..974f7d9b6 100755
--- a/src/op_mode/generate_ovpn_client_file.py
+++ b/src/op_mode/generate_ovpn_client_file.py
@@ -19,42 +19,53 @@ import argparse
from jinja2 import Template
from textwrap import fill
-from vyos.configquery import ConfigTreeQuery
+from vyos.config import Config
from vyos.ifconfig import Section
client_config = """
client
nobind
-remote {{ remote_host }} {{ port }}
+remote {{ local_host if local_host else 'x.x.x.x' }} {{ port }}
remote-cert-tls server
-proto {{ 'tcp-client' if protocol == 'tcp-active' else 'udp' }}
-dev {{ device }}
-dev-type {{ device }}
+proto {{ 'tcp-client' if protocol == 'tcp-passive' else 'udp' }}
+dev {{ device_type }}
+dev-type {{ device_type }}
persist-key
persist-tun
verb 3
# Encryption options
+{# Define the encryption map #}
+{% set encryption_map = {
+ 'des': 'DES-CBC',
+ '3des': 'DES-EDE3-CBC',
+ 'bf128': 'BF-CBC',
+ 'bf256': 'BF-CBC',
+ 'aes128gcm': 'AES-128-GCM',
+ 'aes128': 'AES-128-CBC',
+ 'aes192gcm': 'AES-192-GCM',
+ 'aes192': 'AES-192-CBC',
+ 'aes256gcm': 'AES-256-GCM',
+ 'aes256': 'AES-256-CBC'
+} %}
+
{% if encryption is defined and encryption is not none %}
-{% if encryption.cipher is defined and encryption.cipher is not none %}
-cipher {{ encryption.cipher }}
-{% if encryption.cipher == 'bf128' %}
-keysize 128
-{% elif encryption.cipher == 'bf256' %}
-keysize 256
+{% if encryption.ncp_ciphers is defined and encryption.ncp_ciphers is not none %}
+cipher {% for algo in encryption.ncp_ciphers %}
+{{ encryption_map[algo] if algo in encryption_map.keys() else algo }}{% if not loop.last %}:{% endif %}
+{% endfor %}
+
+data-ciphers {% for algo in encryption.ncp_ciphers %}
+{{ encryption_map[algo] if algo in encryption_map.keys() else algo }}{% if not loop.last %}:{% endif %}
+{% endfor %}
{% endif %}
-{% endif %}
-{% if encryption.ncp_ciphers is defined and encryption.ncp_ciphers is not none %}
-data-ciphers {{ encryption.ncp_ciphers }}
-{% endif %}
{% endif %}
{% if hash is defined and hash is not none %}
auth {{ hash }}
{% endif %}
-keysize 256
-comp-lzo {{ '' if use_lzo_compression is defined else 'no' }}
+{{ 'comp-lzo' if use_lzo_compression is defined else '' }}
<ca>
-----BEGIN CERTIFICATE-----
@@ -79,7 +90,7 @@ comp-lzo {{ '' if use_lzo_compression is defined else 'no' }}
"""
-config = ConfigTreeQuery()
+config = Config()
base = ['interfaces', 'openvpn']
if not config.exists(base):
@@ -89,10 +100,22 @@ if not config.exists(base):
if __name__ == '__main__':
parser = argparse.ArgumentParser()
- parser.add_argument("-i", "--interface", type=str, help='OpenVPN interface the client is connecting to', required=True)
- parser.add_argument("-a", "--ca", type=str, help='OpenVPN CA cerificate', required=True)
- parser.add_argument("-c", "--cert", type=str, help='OpenVPN client cerificate', required=True)
- parser.add_argument("-k", "--key", type=str, help='OpenVPN client cerificate key', action="store")
+ parser.add_argument(
+ "-i",
+ "--interface",
+ type=str,
+ help='OpenVPN interface the client is connecting to',
+ required=True,
+ )
+ parser.add_argument(
+ "-a", "--ca", type=str, help='OpenVPN CA cerificate', required=True
+ )
+ parser.add_argument(
+ "-c", "--cert", type=str, help='OpenVPN client cerificate', required=True
+ )
+ parser.add_argument(
+ "-k", "--key", type=str, help='OpenVPN client cerificate key', action="store"
+ )
args = parser.parse_args()
interface = args.interface
@@ -114,33 +137,25 @@ if __name__ == '__main__':
if not config.exists(['pki', 'certificate', cert, 'private', 'key']):
exit(f'OpenVPN certificate key "{key}" does not exist!')
- ca = config.value(['pki', 'ca', ca, 'certificate'])
+ config = config.get_config_dict(
+ base + [interface],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True,
+ with_pki=True,
+ )
+
+ ca = config['pki']['ca'][ca]['certificate']
ca = fill(ca, width=64)
- cert = config.value(['pki', 'certificate', cert, 'certificate'])
+ cert = config['pki']['certificate'][cert]['certificate']
cert = fill(cert, width=64)
- key = config.value(['pki', 'certificate', key, 'private', 'key'])
+ key = config['pki']['certificate'][key]['private']['key']
key = fill(key, width=64)
- remote_host = config.value(base + [interface, 'local-host'])
-
- ovpn_conf = config.get_config_dict(base + [interface], key_mangling=('-', '_'), get_first_key=True)
-
- port = '1194' if 'local_port' not in ovpn_conf else ovpn_conf['local_port']
- proto = 'udp' if 'protocol' not in ovpn_conf else ovpn_conf['protocol']
- device = 'tun' if 'device_type' not in ovpn_conf else ovpn_conf['device_type']
-
- config = {
- 'interface' : interface,
- 'ca' : ca,
- 'cert' : cert,
- 'key' : key,
- 'device' : device,
- 'port' : port,
- 'proto' : proto,
- 'remote_host' : remote_host,
- 'address' : [],
- }
-
-# Clear out terminal first
-print('\x1b[2J\x1b[H')
-client = Template(client_config, trim_blocks=True).render(config)
-print(client)
+
+ config['ca'] = ca
+ config['cert'] = cert
+ config['key'] = key
+ config['port'] = '1194' if 'local_port' not in config else config['local_port']
+
+ client = Template(client_config, trim_blocks=True).render(config)
+ print(client)
diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py
index c8f5072da..02ba126b4 100755
--- a/src/op_mode/ipsec.py
+++ b/src/op_mode/ipsec.py
@@ -13,7 +13,6 @@
#
# 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 pprint
import re
import sys
import typing
diff --git a/src/op_mode/serial.py b/src/op_mode/serial.py
new file mode 100644
index 000000000..a5864872b
--- /dev/null
+++ b/src/op_mode/serial.py
@@ -0,0 +1,38 @@
+#!/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, typing
+
+import vyos.opmode
+from vyos.utils.serial import restart_login_consoles as _restart_login_consoles
+
+def restart_console(device_name: typing.Optional[str]):
+ # Service control moved to vyos.utils.serial to unify checks and prompts.
+ # If users are connected, we want to show an informational message and a prompt
+ # to continue, verifying that the user acknowledges possible interruptions.
+ if device_name:
+ _restart_login_consoles(prompt_user=True, quiet=False, devices=[device_name])
+ else:
+ _restart_login_consoles(prompt_user=True, quiet=False)
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)