diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/completion/qos/list_traffic_match_group.py | 35 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces_tunnel.py | 19 | ||||
| -rwxr-xr-x | src/conf_mode/load-balancing_reverse-proxy.py | 13 | ||||
| -rwxr-xr-x | src/conf_mode/nat64.py | 10 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_bfd.py | 2 | ||||
| -rwxr-xr-x | src/conf_mode/qos.py | 77 | ||||
| -rwxr-xr-x | src/conf_mode/service_dns_forwarding.py | 15 | ||||
| -rwxr-xr-x | src/migration-scripts/reverse-proxy/0-to-1 | 48 | ||||
| -rwxr-xr-x | src/op_mode/snmp_v3.py | 3 | 
9 files changed, 204 insertions, 18 deletions
| diff --git a/src/completion/qos/list_traffic_match_group.py b/src/completion/qos/list_traffic_match_group.py new file mode 100644 index 000000000..015d7ada9 --- /dev/null +++ b/src/completion/qos/list_traffic_match_group.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +from vyos.config import Config + + +def get_qos_traffic_match_group(): +    config = Config() +    base = ['qos', 'traffic-match-group'] +    conf = config.get_config_dict(base, key_mangling=('-', '_')) +    groups = [] + +    for group in conf.get('traffic_match_group', []): +        groups.append(group) + +    return groups + + +if __name__ == "__main__": +    groups = get_qos_traffic_match_group() +    print(" ".join(groups)) + diff --git a/src/conf_mode/interfaces_tunnel.py b/src/conf_mode/interfaces_tunnel.py index 43ba72857..98ef98d12 100755 --- a/src/conf_mode/interfaces_tunnel.py +++ b/src/conf_mode/interfaces_tunnel.py @@ -145,11 +145,20 @@ def verify(tunnel):                  # If no IP GRE key is defined we can not have more then one GRE tunnel                  # bound to any one interface/IP address and the same remote. This will                  # result in a OS  PermissionError: add tunnel "gre0" failed: File exists -                if (their_address == our_address or our_source_if == their_source_if) and \ -                    our_remote == their_remote: -                    raise ConfigError(f'Missing required "ip key" parameter when '\ -                                       'running more then one GRE based tunnel on the '\ -                                       'same source-interface/source-address') +                if our_remote == their_remote: +                    if our_address is not None and their_address == our_address:  +                        # If set to the same values, this is always a fail  +                        raise ConfigError(f'Missing required "ip key" parameter when '\ +                                           'running more then one GRE based tunnel on the '\ +                                           'same source-address') + +                    if their_source_if == our_source_if and their_address == our_address: +                        # Note that lack of None check on these is deliberate.  +                        # source-if and source-ip matching while unset (all None) is a fail +                        # source-ifs set and matching with unset source-ips is a fail +                        raise ConfigError(f'Missing required "ip key" parameter when '\ +                                           'running more then one GRE based tunnel on the '\ +                                           'same source-interface')      # Keys are not allowed with ipip and sit tunnels      if tunnel['encapsulation'] in ['ipip', 'sit']: diff --git a/src/conf_mode/load-balancing_reverse-proxy.py b/src/conf_mode/load-balancing_reverse-proxy.py index 1c1252df0..09c68dadd 100755 --- a/src/conf_mode/load-balancing_reverse-proxy.py +++ b/src/conf_mode/load-balancing_reverse-proxy.py @@ -79,12 +79,21 @@ def verify(lb):              raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service')      for back, back_config in lb['backend'].items(): -        if 'http-check' in back_config: -            http_check = back_config['http-check'] +        if 'http_check' in back_config: +            http_check = back_config['http_check']              if 'expect' in http_check and 'status' in http_check['expect'] and 'string' in http_check['expect']:                  raise ConfigError(f'"expect status" and "expect string" can not be configured together!') + +        if 'health_check' in back_config: +            if 'mode' not in back_config or back_config['mode'] != 'tcp': +                raise ConfigError(f'backend "{back}" can only be configured with {back_config["health_check"]} ' + +                                  f'health-check whilst in TCP mode!') +            if 'http_check' in back_config: +                raise ConfigError(f'backend "{back}" cannot be configured with both http-check and health-check!') +          if 'server' not in back_config:              raise ConfigError(f'"{back} server" must be configured!') +          for bk_server, bk_server_conf in back_config['server'].items():              if 'address' not in bk_server_conf or 'port' not in bk_server_conf:                  raise ConfigError(f'"backend {back} server {bk_server} address and port" must be configured!') diff --git a/src/conf_mode/nat64.py b/src/conf_mode/nat64.py index c1e7ebf85..32a1c47d1 100755 --- a/src/conf_mode/nat64.py +++ b/src/conf_mode/nat64.py @@ -20,7 +20,7 @@ import csv  import os  import re -from ipaddress import IPv6Network +from ipaddress import IPv6Network, IPv6Address  from json import dumps as json_write  from vyos import ConfigError @@ -103,8 +103,14 @@ def verify(nat64) -> None:              # Verify that source.prefix is set and is a /96              if not dict_search("source.prefix", instance):                  raise ConfigError(f"Source NAT64 rule {rule} missing source prefix") -            if IPv6Network(instance["source"]["prefix"]).prefixlen != 96: +            src_prefix = IPv6Network(instance["source"]["prefix"]) +            if src_prefix.prefixlen != 96:                  raise ConfigError(f"Source NAT64 rule {rule} source prefix must be /96") +            if (int(src_prefix[0]) & int(IPv6Address('0:0:0:0:ff00::'))) != 0: +                raise ConfigError( +                    f'Source NAT64 rule {rule} source prefix is not RFC6052-compliant: ' +                    'bits 64 to 71 (9th octet) must be zeroed' +                )              pools = dict_search("translation.pool", instance)              if pools: diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 1c01a9013..1361bb1a9 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -49,7 +49,7 @@ def verify(bfd):          for peer, peer_config in bfd['peer'].items():              # IPv6 link local peers require an explicit local address/interface              if is_ipv6_link_local(peer): -                if 'source' not in peer_config or len(peer_config['source'] < 2): +                if 'source' not in peer_config or len(peer_config['source']) < 2:                      raise ConfigError('BFD IPv6 link-local peers require explicit local address and interface setting')              # IPv6 peers require an explicit local address diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py index 8a590cbc6..45248fb4a 100755 --- a/src/conf_mode/qos.py +++ b/src/conf_mode/qos.py @@ -17,6 +17,7 @@  from sys import exit  from netifaces import interfaces +from vyos.base import Warning  from vyos.config import Config  from vyos.configdep import set_dependents  from vyos.configdep import call_dependents @@ -89,6 +90,36 @@ def _clean_conf_dict(conf):          return conf +def _get_group_filters(config: dict, group_name: str, visited=None) -> dict: +    filters = dict() +    if not visited: +        visited = [group_name, ] +    else: +        if group_name in visited: +            return filters +        visited.append(group_name) + +    for filter, filter_config in config.get(group_name, {}).items(): +        if filter == 'match': +            for match, match_config in filter_config.items(): +               filters[f'{group_name}-{match}'] = match_config +        elif filter == 'match_group': +            for group in filter_config: +                filters.update(_get_group_filters(config, group, visited)) + +    return filters + + +def _get_group_match(config:dict, group_name:str) -> dict: +    match = dict() +    for key, val in _get_group_filters(config, group_name).items(): +        # delete duplicate matches +        if val not in match.values(): +            match[key] = val + +    return match + +  def get_config(config=None):      if config:          conf = config @@ -135,11 +166,27 @@ def get_config(config=None):      qos = conf.merge_defaults(qos, recursive=True) +    if 'traffic_match_group' in qos: +        for group, group_config in qos['traffic_match_group'].items(): +            if 'match_group' in group_config: +                qos['traffic_match_group'][group]['match'] = _get_group_match(qos['traffic_match_group'], group) +      for policy in qos.get('policy', []):          for p_name, p_config in qos['policy'][policy].items():              # cleanup empty match config              if 'class' in p_config:                  for cls, cls_config in p_config['class'].items(): +                    if 'match_group' in cls_config: +                        # merge group match to match +                        for group in cls_config['match_group']: +                            for match, match_conf in qos['traffic_match_group'].get(group, {'match': {}})['match'].items(): +                                if 'match' not in cls_config: +                                    cls_config['match'] = dict() +                                if match in cls_config['match']: +                                    cls_config['match'][f'{group}-{match}'] = match_conf +                                else: +                                    cls_config['match'][match] = match_conf +                      if 'match' in cls_config:                          cls_config['match'] = _clean_conf_dict(cls_config['match'])                          if cls_config['match'] == {}: @@ -147,6 +194,22 @@ def get_config(config=None):      return qos + +def _verify_match(cls_config: dict) -> None: +    if 'match' in cls_config: +        for match, match_config in cls_config['match'].items(): +            if {'ip', 'ipv6'} <= set(match_config): +                raise ConfigError( +                    f'Can not use both IPv6 and IPv4 in one match ({match})!') + + +def _verify_match_group_exist(cls_config, qos): +    if 'match_group' in cls_config: +        for group in cls_config['match_group']: +            if 'traffic_match_group' not in qos or group not in qos['traffic_match_group']: +                Warning(f'Match group "{group}" does not exist!') + +  def verify(qos):      if not qos or 'interface' not in qos:          return None @@ -174,11 +237,8 @@ def verify(qos):                          # bandwidth is not mandatory for priority-queue - that is why this is on the exception list                          if 'bandwidth' not in cls_config and policy_type not in ['priority_queue', 'round_robin', 'shaper_hfsc']:                              raise ConfigError(f'Bandwidth must be defined for policy "{policy}" class "{cls}"!') -                    if 'match' in cls_config: -                        for match, match_config in cls_config['match'].items(): -                            if {'ip', 'ipv6'} <= set(match_config): -                                 raise ConfigError(f'Can not use both IPv6 and IPv4 in one match ({match})!') - +                        _verify_match(cls_config) +                        _verify_match_group_exist(cls_config, qos)                  if policy_type in ['random_detect']:                      if 'precedence' in policy_config:                          for precedence, precedence_config in policy_config['precedence'].items(): @@ -216,8 +276,14 @@ def verify(qos):              if direction not in tmp:                  raise ConfigError(f'Selected QoS policy on interface "{interface}" only supports "{tmp}"!') +    if 'traffic_match_group' in qos: +        for group, group_config in qos['traffic_match_group'].items(): +            _verify_match(group_config) +            _verify_match_group_exist(group_config, qos) +      return None +  def generate(qos):      if not qos or 'interface' not in qos:          return None @@ -254,6 +320,7 @@ def apply(qos):      return None +  if __name__ == '__main__':      try:          c = get_config() diff --git a/src/conf_mode/service_dns_forwarding.py b/src/conf_mode/service_dns_forwarding.py index 7e863073a..70686534f 100755 --- a/src/conf_mode/service_dns_forwarding.py +++ b/src/conf_mode/service_dns_forwarding.py @@ -102,7 +102,7 @@ def get_config(config=None):                                  'ttl': rdata['ttl'],                                  'value': address                              }) -                    elif rtype in ['cname', 'ptr', 'ns']: +                    elif rtype in ['cname', 'ptr']:                          if not 'target' in rdata:                              dns['authoritative_zone_errors'].append(f'{subnode}.{node}: target is required')                              continue @@ -113,6 +113,19 @@ def get_config(config=None):                              'ttl': rdata['ttl'],                              'value': '{}.'.format(rdata['target'])                          }) +                    elif rtype == 'ns': +                        if not 'target' in rdata: +                            dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one target is required') +                            continue + +                        for target in rdata['target']: +                            zone['records'].append({ +                                'name': subnode, +                                'type': rtype.upper(), +                                'ttl': rdata['ttl'], +                                'value': f'{target}.' +                            }) +                      elif rtype == 'mx':                          if not 'server' in rdata:                              dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one server is required') diff --git a/src/migration-scripts/reverse-proxy/0-to-1 b/src/migration-scripts/reverse-proxy/0-to-1 new file mode 100755 index 000000000..d61493815 --- /dev/null +++ b/src/migration-scripts/reverse-proxy/0-to-1 @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 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 +# published by the Free Software Foundation. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. + +# T6409: Remove unused 'backend bk-example parameters' node + +from sys import argv, exit +from vyos.configtree import ConfigTree + +if len(argv) < 2: +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +config = ConfigTree(config_file) +base = ['load-balancing', 'reverse-proxy', 'backend'] +if not config.exists(base): +    # Nothing to do +    exit(0) + +# we need to run this for every configured network +for backend in config.list_nodes(base): +    param_node = base + [backend, 'parameters'] +    if config.exists(param_node): +        config.delete(param_node) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1) diff --git a/src/op_mode/snmp_v3.py b/src/op_mode/snmp_v3.py index a1f76f0bc..abeb524dd 100755 --- a/src/op_mode/snmp_v3.py +++ b/src/op_mode/snmp_v3.py @@ -85,7 +85,7 @@ if __name__ == '__main__':          'user': [],          'view': []      } -     +      if c.exists_effective('service snmp v3 group'):          for g in c.list_effective_nodes('service snmp v3 group'):              group = { @@ -146,7 +146,6 @@ if __name__ == '__main__':              data['trap'].append(trap) -    print(data)      if args.all:           # Special case, print all templates !           tmpl = jinja2.Template(GROUP_OUTP_TMPL_SRC) | 
