diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/interfaces_bonding.py | 11 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_bridge.py | 31 | ||||
-rwxr-xr-x | src/op_mode/firewall.py | 12 | ||||
-rwxr-xr-x | src/op_mode/image_installer.py | 24 | ||||
-rwxr-xr-x | src/op_mode/image_manager.py | 28 |
5 files changed, 91 insertions, 15 deletions
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index 371b219c0..5e5d5fba1 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -33,6 +33,7 @@ from vyos.ifconfig import BondIf from vyos.ifconfig.ethernet import EthernetIf from vyos.ifconfig import Section from vyos.template import render_to_string +from vyos.utils.assertion import assert_mac from vyos.utils.dict import dict_search from vyos.utils.dict import dict_to_paths_values from vyos.utils.network import interface_exists @@ -244,6 +245,16 @@ def verify(bond): raise ConfigError('primary interface only works for mode active-backup, ' \ 'transmit-load-balance or adaptive-load-balance') + if 'system_mac' in bond: + if bond['mode'] != '802.3ad': + raise ConfigError('Actor MAC address only available in 802.3ad mode!') + + system_mac = bond['system_mac'] + try: + assert_mac(system_mac, test_all_zero=False) + except: + raise ConfigError(f'Cannot use a multicast MAC address "{system_mac}" as system-mac!') + return None def generate(bond): diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py index 9789f7bd3..7b2c1ee0b 100755 --- a/src/conf_mode/interfaces_bridge.py +++ b/src/conf_mode/interfaces_bridge.py @@ -56,6 +56,17 @@ def get_config(config=None): bridge['member'].update({'interface_remove' : tmp }) else: bridge.update({'member' : {'interface_remove' : tmp }}) + for interface in tmp: + # When using VXLAN member interfaces that are configured for Single + # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to + # re-create VLAN to VNI mappings if required, but only if the interface + # is already live on the system - this must not be done on first commit + if interface.startswith('vxlan') and interface_exists(interface): + set_dependents('vxlan', conf, interface) + # When using Wireless member interfaces we need to inform hostapd + # to properly set-up the bridge + elif interface.startswith('wlan') and interface_exists(interface): + set_dependents('wlan', conf, interface) if dict_search('member.interface', bridge) is not None: for interface in list(bridge['member']['interface']): @@ -91,6 +102,10 @@ def get_config(config=None): # is already live on the system - this must not be done on first commit if interface.startswith('vxlan') and interface_exists(interface): set_dependents('vxlan', conf, interface) + # When using Wireless member interfaces we need to inform hostapd + # to properly set-up the bridge + elif interface.startswith('wlan') and interface_exists(interface): + set_dependents('wlan', conf, interface) # delete empty dictionary keys - no need to run code paths if nothing is there to do if 'member' in bridge: @@ -140,9 +155,6 @@ def verify(bridge): if 'enable_vlan' in bridge: if 'has_vlan' in interface_config: raise ConfigError(error_msg + 'it has VLAN subinterface(s) assigned!') - - if 'wlan' in interface: - raise ConfigError(error_msg + 'VLAN aware cannot be set!') else: for option in ['allowed_vlan', 'native_vlan']: if option in interface_config: @@ -168,12 +180,19 @@ def apply(bridge): else: br.update(bridge) - for interface in dict_search('member.interface', bridge) or []: - if interface.startswith('vxlan') and interface_exists(interface): + tmp = [] + if 'member' in bridge: + if 'interface_remove' in bridge['member']: + tmp.extend(bridge['member']['interface_remove']) + if 'interface' in bridge['member']: + tmp.extend(bridge['member']['interface']) + + for interface in tmp: + if interface.startswith(tuple(['vxlan', 'wlan'])) and interface_exists(interface): try: call_dependents() except ConfigError: - raise ConfigError('Error in updating VXLAN interface after changing bridge!') + raise ConfigError('Error updating member interface configuration after changing bridge!') return None diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index 442c186cc..15fbb65a2 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -531,9 +531,15 @@ def show_firewall_group(name=None): continue for idx, member in enumerate(members): - val = member.get('val', 'N/D') - timeout = str(member.get('timeout', 'N/D')) - expires = str(member.get('expires', 'N/D')) + if isinstance(member, str): + # Only member, and no timeout: + val = member + timeout = "N/D" + expires = "N/D" + else: + val = member.get('val', 'N/D') + timeout = str(member.get('timeout', 'N/D')) + expires = str(member.get('expires', 'N/D')) if args.detail: row.append(f'{val} (timeout: {timeout}, expires: {expires})') diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py index ba0e3b6db..0d2d7076c 100755 --- a/src/op_mode/image_installer.py +++ b/src/op_mode/image_installer.py @@ -23,6 +23,8 @@ from shutil import copy, chown, rmtree, copytree from glob import glob from sys import exit from os import environ +from os import readlink +from os import getpid, getppid from typing import Union from urllib.parse import urlparse from passlib.hosts import linux_context @@ -65,7 +67,7 @@ MSG_INPUT_PASSWORD: str = 'Please enter a password for the "vyos" user:' MSG_INPUT_PASSWORD_CONFIRM: str = 'Please confirm password for the "vyos" user:' MSG_INPUT_ROOT_SIZE_ALL: str = 'Would you like to use all the free space on the drive?' MSG_INPUT_ROOT_SIZE_SET: str = 'Please specify the size (in GB) of the root partition (min is 1.5 GB)?' -MSG_INPUT_CONSOLE_TYPE: str = 'What console should be used by default? (K: KVM, S: Serial, U: USB-Serial)?' +MSG_INPUT_CONSOLE_TYPE: str = 'What console should be used by default? (K: KVM, S: Serial)?' MSG_INPUT_COPY_DATA: str = 'Would you like to copy data to the new image?' MSG_INPUT_CHOOSE_COPY_DATA: str = 'From which image would you like to save config information?' MSG_INPUT_COPY_ENC_DATA: str = 'Would you like to copy the encrypted config to the new image?' @@ -614,6 +616,20 @@ def copy_ssh_host_keys() -> bool: return False +def console_hint() -> str: + pid = getppid() if 'SUDO_USER' in environ else getpid() + try: + path = readlink(f'/proc/{pid}/fd/1') + except OSError: + path = '/dev/tty' + + name = Path(path).name + if name == 'ttyS0': + return 'S' + else: + return 'K' + + def cleanup(mounts: list[str] = [], remove_items: list[str] = []) -> None: """Clean up after installation @@ -709,9 +725,9 @@ def install_image() -> None: # ask for default console console_type: str = ask_input(MSG_INPUT_CONSOLE_TYPE, - default='K', - valid_responses=['K', 'S', 'U']) - console_dict: dict[str, str] = {'K': 'tty', 'S': 'ttyS', 'U': 'ttyUSB'} + default=console_hint(), + valid_responses=['K', 'S']) + console_dict: dict[str, str] = {'K': 'tty', 'S': 'ttyS'} config_boot_list = ['/opt/vyatta/etc/config/config.boot', '/opt/vyatta/etc/config.boot.default'] diff --git a/src/op_mode/image_manager.py b/src/op_mode/image_manager.py index 1cfb5f5a1..fb4286dbc 100755 --- a/src/op_mode/image_manager.py +++ b/src/op_mode/image_manager.py @@ -21,7 +21,7 @@ from argparse import ArgumentParser, Namespace from pathlib import Path from shutil import rmtree from sys import exit -from typing import Optional +from typing import Optional, Literal, TypeAlias, get_args from vyos.system import disk, grub, image, compat from vyos.utils.io import ask_yes_no, select_entry @@ -33,6 +33,8 @@ DELETE_IMAGE_PROMPT_MSG: str = 'Select an image to delete:' MSG_DELETE_IMAGE_RUNNING: str = 'Currently running image cannot be deleted; reboot into another image first' MSG_DELETE_IMAGE_DEFAULT: str = 'Default image cannot be deleted; set another image as default first' +ConsoleType: TypeAlias = Literal['tty', 'ttyS'] + def annotate_list(images_list: list[str]) -> list[str]: """Annotate list of images with additional info @@ -202,6 +204,15 @@ def rename_image(name_old: str, name_new: str) -> None: exit(f'Unable to rename the encrypted config for "{name_old}" to "{name_new}": {err}') +@compat.grub_cfg_update +def set_console_type(console_type: ConsoleType) -> None: + console_choice = get_args(ConsoleType) + if console_type not in console_choice: + exit(f'console type \'{console_type}\' not available') + + grub.set_console_type(console_type) + + def list_images() -> None: """Print list of available images for CLI hints""" images_list: list[str] = grub.version_list() @@ -209,6 +220,13 @@ def list_images() -> None: print(image_name) +def list_console_types() -> None: + """Print list of console types for CLI hints""" + console_types: list[str] = list(get_args(ConsoleType)) + for console_type in console_types: + print(console_type) + + def parse_arguments() -> Namespace: """Parse arguments @@ -217,7 +235,8 @@ def parse_arguments() -> Namespace: """ parser: ArgumentParser = ArgumentParser(description='Manage system images') parser.add_argument('--action', - choices=['delete', 'set', 'rename', 'list'], + choices=['delete', 'set', 'set_console_type', + 'rename', 'list', 'list_console_types'], required=True, help='action to perform with an image') parser.add_argument('--no-prompt', action='store_true', @@ -227,6 +246,7 @@ def parse_arguments() -> Namespace: help= 'a name of an image to add, delete, install, rename, or set as default') parser.add_argument('--image-new-name', help='a new name for image') + parser.add_argument('--console-type', help='console type for boot') args: Namespace = parser.parse_args() # Validate arguments if args.action == 'rename' and (not args.image_name or @@ -243,10 +263,14 @@ if __name__ == '__main__': delete_image(args.image_name, args.no_prompt) if args.action == 'set': set_image(args.image_name) + if args.action == 'set_console_type': + set_console_type(args.console_type) if args.action == 'rename': rename_image(args.image_name, args.image_new_name) if args.action == 'list': list_images() + if args.action == 'list_console_types': + list_console_types() exit() |