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() | 
