diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/configsession.py | 4 | ||||
| -rw-r--r-- | python/vyos/cpu.py | 102 | ||||
| -rw-r--r-- | python/vyos/firewall.py | 78 | ||||
| -rw-r--r-- | python/vyos/frr.py | 2 | ||||
| -rw-r--r-- | python/vyos/migrator.py | 5 | ||||
| -rw-r--r-- | python/vyos/pki.py | 2 | ||||
| -rw-r--r-- | python/vyos/template.py | 7 | ||||
| -rw-r--r-- | python/vyos/util.py | 17 | 
8 files changed, 204 insertions, 13 deletions
| diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index f28ad09c5..3a60f6d92 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -33,6 +33,7 @@ INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url']  REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del']  GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate']  SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show'] +RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset']  # Default "commit via" string  APP = "vyos-http-api" @@ -200,3 +201,6 @@ class ConfigSession(object):          out = self.__run_command(SHOW + path)          return out +    def reset(self, path): +        out = self.__run_command(RESET + path) +        return out diff --git a/python/vyos/cpu.py b/python/vyos/cpu.py new file mode 100644 index 000000000..a0ef864be --- /dev/null +++ b/python/vyos/cpu.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library.  If not, see <http://www.gnu.org/licenses/>. + +""" +Retrieves (or at least attempts to retrieve) the total number of real CPU cores +installed in a Linux system. + +The issue of core count is complicated by existence of SMT, e.g. Intel's Hyper Threading. +GNU nproc returns the number of LOGICAL cores, +which is 2x of the real cores if SMT is enabled. + +The idea is to find all physical CPUs and add up their core counts. +It has special cases for x86_64 and MAY work correctly on other architectures, +but nothing is certain. +""" + +import re + + +def _read_cpuinfo(): +    with open('/proc/cpuinfo', 'r') as f: +        return f.readlines() + +def _split_line(l): +    l = l.strip() +    parts = re.split(r'\s*:\s*', l) +    return (parts[0], ":".join(parts[1:])) + +def _find_cpus(cpuinfo_lines): +    # Make a dict because it's more convenient to work with later, +    # when we need to find physicall distinct CPUs there. +    cpus = {} + +    cpu_number = 0 + +    for l in cpuinfo_lines: +        key, value = _split_line(l) +        if key == 'processor': +            cpu_number = value +            cpus[cpu_number] = {} +        else: +            cpus[cpu_number][key] = value + +    return cpus + +def _find_physical_cpus(): +    cpus = _find_cpus(_read_cpuinfo()) + +    phys_cpus = {} + +    for num in cpus: +        if 'physical id' in cpus[num]: +            # On at least some architectures, CPUs in different sockets +            # have different 'physical id' field, e.g. on x86_64. +            phys_id = cpus[num]['physical id'] +            if phys_id not in phys_cpus: +                phys_cpus[phys_id] = cpus[num] +        else: +            # On other architectures, e.g. on ARM, there's no such field. +            # We just assume they are different CPUs, +            # whether single core ones or cores of physical CPUs. +            phys_cpus[num] = cpu[num] + +    return phys_cpus + +def get_cpus(): +    """ Returns a list of /proc/cpuinfo entries that belong to different CPUs. +    """ +    cpus_dict = _find_physical_cpus() +    return list(cpus_dict.values()) + +def get_core_count(): +    """ Returns the total number of physical CPU cores +        (even if Hyper-Threading or another SMT is enabled and has inflated +        the number of cores in /proc/cpuinfo) +    """ +    physical_cpus = _find_physical_cpus() + +    core_count = 0 + +    for num in physical_cpus: +        # Some architectures, e.g. x86_64, include a field for core count. +        # Since we found unique physical CPU entries, we can sum their core counts. +        if 'cpu cores' in physical_cpus[num]: +            core_count += int(physical_cpus[num]['cpu cores']) +        else: +            core_count += 1 + +    return core_count diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 04fd44173..31fe8b5e3 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2022 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,9 +16,70 @@  import re +from vyos.util import call  from vyos.util import cmd  from vyos.util import dict_search_args + +# Functions for firewall group domain-groups +def get_ips_domains_dict(list_domains): +    """ +    Get list of IPv4 addresses by list of domains +    Ex: get_ips_domains_dict(['ex1.com', 'ex2.com']) +        {'ex1.com': ['192.0.2.1'], 'ex2.com': ['192.0.2.2', '192.0.2.3']} +    """ +    from socket import gethostbyname_ex +    from socket import gaierror + +    ip_dict = {} +    for domain in list_domains: +        try: +            _, _, ips = gethostbyname_ex(domain) +            ip_dict[domain] = ips +        except gaierror: +            pass + +    return ip_dict + +def nft_init_set(group_name, table="filter", family="ip"): +    """ +    table ip filter { +        set GROUP_NAME +            type ipv4_addr +           flags interval +        } +    """ +    return call(f'nft add set ip {table} {group_name} {{ type ipv4_addr\\; flags interval\\; }}') + + +def nft_add_set_elements(group_name, elements, table="filter", family="ip"): +    """ +    table ip filter { +        set GROUP_NAME { +            type ipv4_addr +            flags interval +            elements = { 192.0.2.1, 192.0.2.2 } +        } +    """ +    elements = ", ".join(elements) +    return call(f'nft add element {family} {table} {group_name} {{ {elements} }} ') + +def nft_flush_set(group_name, table="filter", family="ip"): +    """ +    Flush elements of nft set +    """ +    return call(f'nft flush set {family} {table} {group_name}') + +def nft_update_set_elements(group_name, elements, table="filter", family="ip"): +    """ +    Update elements of nft set +    """ +    flush_set = nft_flush_set(group_name, table="filter", family="ip") +    nft_add_set = nft_add_set_elements(group_name, elements, table="filter", family="ip") +    return flush_set, nft_add_set + +# END firewall group domain-group (sets) +  def find_nftables_rule(table, chain, rule_matches=[]):      # Find rule in table/chain that matches all criteria and return the handle      results = cmd(f'sudo nft -a list chain {table} {chain}').split("\n") @@ -118,6 +179,14 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):                          operator = '!='                          group_name = group_name[1:]                      output.append(f'{ip_name} {prefix}addr {operator} $A{def_suffix}_{group_name}') +                # Generate firewall group domain-group +                elif 'domain_group' in group: +                    group_name = group['domain_group'] +                    operator = '' +                    if group_name[0] == '!': +                        operator = '!=' +                        group_name = group_name[1:] +                    output.append(f'{ip_name} {prefix}addr {operator} @{group_name}')                  elif 'network_group' in group:                      group_name = group['network_group']                      operator = '' @@ -148,7 +217,12 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):      if 'log' in rule_conf and rule_conf['log'] == 'enable':          action = rule_conf['action'] if 'action' in rule_conf else 'accept' -        output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}] "') +        output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}]"') + +        if 'log_level' in rule_conf: +            log_level = rule_conf['log_level'] +            output.append(f'level {log_level}') +      if 'hop_limit' in rule_conf:          operators = {'eq': '==', 'gt': '>', 'lt': '<'} diff --git a/python/vyos/frr.py b/python/vyos/frr.py index cbba19ab7..0ffd5cba9 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -85,7 +85,7 @@ LOG.addHandler(ch2)  _frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd',                  'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd', -                'bfdd'] +                'bfdd', 'eigrpd']  path_vtysh = '/usr/bin/vtysh'  path_frr_reload = '/usr/lib/frr/frr-reload.py' diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index a2e0daabd..c6e3435ca 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -105,6 +105,11 @@ class Migrator(object):          sys_keys = list(sys_versions.keys())          sys_keys.sort() +        # XXX 'bgp' needs to follow 'quagga': +        if 'bgp' in sys_keys and 'quagga' in sys_keys: +            sys_keys.insert(sys_keys.index('quagga'), +                            sys_keys.pop(sys_keys.index('bgp'))) +          rev_versions = {}          for key in sys_keys: diff --git a/python/vyos/pki.py b/python/vyos/pki.py index 0b916eaae..fd91fc9bf 100644 --- a/python/vyos/pki.py +++ b/python/vyos/pki.py @@ -247,7 +247,7 @@ def load_private_key(raw_data, passphrase=None, wrap_tags=True):      if wrap_tags:          raw_data = wrap_private_key(raw_data, passphrase) -    if passphrase: +    if passphrase is not None:          passphrase = bytes(passphrase, 'utf-8')      try: diff --git a/python/vyos/template.py b/python/vyos/template.py index 132f5ddde..ee82f8f8f 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -554,7 +554,7 @@ def nft_default_rule(fw_conf, fw_name):      if 'enable_default_log' in fw_conf:          action_suffix = default_action[:1].upper() -        output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}] "') +        output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"')      output.append(nft_action(default_action))      output.append(f'comment "{fw_name} default-action {default_action}"') @@ -564,8 +564,9 @@ def nft_default_rule(fw_conf, fw_name):  def nft_state_policy(conf, state, ipv6=False):      out = [f'ct state {state}'] -    if 'log' in conf and 'enable' in conf['log']: -        out.append('log') +    if 'log' in conf: +        log_level = conf['log'] +        out.append(f'log level {log_level}')      out.append('counter') diff --git a/python/vyos/util.py b/python/vyos/util.py index de55e108b..0d62fbfe9 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -757,21 +757,26 @@ def dict_search_args(dict_object, *path):          dict_object = dict_object[item]      return dict_object -def dict_search_recursive(dict_object, key): +def dict_search_recursive(dict_object, key, path=[]):      """ Traverse a dictionary recurisvely and return the value of the key      we are looking for.      Thankfully copied from https://stackoverflow.com/a/19871956 + +    Modified to yield optional path to found keys      """      if isinstance(dict_object, list):          for i in dict_object: -            for x in dict_search_recursive(i, key): -               yield x +            new_path = path + [i] +            for x in dict_search_recursive(i, key, new_path): +                yield x      elif isinstance(dict_object, dict):          if key in dict_object: -            yield dict_object[key] -        for j in dict_object.values(): -            for x in dict_search_recursive(j, key): +            new_path = path + [key] +            yield dict_object[key], new_path +        for k, j in dict_object.items(): +            new_path = path + [k] +            for x in dict_search_recursive(j, key, new_path):                  yield x  def get_bridge_fdb(interface): | 
