diff options
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/configdict.py | 11 | ||||
-rw-r--r-- | python/vyos/configverify.py | 6 | ||||
-rw-r--r-- | python/vyos/defaults.py | 2 | ||||
-rw-r--r-- | python/vyos/firewall.py | 40 | ||||
-rw-r--r-- | python/vyos/ifconfig/bond.py | 12 | ||||
-rw-r--r-- | python/vyos/ifconfig/bridge.py | 20 | ||||
-rw-r--r-- | python/vyos/ifconfig/ethernet.py | 30 | ||||
-rw-r--r-- | python/vyos/ifconfig/pppoe.py | 37 | ||||
-rw-r--r-- | python/vyos/ifconfig/section.py | 12 | ||||
-rw-r--r-- | python/vyos/opmode.py | 2 | ||||
-rw-r--r-- | python/vyos/template.py | 4 | ||||
-rw-r--r-- | python/vyos/util.py | 57 |
12 files changed, 202 insertions, 31 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 8f822a97d..912bc94f2 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -295,11 +295,18 @@ def is_source_interface(conf, interface, intftype=None): """ ret_val = None intftypes = ['macsec', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan'] - if intftype not in intftypes + [None]: + if not intftype: + intftype = intftypes + + if isinstance(intftype, str): + intftype = [intftype] + elif not isinstance(intftype, list): + raise ValueError(f'Interface type "{type(intftype)}" must be either str or list!') + + if not all(x in intftypes for x in intftype): raise ValueError(f'unknown interface type "{intftype}" or it can not ' 'have a source-interface') - intftype = intftypes if intftype == None else [intftype] for it in intftype: base = ['interfaces', it] for intf in conf.list_nodes(base): diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 2ab3cb408..447ec795c 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -295,6 +295,12 @@ def verify_source_interface(config): raise ConfigError(f'Invalid source-interface "{src_ifname}". Interface ' f'is already a member of bond "{bond_name}"!') + if 'is_source_interface' in config: + tmp = config['is_source_interface'] + src_ifname = config['source_interface'] + raise ConfigError(f'Can not use source-interface "{src_ifname}", it already ' \ + f'belongs to interface "{tmp}"!') + def verify_dhcpv6(config): """ Common helper function used by interface implementations to perform diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 09ae73eac..6894fc4da 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -26,7 +26,7 @@ directories = { "templates": "/usr/share/vyos/templates/", "certbot": "/config/auth/letsencrypt", "api_schema": "/usr/libexec/vyos/services/api/graphql/graphql/schema/", - "api_templates": "/usr/libexec/vyos/services/api/graphql/recipes/templates/", + "api_templates": "/usr/libexec/vyos/services/api/graphql/session/templates/", "vyos_udev_dir": "/run/udev/vyos" } diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 3e2de4c3f..b56caef71 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -52,9 +52,9 @@ def get_ips_domains_dict(list_domains): return ip_dict -def nft_init_set(group_name, table="filter", family="ip"): +def nft_init_set(group_name, table="vyos_filter", family="ip"): """ - table ip filter { + table ip vyos_filter { set GROUP_NAME type ipv4_addr flags interval @@ -63,9 +63,9 @@ def nft_init_set(group_name, table="filter", family="ip"): 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"): +def nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip"): """ - table ip filter { + table ip vyos_filter { set GROUP_NAME { type ipv4_addr flags interval @@ -75,18 +75,18 @@ def nft_add_set_elements(group_name, elements, table="filter", family="ip"): elements = ", ".join(elements) return call(f'nft add element {family} {table} {group_name} {{ {elements} }} ') -def nft_flush_set(group_name, table="filter", family="ip"): +def nft_flush_set(group_name, table="vyos_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"): +def nft_update_set_elements(group_name, elements, table="vyos_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") + flush_set = nft_flush_set(group_name, table="vyos_filter", family="ip") + nft_add_set = nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip") return flush_set, nft_add_set # END firewall group domain-group (sets) @@ -150,7 +150,7 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if suffix[0] == '!': suffix = f'!= {suffix[1:]}' output.append(f'{ip_name} {prefix}addr {suffix}') - + if dict_search_args(side_conf, 'geoip', 'country_code'): operator = '' if dict_search_args(side_conf, 'geoip', 'inverse_match') != None: @@ -265,6 +265,23 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if 'type' in rule_conf[icmp]: output.append(icmp + ' type ' + rule_conf[icmp]['type']) + + if 'packet_length' in rule_conf: + lengths_str = ','.join(rule_conf['packet_length']) + output.append(f'ip{def_suffix} length {{{lengths_str}}}') + + if 'packet_length_exclude' in rule_conf: + negated_lengths_str = ','.join(rule_conf['packet_length_exclude']) + output.append(f'ip{def_suffix} length != {{{negated_lengths_str}}}') + + if 'dscp' in rule_conf: + dscp_str = ','.join(rule_conf['dscp']) + output.append(f'ip{def_suffix} dscp {{{dscp_str}}}') + + if 'dscp_exclude' in rule_conf: + negated_dscp_str = ','.join(rule_conf['dscp_exclude']) + output.append(f'ip{def_suffix} dscp != {{{negated_dscp_str}}}') + if 'ipsec' in rule_conf: if 'match_ipsec' in rule_conf['ipsec']: output.append('meta ipsec == 1') @@ -297,6 +314,11 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if tcp_flags: output.append(parse_tcp_flags(tcp_flags)) + # TCP MSS + tcp_mss = dict_search_args(rule_conf, 'tcp', 'mss') + if tcp_mss: + output.append(f'tcp option maxseg size {tcp_mss}') + output.append('counter') if 'set' in rule_conf: diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 98bf6162b..0edd17055 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-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 @@ -405,10 +405,12 @@ class BondIf(Interface): # Remove ALL bond member interfaces for interface in self.get_slaves(): self.del_port(interface) - # Removing an interface from a bond will always place the underlaying - # physical interface in admin-down state! If physical interface is - # not disabled, re-enable it. - if not dict_search(f'member.interface_remove.{interface}.disable', config): + + # Restore correct interface status based on config + if dict_search(f'member.interface.{interface}.disable', config) is not None or \ + dict_search(f'member.interface_remove.{interface}.disable', config) is not None: + Interface(interface).set_admin_state('down') + else: Interface(interface).set_admin_state('up') # Bonding policy/mode - default value, always present diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 758967fbc..aa818bc5f 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -295,8 +295,24 @@ class BridgeIf(Interface): self.del_port(member) # enable/disable Vlan Filter - vlan_filter = '1' if 'enable_vlan' in config else '0' - self.set_vlan_filter(vlan_filter) + tmp = '1' if 'enable_vlan' in config else '0' + self.set_vlan_filter(tmp) + + # add VLAN interfaces to local 'parent' bridge to allow forwarding + if 'enable_vlan' in config: + for vlan in config.get('vif_remove', {}): + # Remove old VLANs from the bridge + cmd = f'bridge vlan del dev {self.ifname} vid {vlan} self' + self._cmd(cmd) + + for vlan in config.get('vif', {}): + cmd = f'bridge vlan add dev {self.ifname} vid {vlan} self' + self._cmd(cmd) + + # VLAN of bridge parent interface is always 1. VLAN 1 is the default + # VLAN for all unlabeled packets + cmd = f'bridge vlan add dev {self.ifname} vid 1 pvid untagged self' + self._cmd(cmd) tmp = dict_search('member.interface', config) if tmp: diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 1280fc238..32e667038 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -16,6 +16,7 @@ import os import re +from glob import glob from vyos.ethtool import Ethtool from vyos.ifconfig.interface import Interface from vyos.util import run @@ -74,6 +75,10 @@ class EthernetIf(Interface): 'convert': lambda cpus: cpus if cpus else '0', 'location': '/sys/class/net/{ifname}/queues/rx-0/rps_cpus', }, + 'rfs': { + 'convert': lambda num: num if num else '0', + 'location': '/proc/sys/net/core/rps_sock_flow_entries', + }, }} def __init__(self, ifname, **kargs): @@ -236,7 +241,7 @@ class EthernetIf(Interface): enabled, fixed = self.ethtool.get_large_receive_offload() if enabled != state: if not fixed: - return self.set_interface('gro', 'on' if state else 'off') + return self.set_interface('lro', 'on' if state else 'off') else: print('Adapter does not support changing large-receive-offload settings!') return False @@ -258,6 +263,20 @@ class EthernetIf(Interface): # send bitmask representation as hex string without leading '0x' return self.set_interface('rps', rps_cpus) + def set_rfs(self, state): + rfs_flow = 0 + global_rfs_flow = 0 + queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*')) + if state: + global_rfs_flow = 32768 + rfs_flow = int(global_rfs_flow/queues) + + self.set_interface('rfs', global_rfs_flow) + for i in range(0, queues): + self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt', rfs_flow) + + return True + def set_sg(self, state): """ Enable Scatter-Gather support. State can be either True or False. @@ -273,7 +292,7 @@ class EthernetIf(Interface): enabled, fixed = self.ethtool.get_scatter_gather() if enabled != state: if not fixed: - return self.set_interface('gro', 'on' if state else 'off') + return self.set_interface('sg', 'on' if state else 'off') else: print('Adapter does not support changing scatter-gather settings!') return False @@ -293,7 +312,7 @@ class EthernetIf(Interface): enabled, fixed = self.ethtool.get_tcp_segmentation_offload() if enabled != state: if not fixed: - return self.set_interface('gro', 'on' if state else 'off') + return self.set_interface('tso', 'on' if state else 'off') else: print('Adapter does not support changing tcp-segmentation-offload settings!') return False @@ -342,6 +361,9 @@ class EthernetIf(Interface): # RPS - Receive Packet Steering self.set_rps(dict_search('offload.rps', config) != None) + # RFS - Receive Flow Steering + self.set_rfs(dict_search('offload.rfs', config) != None) + # scatter-gather option self.set_sg(dict_search('offload.sg', config) != None) @@ -359,5 +381,5 @@ class EthernetIf(Interface): for rx_tx, size in config['ring_buffer'].items(): self.set_ring_buffer(rx_tx, size) - # call base class first + # call base class last super().update(config) diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py index 63ffc8069..437fe0cae 100644 --- a/python/vyos/ifconfig/pppoe.py +++ b/python/vyos/ifconfig/pppoe.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-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 @@ -14,6 +14,7 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. from vyos.ifconfig.interface import Interface +from vyos.validate import assert_range from vyos.util import get_interface_config @Interface.register @@ -27,6 +28,21 @@ class PPPoEIf(Interface): }, } + _sysfs_get = { + **Interface._sysfs_get,**{ + 'accept_ra_defrtr': { + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra_defrtr', + } + } + } + + _sysfs_set = {**Interface._sysfs_set, **{ + 'accept_ra_defrtr': { + 'validate': lambda value: assert_range(value, 0, 2), + 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra_defrtr', + }, + }} + def _remove_routes(self, vrf=None): # Always delete default routes when interface is removed vrf_cmd = '' @@ -70,6 +86,21 @@ class PPPoEIf(Interface): """ Get a synthetic MAC address. """ return self.get_mac_synthetic() + def set_accept_ra_defrtr(self, enable): + """ + Learn default router in Router Advertisement. + 1: enabled + 0: disable + + Example: + >>> from vyos.ifconfig import PPPoEIf + >>> PPPoEIf('pppoe1').set_accept_ra_defrtr(0) + """ + tmp = self.get_interface('accept_ra_defrtr') + if tmp == enable: + return None + self.set_interface('accept_ra_defrtr', enable) + 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 @@ -107,6 +138,10 @@ class PPPoEIf(Interface): tmp = config['vrf'] vrf = f'-c "vrf {tmp}"' + # learn default router in Router Advertisement. + tmp = '0' if 'no_default_route' in config else '1' + self.set_accept_ra_defrtr(tmp) + if 'no_default_route' not in config: # Set default route(s) pointing to PPPoE interface distance = config['default_route_distance'] diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 91f667b65..5e98cd510 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -88,7 +88,7 @@ class Section: raise ValueError(f'No type found for interface name: {name}') @classmethod - def _intf_under_section (cls,section=''): + def _intf_under_section (cls,section='',vlan=True): """ return a generator with the name of the configured interface which are under a section @@ -103,6 +103,9 @@ class Section: if section and ifsection != section: continue + if vlan == False and '.' in ifname: + continue + yield ifname @classmethod @@ -135,13 +138,14 @@ class Section: return l @classmethod - def interfaces(cls, section=''): + def interfaces(cls, section='', vlan=True): """ return a list of the name of the configured interface which are under a section - if no section is provided, then it returns all configured interfaces + if no section is provided, then it returns all configured interfaces. + If vlan is True, also Vlan subinterfaces will be returned """ - return cls._sort_interfaces(cls._intf_under_section(section)) + return cls._sort_interfaces(cls._intf_under_section(section, vlan)) @classmethod def _intf_with_feature(cls, feature=''): diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 628f7b3a2..7e3545c87 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -105,6 +105,8 @@ def run(module): subparser = subparsers.add_parser(function_name, help=functions[function_name].__doc__) type_hints = typing.get_type_hints(functions[function_name]) + if 'return' in type_hints: + del type_hints['return'] for opt in type_hints: th = type_hints[opt] diff --git a/python/vyos/template.py b/python/vyos/template.py index eb7f06480..9804308c1 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -1,4 +1,4 @@ -# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-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 @@ -550,7 +550,7 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'): @register_filter('nft_default_rule') def nft_default_rule(fw_conf, fw_name): output = ['counter'] - default_action = fw_conf.get('default_action', 'accept') + default_action = fw_conf['default_action'] if 'enable_default_log' in fw_conf: action_suffix = default_action[:1].upper() diff --git a/python/vyos/util.py b/python/vyos/util.py index b86b1949c..461df9a6e 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-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 @@ -471,6 +471,35 @@ def process_named_running(name): return p.pid return None +def is_list_equal(first: list, second: list) -> bool: + """ Check if 2 lists are equal and list not empty """ + if len(first) != len(second) or len(first) == 0: + return False + return sorted(first) == sorted(second) + +def is_listen_port_bind_service(port: int, service: str) -> bool: + """Check if listen port bound to expected program name + :param port: Bind port + :param service: Program name + :return: bool + + Example: + % is_listen_port_bind_service(443, 'nginx') + True + % is_listen_port_bind_service(443, 'ocservr-main') + False + """ + from psutil import net_connections as connections + from psutil import Process as process + for connection in connections(): + addr = connection.laddr + pid = connection.pid + pid_name = process(pid).name() + pid_port = addr.port + if service == pid_name and port == pid_port: + return True + return False + def seconds_to_human(s, separator=""): """ Converts number of seconds passed to a human-readable interval such as 1w4d18h35m59s @@ -800,6 +829,32 @@ def dict_search_recursive(dict_object, key, path=[]): for x in dict_search_recursive(j, key, new_path): yield x +def convert_data(data): + """Convert multiple types of data to types usable in CLI + + Args: + data (str | bytes | list | OrderedDict): input data + + Returns: + str | list | dict: converted data + """ + from collections import OrderedDict + + if isinstance(data, str): + return data + if isinstance(data, bytes): + return data.decode() + if isinstance(data, list): + list_tmp = [] + for item in data: + list_tmp.append(convert_data(item)) + return list_tmp + if isinstance(data, OrderedDict): + dict_tmp = {} + for key, value in data.items(): + dict_tmp[key] = convert_data(value) + return dict_tmp + def get_bridge_fdb(interface): """ Returns the forwarding database entries for a given interface """ if not os.path.exists(f'/sys/class/net/{interface}'): |