diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/config_mgmt.py | 2 | ||||
| -rw-r--r-- | python/vyos/firewall.py | 49 | ||||
| -rw-r--r-- | python/vyos/ifconfig/bond.py | 38 | ||||
| -rw-r--r-- | python/vyos/nat.py | 6 | ||||
| -rw-r--r-- | python/vyos/qos/base.py | 16 | ||||
| -rw-r--r-- | python/vyos/qos/randomdetect.py | 34 | ||||
| -rw-r--r-- | python/vyos/system/compat.py | 10 | ||||
| -rw-r--r-- | python/vyos/system/image.py | 10 | ||||
| -rw-r--r-- | python/vyos/template.py | 21 | ||||
| -rw-r--r-- | python/vyos/tpm.py | 2 | ||||
| -rw-r--r-- | python/vyos/utils/assertion.py | 4 | ||||
| -rw-r--r-- | python/vyos/utils/io.py | 2 | ||||
| -rw-r--r-- | python/vyos/version.py | 12 | ||||
| -rw-r--r-- | python/vyos/xml_ref/__init__.py | 6 | ||||
| -rw-r--r-- | python/vyos/xml_ref/definition.py | 27 | 
15 files changed, 151 insertions, 88 deletions
| diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py index fc51d781c..70b6ea203 100644 --- a/python/vyos/config_mgmt.py +++ b/python/vyos/config_mgmt.py @@ -283,6 +283,8 @@ Proceed ?'''          rollback_ct = self._get_config_tree_revision(rev)          try:              load(rollback_ct, switch='explicit') +            print('Rollback diff has been applied.') +            print('Use "compare" to review the changes or "commit" to apply them.')          except LoadConfigError as e:              raise ConfigMgmtError(e) from e diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index d9d605a9d..d7b7b80a8 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -32,7 +32,6 @@ from vyos.utils.process import cmd  from vyos.utils.process import run  # Conntrack -  def conntrack_required(conf):      required_nodes = ['nat', 'nat66', 'load-balancing wan'] @@ -454,8 +453,28 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):                  else:                      output.append(f'set update ip{def_suffix} saddr @DA{def_suffix}_{dyn_group}') +    set_table = False      if 'set' in rule_conf: -        output.append(parse_policy_set(rule_conf['set'], def_suffix)) +        # Parse set command used in policy route: +        if 'connection_mark' in rule_conf['set']: +            conn_mark = rule_conf['set']['connection_mark'] +            output.append(f'ct mark set {conn_mark}') +        if 'dscp' in rule_conf['set']: +            dscp = rule_conf['set']['dscp'] +            output.append(f'ip{def_suffix} dscp set {dscp}') +        if 'mark' in rule_conf['set']: +            mark = rule_conf['set']['mark'] +            output.append(f'meta mark set {mark}') +        if 'table' in rule_conf['set']: +            set_table = True +            table = rule_conf['set']['table'] +            if table == 'main': +                table = '254' +            mark = 0x7FFFFFFF - int(table) +            output.append(f'meta mark set {mark}') +        if 'tcp_mss' in rule_conf['set']: +            mss = rule_conf['set']['tcp_mss'] +            output.append(f'tcp option maxseg size set {mss}')      if 'action' in rule_conf:          # Change action=return to action=action @@ -488,6 +507,10 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):              if synproxy_ws:                  output.append(f'wscale {synproxy_ws} timestamp sack-perm') +    else: +        if set_table: +            output.append('return') +      output.append(f'comment "{family}-{hook}-{fw_name}-{rule_id}"')      return " ".join(output) @@ -518,28 +541,6 @@ def parse_time(time):          out.append(f'day {{{",".join(out_days)}}}')      return " ".join(out) -def parse_policy_set(set_conf, def_suffix): -    out = [] -    if 'connection_mark' in set_conf: -        conn_mark = set_conf['connection_mark'] -        out.append(f'ct mark set {conn_mark}') -    if 'dscp' in set_conf: -        dscp = set_conf['dscp'] -        out.append(f'ip{def_suffix} dscp set {dscp}') -    if 'mark' in set_conf: -        mark = set_conf['mark'] -        out.append(f'meta mark set {mark}') -    if 'table' in set_conf: -        table = set_conf['table'] -        if table == 'main': -            table = '254' -        mark = 0x7FFFFFFF - int(table) -        out.append(f'meta mark set {mark}') -    if 'tcp_mss' in set_conf: -        mss = set_conf['tcp_mss'] -        out.append(f'tcp option maxseg size set {mss}') -    return " ".join(out) -  # GeoIP  nftables_geoip_conf = '/run/nftables-geoip.conf' diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index c6d0f1cff..b8ea90049 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -18,6 +18,7 @@ import os  from vyos.ifconfig.interface import Interface  from vyos.utils.dict import dict_search  from vyos.utils.assertion import assert_list +from vyos.utils.assertion import assert_mac  from vyos.utils.assertion import assert_positive  @Interface.register @@ -54,6 +55,10 @@ class BondIf(Interface):              'validate': lambda v: assert_list(v, ['slow', 'fast']),              'location': '/sys/class/net/{ifname}/bonding/lacp_rate',          }, +        'bond_system_mac': { +            'validate': lambda v: assert_mac(v, test_all_zero=False), +            'location': '/sys/class/net/{ifname}/bonding/ad_actor_system', +        },          'bond_miimon': {              'validate': assert_positive,              'location': '/sys/class/net/{ifname}/bonding/miimon' @@ -385,6 +390,24 @@ class BondIf(Interface):          """          return self.set_interface('bond_mode', mode) +    def set_system_mac(self, mac): +        """ +        In an AD system, this specifies the mac-address for the actor in +	    protocol packet exchanges (LACPDUs). The value cannot be NULL or +	    multicast. It is preferred to have the local-admin bit set for this +	    mac but driver does not enforce it. If the value is not given then +	    system defaults to using the masters' mac address as actors' system +	    address. + +	    This parameter has effect only in 802.3ad mode and is available through +	    SysFs interface. + +        Example: +        >>> from vyos.ifconfig import BondIf +        >>> BondIf('bond0').set_system_mac('00:50:ab:cd:ef:01') +        """ +        return self.set_interface('bond_system_mac', mac) +      def update(self, config):          """ General helper function which works on a dictionary retrived by          get_config_dict(). It's main intention is to consolidate the scattered @@ -426,14 +449,13 @@ class BondIf(Interface):                      Interface(interface).set_admin_state('up')              # Bonding policy/mode - default value, always present -            mode = config.get('mode') -            self.set_mode(mode) +            self.set_mode(config['mode'])              # LACPDU transmission rate - default value -            if mode == '802.3ad': +            if config['mode'] == '802.3ad':                  self.set_lacp_rate(config.get('lacp_rate')) -            if mode not in ['802.3ad', 'balance-tlb', 'balance-alb']: +            if config['mode'] not in ['802.3ad', 'balance-tlb', 'balance-alb']:                  tmp = dict_search('arp_monitor.interval', config)                  value = tmp if (tmp != None) else '0'                  self.set_arp_interval(value) @@ -468,6 +490,14 @@ class BondIf(Interface):                  Interface(interface).flush_addrs()                  self.add_port(interface) +        # Add system mac address for 802.3ad - default address is all zero +        # mode is always present (defaultValue) +        if config['mode'] == '802.3ad': +            mac = '00:00:00:00:00:00' +            if 'system_mac' in config: +                mac = config['system_mac'] +            self.set_system_mac(mac) +          # Primary device interface - must be set after 'mode'          value = config.get('primary')          if value: self.set_primary(value) diff --git a/python/vyos/nat.py b/python/vyos/nat.py index 2ada29add..e54548788 100644 --- a/python/vyos/nat.py +++ b/python/vyos/nat.py @@ -300,12 +300,12 @@ def parse_nat_static_rule(rule_conf, rule_id, nat_type):      output.append('counter') -    if translation_str: -        output.append(translation_str) -      if 'log' in rule_conf:          output.append(f'log prefix "[{log_prefix}{log_suffix}]"') +    if translation_str: +        output.append(translation_str) +      output.append(f'comment "{log_prefix}"')      return " ".join(output) diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py index 4173a1a43..98e486e42 100644 --- a/python/vyos/qos/base.py +++ b/python/vyos/qos/base.py @@ -90,13 +90,14 @@ class QoSBase:          else:              return value -    def _calc_random_detect_queue_params(self, avg_pkt, max_thr, limit=None, min_thr=None, mark_probability=None): +    def _calc_random_detect_queue_params(self, avg_pkt, max_thr, limit=None, min_thr=None, +                                         mark_probability=None, precedence=0):          params = dict()          avg_pkt = int(avg_pkt)          max_thr = int(max_thr)          mark_probability = int(mark_probability)          limit = int(limit) if limit else 4 * max_thr -        min_thr = int(min_thr) if min_thr else (9 * max_thr) // 18 +        min_thr = int(min_thr) if min_thr else ((9 + precedence) * max_thr) // 18          params['avg_pkt'] = avg_pkt          params['limit'] = limit * avg_pkt @@ -246,9 +247,15 @@ class QoSBase:                  filter_cmd_base += ' protocol all'                  if 'match' in cls_config: -                    is_filtered = False +                    has_filter = False                      for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1):                          filter_cmd = filter_cmd_base +                        if not has_filter: +                            for key in ['mark', 'vif', 'ip', 'ipv6']: +                                if key in match_config: +                                    has_filter = True +                                    break +                          if self.qostype == 'shaper' and 'prio ' not in filter_cmd:                              filter_cmd += f' prio {index}'                          if 'mark' in match_config: @@ -331,13 +338,12 @@ class QoSBase:                                  cls = int(cls)                                  filter_cmd += f' flowid {self._parent:x}:{cls:x}'                                  self._cmd(filter_cmd) -                                is_filtered = True                      vlan_expression = "match.*.vif"                      match_vlan = jmespath.search(vlan_expression, cls_config)                      if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config) \ -                        and is_filtered: +                        and has_filter:                          # For "vif" "basic match" is used instead of "action police" T5961                          if not match_vlan:                              filter_cmd += f' action police' diff --git a/python/vyos/qos/randomdetect.py b/python/vyos/qos/randomdetect.py index d7d84260f..a3a39da36 100644 --- a/python/vyos/qos/randomdetect.py +++ b/python/vyos/qos/randomdetect.py @@ -21,33 +21,25 @@ class RandomDetect(QoSBase):      # https://man7.org/linux/man-pages/man8/tc.8.html      def update(self, config, direction): -        tmp = f'tc qdisc add dev {self._interface} root handle {self._parent}:0 dsmark indices 8 set_tc_index' +        # # Generalized Random Early Detection +        handle = self._parent +        tmp = f'tc qdisc add dev {self._interface} root handle {self._parent}:0 gred setup DPs 8 default 0 grio'          self._cmd(tmp) - -        tmp = f'tc filter add dev {self._interface} parent {self._parent}:0 protocol ip prio 1 tcindex mask 0xe0 shift 5' -        self._cmd(tmp) - -        # Generalized Random Early Detection -        handle = self._parent +1 -        tmp = f'tc qdisc add dev {self._interface} parent {self._parent}:0 handle {handle}:0 gred setup DPs 8 default 0 grio' -        self._cmd(tmp) -          bandwidth = self._rate_convert(config['bandwidth'])          # set VQ (virtual queue) parameters          for precedence, precedence_config in config['precedence'].items():              precedence = int(precedence) -            avg_pkt = int(precedence_config['average_packet']) -            limit = int(precedence_config['queue_limit']) * avg_pkt -            min_val = int(precedence_config['minimum_threshold']) * avg_pkt -            max_val = int(precedence_config['maximum_threshold']) * avg_pkt - -            tmp  = f'tc qdisc change dev {self._interface} handle {handle}:0 gred limit {limit} min {min_val} max {max_val} avpkt {avg_pkt} ' - -            burst = (2 * int(precedence_config['minimum_threshold']) + int(precedence_config['maximum_threshold'])) // 3 -            probability = 1 / int(precedence_config['mark_probability']) -            tmp += f'burst {burst} bandwidth {bandwidth} probability {probability} DP {precedence} prio {8 - precedence:x}' - +            qparams = self._calc_random_detect_queue_params( +                avg_pkt=precedence_config.get('average_packet'), +                max_thr=precedence_config.get('maximum_threshold'), +                limit=precedence_config.get('queue_limit'), +                min_thr=precedence_config.get('minimum_threshold'), +                mark_probability=precedence_config.get('mark_probability'), +                precedence=precedence +            ) +            tmp = f'tc qdisc change dev {self._interface} handle {handle}:0 gred limit {qparams["limit"]} min {qparams["min_val"]} max {qparams["max_val"]} avpkt {qparams["avg_pkt"]} ' +            tmp += f'burst {qparams["burst"]} bandwidth {bandwidth} probability {qparams["probability"]} DP {precedence} prio {8 - precedence:x}'              self._cmd(tmp)          # call base class diff --git a/python/vyos/system/compat.py b/python/vyos/system/compat.py index 1b487c1d2..d35bddea2 100644 --- a/python/vyos/system/compat.py +++ b/python/vyos/system/compat.py @@ -220,14 +220,8 @@ def get_default(data: dict, root_dir: str = '') -> Union[int, None]:      sublist = list(filter(lambda x: (x.get('version') == image_name and                                       x.get('console_type') == console_type and -                                     x.get('console_num') == console_num and                                       x.get('bootmode') == 'normal'),                            menu_entries)) -    # legacy images added with legacy tools omitted 'ttyUSB'; if entry not -    # available, default to initial entry of version -    if not sublist: -        sublist = list(filter(lambda x: x.get('version') == image_name, -                              menu_entries))      if sublist:          return menu_entries.index(sublist[0]) @@ -253,6 +247,10 @@ def update_version_list(root_dir: str = '') -> list[dict]:      menu_entries = parse_menuentries(grub_cfg_main)      menu_versions = find_versions(menu_entries) +    # remove deprecated console-type ttyUSB +    menu_entries = list(filter(lambda x: x.get('console_type') != 'ttyUSB', +                               menu_entries)) +      # get list of versions added/removed by image-tools      current_versions = grub.version_list(root_dir) diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py index ba9a6dfa7..aae52e770 100644 --- a/python/vyos/system/image.py +++ b/python/vyos/system/image.py @@ -18,8 +18,9 @@ from re import compile as re_compile  from functools import wraps  from tempfile import TemporaryDirectory  from typing import TypedDict +from json import loads -from vyos import version +from vyos.defaults import directories  from vyos.system import disk, grub  # Define variables @@ -201,9 +202,12 @@ def get_running_image() -> str:      if running_image_result:          running_image: str = running_image_result.groupdict().get(              'image_version', '') -    # we need to have a fallback for live systems +    # we need to have a fallback for live systems: +    # explicit read from version file      if not running_image: -        running_image: str = version.get_version() +        json_data: str = Path(directories['data']).joinpath('version.json').read_text() +        dict_data: dict = loads(json_data) +        running_image: str = dict_data['version']      return running_image diff --git a/python/vyos/template.py b/python/vyos/template.py index ac77e8a3d..fbc5f1456 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -25,6 +25,14 @@ from vyos.utils.file import makedir  from vyos.utils.permission import chmod  from vyos.utils.permission import chown +# We use a mutable global variable for the default template directory +# to make it possible to call scripts from this repository +# outside of live VyOS systems. +# If something (like the image build scripts) +# want to call a script, they can modify the default location +# to the repository path. +DEFAULT_TEMPLATE_DIR = directories["templates"] +  # Holds template filters registered via register_filter()  _FILTERS = {}  _TESTS = {} @@ -35,18 +43,7 @@ def _get_environment(location=None):      from os import getenv      if location is None: -        # Sometimes functions that rely on templates need to be executed outside of VyOS installations: -        # for example, installer functions are executed for image builds, -        # and anything may be invoked for testing from a developer's machine. -        # This environment variable allows running any unmodified code -        # with a custom template location. -        location_env_var = getenv("VYOS_TEMPLATE_DIR") -        if location_env_var: -            print(f"Using environment variable {location_env_var}") -            template_dir = location_env_var -        else: -            template_dir = directories["templates"] -        loc_loader=FileSystemLoader(template_dir) +        loc_loader=FileSystemLoader(DEFAULT_TEMPLATE_DIR)      else:          loc_loader=FileSystemLoader(location)      env = Environment( diff --git a/python/vyos/tpm.py b/python/vyos/tpm.py index b9f28546f..a24f149fd 100644 --- a/python/vyos/tpm.py +++ b/python/vyos/tpm.py @@ -15,7 +15,7 @@  import os  import tempfile -from vyos.util import rc_cmd +from vyos.utils.process import rc_cmd  default_pcrs = ['0','2','4','7']  tpm_handle = 0x81000000 diff --git a/python/vyos/utils/assertion.py b/python/vyos/utils/assertion.py index 1aaa54dff..c7fa220c3 100644 --- a/python/vyos/utils/assertion.py +++ b/python/vyos/utils/assertion.py @@ -53,7 +53,7 @@ def assert_mtu(mtu, ifname):      if (max_mtu and cur_mtu > max_mtu) or cur_mtu > 65536:          raise ValueError(f'MTU is too small for interface "{ifname}": {mtu} > {max_mtu}') -def assert_mac(m): +def assert_mac(m, test_all_zero=True):      split = m.split(':')      size = len(split) @@ -74,7 +74,7 @@ def assert_mac(m):          raise ValueError(f'{m} is a multicast MAC address')      # overall mac address is not allowed to be 00:00:00:00:00:00 -    if sum(octets) == 0: +    if test_all_zero and sum(octets) == 0:          raise ValueError('00:00:00:00:00:00 is not a valid MAC address')      if octets[:5] == (0, 0, 94, 0, 1): diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py index a8c430f28..205210b66 100644 --- a/python/vyos/utils/io.py +++ b/python/vyos/utils/io.py @@ -72,6 +72,8 @@ def ask_yes_no(question, default=False) -> bool:                  stdout.write("Please respond with yes/y or no/n\n")          except EOFError:              stdout.write("\nPlease respond with yes/y or no/n\n") +        except KeyboardInterrupt: +            return False  def is_interactive():      """Try to determine if the routine was called from an interactive shell.""" diff --git a/python/vyos/version.py b/python/vyos/version.py index b5ed2705b..86e96d0ec 100644 --- a/python/vyos/version.py +++ b/python/vyos/version.py @@ -33,11 +33,11 @@ import os  import requests  import vyos.defaults +from vyos.system.image import is_live_boot  from vyos.utils.file import read_file  from vyos.utils.file import read_json  from vyos.utils.process import popen -from vyos.utils.process import run  from vyos.utils.process import DEVNULL  version_file = os.path.join(vyos.defaults.directories['data'], 'version.json') @@ -81,16 +81,14 @@ def get_full_version_data(fname=version_file):      else:          version_data['system_type'] = f"{hypervisor} guest" -    # Get boot type, it can be livecd, installed image, or, possible, a system installed -    # via legacy "install system" mechanism +    # Get boot type, it can be livecd or installed image      # In installed images, the squashfs image file is named after its image version,      # while on livecd it's just "filesystem.squashfs", that's how we tell a livecd boot      # from an installed image -    boot_via = "installed image" -    if run(""" grep -e '^overlay.*/filesystem.squashfs' /proc/mounts >/dev/null""") == 0: +    if is_live_boot():          boot_via = "livecd" -    elif run(""" grep '^overlay /' /proc/mounts >/dev/null """) != 0: -        boot_via = "legacy non-image installation" +    else: +        boot_via = "installed image"      version_data['boot_via'] = boot_via      # Get hardware details from DMI diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py index bf434865d..2ba3da4e8 100644 --- a/python/vyos/xml_ref/__init__.py +++ b/python/vyos/xml_ref/__init__.py @@ -53,6 +53,12 @@ def is_valueless(path: list) -> bool:  def is_leaf(path: list) -> bool:      return load_reference().is_leaf(path) +def owner(path: list) -> str: +    return load_reference().owner(path) + +def priority(path: list) -> str: +    return load_reference().priority(path) +  def cli_defined(path: list, node: str, non_local=False) -> bool:      return load_reference().cli_defined(path, node, non_local=non_local) diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py index c90c5ddbc..c85835ffd 100644 --- a/python/vyos/xml_ref/definition.py +++ b/python/vyos/xml_ref/definition.py @@ -135,6 +135,33 @@ class Xml:          d = self._get_ref_path(path)          return self._is_leaf_node(d) +    def _least_upper_data(self, path: list, name: str) -> str: +        ref_path = path.copy() +        d = self.ref +        data = '' +        while ref_path and d: +            d = d.get(ref_path[0], {}) +            ref_path.pop(0) +            if self._is_tag_node(d) and ref_path: +                ref_path.pop(0) +            if self._is_leaf_node(d) and ref_path: +                ref_path.pop(0) +            res = self._get_ref_node_data(d, name) +            if res is not None: +                data = res + +        return data + +    def owner(self, path: list) -> str: +        from pathlib import Path +        data = self._least_upper_data(path, 'owner') +        if data: +            data = Path(data.split()[0]).name +        return data + +    def priority(self, path: list) -> str: +        return self._least_upper_data(path, 'priority') +      @staticmethod      def _dict_get(d: dict, path: list) -> dict:          for i in path: | 
