diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/firewall.py | 74 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-vxlan.py | 7 | ||||
| -rwxr-xr-x | src/conf_mode/load-balancing-haproxy.py | 14 | ||||
| -rwxr-xr-x | src/conf_mode/nat.py | 14 | ||||
| -rwxr-xr-x | src/conf_mode/nat66.py | 22 | ||||
| -rwxr-xr-x | src/migration-scripts/cluster/1-to-2 | 193 | ||||
| -rwxr-xr-x | src/migration-scripts/firewall/10-to-11 | 185 | ||||
| -rwxr-xr-x | src/migration-scripts/firewall/11-to-12 | 75 | ||||
| -rwxr-xr-x | src/migration-scripts/interfaces/31-to-32 | 46 | ||||
| -rwxr-xr-x | src/migration-scripts/nat/5-to-6 | 62 | ||||
| -rwxr-xr-x | src/migration-scripts/nat/6-to-7 | 67 | ||||
| -rwxr-xr-x | src/migration-scripts/nat66/1-to-2 | 63 | ||||
| -rwxr-xr-x | src/op_mode/generate_tech-support_archive.py | 4 | ||||
| -rwxr-xr-x | src/op_mode/interfaces_wireless.py | 186 | ||||
| -rwxr-xr-x | src/op_mode/lldp.py | 5 | ||||
| -rwxr-xr-x | src/op_mode/show_wireless.py | 149 | ||||
| -rwxr-xr-x | src/system/uacctd_stop.py | 7 | 
17 files changed, 809 insertions, 364 deletions
| diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index f6480ab0a..8028492a7 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -283,8 +283,8 @@ def verify_rule(firewall, rule_conf, ipv6):      for direction in ['inbound_interface','outbound_interface']:          if direction in rule_conf: -            if 'interface_name' in rule_conf[direction] and 'interface_group' in rule_conf[direction]: -                raise ConfigError(f'Cannot specify both interface-group and interface-name for {direction}') +            if 'name' in rule_conf[direction] and 'group' in rule_conf[direction]: +                raise ConfigError(f'Cannot specify both interface group and interface name for {direction}')  def verify_nested_group(group_name, group, groups, seen):      if 'include' not in group: @@ -374,12 +374,82 @@ def verify(firewall):                          for rule_id, rule_conf in name_conf['rule'].items():                              verify_rule(firewall, rule_conf, True) +    #### ZONESSSS +    local_zone = False +    zone_interfaces = [] + +    if 'zone' in firewall: +        for zone, zone_conf in firewall['zone'].items(): +            if 'local_zone' not in zone_conf and 'interface' not in zone_conf: +                raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') + +            if 'local_zone' in zone_conf: +                if local_zone: +                    raise ConfigError('There cannot be multiple local zones') +                if 'interface' in zone_conf: +                    raise ConfigError('Local zone cannot have interfaces assigned') +                if 'intra_zone_filtering' in zone_conf: +                    raise ConfigError('Local zone cannot use intra-zone-filtering') +                local_zone = True + +            if 'interface' in zone_conf: +                found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces] + +                if found_duplicates: +                    raise ConfigError(f'Interfaces cannot be assigned to multiple zones') + +                zone_interfaces += zone_conf['interface'] + +            if 'intra_zone_filtering' in zone_conf: +                intra_zone = zone_conf['intra_zone_filtering'] + +                if len(intra_zone) > 1: +                    raise ConfigError('Only one intra-zone-filtering action must be specified') + +                if 'firewall' in intra_zone: +                    v4_name = dict_search_args(intra_zone, 'firewall', 'name') +                    if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name): +                        raise ConfigError(f'Firewall name "{v4_name}" does not exist') + +                    v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name') +                    if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): +                        raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') + +                    if not v4_name and not v6_name: +                        raise ConfigError('No firewall names specified for intra-zone-filtering') + +            if 'from' in zone_conf: +                for from_zone, from_conf in zone_conf['from'].items(): +                    if from_zone not in firewall['zone']: +                        raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') + +                    v4_name = dict_search_args(from_conf, 'firewall', 'name') +                    if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name): +                        raise ConfigError(f'Firewall name "{v4_name}" does not exist') + +                    v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name') +                    if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): +                        raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') +      return None  def generate(firewall):      if not os.path.exists(nftables_conf):          firewall['first_install'] = True +    if 'zone' in firewall: +        for local_zone, local_zone_conf in firewall['zone'].items(): +            if 'local_zone' not in local_zone_conf: +                continue + +            local_zone_conf['from_local'] = {} + +            for zone, zone_conf in firewall['zone'].items(): +                if zone == local_zone or 'from' not in zone_conf: +                    continue +                if local_zone in zone_conf['from']: +                    local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone] +      render(nftables_conf, 'firewall/nftables.j2', firewall)      return None diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 05f68112a..ff8144e74 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -168,6 +168,13 @@ def verify(vxlan):      verify_address(vxlan)      verify_bond_bridge_member(vxlan)      verify_mirror_redirect(vxlan) + +    # We use a defaultValue for port, thus it's always safe to use +    if vxlan['port'] == '8472': +        Warning('Starting from VyOS 1.4, the default port for VXLAN '\ +                'has been changed to 4789. This matches the IANA assigned '\ +                'standard port number!') +      return None  def generate(vxlan): diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py index 8fe429653..ec4311bb5 100755 --- a/src/conf_mode/load-balancing-haproxy.py +++ b/src/conf_mode/load-balancing-haproxy.py @@ -94,8 +94,8 @@ def generate(lb):              if os.path.isfile(file):                  os.unlink(file)          # Delete old directories -        #if os.path.isdir(load_balancing_dir): -        #    rmtree(load_balancing_dir, ignore_errors=True) +        if os.path.isdir(load_balancing_dir): +            rmtree(load_balancing_dir, ignore_errors=True)          return None @@ -106,15 +106,12 @@ def generate(lb):      # SSL Certificates for frontend      for front, front_config in lb['service'].items():          if 'ssl' in front_config: -            cert_file_path = os.path.join(load_balancing_dir, 'cert.pem') -            cert_key_path = os.path.join(load_balancing_dir, 'cert.pem.key') -            ca_cert_file_path = os.path.join(load_balancing_dir, 'ca.pem')              if 'certificate' in front_config['ssl']: -                #cert_file_path = os.path.join(load_balancing_dir, 'cert.pem') -                #cert_key_path = os.path.join(load_balancing_dir, 'cert.key')                  cert_name = front_config['ssl']['certificate']                  pki_cert = lb['pki']['certificate'][cert_name] +                cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem') +                cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')                  with open(cert_file_path, 'w') as f:                      f.write(wrap_certificate(pki_cert['certificate'])) @@ -126,6 +123,7 @@ def generate(lb):              if 'ca_certificate' in front_config['ssl']:                  ca_name = front_config['ssl']['ca_certificate']                  pki_ca_cert = lb['pki']['ca'][ca_name] +                ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem')                  with open(ca_cert_file_path, 'w') as f:                      f.write(wrap_certificate(pki_ca_cert['certificate'])) @@ -133,11 +131,11 @@ def generate(lb):      # SSL Certificates for backend      for back, back_config in lb['backend'].items():          if 'ssl' in back_config: -            ca_cert_file_path = os.path.join(load_balancing_dir, 'ca.pem')              if 'ca_certificate' in back_config['ssl']:                  ca_name = back_config['ssl']['ca_certificate']                  pki_ca_cert = lb['pki']['ca'][ca_name] +                ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem')                  with open(ca_cert_file_path, 'w') as f:                      f.write(wrap_certificate(pki_ca_cert['certificate'])) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 52a7a71fd..44b13d413 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -151,8 +151,11 @@ def verify(nat):              err_msg = f'Source NAT configuration error in rule {rule}:'              if 'outbound_interface' in config: -                if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces(): -                    Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') +                if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']: +                    raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for nat source rule "{rule}"') +                elif 'name' in config['outbound_interface']: +                    if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces(): +                        Warning(f'{err_msg} - interface "{config["outbound_interface"]["name"]}" does not exist on this system')              if not dict_search('translation.address', config) and not dict_search('translation.port', config):                  if 'exclude' not in config and 'backend' not in config['load_balance']: @@ -172,8 +175,11 @@ def verify(nat):              err_msg = f'Destination NAT configuration error in rule {rule}:'              if 'inbound_interface' in config: -                if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): -                    Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') +                if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']: +                    raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for destination nat rule "{rule}"') +                elif 'name' in config['inbound_interface']: +                    if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces(): +                        Warning(f'{err_msg} -  interface "{config["inbound_interface"]["name"]}" does not exist on this system')              if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']:                  if 'exclude' not in config and 'backend' not in config['load_balance']: diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index 46d796bc8..0ba08aef3 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -62,11 +62,13 @@ def verify(nat):      if dict_search('source.rule', nat):          for rule, config in dict_search('source.rule', nat).items():              err_msg = f'Source NAT66 configuration error in rule {rule}:' -            if 'outbound_interface' not in config: -                raise ConfigError(f'{err_msg} outbound-interface not specified') -            if config['outbound_interface'] not in interfaces(): -                raise ConfigError(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system') +            if 'outbound_interface' in config: +                if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']: +                    raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for nat source rule "{rule}"') +                elif 'name' in config['outbound_interface']: +                    if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces(): +                        Warning(f'{err_msg} - interface "{config["outbound_interface"]["name"]}" does not exist on this system')              addr = dict_search('translation.address', config)              if addr != None: @@ -85,12 +87,12 @@ def verify(nat):          for rule, config in dict_search('destination.rule', nat).items():              err_msg = f'Destination NAT66 configuration error in rule {rule}:' -            if 'inbound_interface' not in config: -                raise ConfigError(f'{err_msg}\n' \ -                                  'inbound-interface not specified') -            else: -                if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): -                    Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') +            if 'inbound_interface' in config: +                if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']: +                    raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for destination nat rule "{rule}"') +                elif 'name' in config['inbound_interface']: +                    if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces(): +                        Warning(f'{err_msg} -  interface "{config["inbound_interface"]["name"]}" does not exist on this system')      return None diff --git a/src/migration-scripts/cluster/1-to-2 b/src/migration-scripts/cluster/1-to-2 new file mode 100755 index 000000000..a2e589155 --- /dev/null +++ b/src/migration-scripts/cluster/1-to-2 @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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/>. +# + +import re +import sys + +from vyos.configtree import ConfigTree + +if __name__ == '__main__': +    if len(sys.argv) < 2: +        print("Must specify file name!") +        sys.exit(1) + +    file_name = sys.argv[1] + +    with open(file_name, 'r') as f: +        config_file = f.read() + +    config = ConfigTree(config_file) + +    if not config.exists(['cluster']): +        # Cluster is not set -- nothing to do at all +        sys.exit(0) + +    # If at least one cluster group is defined, we have real work to do. +    # If there are no groups, we remove the top-level cluster node at the end of this script anyway. +    if config.exists(['cluster', 'group']): +        # First, gather timer and interface settings to duplicate them in all groups, +        # since in the old cluster they are global, but in VRRP they are always per-group + +        global_interface = None +        if config.exists(['cluster', 'interface']): +            global_interface = config.return_value(['cluster', 'interface']) +        else: +            # Such configs shouldn't exist in practice because interface is a required option. +            # But since it's possible to specify interface inside 'service' options, +            # we may be able to convert such configs nonetheless. +            print("Warning: incorrect cluster config: interface is not defined.", file=sys.stderr) + +        # There are three timers: advertise-interval, dead-interval, and monitor-dead-interval +        # Only the first one makes sense for the VRRP, we translate it to advertise-interval +        advertise_interval = None +        if config.exists(['cluster', 'keepalive-interval']): +            advertise_interval = config.return_value(['cluster', 'keepalive-interval']) + +        if advertise_interval is not None: +            # Cluster had all timers in milliseconds, so we need to convert them to seconds +            # And ensure they are not shorter than one second +            advertise_interval = int(advertise_interval) // 1000 +            if advertise_interval < 1: +                advertise_interval = 1 + +        # Cluster had password as a global option, in VRRP it's per-group +        password = None +        if config.exists(['cluster', 'pre-shared-secret']): +            password = config.return_value(['cluster', 'pre-shared-secret']) + +        # Set up the stage for converting cluster groups to VRRP groups +        free_vrids = set(range(1,255)) +        vrrp_base_path = ['high-availability', 'vrrp', 'group'] +        if not config.exists(vrrp_base_path): +            # If VRRP is not set up, create a node and set it to 'tag node' +            # Setting it to 'tag' is not mandatory but it's better to be consistent +            # with configs produced by 'save' +            config.set(vrrp_base_path) +            config.set_tag(vrrp_base_path) +        else: +            # If there are VRRP groups already, we need to find the set of unused VRID numbers to avoid conflicts +            existing_vrids = set() +            for vg in config.list_nodes(vrrp_base_path): +                existing_vrids.add(int(config.return_value(vrrp_base_path + [vg, 'vrid']))) +            free_vrids = free_vrids.difference(existing_vrids) + +        # Now handle cluster groups +        groups = config.list_nodes(['cluster', 'group']) +        for g in groups: +            base_path = ['cluster', 'group', g] +            service_names = config.return_values(base_path + ['service']) + +            # Cluster used to allow services other than IP addresses, at least nominally +            # Whether that ever worked is a big question, but we need to consider that, +            # since configs with custom services are definitely impossible to meaningfully migrate now +            services = {"ip": [], "other": []} +            for s in service_names: +                if re.match(r'^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2})(/[a-z]+\d+)?$', s): +                    services["ip"].append(s) +                else: +                    services["other"].append(s) + +            if services["other"]: +                print("Cluster config includes non-IP address services and cannot be migrated", file=sys.stderr) +                sys.exit(1) + +            # Cluster allowed virtual IPs for different interfaces within a single group. +            # VRRP groups are by definition bound to interfaces, so we cannot migrate such configurations. +            # Thus we need to find out if all addresses either leave the interface unspecified +            # (in that case the global 'cluster interface' option is used), +            # or have the same interface, or have the same interface as the global 'cluster interface'. + +            # First, we collect all addresses and check if they have interface specified +            # If not, we substitute the global interface option +            # or throw an error if it's not in the config. +            ips = [] +            for ip in services["ip"]: +                ip_with_intf = re.match(r'^(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2})/(?P<intf>[a-z]+\d+)$', ip) +                if ip_with_intf: +                    ips.append({"ip": ip_with_intf.group("ip"), "interface": ip_with_intf.group("intf")}) +                else: +                    if global_interface is not None: +                        ips.append({"ip": ip, "interface": global_interface}) +                    else: +                        print("Error: cluster has groups with IPs without interfaces and 'cluster interface' is not specified.", file=sys.stderr) +                        sys.exit(1) + +            # Then we check if all addresses are for the same interface. +            intfs_set = set(map(lambda i: i["interface"], ips)) +            if len(intfs_set) > 1: +                print("Error: cluster group has addresses for different interfaces", file=sys.stderr) +                sys.exit(1) + +            # If we got this far, the group is migratable. + +            # Extract the interface from the set -- we know there's only a single member. +            interface = intfs_set.pop() + +            addresses = list(map(lambda i: i["ip"], ips)) +            vrrp_path = ['high-availability', 'vrrp', 'group', g] + +            # If there's already a VRRP group with exactly the same name, +            # we probably shouldn't try to make up a unique name, just leave migration to the user... +            if config.exists(vrrp_path): +                print("Error: VRRP group with the same name as a cluster group already exists", file=sys.stderr) +                sys.exit(1) + +            config.set(vrrp_path + ['interface'], value=interface) +            for a in addresses: +                config.set(vrrp_path + ['virtual-address'], value=a, replace=False) + +            # Take the next free VRID and assign it to the group +            vrid = free_vrids.pop() +            config.set(vrrp_path + ['vrid'], value=vrid) + +            # Convert the monitor option to VRRP ping health check +            if config.exists(base_path + ['monitor']): +                monitor_ip = config.return_value(base_path + ['monitor']) +                config.set(vrrp_path + ['health-check', 'ping'], value=monitor_ip) + +            # Convert "auto-failback" to "no-preempt", if necessary +            if config.exists(base_path + ['auto-failback']): +                # It's a boolean node that requires "true" or "false" +                # so if it exists we still need to check its value +                auto_failback = config.return_value(base_path + ['auto-failback']) +                if auto_failback == "false": +                    config.set(vrrp_path + ['no-preempt']) +                else: +                    # It's "true" or we assume it is, which means preemption is desired, +                    # and in VRRP config it's the default +                    pass +            else: +                # The old default for that option is false +                config.set(vrrp_path + ['no-preempt']) + +            # Inject settings from the global cluster config that have to be per-group in VRRP +            if advertise_interval is not None: +                config.set(vrrp_path + ['advertise-interval'], value=advertise_interval) + +            if password is not None: +                config.set(vrrp_path + ['authentication', 'password'], value=password) +                config.set(vrrp_path + ['authentication', 'type'], value='plaintext-password') + +    # Finally, clean up the old cluster node +    config.delete(['cluster']) + +    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)) +        sys.exit(1) diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11 index 716c5a240..b739fb139 100755 --- a/src/migration-scripts/firewall/10-to-11 +++ b/src/migration-scripts/firewall/10-to-11 @@ -181,191 +181,6 @@ if config.exists(base + ['interface']):      config.delete(base + ['interface']) - -### Migration of zones: -### User interface groups  -if config.exists(base + ['zone']): -    inp_ipv4_rule = 101 -    inp_ipv6_rule = 101 -    fwd_ipv4_rule = 101 -    fwd_ipv6_rule = 101 -    out_ipv4_rule = 101 -    out_ipv6_rule = 101 -    local_zone = 'False' - -    for zone in config.list_nodes(base + ['zone']): -        if config.exists(base + ['zone', zone, 'local-zone']): -            local_zone = 'True' -            # Add default-action== accept for compatibility reasons: -            config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept') -            config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept') -            config.set(base + ['ipv4', 'output', 'filter', 'default-action'], value='accept') -            config.set(base + ['ipv6', 'output', 'filter', 'default-action'], value='accept') -            for from_zone in config.list_nodes(base + ['zone', zone, 'from']): -                group_name = 'IG_' + from_zone -                if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']): -                    # ipv4 input ruleset -                    target_ipv4_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']) -                    config.set(base + ['ipv4', 'input', 'filter', 'rule']) -                    config.set_tag(base + ['ipv4', 'input', 'filter', 'rule']) -                    config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'inbound-interface', 'interface-group'], value=group_name) -                    config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'action'], value='jump') -                    config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'jump-target'], value=target_ipv4_chain) -                    inp_ipv4_rule = inp_ipv4_rule + 5 -                if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']): -                    # ipv6 input ruleset -                    target_ipv6_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']) -                    config.set(base + ['ipv6', 'input', 'filter', 'rule']) -                    config.set_tag(base + ['ipv6', 'input', 'filter', 'rule']) -                    config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'inbound-interface', 'interface-group'], value=group_name) -                    config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'action'], value='jump') -                    config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'jump-target'], value=target_ipv6_chain) -                    inp_ipv6_rule = inp_ipv6_rule + 5 - -            # Migrate: set firewall zone <zone> default-action <action> -            # Options: drop or reject. If not specified, is drop -            if config.exists(base + ['zone', zone, 'default-action']): -                local_def_action = config.return_value(base + ['zone', zone, 'default-action']) -            else: -                local_def_action = 'drop' -            config.set(base + ['ipv4', 'input', 'filter', 'rule']) -            config.set_tag(base + ['ipv4', 'input', 'filter', 'rule']) -            config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'action'], value=local_def_action) -            config.set(base + ['ipv6', 'input', 'filter', 'rule']) -            config.set_tag(base + ['ipv6', 'input', 'filter', 'rule']) -            config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'action'], value=local_def_action) -            if config.exists(base + ['zone', zone, 'enable-default-log']): -                config.set(base + ['ipv4', 'input', 'filter', 'rule', inp_ipv4_rule, 'log'], value='enable') -                config.set(base + ['ipv6', 'input', 'filter', 'rule', inp_ipv6_rule, 'log'], value='enable') - -        else: -        # It's not a local zone -            group_name = 'IG_' + zone -            # Add default-action== accept for compatibility reasons: -            config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') -            config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') -            # intra-filtering migration. By default accept -            intra_zone_ipv4_action = 'accept' -            intra_zone_ipv6_action = 'accept' -             -            if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'action']): -                intra_zone_ipv4_action = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'action']) -                intra_zone_ipv6_action = intra_zone_ipv4_action -            else: -                if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']): -                    intra_zone_ipv4_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']) -                    intra_zone_ipv4_action = 'jump' -                if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']): -                    intra_zone_ipv6_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']) -                    intra_zone_ipv6_action = 'jump' -            config.set(base + ['ipv4', 'forward', 'filter', 'rule']) -            config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule']) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'inbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value=intra_zone_ipv4_action) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule']) -            config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule']) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'inbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value=intra_zone_ipv6_action) -            if intra_zone_ipv4_action == 'jump': -                if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']): -                    intra_zone_ipv4_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'name']) -                    config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'jump-target'], value=intra_zone_ipv4_target) -            if intra_zone_ipv6_action == 'jump': -                if config.exists(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']): -                    intra_zone_ipv6_target = config.return_value(base + ['zone', zone, 'intra-zone-filtering', 'firewall', 'ipv6-name']) -                    config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'jump-target'], value=intra_zone_ipv6_target) -            fwd_ipv4_rule = fwd_ipv4_rule + 5 -            fwd_ipv6_rule = fwd_ipv6_rule + 5 - -            if config.exists(base + ['zone', zone, 'interface']): -                # Create interface group IG_<zone> -                group_name = 'IG_' + zone -                config.set(base + ['group', 'interface-group'], value=group_name) -                config.set_tag(base + ['group', 'interface-group']) -                for iface in config.return_values(base + ['zone', zone, 'interface']): -                    config.set(base + ['group', 'interface-group', group_name, 'interface'], value=iface, replace=False) - -            if config.exists(base + ['zone', zone, 'from']): -                for from_zone in config.list_nodes(base + ['zone', zone, 'from']): -                    from_group = 'IG_' + from_zone -                    if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']): -                        target_ipv4_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'name']) -                        if config.exists(base + ['zone', from_zone, 'local-zone']): -                            # It's from LOCAL zone -> Output filtering  -                            config.set(base + ['ipv4', 'output', 'filter', 'rule']) -                            config.set_tag(base + ['ipv4', 'output', 'filter', 'rule']) -                            config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name) -                            config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'action'], value='jump') -                            config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'jump-target'], value=target_ipv4_chain) -                            out_ipv4_rule = out_ipv4_rule + 5 -                        else: -                            # It's not LOCAL zone -> forward filtering -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule']) -                            config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule']) -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name) -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'inbound-interface', 'interface-group'], value=from_group) -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value='jump') -                            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'jump-target'], value=target_ipv4_chain) -                            fwd_ipv4_rule = fwd_ipv4_rule + 5 -                    if config.exists(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']): -                        target_ipv6_chain = config.return_value(base + ['zone', zone, 'from', from_zone, 'firewall', 'ipv6-name']) -                        if config.exists(base + ['zone', from_zone, 'local-zone']): -                            # It's from LOCAL zone -> Output filtering -                            config.set(base + ['ipv6', 'output', 'filter', 'rule']) -                            config.set_tag(base + ['ipv6', 'output', 'filter', 'rule']) -                            config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name) -                            config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'action'], value='jump') -                            config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'jump-target'], value=target_ipv6_chain) -                            out_ipv6_rule = out_ipv6_rule + 5 -                        else: -                            # It's not LOCAL zone -> forward filtering -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule']) -                            config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule']) -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name) -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'inbound-interface', 'interface-group'], value=from_group) -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value='jump') -                            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'jump-target'], value=target_ipv6_chain) -                            fwd_ipv6_rule = fwd_ipv6_rule + 5 - -            ## Now need to migrate: set firewall zone <zone> default-action <action>    # action=drop if not specified. -            if config.exists(base + ['zone', zone, 'default-action']): -                def_action = config.return_value(base + ['zone', zone, 'default-action']) -            else: -                def_action = 'drop' -            config.set(base + ['ipv4', 'forward', 'filter', 'rule']) -            config.set_tag(base + ['ipv4', 'forward', 'filter', 'rule']) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'outbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'action'], value=def_action) -            description = 'zone_' + zone + ' default-action' -            config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'description'], value=description) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule']) -            config.set_tag(base + ['ipv6', 'forward', 'filter', 'rule']) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'outbound-interface', 'interface-group'], value=group_name) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'action'], value=def_action) -            config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'description'], value=description) - -            if config.exists(base + ['zone', zone, 'enable-default-log']): -                config.set(base + ['ipv4', 'forward', 'filter', 'rule', fwd_ipv4_rule, 'log'], value='enable') -                config.set(base + ['ipv6', 'forward', 'filter', 'rule', fwd_ipv6_rule, 'log'], value='enable') -            fwd_ipv4_rule = fwd_ipv4_rule + 5 -            fwd_ipv6_rule = fwd_ipv6_rule + 5 - -    # Migrate default-action (force to be drop in output chain) if local zone is defined -    if local_zone == 'True': -        # General drop in output change if needed -        config.set(base + ['ipv4', 'output', 'filter', 'rule']) -        config.set_tag(base + ['ipv4', 'output', 'filter', 'rule']) -        config.set(base + ['ipv4', 'output', 'filter', 'rule', out_ipv4_rule, 'action'], value=local_def_action) -        config.set(base + ['ipv6', 'output', 'filter', 'rule']) -        config.set_tag(base + ['ipv6', 'output', 'filter', 'rule']) -        config.set(base + ['ipv6', 'output', 'filter', 'rule', out_ipv6_rule, 'action'], value=local_def_action) - -    config.delete(base + ['zone']) - -###### END migration zones -  try:      with open(file_name, 'w') as f:          f.write(config.to_string()) diff --git a/src/migration-scripts/firewall/11-to-12 b/src/migration-scripts/firewall/11-to-12 new file mode 100755 index 000000000..51b2fa860 --- /dev/null +++ b/src/migration-scripts/firewall/11-to-12 @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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/>. + +# T5681: Firewall re-writing. Simplify cli when mathcing interface +# From +    # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-name <iface> +    # set firewall ... rule <rule> [inbound-interface | outboubd-interface] interface-group <iface_group> +# To +    # set firewall ... rule <rule> [inbound-interface | outboubd-interface] name <iface> +    # set firewall ... rule <rule> [inbound-interface | outboubd-interface] group <iface_group> + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +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() + +base = ['firewall'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +## FORT +## Migration from base chains +#if config.exists(base + ['interface', iface, direction]): +for family in ['ipv4', 'ipv6']: +    if config.exists(base + [family]): +        for hook in ['forward', 'input', 'output', 'name']: +            if config.exists(base + [family, hook]): +                for priority in config.list_nodes(base + [family, hook]): +                    if config.exists(base + [family, hook, priority, 'rule']): +                        for rule in config.list_nodes(base + [family, hook, priority, 'rule']): +                            for direction in ['inbound-interface', 'outbound-interface']: +                                if config.exists(base + [family, hook, priority, 'rule', rule, direction]): +                                    if config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']): +                                        iface = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) +                                        config.set(base + [family, hook, priority, 'rule', rule, direction, 'name'], value=iface) +                                        config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-name']) +                                    elif config.exists(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']): +                                        group = config.return_value(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) +                                        config.set(base + [family, hook, priority, 'rule', rule, direction, 'group'], value=group) +                                        config.delete(base + [family, hook, priority, 'rule', rule, direction, 'interface-group']) + +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)
\ No newline at end of file diff --git a/src/migration-scripts/interfaces/31-to-32 b/src/migration-scripts/interfaces/31-to-32 new file mode 100755 index 000000000..35b397c39 --- /dev/null +++ b/src/migration-scripts/interfaces/31-to-32 @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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/>. +# +# T5671: change port to IANA assigned default port + +from sys import argv +from sys import 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() +    base = ['interfaces', 'vxlan'] + +config = ConfigTree(config_file) +if not config.exists(base): +    # Nothing to do +    exit(0) + +for vxlan in config.list_nodes(base): +    if not config.exists(base + ['port']): +        config.set(base + [vxlan, 'port'], value='8472') + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print(f'Failed to save the modified config: {e}') +    exit(1) diff --git a/src/migration-scripts/nat/5-to-6 b/src/migration-scripts/nat/5-to-6 new file mode 100755 index 000000000..de3830582 --- /dev/null +++ b/src/migration-scripts/nat/5-to-6 @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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/>. + +# T5643: move from 'set nat [source|destination] rule X [inbound-interface|outbound interface] <iface>' +# to +# 'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>' + +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) + +if not config.exists(['nat']): +    # Nothing to do +    exit(0) + +for direction in ['source', 'destination']: +    # If a node doesn't exist, we obviously have nothing to do. +    if not config.exists(['nat', direction]): +        continue + +    # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, +    # but there are no rules under it. +    if not config.list_nodes(['nat', direction]): +        continue + +    for rule in config.list_nodes(['nat', direction, 'rule']): +        base = ['nat', direction, 'rule', rule] +        for iface in ['inbound-interface','outbound-interface']: +            if config.exists(base + [iface]): +                tmp = config.return_value(base + [iface]) +                config.delete(base + [iface]) +                config.set(base + [iface, 'interface-name'], value=tmp) + +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/migration-scripts/nat/6-to-7 b/src/migration-scripts/nat/6-to-7 new file mode 100755 index 000000000..b5f6328ef --- /dev/null +++ b/src/migration-scripts/nat/6-to-7 @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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/>. + +# T5681: Firewall re-writing. Simplify cli when mathcing interface +# From +#   'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-name <iface>' +#   'set nat [source|destination] rule X [inbound-interface|outbound interface] interface-group <iface_group>' +# to +#   'set nat [source|destination] rule X [inbound-interface|outbound interface] name <iface>' +#   'set nat [source|destination] rule X [inbound-interface|outbound interface] group <iface_group>' + +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) + +if not config.exists(['nat']): +    # Nothing to do +    exit(0) + +for direction in ['source', 'destination']: +    # If a node doesn't exist, we obviously have nothing to do. +    if not config.exists(['nat', direction]): +        continue + +    # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, +    # but there are no rules under it. +    if not config.list_nodes(['nat', direction]): +        continue + +    for rule in config.list_nodes(['nat', direction, 'rule']): +        base = ['nat', direction, 'rule', rule] +        for iface in ['inbound-interface','outbound-interface']: +            if config.exists(base + [iface]): +                if config.exists(base + [iface, 'interface-name']): +                    tmp = config.return_value(base + [iface, 'interface-name']) +                    config.delete(base + [iface, 'interface-name']) +                    config.set(base + [iface, 'name'], value=tmp) + +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/migration-scripts/nat66/1-to-2 b/src/migration-scripts/nat66/1-to-2 new file mode 100755 index 000000000..b7d4e3f6b --- /dev/null +++ b/src/migration-scripts/nat66/1-to-2 @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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/>. + +# T5681: Firewall re-writing. Simplify cli when mathcing interface +# From +#   'set nat66 [source|destination] rule X [inbound-interface|outbound interface] <iface>' +# to +#   'set nat66 [source|destination] rule X [inbound-interface|outbound interface] name <iface>' + +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) +if not config.exists(['nat66']): +    # Nothing to do +    exit(0) + +for direction in ['source', 'destination']: +    # If a node doesn't exist, we obviously have nothing to do. +    if not config.exists(['nat66', direction]): +        continue + +    # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist, +    # but there are no rules under it. +    if not config.list_nodes(['nat66', direction]): +        continue + +    for rule in config.list_nodes(['nat66', direction, 'rule']): +        base = ['nat66', direction, 'rule', rule] +        for iface in ['inbound-interface','outbound-interface']: +            if config.exists(base + [iface]): +                tmp = config.return_value(base + [iface]) +                config.delete(base + [iface]) +                config.set(base + [iface, 'name'], value=tmp) + +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/generate_tech-support_archive.py b/src/op_mode/generate_tech-support_archive.py index 23d81f986..c490b0137 100755 --- a/src/op_mode/generate_tech-support_archive.py +++ b/src/op_mode/generate_tech-support_archive.py @@ -100,7 +100,7 @@ if __name__ == '__main__':      location_path = args.path[:-1] if args.path[-1] == '/' else args.path      hostname: str = gethostname() -    time_now: str = datetime.now().isoformat(timespec='seconds') +    time_now: str = datetime.now().isoformat(timespec='seconds').replace(":", "-")      remote = False      tmp_path = '' @@ -145,4 +145,4 @@ if __name__ == '__main__':              rmtree(tmp_dir)      finally:          # cleanup -        exit()
\ No newline at end of file +        exit() diff --git a/src/op_mode/interfaces_wireless.py b/src/op_mode/interfaces_wireless.py new file mode 100755 index 000000000..dfe50e2cb --- /dev/null +++ b/src/op_mode/interfaces_wireless.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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/>. + +import re +import sys +import typing +import vyos.opmode + +from copy import deepcopy +from tabulate import tabulate +from vyos.utils.process import popen +from vyos.configquery import ConfigTreeQuery + +def _verify(func): +    """Decorator checks if Wireless LAN config exists""" +    from functools import wraps + +    @wraps(func) +    def _wrapper(*args, **kwargs): +        config = ConfigTreeQuery() +        if not config.exists(['interfaces', 'wireless']): +            raise vyos.opmode.UnconfiguredSubsystem(unconf_message) +        return func(*args, **kwargs) +    return _wrapper + +def _get_raw_info_data(): +    output_data = [] + +    config = ConfigTreeQuery() +    raw = config.get_config_dict(['interfaces', 'wireless'], effective=True, +                                 get_first_key=True, key_mangling=('-', '_')) +    for interface, interface_config in raw.items(): +        tmp = {'name' : interface} + +        if 'type' in interface_config: +            tmp.update({'type' : interface_config['type']}) +        else: +            tmp.update({'type' : '-'}) + +        if 'ssid' in interface_config: +            tmp.update({'ssid' : interface_config['ssid']}) +        else: +            tmp.update({'ssid' : '-'}) + +        if 'channel' in interface_config: +            tmp.update({'channel' : interface_config['channel']}) +        else: +            tmp.update({'channel' : '-'}) + +        output_data.append(tmp) + +    return output_data + +def _get_formatted_info_output(raw_data): +    output=[] +    for ssid in raw_data: +        output.append([ssid['name'], ssid['type'], ssid['ssid'], ssid['channel']]) + +    headers = ["Interface", "Type", "SSID", "Channel"] +    print(tabulate(output, headers, numalign="left")) + +def _get_raw_scan_data(intf_name): +    # XXX: This ignores errors +    tmp, _ = popen(f'iw dev {intf_name} scan ap-force') +    networks = [] +    data = { +        'ssid': '', +        'mac': '', +        'channel': '', +        'signal': '' +    } +    re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') +    for line in tmp.splitlines(): +        if line.startswith('BSS '): +            ssid = deepcopy(data) +            ssid['mac'] = re.search(re_mac, line).group() + +        elif line.lstrip().startswith('SSID: '): +            # SSID can be "    SSID: WLAN-57 6405", thus strip all leading whitespaces +            ssid['ssid'] = line.lstrip().split(':')[-1].lstrip() + +        elif line.lstrip().startswith('signal: '): +            # Siganl can be "   signal: -67.00 dBm", thus strip all leading whitespaces +            ssid['signal'] = line.lstrip().split(':')[-1].split()[0] + +        elif line.lstrip().startswith('DS Parameter set: channel'): +            # Channel can be "        DS Parameter set: channel 6" , thus +            # strip all leading whitespaces +            ssid['channel'] = line.lstrip().split(':')[-1].split()[-1] +            networks.append(ssid) +            continue + +    return networks + +def _format_scan_data(raw_data): +    output=[] +    for ssid in raw_data: +        output.append([ssid['mac'], ssid['ssid'], ssid['channel'], ssid['signal']]) +    headers = ["Address", "SSID", "Channel", "Signal (dbm)"] +    return tabulate(output, headers, numalign="left") + +def _get_raw_station_data(intf_name): +    # XXX: This ignores errors +    tmp, _ = popen(f'iw dev {intf_name} station dump') +    clients = [] +    data = { +        'mac': '', +        'signal': '', +        'rx_bytes': '', +        'rx_packets': '', +        'tx_bytes': '', +        'tx_packets': '' +    } +    re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') +    for line in tmp.splitlines(): +        if line.startswith('Station'): +            client = deepcopy(data) +            client['mac'] = re.search(re_mac, line).group() + +        elif line.lstrip().startswith('signal avg:'): +            client['signal'] = line.lstrip().split(':')[-1].lstrip().split()[0] + +        elif line.lstrip().startswith('rx bytes:'): +            client['rx_bytes'] = line.lstrip().split(':')[-1].lstrip() + +        elif line.lstrip().startswith('rx packets:'): +            client['rx_packets'] = line.lstrip().split(':')[-1].lstrip() + +        elif line.lstrip().startswith('tx bytes:'): +            client['tx_bytes'] = line.lstrip().split(':')[-1].lstrip() + +        elif line.lstrip().startswith('tx packets:'): +            client['tx_packets'] = line.lstrip().split(':')[-1].lstrip() +            clients.append(client) +            continue + +    return clients + +def _format_station_data(raw_data): +    output=[] +    for ssid in raw_data: +        output.append([ssid['mac'], ssid['signal'], ssid['rx_bytes'], ssid['rx_packets'], ssid['tx_bytes'], ssid['tx_packets']]) +    headers = ["Station", "Signal", "RX bytes", "RX packets", "TX bytes", "TX packets"] +    return tabulate(output, headers, numalign="left") + +@_verify +def show_info(raw: bool): +    info_data = _get_raw_info_data() +    if raw: +        return info_data +    return _get_formatted_info_output(info_data) + +def show_scan(raw: bool, intf_name: str): +    data = _get_raw_scan_data(intf_name) +    if raw: +        return data +    return _format_scan_data(data) + +@_verify +def show_stations(raw: bool, intf_name: str): +    data = _get_raw_station_data(intf_name) +    if raw: +        return data +    return _format_station_data(data) + +if __name__ == '__main__': +    try: +        res = vyos.opmode.run(sys.modules[__name__]) +        if res: +            print(res) +    except (ValueError, vyos.opmode.Error) as e: +        print(e) +        sys.exit(1) diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py index c287b8fa6..58cfce443 100755 --- a/src/op_mode/lldp.py +++ b/src/op_mode/lldp.py @@ -114,7 +114,10 @@ def _get_formatted_output(raw_data):              # Remote software platform              platform = jmespath.search('chassis.[*][0][0].descr', values) -            tmp.append(platform[:37]) +            if platform: +                tmp.append(platform[:37]) +            else: +                tmp.append('')              # Remote interface              interface = jmespath.search('port.descr', values) diff --git a/src/op_mode/show_wireless.py b/src/op_mode/show_wireless.py deleted file mode 100755 index 340163057..000000000 --- a/src/op_mode/show_wireless.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019-2023 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/>. - -import argparse -import re - -from sys import exit -from copy import deepcopy - -from vyos.config import Config -from vyos.utils.process import popen - -parser = argparse.ArgumentParser() -parser.add_argument("-s", "--scan", help="Scan for Wireless APs on given interface, e.g. 'wlan0'") -parser.add_argument("-b", "--brief", action="store_true", help="Show wireless configuration") -parser.add_argument("-c", "--stations", help="Show wireless clients connected on interface, e.g. 'wlan0'") - -def show_brief(): -    config = Config() -    if len(config.list_effective_nodes('interfaces wireless')) == 0: -        print("No Wireless interfaces configured") -        exit(0) - -    interfaces = [] -    for intf in config.list_effective_nodes('interfaces wireless'): -        config.set_level(f'interfaces wireless {intf}') -        data = { 'name': intf } -        data['type'] = config.return_effective_value('type') or '-' -        data['ssid'] = config.return_effective_value('ssid') or '-' -        data['channel'] = config.return_effective_value('channel') or '-' -        interfaces.append(data) - -    return interfaces - -def ssid_scan(intf): -    # XXX: This ignores errors -    tmp, _ = popen(f'/sbin/iw dev {intf} scan ap-force') -    networks = [] -    data = { -        'ssid': '', -        'mac': '', -        'channel': '', -        'signal': '' -    } -    re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') -    for line in tmp.splitlines(): -        if line.startswith('BSS '): -            ssid = deepcopy(data) -            ssid['mac'] = re.search(re_mac, line).group() - -        elif line.lstrip().startswith('SSID: '): -            # SSID can be "    SSID: WLAN-57 6405", thus strip all leading whitespaces -            ssid['ssid'] = line.lstrip().split(':')[-1].lstrip() - -        elif line.lstrip().startswith('signal: '): -            # Siganl can be "   signal: -67.00 dBm", thus strip all leading whitespaces -            ssid['signal'] = line.lstrip().split(':')[-1].split()[0] - -        elif line.lstrip().startswith('DS Parameter set: channel'): -            # Channel can be "        DS Parameter set: channel 6" , thus -            # strip all leading whitespaces -            ssid['channel'] = line.lstrip().split(':')[-1].split()[-1] -            networks.append(ssid) -            continue - -    return networks - -def show_clients(intf): -    # XXX: This ignores errors -    tmp, _ = popen(f'/sbin/iw dev {intf} station dump') -    clients = [] -    data = { -        'mac': '', -        'signal': '', -        'rx_bytes': '', -        'rx_packets': '', -        'tx_bytes': '', -        'tx_packets': '' -    } -    re_mac = re.compile(r'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') -    for line in tmp.splitlines(): -        if line.startswith('Station'): -            client = deepcopy(data) -            client['mac'] = re.search(re_mac, line).group() - -        elif line.lstrip().startswith('signal avg:'): -            client['signal'] = line.lstrip().split(':')[-1].lstrip().split()[0] - -        elif line.lstrip().startswith('rx bytes:'): -            client['rx_bytes'] = line.lstrip().split(':')[-1].lstrip() - -        elif line.lstrip().startswith('rx packets:'): -            client['rx_packets'] = line.lstrip().split(':')[-1].lstrip() - -        elif line.lstrip().startswith('tx bytes:'): -            client['tx_bytes'] = line.lstrip().split(':')[-1].lstrip() - -        elif line.lstrip().startswith('tx packets:'): -            client['tx_packets'] = line.lstrip().split(':')[-1].lstrip() -            clients.append(client) -            continue - -    return clients - -if __name__ == '__main__': -    args = parser.parse_args() - -    if args.scan: -        print("Address            SSID                          Channel  Signal (dbm)") -        for network in ssid_scan(args.scan): -            print("{:<17}  {:<32}  {:>3}  {}".format(network['mac'], -                                                     network['ssid'], -                                                     network['channel'], -                                                     network['signal'])) -        exit(0) - -    elif args.brief: -        print("Interface  Type          SSID                         Channel") -        for intf in show_brief(): -            print("{:<9}  {:<12}  {:<32} {:>3}".format(intf['name'], -                                                      intf['type'], -                                                      intf['ssid'], -                                                      intf['channel'])) -        exit(0) - -    elif args.stations: -        print("Station            Signal     RX: bytes    packets        TX: bytes     packets") -        for client in show_clients(args.stations): -            print("{:<17}  {:>3}  {:>15}  {:>9}  {:>15}  {:>10} ".format(client['mac'], -                                                 client['signal'], client['rx_bytes'], client['rx_packets'], client['tx_bytes'], client['tx_packets'])) - -        exit(0) - -    else: -        parser.print_help() -        exit(1) diff --git a/src/system/uacctd_stop.py b/src/system/uacctd_stop.py index 7fbac0566..a1b57335b 100755 --- a/src/system/uacctd_stop.py +++ b/src/system/uacctd_stop.py @@ -21,7 +21,7 @@  # send some packets to pmacct to wake it up  from argparse import ArgumentParser -from socket import socket +from socket import socket, AF_INET, SOCK_DGRAM  from sys import exit  from time import sleep @@ -42,11 +42,12 @@ def stop_process(pid: int, timeout: int) -> None:      uacctd.terminate()      # create a socket -    trigger = socket() +    trigger = socket(AF_INET, SOCK_DGRAM)      first_cycle: bool = True      while uacctd.is_running() and timeout: -        trigger.sendto(b'WAKEUP', ('127.0.254.0', 0)) +        print('sending a packet to uacctd...') +        trigger.sendto(b'WAKEUP', ('127.0.254.0', 1))          # do not sleep during first attempt          if not first_cycle:              sleep(1) | 
