diff options
Diffstat (limited to 'src')
418 files changed, 4677 insertions, 10113 deletions
diff --git a/src/completion/list_dumpable_interfaces.py b/src/completion/list_dumpable_interfaces.py index 67bf6206b..f9748352f 100755 --- a/src/completion/list_dumpable_interfaces.py +++ b/src/completion/list_dumpable_interfaces.py @@ -4,7 +4,7 @@ import re -from vyos.util import cmd +from vyos.utils.process import cmd if __name__ == '__main__': out = cmd('tcpdump -D').split('\n') diff --git a/src/completion/list_ipoe.py b/src/completion/list_ipoe.py index c386b46a2..5a8f4b0c5 100755 --- a/src/completion/list_ipoe.py +++ b/src/completion/list_ipoe.py @@ -1,7 +1,21 @@ #!/usr/bin/env python3 +# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. import argparse -from vyos.util import popen +from vyos.utils.process import popen if __name__ == '__main__': parser = argparse.ArgumentParser() diff --git a/src/completion/list_ipsec_profile_tunnels.py b/src/completion/list_ipsec_profile_tunnels.py index df6c52f6d..4a917dbd6 100644 --- a/src/completion/list_ipsec_profile_tunnels.py +++ b/src/completion/list_ipsec_profile_tunnels.py @@ -19,7 +19,7 @@ import sys import argparse from vyos.config import Config -from vyos.util import dict_search +from vyos.utils.dict import dict_search def get_tunnels_from_ipsecprofile(profile): config = Config() diff --git a/src/completion/list_openconnect_users.py b/src/completion/list_openconnect_users.py index a266fd893..db2f4b4da 100755 --- a/src/completion/list_openconnect_users.py +++ b/src/completion/list_openconnect_users.py @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from vyos.config import Config -from vyos.util import dict_search +from vyos.utils.dict import dict_search def get_user_from_ocserv(): config = Config() diff --git a/src/completion/list_openvpn_users.py b/src/completion/list_openvpn_users.py index c472dbeab..b2b0149fc 100755 --- a/src/completion/list_openvpn_users.py +++ b/src/completion/list_openvpn_users.py @@ -19,7 +19,7 @@ import sys import argparse from vyos.config import Config -from vyos.util import dict_search +from vyos.utils.dict import dict_search def get_user_from_interface(interface): config = Config() diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py index 7dc5206e0..b141f1141 100755 --- a/src/conf_mode/arp.py +++ b/src/conf_mode/arp.py @@ -18,7 +18,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import node_changed -from vyos.util import call +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py index 39a2971ce..31c552f5a 100755 --- a/src/conf_mode/bcast_relay.py +++ b/src/conf_mode/bcast_relay.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2017-2022 VyOS maintainers and contributors +# Copyright (C) 2017-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 @@ -17,12 +17,14 @@ import os from glob import glob -from netifaces import interfaces +from netifaces import AF_INET from sys import exit from vyos.config import Config -from vyos.util import call +from vyos.configverify import verify_interface_exists from vyos.template import render +from vyos.utils.process import call +from vyos.utils.network import is_afi_configured from vyos import ConfigError from vyos import airbag airbag.enable() @@ -50,18 +52,16 @@ def verify(relay): # we certainly require a UDP port to listen to if 'port' not in config: - raise ConfigError(f'Port number mandatory for udp broadcast relay "{instance}"') + raise ConfigError(f'Port number is mandatory for UDP broadcast relay "{instance}"') - # if only oone interface is given it's a string -> move to list - if isinstance(config.get('interface', []), str): - config['interface'] = [ config['interface'] ] # Relaying data without two interface is kinda senseless ... if len(config.get('interface', [])) < 2: - raise ConfigError('At least two interfaces are required for udp broadcast relay "{instance}"') + raise ConfigError('At least two interfaces are required for UDP broadcast relay "{instance}"') for interface in config.get('interface', []): - if interface not in interfaces(): - raise ConfigError('Interface "{interface}" does not exist!') + verify_interface_exists(interface) + if not is_afi_configured(interface, AF_INET): + raise ConfigError(f'Interface "{interface}" has no IPv4 address configured!') return None diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py index 82289526f..9c43640a9 100755 --- a/src/conf_mode/conntrack.py +++ b/src/conf_mode/conntrack.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -20,15 +20,13 @@ import re from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.firewall import find_nftables_rule from vyos.firewall import remove_nftables_rule -from vyos.util import cmd -from vyos.util import run -from vyos.util import process_named_running -from vyos.util import dict_search +from vyos.utils.process import process_named_running +from vyos.utils.dict import dict_search +from vyos.utils.process import cmd +from vyos.utils.process import run from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -77,16 +75,8 @@ def get_config(config=None): base = ['system', 'conntrack'] conntrack = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'timeout' in default_values and 'custom' in default_values['timeout']: - del default_values['timeout']['custom'] - conntrack = dict_merge(default_values, conntrack) + get_first_key=True, + with_recursive_defaults=True) return conntrack diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py index c4b2bb488..4fb2ce27f 100755 --- a/src/conf_mode/conntrack_sync.py +++ b/src/conf_mode/conntrack_sync.py @@ -18,17 +18,15 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_interface_exists -from vyos.util import call -from vyos.util import dict_search -from vyos.util import process_named_running -from vyos.util import read_file -from vyos.util import run +from vyos.utils.dict import dict_search +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import call +from vyos.utils.process import run from vyos.template import render from vyos.template import get_ipv4 -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -50,11 +48,7 @@ def get_config(config=None): return None conntrack = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - conntrack = dict_merge(default_values, conntrack) + get_first_key=True, with_defaults=True) conntrack['hash_size'] = read_file('/sys/module/nf_conntrack/parameters/hashsize') conntrack['table_size'] = read_file('/proc/sys/net/netfilter/nf_conntrack_max') diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index aceb27fb0..46eb10714 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -28,16 +28,17 @@ from vyos.configdict import node_changed from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.ifconfig import Interface -from vyos.util import call -from vyos.util import cmd -from vyos.util import run -from vyos.util import rc_cmd -from vyos.util import write_file +from vyos.utils.file import write_file +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.utils.process import rc_cmd +from vyos.template import bracketize_ipv6 from vyos.template import inc_ip from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.template import render -from vyos.xml import defaults +from vyos.xml_ref import default_value from vyos import ConfigError from vyos import airbag airbag.enable() @@ -66,58 +67,26 @@ def get_config(config=None): base = ['container'] container = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - # container base default values can not be merged here - remove and add them later - if 'name' in default_values: - del default_values['name'] - # registry will be handled below - if 'registry' in default_values: - del default_values['registry'] - container = dict_merge(default_values, container) - - # Merge per-container default values - if 'name' in container: - default_values = defaults(base + ['name']) - if 'port' in default_values: - del default_values['port'] - if 'volume' in default_values: - del default_values['volume'] - for name in container['name']: - container['name'][name] = dict_merge(default_values, container['name'][name]) - - # T5047: Any container related configuration changed? We only - # wan't to restart the required containers and not all of them ... - tmp = is_node_changed(conf, base + ['name', name]) - if tmp: - if 'container_restart' not in container: - container['container_restart'] = [name] - else: - container['container_restart'].append(name) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'port' in container['name'][name]: - for port in container['name'][name]['port']: - default_values_port = defaults(base + ['name', 'port']) - container['name'][name]['port'][port] = dict_merge( - default_values_port, container['name'][name]['port'][port]) - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'volume' in container['name'][name]: - for volume in container['name'][name]['volume']: - default_values_volume = defaults(base + ['name', 'volume']) - container['name'][name]['volume'][volume] = dict_merge( - default_values_volume, container['name'][name]['volume'][volume]) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + for name in container.get('name', []): + # T5047: Any container related configuration changed? We only + # wan't to restart the required containers and not all of them ... + tmp = is_node_changed(conf, base + ['name', name]) + if tmp: + if 'container_restart' not in container: + container['container_restart'] = [name] + else: + container['container_restart'].append(name) # registry is a tagNode with default values - merge the list from # default_values['registry'] into the tagNode variables if 'registry' not in container: container.update({'registry' : {}}) - default_values = defaults(base) - for registry in default_values['registry'].split(): + default_values = default_value(base + ['registry']) + for registry in default_values: tmp = {registry : {}} container['registry'] = dict_merge(tmp, container['registry']) @@ -209,6 +178,11 @@ def verify(container): if 'value' not in cfg: raise ConfigError(f'Environment variable {var} has no value assigned!') + if 'label' in container_config: + for var, cfg in container_config['label'].items(): + if 'value' not in cfg: + raise ConfigError(f'Label variable {var} has no value assigned!') + if 'volume' in container_config: for volume, volume_config in container_config['volume'].items(): if 'source' not in volume_config: @@ -299,6 +273,12 @@ def generate_run_arguments(name, container_config): for k, v in container_config['environment'].items(): env_opt += f" --env \"{k}={v['value']}\"" + # Check/set label options "--label foo=bar" + env_opt = '' + if 'label' in container_config: + for k, v in container_config['label'].items(): + env_opt += f" --label \"{k}={v['value']}\"" + hostname = '' if 'host_name' in container_config: hostname = container_config['host_name'] @@ -312,7 +292,15 @@ def generate_run_arguments(name, container_config): protocol = container_config['port'][portmap]['protocol'] sport = container_config['port'][portmap]['source'] dport = container_config['port'][portmap]['destination'] - port += f' --publish {sport}:{dport}/{protocol}' + listen_addresses = container_config['port'][portmap].get('listen_address', []) + + # If listen_addresses is not empty, include them in the publish command + if listen_addresses: + for listen_address in listen_addresses: + port += f' --publish {bracketize_ipv6(listen_address)}:{sport}:{dport}/{protocol}' + else: + # If listen_addresses is empty, just include the standard publish command + port += f' --publish {sport}:{dport}/{protocol}' # Bind volume volume = '' @@ -321,7 +309,8 @@ def generate_run_arguments(name, container_config): svol = vol_config['source'] dvol = vol_config['destination'] mode = vol_config['mode'] - volume += f' --volume {svol}:{dvol}:{mode}' + prop = vol_config['propagation'] + volume += f' --volume {svol}:{dvol}:{mode},{prop}' container_base_cmd = f'--detach --interactive --tty --replace {cap_add} ' \ f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \ @@ -485,6 +474,7 @@ def apply(container): # it to a VRF as there's no consumer, yet. if os.path.exists(f'/sys/class/net/{network_name}'): tmp = Interface(network_name) + tmp.add_ipv6_eui64_address('fe80::/64') tmp.set_vrf(network_config.get('vrf', '')) return None diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index 7e702a446..37d708847 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -20,12 +20,10 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.base import Warning -from vyos.util import call -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() @@ -41,17 +39,15 @@ def get_config(config=None): if not conf.exists(base): return None - relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - relay = dict_merge(default_values, relay) + relay = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return relay def verify(relay): # bail out early - looks like removal from running config - if not relay: + if not relay or 'disable' in relay: return None if 'lo' in (dict_search('interface', relay) or []): @@ -78,7 +74,7 @@ def verify(relay): def generate(relay): # bail out early - looks like removal from running config - if not relay: + if not relay or 'disable' in relay: return None render(config_file, 'dhcp-relay/dhcrelay.conf.j2', relay) @@ -87,7 +83,7 @@ def generate(relay): def apply(relay): # bail out early - looks like removal from running config service_name = 'isc-dhcp-relay.service' - if not relay: + if not relay or 'disable' in relay: call(f'systemctl stop {service_name}') if os.path.exists(config_file): os.unlink(config_file) diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 2b2af252d..c4c72aae9 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -23,14 +23,12 @@ from netaddr import IPRange from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render -from vyos.util import call -from vyos.util import dict_search -from vyos.util import run -from vyos.validate import is_subnet_connected -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.dict import dict_search +from vyos.utils.process import call +from vyos.utils.process import run +from vyos.utils.network import is_subnet_connected +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -109,19 +107,15 @@ def get_config(config=None): if not conf.exists(base): return None - dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - # T2665: defaults include lease time per TAG node which need to be added to - # individual subnet definitions - default_values = defaults(base + ['shared-network-name', 'subnet']) + dhcp = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) if 'shared_network_name' in dhcp: for network, network_config in dhcp['shared_network_name'].items(): if 'subnet' in network_config: for subnet, subnet_config in network_config['subnet'].items(): - if 'lease' not in subnet_config: - dhcp['shared_network_name'][network]['subnet'][subnet] = dict_merge( - default_values, dhcp['shared_network_name'][network]['subnet'][subnet]) - # If exclude IP addresses are defined we need to slice them out of # the defined ranges if {'exclude', 'range'} <= set(subnet_config): @@ -302,6 +296,10 @@ def generate(dhcp): render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp, formater=lambda _: _.replace(""", '"')) + # Clean up configuration test file + if os.path.exists(tmp_file): + os.unlink(tmp_file) + return None def apply(dhcp): diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index c1bd51f62..6537ca3c2 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -19,13 +19,11 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.ifconfig import Interface from vyos.template import render -from vyos.util import call -from vyos.util import dict_search -from vyos.validate import is_ipv6_link_local -from vyos.xml import defaults +from vyos.template import is_ipv6 +from vyos.utils.process import call +from vyos.utils.network import is_ipv6_link_local from vyos import ConfigError from vyos import airbag airbag.enable() @@ -41,17 +39,15 @@ def get_config(config=None): if not conf.exists(base): return None - relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - relay = dict_merge(default_values, relay) + relay = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return relay def verify(relay): # bail out early - looks like removal from running config - if not relay: + if not relay or 'disable' in relay: return None if 'upstream_interface' not in relay: @@ -69,7 +65,7 @@ def verify(relay): for interface in relay['listen_interface']: has_global = False for addr in Interface(interface).get_addr(): - if not is_ipv6_link_local(addr): + if is_ipv6(addr) and not is_ipv6_link_local(addr): has_global = True if not has_global: raise ConfigError(f'Interface {interface} does not have global '\ @@ -79,7 +75,7 @@ def verify(relay): def generate(relay): # bail out early - looks like removal from running config - if not relay: + if not relay or 'disable' in relay: return None render(config_file, 'dhcp-relay/dhcrelay6.conf.j2', relay) @@ -88,7 +84,7 @@ def generate(relay): def apply(relay): # bail out early - looks like removal from running config service_name = 'isc-dhcp-relay6.service' - if not relay: + if not relay or 'disable' in relay: # DHCPv6 relay support is removed in the commit call(f'systemctl stop {service_name}') if os.path.exists(config_file): diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 078ff327c..427001609 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -23,9 +23,9 @@ from sys import exit from vyos.config import Config from vyos.template import render from vyos.template import is_ipv6 -from vyos.util import call -from vyos.util import dict_search -from vyos.validate import is_subnet_connected +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.network import is_subnet_connected from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py new file mode 100755 index 000000000..ab80defe8 --- /dev/null +++ b/src/conf_mode/dns_dynamic.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-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 os + +from sys import exit + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/run/ddclient/ddclient.conf' +systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf' + +# Protocols that require zone +zone_allowed = ['cloudflare', 'godaddy', 'hetzner', 'gandi', 'nfsn'] + +# Protocols that do not require username +username_unnecessary = ['1984', 'cloudflare', 'cloudns', 'duckdns', 'freemyip', 'hetzner', 'keysystems', 'njalla'] + +# Protocols that support both IPv4 and IPv6 +dualstack_supported = ['cloudflare', 'dyndns2', 'freedns', 'njalla'] + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base_level = ['service', 'dns', 'dynamic'] + if not conf.exists(base_level): + return None + + dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + dyndns['config_file'] = config_file + return dyndns + +def verify(dyndns): + # bail out early - looks like removal from running config + if not dyndns or 'address' not in dyndns: + return None + + for address in dyndns['address']: + # RFC2136 - configuration validation + if 'rfc2136' in dyndns['address'][address]: + for config in dyndns['address'][address]['rfc2136'].values(): + for field in ['host_name', 'zone', 'server', 'key']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" is required for RFC2136 ' + f'based Dynamic DNS service on "{address}"') + + # Dynamic DNS service provider - configuration validation + if 'service' in dyndns['address'][address]: + for service, config in dyndns['address'][address]['service'].items(): + error_msg = f'is required for Dynamic DNS service "{service}" on "{address}"' + + for field in ['host_name', 'password', 'protocol']: + if field not in config: + raise ConfigError(f'"{field.replace("_", "-")}" {error_msg}') + + if config['protocol'] in zone_allowed and 'zone' not in config: + raise ConfigError(f'"zone" {error_msg}') + + if config['protocol'] not in zone_allowed and 'zone' in config: + raise ConfigError(f'"{config["protocol"]}" does not support "zone"') + + if config['protocol'] not in username_unnecessary: + if 'username' not in config: + raise ConfigError(f'"username" {error_msg}') + + if config['ip_version'] == 'both': + if config['protocol'] not in dualstack_supported: + raise ConfigError(f'"{config["protocol"]}" does not support ' + f'both IPv4 and IPv6 at the same time') + # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org) + if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] != 'members.dyndns.org': + raise ConfigError(f'"{config["protocol"]}" does not support ' + f'both IPv4 and IPv6 at the same time for "{config["server"]}"') + + return None + +def generate(dyndns): + # bail out early - looks like removal from running config + if not dyndns or 'address' not in dyndns: + return None + + render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns) + render(systemd_override, 'dns-dynamic/override.conf.j2', dyndns) + return None + +def apply(dyndns): + systemd_service = 'ddclient.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + + # bail out early - looks like removal from running config + if not dyndns or 'address' not in dyndns: + call(f'systemctl stop {systemd_service}') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call(f'systemctl reload-or-restart {systemd_service}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 0d86c6a52..c186f47af 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -21,14 +21,12 @@ from sys import exit from glob import glob from vyos.config import Config -from vyos.configdict import dict_merge from vyos.hostsd_client import Client as hostsd_client from vyos.template import render from vyos.template import bracketize_ipv6 -from vyos.util import call -from vyos.util import chown -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag @@ -52,31 +50,10 @@ def get_config(config=None): if not conf.exists(base): return None - dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrieved. - default_values = defaults(base) - # T2665 due to how defaults under tag nodes work, we must clear these out before we merge - del default_values['authoritative_domain'] - del default_values['name_server'] - del default_values['domain']['name_server'] - dns = dict_merge(default_values, dns) - - # T2665: we cleared default values for tag node 'name_server' above. - # We now need to add them back back in a granular way. - if 'name_server' in dns: - default_values = defaults(base + ['name-server']) - for server in dns['name_server']: - dns['name_server'][server] = dict_merge(default_values, dns['name_server'][server]) - - # T2665: we cleared default values for tag node 'domain' above. - # We now need to add them back back in a granular way. - if 'domain' in dns: - default_values = defaults(base + ['domain', 'name-server']) - for domain in dns['domain'].keys(): - for server in dns['domain'][domain]['name_server']: - dns['domain'][domain]['name_server'][server] = dict_merge( - default_values, dns['domain'][domain]['name_server'][server]) + dns = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) # some additions to the default dictionary if 'system' in dns: @@ -109,9 +86,6 @@ def get_config(config=None): rdata = recorddata[rtype][subnode] if rtype in [ 'a', 'aaaa' ]: - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - rdata = dict_merge(rdefaults, rdata) - if not 'address' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one address is required') continue @@ -127,9 +101,6 @@ def get_config(config=None): 'value': address }) elif rtype in ['cname', 'ptr', 'ns']: - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - rdata = dict_merge(rdefaults, rdata) - if not 'target' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: target is required') continue @@ -141,18 +112,12 @@ def get_config(config=None): 'value': '{}.'.format(rdata['target']) }) elif rtype == 'mx': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - del rdefaults['server'] - rdata = dict_merge(rdefaults, rdata) - if not 'server' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one server is required') continue for servername in rdata['server']: serverdata = rdata['server'][servername] - serverdefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'server']) # T2665 - serverdata = dict_merge(serverdefaults, serverdata) zone['records'].append({ 'name': subnode, 'type': rtype.upper(), @@ -160,9 +125,6 @@ def get_config(config=None): 'value': '{} {}.'.format(serverdata['priority'], servername) }) elif rtype == 'txt': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - rdata = dict_merge(rdefaults, rdata) - if not 'value' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one value is required') continue @@ -175,9 +137,6 @@ def get_config(config=None): 'value': "\"{}\"".format(value.replace("\"", "\\\"")) }) elif rtype == 'spf': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - rdata = dict_merge(rdefaults, rdata) - if not 'value' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: value is required') continue @@ -189,19 +148,12 @@ def get_config(config=None): 'value': '"{}"'.format(rdata['value'].replace("\"", "\\\"")) }) elif rtype == 'srv': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - del rdefaults['entry'] - rdata = dict_merge(rdefaults, rdata) - if not 'entry' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one entry is required') continue for entryno in rdata['entry']: entrydata = rdata['entry'][entryno] - entrydefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'entry']) # T2665 - entrydata = dict_merge(entrydefaults, entrydata) - if not 'hostname' in entrydata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: hostname is required for entry {entryno}') continue @@ -217,19 +169,12 @@ def get_config(config=None): 'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname']) }) elif rtype == 'naptr': - rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 - del rdefaults['rule'] - rdata = dict_merge(rdefaults, rdata) - - if not 'rule' in rdata: dns['authoritative_zone_errors'].append(f'{subnode}.{node}: at least one rule is required') continue for ruleno in rdata['rule']: ruledata = rdata['rule'][ruleno] - ruledefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'rule']) # T2665 - ruledata = dict_merge(ruledefaults, ruledata) flags = "" if 'lookup-srv' in ruledata: flags += "S" diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py deleted file mode 100755 index 426e3d693..000000000 --- a/src/conf_mode/dynamic_dns.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2020 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 os - -from sys import exit - -from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.template import render -from vyos.util import call -from vyos.xml import defaults -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/run/ddclient/ddclient.conf' - -# Mapping of service name to service protocol -default_service_protocol = { - 'afraid': 'freedns', - 'changeip': 'changeip', - 'cloudflare': 'cloudflare', - 'dnspark': 'dnspark', - 'dslreports': 'dslreports1', - 'dyndns': 'dyndns2', - 'easydns': 'easydns', - 'namecheap': 'namecheap', - 'noip': 'noip', - 'sitelutions': 'sitelutions', - 'zoneedit': 'zoneedit1' -} - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - base_level = ['service', 'dns', 'dynamic'] - if not conf.exists(base_level): - return None - - dyndns = conf.get_config_dict(base_level, key_mangling=('-', '_'), get_first_key=True) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - for interface in dyndns['interface']: - if 'service' in dyndns['interface'][interface]: - # 'Autodetect' protocol used by DynDNS service - for service in dyndns['interface'][interface]['service']: - if service in default_service_protocol: - dyndns['interface'][interface]['service'][service].update( - {'protocol' : default_service_protocol.get(service)}) - else: - dyndns['interface'][interface]['service'][service].update( - {'custom': ''}) - - if 'rfc2136' in dyndns['interface'][interface]: - default_values = defaults(base_level + ['interface', 'rfc2136']) - for rfc2136 in dyndns['interface'][interface]['rfc2136']: - dyndns['interface'][interface]['rfc2136'][rfc2136] = dict_merge( - default_values, dyndns['interface'][interface]['rfc2136'][rfc2136]) - - return dyndns - -def verify(dyndns): - # bail out early - looks like removal from running config - if not dyndns: - return None - - # A 'node' corresponds to an interface - if 'interface' not in dyndns: - return None - - for interface in dyndns['interface']: - # RFC2136 - configuration validation - if 'rfc2136' in dyndns['interface'][interface]: - for rfc2136, config in dyndns['interface'][interface]['rfc2136'].items(): - - for tmp in ['record', 'zone', 'server', 'key']: - if tmp not in config: - raise ConfigError(f'"{tmp}" required for rfc2136 based ' - f'DynDNS service on "{interface}"') - - if not os.path.isfile(config['key']): - raise ConfigError(f'"key"-file not found for rfc2136 based ' - f'DynDNS service on "{interface}"') - - # DynDNS service provider - configuration validation - if 'service' in dyndns['interface'][interface]: - for service, config in dyndns['interface'][interface]['service'].items(): - error_msg = f'required for DynDNS service "{service}" on "{interface}"' - if 'host_name' not in config: - raise ConfigError(f'"host-name" {error_msg}') - - if 'login' not in config: - if service != 'cloudflare' and ('protocol' not in config or config['protocol'] != 'cloudflare'): - raise ConfigError(f'"login" (username) {error_msg}, unless using CloudFlare') - - if 'password' not in config: - raise ConfigError(f'"password" {error_msg}') - - if 'zone' in config: - if service != 'cloudflare' and ('protocol' not in config or config['protocol'] != 'cloudflare'): - raise ConfigError(f'"zone" option only supported with CloudFlare') - - if 'custom' in config: - if 'protocol' not in config: - raise ConfigError(f'"protocol" {error_msg}') - - if 'server' not in config: - raise ConfigError(f'"server" {error_msg}') - - return None - -def generate(dyndns): - # bail out early - looks like removal from running config - if not dyndns: - return None - - render(config_file, 'dynamic-dns/ddclient.conf.j2', dyndns) - return None - -def apply(dyndns): - if not dyndns: - call('systemctl stop ddclient.service') - if os.path.exists(config_file): - os.unlink(config_file) - else: - call('systemctl restart ddclient.service') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 190587980..c3b1ee015 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -23,7 +23,6 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configdiff import get_config_diff, Diff from vyos.configdep import set_dependents, call_dependents @@ -31,13 +30,12 @@ from vyos.configdep import set_dependents, call_dependents from vyos.firewall import fqdn_config_parse from vyos.firewall import geoip_update from vyos.template import render -from vyos.util import call -from vyos.util import cmd -from vyos.util import dict_search_args -from vyos.util import dict_search_recursive -from vyos.util import process_named_running -from vyos.util import rc_cmd -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search_recursive +from vyos.utils.process import process_named_running +from vyos.utils.process import rc_cmd from vyos import ConfigError from vyos import airbag airbag.enable() @@ -56,7 +54,6 @@ sysfs_config = { 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians'}, 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects'}, 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects'}, - 'source_validation': {'sysfs': '/proc/sys/net/ipv4/conf/*/rp_filter', 'disable': '0', 'strict': '1', 'loose': '2'}, 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies'}, 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'} } @@ -97,19 +94,22 @@ def geoip_updated(conf, firewall): updated = False for key, path in dict_search_recursive(firewall, 'geoip'): - set_name = f'GEOIP_CC_{path[1]}_{path[3]}' - if path[0] == 'name': + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' + if (path[0] == 'ipv4'): out['name'].append(set_name) - elif path[0] == 'ipv6_name': + elif (path[0] == 'ipv6'): + set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}' out['ipv6_name'].append(set_name) + updated = True if 'delete' in node_diff: for key, path in dict_search_recursive(node_diff['delete'], 'geoip'): - set_name = f'GEOIP_CC_{path[1]}_{path[3]}' - if path[0] == 'name': + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' + if (path[0] == 'ipv4'): out['deleted_name'].append(set_name) - elif path[0] == 'ipv6-name': + elif (path[0] == 'ipv6'): + set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}' out['deleted_ipv6_name'].append(set_name) updated = True @@ -125,54 +125,17 @@ def get_config(config=None): conf = Config() base = ['firewall'] - firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary retrived. - # XXX: T2665: we currently have no nice way for defaults under tag - # nodes, thus we load the defaults "by hand" - default_values = defaults(base) - for tmp in ['name', 'ipv6_name']: - if tmp in default_values: - del default_values[tmp] - - if 'zone' in default_values: - del default_values['zone'] - - firewall = dict_merge(default_values, firewall) - - # Merge in defaults for IPv4 ruleset - if 'name' in firewall: - default_values = defaults(base + ['name']) - for name in firewall['name']: - firewall['name'][name] = dict_merge(default_values, - firewall['name'][name]) - - # Merge in defaults for IPv6 ruleset - if 'ipv6_name' in firewall: - default_values = defaults(base + ['ipv6-name']) - for ipv6_name in firewall['ipv6_name']: - firewall['ipv6_name'][ipv6_name] = dict_merge(default_values, - firewall['ipv6_name'][ipv6_name]) - - if 'zone' in firewall: - default_values = defaults(base + ['zone']) - for zone in firewall['zone']: - firewall['zone'][zone] = dict_merge(default_values, firewall['zone'][zone]) + firewall = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + firewall['group_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) if firewall['group_resync']: # Update nat and policy-route as firewall groups were updated set_dependents('group_resync', conf) - if 'config_trap' in firewall and firewall['config_trap'] == 'enable': - diff = get_config_diff(conf) - firewall['trap_diff'] = diff.get_child_nodes_diff_str(base) - firewall['trap_targets'] = conf.get_config_dict(['service', 'snmp', 'trap-target'], - key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - firewall['geoip_updated'] = geoip_updated(conf, firewall) fqdn_config_parse(firewall) @@ -191,11 +154,11 @@ def verify_rule(firewall, rule_conf, ipv6): raise ConfigError('jump-target defined, but action jump needed and it is not defined') target = rule_conf['jump_target'] if not ipv6: - if target not in dict_search_args(firewall, 'name'): + if target not in dict_search_args(firewall, 'ipv4', 'name'): raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') else: - if target not in dict_search_args(firewall, 'ipv6_name'): - raise ConfigError(f'Invalid jump-target. Firewall ipv6-name {target} does not exist on the system') + if target not in dict_search_args(firewall, 'ipv6', 'name'): + raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system') if 'queue_options' in rule_conf: if 'queue' not in rule_conf['action']: @@ -295,6 +258,11 @@ def verify_rule(firewall, rule_conf, ipv6): if 'queue_threshold' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']: raise ConfigError('log-options queue-threshold defined, but log group is not define') + 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}') + def verify_nested_group(group_name, group, groups, seen): if 'include' not in group: return @@ -312,10 +280,6 @@ def verify_nested_group(group_name, group, groups, seen): verify_nested_group(g, groups[g], groups, seen) def verify(firewall): - if 'config_trap' in firewall and firewall['config_trap'] == 'enable': - if not firewall['trap_targets']: - raise ConfigError(f'Firewall config-trap enabled but "service snmp trap-target" is not defined') - if 'group' in firewall: for group_type in nested_group_types: if group_type in firewall['group']: @@ -323,95 +287,45 @@ def verify(firewall): for group_name, group in groups.items(): verify_nested_group(group_name, group, groups, []) - for name in ['name', 'ipv6_name']: - if name in firewall: - for name_id, name_conf in firewall[name].items(): - if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: - raise ConfigError('default-action set to jump, but no default-jump-target specified') - if 'default_jump_target' in name_conf: - target = name_conf['default_jump_target'] - if 'jump' not in name_conf['default_action']: - raise ConfigError('default-jump-target defined,but default-action jump needed and it is not defined') - if name_conf['default_jump_target'] == name_id: - raise ConfigError(f'Loop detected on default-jump-target.') - ## Now need to check that default-jump-target exists (other firewall chain/name) - if target not in dict_search_args(firewall, name): - raise ConfigError(f'Invalid jump-target. Firewall {name} {target} does not exist on the system') - - if 'rule' in name_conf: - for rule_id, rule_conf in name_conf['rule'].items(): - verify_rule(firewall, rule_conf, name == 'ipv6_name') - - if 'interface' in firewall: - for ifname, if_firewall in firewall['interface'].items(): - # verify ifname needs to be disabled, dynamic devices come up later - # verify_interface_exists(ifname) - - for direction in ['in', 'out', 'local']: - name = dict_search_args(if_firewall, direction, 'name') - ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') - - if name and dict_search_args(firewall, 'name', name) == None: - raise ConfigError(f'Invalid firewall name "{name}" referenced on interface {ifname}') - - if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None: - raise ConfigError(f'Invalid firewall ipv6-name "{ipv6_name}" referenced on interface {ifname}') - - 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, '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, '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') + if 'ipv4' in firewall: + for name in ['name','forward','input','output']: + if name in firewall['ipv4']: + for name_id, name_conf in firewall['ipv4'][name].items(): + if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: + raise ConfigError('default-action set to jump, but no default-jump-target specified') + if 'default_jump_target' in name_conf: + target = name_conf['default_jump_target'] + if 'jump' not in name_conf['default_action']: + raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') + if name_conf['default_jump_target'] == name_id: + raise ConfigError(f'Loop detected on default-jump-target.') + ## Now need to check that default-jump-target exists (other firewall chain/name) + if target not in dict_search_args(firewall['ipv4'], 'name'): + raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + + if 'rule' in name_conf: + for rule_id, rule_conf in name_conf['rule'].items(): + verify_rule(firewall, rule_conf, False) + + if 'ipv6' in firewall: + for name in ['name','forward','input','output']: + if name in firewall['ipv6']: + for name_id, name_conf in firewall['ipv6'][name].items(): + if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: + raise ConfigError('default-action set to jump, but no default-jump-target specified') + if 'default_jump_target' in name_conf: + target = name_conf['default_jump_target'] + if 'jump' not in name_conf['default_action']: + raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') + if name_conf['default_jump_target'] == name_id: + raise ConfigError(f'Loop detected on default-jump-target.') + ## Now need to check that default-jump-target exists (other firewall chain/name) + if target not in dict_search_args(firewall['ipv6'], 'name'): + raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + + if 'rule' in name_conf: + for rule_id, rule_conf in name_conf['rule'].items(): + verify_rule(firewall, rule_conf, True) return None @@ -419,18 +333,16 @@ 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 + # Determine if conntrack is needed + firewall['ipv4_conntrack_action'] = 'return' + firewall['ipv6_conntrack_action'] = 'return' - 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] + for rules, path in dict_search_recursive(firewall, 'rule'): + if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()): + if path[0] == 'ipv4': + firewall['ipv4_conntrack_action'] = 'accept' + elif path[0] == 'ipv6': + firewall['ipv6_conntrack_action'] = 'accept' render(nftables_conf, 'firewall/nftables.j2', firewall) return None @@ -440,9 +352,8 @@ def apply_sysfs(firewall): paths = glob(conf['sysfs']) value = None - if name in firewall: - conf_value = firewall[name] - + if name in firewall['global_options']: + conf_value = firewall['global_options'][name] if conf_value in conf: value = conf[conf_value] elif conf_value == 'enable': @@ -455,42 +366,6 @@ def apply_sysfs(firewall): with open(path, 'w') as f: f.write(value) -def post_apply_trap(firewall): - if 'first_install' in firewall: - return None - - if 'config_trap' not in firewall or firewall['config_trap'] != 'enable': - return None - - if not process_named_running('snmpd'): - return None - - trap_username = os.getlogin() - - for host, target_conf in firewall['trap_targets'].items(): - community = target_conf['community'] if 'community' in target_conf else 'public' - port = int(target_conf['port']) if 'port' in target_conf else 162 - - base_cmd = f'snmptrap -v2c -c {community} {host}:{port} 0 {snmp_trap_mib}::{snmp_trap_name} ' - - for change_type, changes in firewall['trap_diff'].items(): - for path_str, value in changes.items(): - objects = [ - f'mgmtEventUser s "{trap_username}"', - f'mgmtEventSource i {snmp_event_source}', - f'mgmtEventType i {snmp_change_type[change_type]}' - ] - - if change_type == 'add': - objects.append(f'mgmtEventCurrCfg s "{path_str} {value}"') - elif change_type == 'delete': - objects.append(f'mgmtEventPrevCfg s "{path_str} {value}"') - elif change_type == 'change': - objects.append(f'mgmtEventPrevCfg s "{path_str} {value[0]}"') - objects.append(f'mgmtEventCurrCfg s "{path_str} {value[1]}"') - - cmd(base_cmd + ' '.join(objects)) - def apply(firewall): install_result, output = rc_cmd(f'nft -f {nftables_conf}') if install_result == 1: @@ -515,8 +390,6 @@ def apply(firewall): print('Updating GeoIP. Please wait...') geoip_update(firewall) - post_apply_trap(firewall) - return None if __name__ == '__main__': diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index f67f1710e..71acd69fa 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -18,20 +18,17 @@ import os import re from sys import exit -import ipaddress - from ipaddress import ip_address from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.config import config_dict_merge +from vyos.configverify import verify_vrf from vyos.ifconfig import Section -from vyos.ifconfig import Interface from vyos.template import render -from vyos.util import call -from vyos.util import cmd -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -130,30 +127,19 @@ def get_config(config=None): flow_accounting = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) + # We have gathered the dict representation of the CLI, but there are + # default values which we need to conditionally update into the + # dictionary retrieved. + default_values = conf.get_config_defaults(**flow_accounting.kwargs, + recursive=True) - # delete individual flow type default - should only be added if user uses - # this feature + # delete individual flow type defaults - should only be added if user + # sets this feature for flow_type in ['sflow', 'netflow']: - if flow_type in default_values: + if flow_type not in flow_accounting and flow_type in default_values: del default_values[flow_type] - flow_accounting = dict_merge(default_values, flow_accounting) - for flow_type in ['sflow', 'netflow']: - if flow_type in flow_accounting: - default_values = defaults(base + [flow_type]) - # we need to merge individual server configurations - if 'server' in default_values: - del default_values['server'] - flow_accounting[flow_type] = dict_merge(default_values, flow_accounting[flow_type]) - - if 'server' in flow_accounting[flow_type]: - default_values = defaults(base + [flow_type, 'server']) - for server in flow_accounting[flow_type]['server']: - flow_accounting[flow_type]['server'][server] = dict_merge( - default_values,flow_accounting[flow_type]['server'][server]) + flow_accounting = config_dict_merge(default_values, flow_accounting) return flow_accounting @@ -194,6 +180,7 @@ def verify(flow_config): sflow_collector_ipver = ip_address(server).version # check if vrf is defined for Sflow + verify_vrf(flow_config) sflow_vrf = None if 'vrf' in flow_config: sflow_vrf = flow_config['vrf'] @@ -211,7 +198,7 @@ def verify(flow_config): if not is_addr_assigned(tmp, sflow_vrf): raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!') - # Check if configured netflow source-address exist in the system + # Check if configured sflow source-address exist in the system if 'source_address' in flow_config['sflow']: if not is_addr_assigned(flow_config['sflow']['source_address'], sflow_vrf): tmp = flow_config['sflow']['source_address'] @@ -219,13 +206,18 @@ def verify(flow_config): # check NetFlow configuration if 'netflow' in flow_config: + # check if vrf is defined for netflow + netflow_vrf = None + if 'vrf' in flow_config: + netflow_vrf = flow_config['vrf'] + # check if at least one NetFlow collector is configured if NetFlow configuration is presented if 'server' not in flow_config['netflow']: raise ConfigError('You need to configure at least one NetFlow server!') # Check if configured netflow source-address exist in the system if 'source_address' in flow_config['netflow']: - if not is_addr_assigned(flow_config['netflow']['source_address']): + if not is_addr_assigned(flow_config['netflow']['source_address'], netflow_vrf): tmp = flow_config['netflow']['source_address'] raise ConfigError(f'Configured "netflow source-address {tmp}" does not exist on the system!') diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index e18b426b1..626a3757e 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -14,7 +14,6 @@ # 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 os from sys import exit from ipaddress import ip_interface @@ -23,14 +22,11 @@ from ipaddress import IPv6Interface from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.ifconfig.vrrp import VRRP from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 -from vyos.util import call -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,42 +38,12 @@ def get_config(config=None): conf = Config() base = ['high-availability'] - base_vrrp = ['high-availability', 'vrrp'] if not conf.exists(base): return None ha = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - if 'vrrp' in ha: - if dict_search('vrrp.global_parameters.garp', ha) != None: - default_values = defaults(base_vrrp + ['global-parameters', 'garp']) - ha['vrrp']['global_parameters']['garp'] = dict_merge( - default_values, ha['vrrp']['global_parameters']['garp']) - - if 'group' in ha['vrrp']: - default_values = defaults(base_vrrp + ['group']) - default_values_garp = defaults(base_vrrp + ['group', 'garp']) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'garp' in default_values: - del default_values['garp'] - for group in ha['vrrp']['group']: - ha['vrrp']['group'][group] = dict_merge(default_values, ha['vrrp']['group'][group]) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'garp' in ha['vrrp']['group'][group]: - ha['vrrp']['group'][group]['garp'] = dict_merge( - default_values_garp, ha['vrrp']['group'][group]['garp']) - - # Merge per virtual-server default values - if 'virtual_server' in ha: - default_values = defaults(base + ['virtual-server']) - for vs in ha['virtual_server']: - ha['virtual_server'][vs] = dict_merge(default_values, ha['virtual_server'][vs]) + no_tag_node_value_mangle=True, + get_first_key=True, with_defaults=True) ## Get the sync group used for conntrack-sync conntrack_path = ['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'] @@ -112,7 +78,7 @@ def verify(ha): from vyos.utils.dict import check_mutually_exclusive_options try: check_mutually_exclusive_options(group_config["health_check"], health_check_types, required=True) - except ValueError as e: + except ValueError: Warning(f'Health check configuration for VRRP group "{group}" will remain unused ' \ f'until it has one of the following options: {health_check_types}') # XXX: health check has default options so we need to remove it @@ -175,6 +141,11 @@ def verify(ha): # Virtual-server if 'virtual_server' in ha: for vs, vs_config in ha['virtual_server'].items(): + + if 'address' not in vs_config and 'fwmark' not in vs_config: + raise ConfigError('Either address or fwmark is required ' + f'but not set for virtual-server "{vs}"') + if 'port' not in vs_config and 'fwmark' not in vs_config: raise ConfigError(f'Port or fwmark is required but not set for virtual-server "{vs}"') if 'port' in vs_config and 'fwmark' in vs_config: diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 93f244f42..36d1f6493 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2021 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -18,16 +18,15 @@ import re import sys import copy -import vyos.util import vyos.hostsd_client from vyos.base import Warning from vyos.config import Config from vyos.ifconfig import Section from vyos.template import is_ip -from vyos.util import cmd -from vyos.util import call -from vyos.util import process_named_running +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import process_named_running from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index 7e801eb26..793a90d88 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -24,12 +24,9 @@ from copy import deepcopy import vyos.defaults from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdep import set_dependents, call_dependents from vyos.template import render -from vyos.util import cmd -from vyos.util import call -from vyos.xml import defaults +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() @@ -72,8 +69,9 @@ def get_config(config=None): return None api_dict = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) # One needs to 'flatten' the keys dict from the config into the # http-api.conf format for api_keys: @@ -93,8 +91,8 @@ def get_config(config=None): if 'api_keys' in api_dict: keys_added = True - if 'graphql' in api_dict: - api_dict = dict_merge(defaults(base), api_dict) + if api_dict.from_defaults(['graphql']): + del api_dict['graphql'] http_api.update(api_dict) diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index b0c38e8d3..010490c7e 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -28,10 +28,10 @@ from vyos import ConfigError from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render -from vyos.util import call -from vyos.util import check_port_availability -from vyos.util import is_listen_port_bind_service -from vyos.util import write_file +from vyos.utils.process import call +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_listen_port_bind_service +from vyos.utils.file import write_file from vyos import airbag airbag.enable() diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index de6a51c64..40db417dd 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -21,11 +21,9 @@ from netifaces import interfaces from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render -from vyos.util import call -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() @@ -39,16 +37,9 @@ def get_config(config=None): conf = Config() base = ['protocols', 'igmp-proxy'] - igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - if 'interface' in igmp_proxy: - # T2665: we must add the tagNode defaults individually until this is - # moved to the base class - default_values = defaults(base + ['interface']) - for interface in igmp_proxy['interface']: - igmp_proxy['interface'][interface] = dict_merge(default_values, - igmp_proxy['interface'][interface]) - + igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_defaults=True) if conf.exists(['protocols', 'igmp']): igmp_proxy.update({'igmp_configured': ''}) diff --git a/src/conf_mode/intel_qat.py b/src/conf_mode/intel_qat.py index dd04a002d..e4b248675 100755 --- a/src/conf_mode/intel_qat.py +++ b/src/conf_mode/intel_qat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# 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 @@ -20,7 +20,8 @@ import re from sys import exit from vyos.config import Config -from vyos.util import popen, run +from vyos.utils.process import popen +from vyos.utils.process import run from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 9936620c8..0bd306ed0 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -35,9 +35,9 @@ from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ifconfig import BondIf from vyos.ifconfig import Section -from vyos.util import dict_search -from vyos.validate import has_address_configured -from vyos.validate import has_vrf_configured +from vyos.utils.dict import dict_search +from vyos.configdict import has_address_configured +from vyos.configdict import has_vrf_configured from vyos import ConfigError from vyos import airbag airbag.enable() @@ -195,11 +195,11 @@ def verify(bond): raise ConfigError(error_msg + 'it does not exist!') if 'is_bridge_member' in interface_config: - tmp = interface_config['is_bridge_member'] + tmp = next(iter(interface_config['is_bridge_member'])) raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') if 'is_bond_member' in interface_config: - tmp = interface_config['is_bond_member'] + tmp = next(iter(interface_config['is_bond_member'])) raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') if 'is_source_interface' in interface_config: diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 4da3b097f..c82f01e53 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -14,10 +14,7 @@ # 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 os - from sys import exit -from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict @@ -25,17 +22,14 @@ from vyos.configdict import node_changed from vyos.configdict import is_member from vyos.configdict import is_source_interface from vyos.configdict import has_vlan_subinterface_configured -from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf -from vyos.validate import has_address_configured -from vyos.validate import has_vrf_configured -from vyos.xml import defaults +from vyos.configdict import has_address_configured +from vyos.configdict import has_vrf_configured -from vyos.util import cmd -from vyos.util import dict_search +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag @@ -61,22 +55,8 @@ def get_config(config=None): else: bridge.update({'member' : {'interface_remove' : tmp }}) - if dict_search('member.interface', bridge) != None: - # XXX: T2665: we need a copy of the dict keys for iteration, else we will get: - # RuntimeError: dictionary changed size during iteration + if dict_search('member.interface', bridge) is not None: for interface in list(bridge['member']['interface']): - for key in ['cost', 'priority']: - if interface == key: - del bridge['member']['interface'][key] - continue - - # the default dictionary is not properly paged into the dict (see T2665) - # thus we will ammend it ourself - default_member_values = defaults(base + ['member', 'interface']) - for interface,interface_config in bridge['member']['interface'].items(): - bridge['member']['interface'][interface] = dict_merge( - default_member_values, bridge['member']['interface'][interface]) - # Check if member interface is already member of another bridge tmp = is_member(conf, interface, 'bridge') if tmp and bridge['ifname'] not in tmp: diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 31cfab368..f3e65ad5e 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -40,9 +40,9 @@ from vyos.pki import encode_certificate from vyos.pki import load_certificate from vyos.pki import wrap_private_key from vyos.template import render -from vyos.util import call -from vyos.util import dict_search -from vyos.util import write_file +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file from vyos import ConfigError from vyos import airbag airbag.enable() @@ -145,12 +145,6 @@ def verify(ethernet): raise ConfigError('Xen netback drivers requires scatter-gatter offloading '\ 'for MTU size larger then 1500 bytes') - # XDP requires multiple TX queues - if 'xdp' in ethernet: - queues = glob(f'/sys/class/net/{ifname}/queues/tx-*') - if len(queues) < 2: - raise ConfigError('XDP requires additional TX queues, too few available!') - if {'is_bond_member', 'mac'} <= set(ethernet): Warning(f'changing mac address "{mac}" will be ignored as "{ifname}" ' \ f'is a member of bond "{is_bond_member}"'.format(**ethernet)) @@ -192,14 +186,15 @@ def generate(ethernet): if 'ca_certificate' in ethernet['eapol']: ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem') - ca_cert_name = ethernet['eapol']['ca_certificate'] - pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] + ca_chains = [] - loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) - ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + for ca_cert_name in ethernet['eapol']['ca_certificate']: + pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + ca_chains.append('\n'.join(encode_certificate(c) for c in ca_full_chain)) - write_file(ca_cert_file_path, - '\n'.join(encode_certificate(c) for c in ca_full_chain)) + write_file(ca_cert_file_path, '\n'.join(ca_chains)) return None diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index ca321e01d..e1db3206e 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -28,8 +28,8 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import L2TPv3If -from vyos.util import check_kmod -from vyos.validate import is_addr_assigned +from vyos.utils.kernel import check_kmod +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 649ea8d50..0a927ac88 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -33,9 +33,9 @@ from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import MACsecIf from vyos.ifconfig import Interface from vyos.template import render -from vyos.util import call -from vyos.util import dict_search -from vyos.util import is_systemd_service_running +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() @@ -43,6 +43,14 @@ airbag.enable() # XXX: wpa_supplicant works on the source interface wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' +# Constants +## gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit +GCM_AES_128_LEN: int = 32 +GCM_128_KEY_ERROR = 'gcm-aes-128 requires a 128bit long key!' +## gcm-aes-256 requires a 256bit long key - 64 characters (string) = 32byte = 256bit +GCM_AES_256_LEN: int = 64 +GCM_256_KEY_ERROR = 'gcm-aes-256 requires a 256bit long key!' + def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -89,18 +97,54 @@ def verify(macsec): raise ConfigError('Cipher suite must be set for MACsec "{ifname}"'.format(**macsec)) if dict_search('security.encrypt', macsec) != None: - if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None: - raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!') + # Check that only static or MKA config is present + if dict_search('security.static', macsec) != None and (dict_search('security.mka.cak', macsec) != None or dict_search('security.mka.ckn', macsec) != None): + raise ConfigError('Only static or MKA can be used!') + + # Logic to check static configuration + if dict_search('security.static', macsec) != None: + # tx-key must be defined + if dict_search('security.static.key', macsec) == None: + raise ConfigError('Static MACsec tx-key must be defined.') + + tx_len = len(dict_search('security.static.key', macsec)) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and tx_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + if dict_search('security.cipher', macsec) == 'gcm-aes-256' and tx_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + # Make sure at least one peer is defined + if 'peer' not in macsec['security']['static']: + raise ConfigError('Must have at least one peer defined for static MACsec') + + # For every enabled peer, make sure a MAC and rx-key is defined + for peer, peer_config in macsec['security']['static']['peer'].items(): + if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config): + raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.') + + # check rx-key length against cipher suite + rx_len = len(peer_config['key']) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + if dict_search('security.cipher', macsec) == 'gcm-aes-256' and rx_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + # Logic to check MKA configuration + else: + if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None: + raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!') - cak_len = len(dict_search('security.mka.cak', macsec)) + cak_len = len(dict_search('security.mka.cak', macsec)) - if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != 32: - # gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit - raise ConfigError('gcm-aes-128 requires a 128bit long key!') + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) - elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != 64: - # gcm-aes-128 requires a 128bit long key - 64 characters (string) = 32byte = 256bit - raise ConfigError('gcm-aes-128 requires a 256bit long key!') + elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) if 'source_interface' in macsec: # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad @@ -115,7 +159,9 @@ def verify(macsec): def generate(macsec): - render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec) + # Only generate wpa_supplicant config if using MKA + if dict_search('security.mka.cak', macsec): + render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec) return None @@ -142,8 +188,10 @@ def apply(macsec): i = MACsecIf(**macsec) i.update(macsec) - if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec: - call(f'systemctl reload-or-restart {systemd_service}') + # Only reload/restart if using MKA + if dict_search('security.mka.cak', macsec): + if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec: + call(f'systemctl reload-or-restart {systemd_service}') return None diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 6f227b0d1..1d0feb56f 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -50,16 +50,18 @@ from vyos.pki import wrap_private_key from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 -from vyos.util import call -from vyos.util import chown -from vyos.util import cmd -from vyos.util import dict_search -from vyos.util import dict_search_args -from vyos.util import is_list_equal -from vyos.util import makedir -from vyos.util import read_file -from vyos.util import write_file -from vyos.validate import is_addr_assigned +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.list import is_list_equal +from vyos.utils.file import makedir +from vyos.utils.file import read_file +from vyos.utils.file import write_file +from vyos.utils.kernel import check_kmod +from vyos.utils.kernel import unload_kmod +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos.utils.process import cmd +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag @@ -86,30 +88,45 @@ def get_config(config=None): conf = Config() base = ['interfaces', 'openvpn'] - tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - ifname, openvpn = get_interface_dict(conf, base) - - if 'deleted' not in openvpn: - openvpn['pki'] = tmp_pki - if is_node_changed(conf, base + [ifname, 'openvpn-option']): - openvpn.update({'restart_required': {}}) - - # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' - # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. - tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True) - - # We have to cleanup the config dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: server mfa totp - # originate comes with defaults, which will enable the - # totp plugin, even when not set via CLI so we - # need to check this first and drop those keys - if dict_search('server.mfa.totp', tmp) == None: - del openvpn['server']['mfa'] - openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn) + if 'deleted' in openvpn: + return openvpn + + openvpn['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + if is_node_changed(conf, base + [ifname, 'openvpn-option']): + openvpn.update({'restart_required': {}}) + if is_node_changed(conf, base + [ifname, 'enable-dco']): + openvpn.update({'restart_required': {}}) + + # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' + # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. + tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True) + + # We have to cleanup the config dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: server mfa totp + # originate comes with defaults, which will enable the + # totp plugin, even when not set via CLI so we + # need to check this first and drop those keys + if dict_search('server.mfa.totp', tmp) == None: + del openvpn['server']['mfa'] + + # OpenVPN Data-Channel-Offload (DCO) is a Kernel module. If loaded it applies to all + # OpenVPN interfaces. Check if DCO is used by any other interface instance. + tmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + for interface, interface_config in tmp.items(): + # If one interface has DCO configured, enable it. No need to further check + # all other OpenVPN interfaces. We must use a dedicated key to indicate + # the Kernel module must be loaded or not. The per interface "offload.dco" + # key is required per OpenVPN interface instance. + if dict_search('offload.dco', interface_config) != None: + openvpn['module_load_dco'] = {} + break + return openvpn def is_ec_private_key(pki, cert_name): @@ -149,17 +166,23 @@ def verify_pki(openvpn): raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}') if tls: - if 'ca_certificate' not in tls: - raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface}') + if (mode in ['server', 'client']) and ('ca_certificate' not in tls): + raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface},\ + it is required in server and client modes') + else: + if ('ca_certificate' not in tls) and ('peer_fingerprint' not in tls): + raise ConfigError('Either "tls ca-certificate" or "tls peer-fingerprint" is required\ + on openvpn interface {interface} in site-to-site mode') - for ca_name in tls['ca_certificate']: - if ca_name not in pki['ca']: - raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') + if 'ca_certificate' in tls: + for ca_name in tls['ca_certificate']: + if ca_name not in pki['ca']: + raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') - if len(tls['ca_certificate']) > 1: - sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca']) - if not verify_ca_chain(sorted_chain, pki['ca']): - raise ConfigError(f'CA certificates are not a valid chain') + if len(tls['ca_certificate']) > 1: + sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca']) + if not verify_ca_chain(sorted_chain, pki['ca']): + raise ConfigError(f'CA certificates are not a valid chain') if mode != 'client' and 'auth_key' not in tls: if 'certificate' not in tls: @@ -172,16 +195,7 @@ def verify_pki(openvpn): if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected') is not None: raise ConfigError(f'Cannot use encrypted private key on openvpn interface {interface}') - if mode == 'server' and 'dh_params' not in tls and not is_ec_private_key(pki, tls['certificate']): - raise ConfigError('Must specify "tls dh-params" when not using EC keys in server mode') - if 'dh_params' in tls: - if 'dh' not in pki: - raise ConfigError('There are no DH parameters in PKI configuration') - - if tls['dh_params'] not in pki['dh']: - raise ConfigError(f'Invalid dh-params on openvpn interface {interface}') - pki_dh = pki['dh'][tls['dh_params']] dh_params = load_dh_parameters(pki_dh['parameters']) dh_numbers = dh_params.parameter_numbers() @@ -190,6 +204,7 @@ def verify_pki(openvpn): if dh_bits < 2048: raise ConfigError(f'Minimum DH key-size is 2048 bits') + if 'auth_key' in tls or 'crypt_key' in tls: if not dict_search_args(pki, 'openvpn', 'shared_secret'): raise ConfigError('There are no openvpn shared-secrets in PKI configuration') @@ -478,9 +493,6 @@ def verify(openvpn): if openvpn['protocol'] == 'tcp-active': raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"') - if not dict_search('tls.dh_params', openvpn): - raise ConfigError('Must specify "tls dh-params" when "tls role" is "passive"') - if 'certificate' in openvpn['tls'] and is_ec_private_key(openvpn['pki'], openvpn['tls']['certificate']): if 'dh_params' in openvpn['tls']: print('Warning: using dh-params and EC keys simultaneously will ' \ @@ -670,6 +682,15 @@ def apply(openvpn): if interface in interfaces(): VTunIf(interface).remove() + # dynamically load/unload DCO Kernel extension if requested + dco_module = 'ovpn_dco_v2' + if 'module_load_dco' in openvpn: + check_kmod(dco_module) + else: + unload_kmod(dco_module) + + # Now bail out early if interface is disabled or got deleted + if 'deleted' in openvpn or 'disable' in openvpn: return None # verify specified IP address is present on any interface on this system diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 5f0b76f90..fca91253c 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -32,8 +32,8 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import PPPoEIf from vyos.template import render -from vyos.util import call -from vyos.util import is_systemd_service_running +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_running from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/interfaces-sstpc.py b/src/conf_mode/interfaces-sstpc.py index b5cc4cf4e..b588910dc 100755 --- a/src/conf_mode/interfaces-sstpc.py +++ b/src/conf_mode/interfaces-sstpc.py @@ -27,10 +27,10 @@ from vyos.pki import encode_certificate from vyos.pki import find_chain from vyos.pki import load_certificate from vyos.template import render -from vyos.util import call -from vyos.util import dict_search -from vyos.util import is_systemd_service_running -from vyos.util import write_file +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.process import is_systemd_service_running +from vyos.utils.file import write_file from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 0a3726e94..91aed9cc3 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -33,8 +33,8 @@ from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.ifconfig import TunnelIf -from vyos.util import get_interface_config -from vyos.util import dict_search +from vyos.utils.network import get_interface_config +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() @@ -55,6 +55,9 @@ def get_config(config=None): tmp = is_node_changed(conf, base + [ifname, 'encapsulation']) if tmp: tunnel.update({'encapsulation_changed': {}}) + tmp = is_node_changed(conf, base + [ifname, 'parameters', 'ip', 'key']) + if tmp: tunnel.update({'key_changed': {}}) + # We also need to inspect other configured tunnels as there are Kernel # restrictions where we need to comply. E.g. GRE tunnel key can't be used # twice, or with multiple GRE tunnels to the same location we must specify @@ -197,7 +200,8 @@ def apply(tunnel): remote = dict_search('linkinfo.info_data.remote', tmp) if ('deleted' in tunnel or 'encapsulation_changed' in tunnel or encap in - ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any']): + ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any'] or + 'key_changed' in tunnel): if interface in interfaces(): tmp = Interface(interface) tmp.remove() diff --git a/src/conf_mode/interfaces-vti.py b/src/conf_mode/interfaces-vti.py index f4b0436af..9871810ae 100755 --- a/src/conf_mode/interfaces-vti.py +++ b/src/conf_mode/interfaces-vti.py @@ -21,7 +21,7 @@ from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configverify import verify_mirror_redirect from vyos.ifconfig import VTIIf -from vyos.util import dict_search +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index b1536148c..a3b0867e0 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -87,8 +87,8 @@ def verify(vxlan): raise ConfigError('Multicast VXLAN requires an underlaying interface') verify_source_interface(vxlan) - if not any(tmp in ['group', 'remote', 'source_address'] for tmp in vxlan): - raise ConfigError('Group, remote or source-address must be configured') + if not any(tmp in ['group', 'remote', 'source_address', 'source_interface'] for tmp in vxlan): + raise ConfigError('Group, remote, source-address or source-interface must be configured') if 'vni' not in vxlan and 'external' not in vxlan: raise ConfigError( diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 762bad94f..122d9589a 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -27,12 +27,14 @@ from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import WireGuardIf -from vyos.util import check_kmod -from vyos.util import check_port_availability +from vyos.utils.kernel import check_kmod +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_wireguard_key_pair from vyos import ConfigError from vyos import airbag airbag.enable() + def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -88,7 +90,6 @@ def verify(wireguard): # run checks on individual configured WireGuard peer public_keys = [] - for tmp in wireguard['peer']: peer = wireguard['peer'][tmp] @@ -105,6 +106,10 @@ def verify(wireguard): if peer['public_key'] in public_keys: raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"') + if 'disable' not in peer: + if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']): + raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"') + public_keys.append(peer['public_key']) def apply(wireguard): diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index dd798b5a2..02b4a2500 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -25,16 +25,14 @@ from vyos.configdict import get_interface_dict from vyos.configdict import dict_merge from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete -from vyos.configverify import verify_dhcpv6 -from vyos.configverify import verify_source_interface from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import WiFiIf from vyos.template import render -from vyos.util import call -from vyos.util import dict_search +from vyos.utils.process import call +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,6 +40,8 @@ airbag.enable() # XXX: wpa_supplicant works on the source interface wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' hostapd_conf = '/run/hostapd/{ifname}.conf' +hostapd_accept_station_conf = '/run/hostapd/{ifname}_station_accept.conf' +hostapd_deny_station_conf = '/run/hostapd/{ifname}_station_deny.conf' def find_other_stations(conf, base, ifname): """ @@ -79,30 +79,14 @@ def get_config(config=None): ifname, wifi = get_interface_dict(conf, base) - # Cleanup "delete" default values when required user selectable values are - # not defined at all - tmp = conf.get_config_dict(base + [ifname], key_mangling=('-', '_'), - get_first_key=True) - if not (dict_search('security.wpa.passphrase', tmp) or - dict_search('security.wpa.radius', tmp)): - if 'deleted' not in wifi: + if 'deleted' not in wifi: + # then get_interface_dict provides default keys + if wifi.from_defaults(['security', 'wep']): # if not set by user + del wifi['security']['wep'] + if wifi.from_defaults(['security', 'wpa']): # if not set by user del wifi['security']['wpa'] - # if 'security' key is empty, drop it too - if len(wifi['security']) == 0: - del wifi['security'] - - # defaults include RADIUS server specifics per TAG node which need to be - # added to individual RADIUS servers instead - so we can simply delete them - if dict_search('security.wpa.radius.server.port', wifi) != None: - del wifi['security']['wpa']['radius']['server']['port'] - if not len(wifi['security']['wpa']['radius']['server']): - del wifi['security']['wpa']['radius'] - if not len(wifi['security']['wpa']): - del wifi['security']['wpa'] - if not len(wifi['security']): - del wifi['security'] - if 'security' in wifi and 'wpa' in wifi['security']: + if dict_search('security.wpa', wifi) != None: wpa_cipher = wifi['security']['wpa'].get('cipher') wpa_mode = wifi['security']['wpa'].get('mode') if not wpa_cipher: @@ -120,13 +104,9 @@ def get_config(config=None): tmp = find_other_stations(conf, base, wifi['ifname']) if tmp: wifi['station_interfaces'] = tmp - # Add individual RADIUS server default values - if dict_search('security.wpa.radius.server', wifi): - default_values = defaults(base + ['security', 'wpa', 'radius', 'server']) - - for server in dict_search('security.wpa.radius.server', wifi): - wifi['security']['wpa']['radius']['server'][server] = dict_merge( - default_values, wifi['security']['wpa']['radius']['server'][server]) + # used in hostapt.conf.j2 + wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi) + wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi) return wifi @@ -142,7 +122,7 @@ def verify(wifi): raise ConfigError('You must specify a WiFi mode') if 'ssid' not in wifi and wifi['type'] != 'monitor': - raise ConfigError('SSID must be configured') + raise ConfigError('SSID must be configured unless type is set to "monitor"!') if wifi['type'] == 'access-point': if 'country_code' not in wifi: @@ -215,7 +195,10 @@ def generate(wifi): if 'deleted' in wifi: if os.path.isfile(hostapd_conf.format(**wifi)): os.unlink(hostapd_conf.format(**wifi)) - + if os.path.isfile(hostapd_accept_station_conf.format(**wifi)): + os.unlink(hostapd_accept_station_conf.format(**wifi)) + if os.path.isfile(hostapd_deny_station_conf.format(**wifi)): + os.unlink(hostapd_deny_station_conf.format(**wifi)) if os.path.isfile(wpa_suppl_conf.format(**wifi)): os.unlink(wpa_suppl_conf.format(**wifi)) @@ -250,12 +233,12 @@ def generate(wifi): # render appropriate new config files depending on access-point or station mode if wifi['type'] == 'access-point': - render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', - wifi) + render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.j2', wifi) + render(hostapd_accept_station_conf.format(**wifi), 'wifi/hostapd_accept_station.conf.j2', wifi) + render(hostapd_deny_station_conf.format(**wifi), 'wifi/hostapd_deny_station.conf.j2', wifi) elif wifi['type'] == 'station': - render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2', - wifi) + render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.j2', wifi) return None diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 9ca495476..2515dc838 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -27,12 +27,12 @@ from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_vrf from vyos.ifconfig import WWANIf -from vyos.util import cmd -from vyos.util import call -from vyos.util import dict_search -from vyos.util import DEVNULL -from vyos.util import is_systemd_service_active -from vyos.util import write_file +from vyos.utils.dict import dict_search +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import DEVNULL +from vyos.utils.process import is_systemd_service_active +from vyos.utils.file import write_file from vyos import ConfigError from vyos import airbag airbag.enable() @@ -75,7 +75,6 @@ def get_config(config=None): # We need to know the amount of other WWAN interfaces as ModemManager needs # to be started or stopped. - conf.set_level(base) wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) diff --git a/src/conf_mode/le_cert.py b/src/conf_mode/le_cert.py index 6e169a3d5..06c7e7b72 100755 --- a/src/conf_mode/le_cert.py +++ b/src/conf_mode/le_cert.py @@ -20,9 +20,9 @@ import os import vyos.defaults from vyos.config import Config from vyos import ConfigError -from vyos.util import cmd -from vyos.util import call -from vyos.util import is_systemd_service_running +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import is_systemd_service_running from vyos import airbag airbag.enable() diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index c703c1fe0..c2e87d171 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -20,13 +20,11 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.validate import is_addr_assigned -from vyos.validate import is_loopback_addr +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import is_loopback_addr from vyos.version import get_version_data -from vyos.util import call -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.dict import dict_search from vyos.template import render from vyos import ConfigError from vyos import airbag @@ -46,7 +44,9 @@ def get_config(config=None): return {} lldp = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) if conf.exists(['service', 'snmp']): lldp['system_snmp_enabled'] = '' @@ -54,27 +54,12 @@ def get_config(config=None): version_data = get_version_data() lldp['version'] = version_data['version'] - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - # location coordinates have a default value - if 'interface' in lldp: - for interface, interface_config in lldp['interface'].items(): - default_values = defaults(base + ['interface']) - if dict_search('location.coordinate_based', interface_config) == None: - # no location specified - no need to add defaults - del default_values['location']['coordinate_based']['datum'] - del default_values['location']['coordinate_based']['altitude'] - - # cleanup default_values dictionary from inner to outer - # this might feel overkill here, but it does support easy extension - # in the future with additional default values - if len(default_values['location']['coordinate_based']) == 0: - del default_values['location']['coordinate_based'] - if len(default_values['location']) == 0: - del default_values['location'] - - lldp['interface'][interface] = dict_merge(default_values, - lldp['interface'][interface]) + # prune location information if not set by user + for interface in lldp.get('interface', []): + if lldp.from_defaults(['interface', interface, 'location']): + del lldp['interface'][interface]['location'] + elif lldp.from_defaults(['interface', interface, 'location','coordinate_based']): + del lldp['interface'][interface]['location']['coordinate_based'] return lldp diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py index b29fdffc7..8fe429653 100755 --- a/src/conf_mode/load-balancing-haproxy.py +++ b/src/conf_mode/load-balancing-haproxy.py @@ -20,14 +20,12 @@ from sys import exit from shutil import rmtree from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.util import call -from vyos.util import check_port_availability -from vyos.util import is_listen_port_bind_service +from vyos.utils.process import call +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_listen_port_bind_service from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -54,18 +52,8 @@ def get_config(config=None): lb['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - if 'backend' in default_values: - del default_values['backend'] if lb: - lb = dict_merge(default_values, lb) - - if 'backend' in lb: - for backend in lb['backend']: - default_balues_backend = defaults(base + ['backend']) - lb['backend'][backend] = dict_merge(default_balues_backend, lb['backend'][backend]) + lb = conf.merge_defaults(lb, recursive=True) return lb diff --git a/src/conf_mode/load-balancing-wan.py b/src/conf_mode/load-balancing-wan.py index 7086aaf8b..ad9c80d72 100755 --- a/src/conf_mode/load-balancing-wan.py +++ b/src/conf_mode/load-balancing-wan.py @@ -21,10 +21,8 @@ from shutil import rmtree from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.util import cmd +from vyos.utils.process import cmd from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -41,48 +39,15 @@ def get_config(config=None): conf = Config() base = ['load-balancing', 'wan'] - lb = conf.get_config_dict(base, + lb = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, get_first_key=True, - key_mangling=('-', '_'), - no_tag_node_value_mangle=True) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - # lb base default values can not be merged here - remove and add them later - if 'interface_health' in default_values: - del default_values['interface_health'] - if 'rule' in default_values: - del default_values['rule'] - lb = dict_merge(default_values, lb) - - if 'interface_health' in lb: - for iface in lb.get('interface_health'): - default_values_iface = defaults(base + ['interface-health']) - if 'test' in default_values_iface: - del default_values_iface['test'] - lb['interface_health'][iface] = dict_merge( - default_values_iface, lb['interface_health'][iface]) - if 'test' in lb['interface_health'][iface]: - for node_test in lb['interface_health'][iface]['test']: - default_values_test = defaults(base + - ['interface-health', 'test']) - lb['interface_health'][iface]['test'][node_test] = dict_merge( - default_values_test, - lb['interface_health'][iface]['test'][node_test]) - - if 'rule' in lb: - for rule in lb.get('rule'): - default_values_rule = defaults(base + ['rule']) - if 'interface' in default_values_rule: - del default_values_rule['interface'] - lb['rule'][rule] = dict_merge(default_values_rule, lb['rule'][rule]) - if not conf.exists(base + ['rule', rule, 'limit']): - del lb['rule'][rule]['limit'] - if 'interface' in lb['rule'][rule]: - for iface in lb['rule'][rule]['interface']: - default_values_rule_iface = defaults(base + ['rule', 'interface']) - lb['rule'][rule]['interface'][iface] = dict_merge(default_values_rule_iface, lb['rule'][rule]['interface'][iface]) + with_recursive_defaults=True) + + # prune limit key if not set by user + for rule in lb.get('rule', []): + if lb.from_defaults(['rule', rule, 'limit']): + del lb['rule'][rule]['limit'] return lb diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 9f8221514..9da7fbe80 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -25,16 +25,14 @@ from netifaces import interfaces from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render from vyos.template import is_ip_network -from vyos.util import cmd -from vyos.util import run -from vyos.util import check_kmod -from vyos.util import dict_search -from vyos.util import dict_search_args -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.kernel import check_kmod +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag @@ -72,6 +70,7 @@ def verify_rule(config, err_msg, groups_dict): """ Common verify steps used for both source and destination NAT """ if (dict_search('translation.port', config) != None or + dict_search('translation.redirect.port', config) != None or dict_search('destination.port', config) != None or dict_search('source.port', config)): @@ -125,6 +124,18 @@ def verify_rule(config, err_msg, groups_dict): if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']: raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port-group') + if 'load_balance' in config: + for item in ['source-port', 'destination-port']: + if item in config['load_balance']['hash'] and config['protocol'] not in ['tcp', 'udp']: + raise ConfigError('Protocol must be tcp or udp when specifying hash ports') + count = 0 + if 'backend' in config['load_balance']: + for member in config['load_balance']['backend']: + weight = config['load_balance']['backend'][member]['weight'] + count = count + int(weight) + if count != 100: + Warning(f'Sum of weight for nat load balance rule is not 100. You may get unexpected behaviour') + def get_config(config=None): if config: conf = config @@ -132,16 +143,9 @@ def get_config(config=None): conf = Config() base = ['nat'] - nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # T2665: we must add the tagNode defaults individually until this is - # moved to the base class - for direction in ['source', 'destination', 'static']: - if direction in nat: - default_values = defaults(base + [direction, 'rule']) - for rule in dict_search(f'{direction}.rule', nat) or []: - nat[direction]['rule'][rule] = dict_merge(default_values, - nat[direction]['rule'][rule]) + nat = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) # read in current nftable (once) for further processing tmp = cmd('nft -j list table raw') @@ -198,7 +202,7 @@ def verify(nat): Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" 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: + if 'exclude' not in config and 'backend' not in config['load_balance']: raise ConfigError(f'{err_msg} translation requires address and/or port') addr = dict_search('translation.address', config) @@ -210,7 +214,6 @@ def verify(nat): # common rule verification verify_rule(config, err_msg, nat['firewall_group']) - if dict_search('destination.rule', nat): for rule, config in dict_search('destination.rule', nat).items(): err_msg = f'Destination NAT configuration error in rule {rule}:' @@ -221,8 +224,8 @@ def verify(nat): elif 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 not dict_search('translation.address', config) and not dict_search('translation.port', config): - if 'exclude' not in config: + 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']: raise ConfigError(f'{err_msg} translation requires address and/or port') # common rule verification diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index d8f913b0c..4c12618bc 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -23,13 +23,11 @@ from netifaces import interfaces from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render -from vyos.util import cmd -from vyos.util import check_kmod -from vyos.util import dict_search +from vyos.utils.process import cmd +from vyos.utils.kernel import check_kmod +from vyos.utils.dict import dict_search from vyos.template import is_ipv6 -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -60,16 +58,6 @@ def get_config(config=None): base = ['nat66'] nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # T2665: we must add the tagNode defaults individually until this is - # moved to the base class - for direction in ['source', 'destination']: - if direction in nat: - default_values = defaults(base + [direction, 'rule']) - if 'rule' in nat[direction]: - for rule in nat[direction]['rule']: - nat[direction]['rule'][rule] = dict_merge(default_values, - nat[direction]['rule'][rule]) - # read in current nftable (once) for further processing tmp = cmd('nft -j list table ip6 raw') nftable_json = json.loads(tmp) diff --git a/src/conf_mode/netns.py b/src/conf_mode/netns.py index 20129ce65..95ab83dbc 100755 --- a/src/conf_mode/netns.py +++ b/src/conf_mode/netns.py @@ -22,9 +22,9 @@ from tempfile import NamedTemporaryFile from vyos.config import Config from vyos.configdict import node_changed from vyos.ifconfig import Interface -from vyos.util import call -from vyos.util import dict_search -from vyos.util import get_interface_config +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index 92cb73aab..1cc23a7df 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -20,10 +20,11 @@ from vyos.config import Config from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_interface_exists -from vyos.util import call -from vyos.util import chmod_750 -from vyos.util import get_interface_config +from vyos.utils.process import call +from vyos.utils.permission import chmod_750 +from vyos.utils.network import get_interface_config from vyos.template import render +from vyos.template import is_ipv4 from vyos import ConfigError from vyos import airbag airbag.enable() @@ -62,16 +63,29 @@ def verify(ntp): if 'interface' in ntp: # If ntpd should listen on a given interface, ensure it exists - for interface in ntp['interface']: - verify_interface_exists(interface) - - # If we run in a VRF, our interface must belong to this VRF, too - if 'vrf' in ntp: - tmp = get_interface_config(interface) - vrf_name = ntp['vrf'] - if 'master' not in tmp or tmp['master'] != vrf_name: - raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\ - f'does not belong to this VRF!') + interface = ntp['interface'] + verify_interface_exists(interface) + + # If we run in a VRF, our interface must belong to this VRF, too + if 'vrf' in ntp: + tmp = get_interface_config(interface) + vrf_name = ntp['vrf'] + if 'master' not in tmp or tmp['master'] != vrf_name: + raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\ + f'does not belong to this VRF!') + + if 'listen_address' in ntp: + ipv4_addresses = 0 + ipv6_addresses = 0 + for address in ntp['listen_address']: + if is_ipv4(address): + ipv4_addresses += 1 + else: + ipv6_addresses += 1 + if ipv4_addresses > 1: + raise ConfigError(f'NTP Only admits one ipv4 value for listen-address parameter ') + if ipv6_addresses > 1: + raise ConfigError(f'NTP Only admits one ipv6 value for listen-address parameter ') return None diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index 54de467ca..34ba2fe69 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -18,7 +18,6 @@ from sys import exit from vyos.config import Config from vyos.configdep import set_dependents, call_dependents -from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.pki import is_ca_certificate from vyos.pki import load_certificate @@ -26,9 +25,8 @@ from vyos.pki import load_public_key from vyos.pki import load_private_key from vyos.pki import load_crl from vyos.pki import load_dh_parameters -from vyos.util import dict_search_args -from vyos.util import dict_search_recursive -from vyos.xml import defaults +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search_recursive from vyos import ConfigError from vyos import airbag airbag.enable() @@ -113,8 +111,7 @@ def get_config(config=None): # We only merge on the defaults of there is a configuration at all if conf.exists(base): - default_values = defaults(base) - pki = dict_merge(default_values, pki) + pki = conf.merge_defaults(pki, recursive=True) # We need to get the entire system configuration to verify that we are not # deleting a certificate that is still referenced somewhere! diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 3f834f55c..79526f82a 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -24,7 +24,7 @@ from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configdict import leaf_node_changed from vyos.template import render -from vyos.util import call +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 40a32efb3..adad012de 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -22,9 +22,9 @@ from sys import exit from vyos.base import Warning from vyos.config import Config from vyos.template import render -from vyos.util import cmd -from vyos.util import dict_search_args -from vyos.util import run +from vyos.utils.dict import dict_search_args +from vyos.utils.process import cmd +from vyos.utils.process import run from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/policy.py b/src/conf_mode/policy.py index 331194fec..4df893ebf 100755 --- a/src/conf_mode/policy.py +++ b/src/conf_mode/policy.py @@ -19,7 +19,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import dict_merge from vyos.template import render_to_string -from vyos.util import dict_search +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import frr from vyos import airbag diff --git a/src/conf_mode/protocols_babel.py b/src/conf_mode/protocols_babel.py index 20821c7f2..104711b55 100755 --- a/src/conf_mode/protocols_babel.py +++ b/src/conf_mode/protocols_babel.py @@ -19,13 +19,13 @@ import os from sys import exit from vyos.config import Config +from vyos.config import config_dict_merge from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.dict import dict_search from vyos.template import render_to_string from vyos import ConfigError from vyos import frr @@ -38,7 +38,8 @@ def get_config(config=None): else: conf = Config() base = ['protocols', 'babel'] - babel = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + babel = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) # FRR has VRF support for different routing daemons. As interfaces belong # to VRFs - or the global VRF, we need to check for changed interfaces so @@ -54,15 +55,13 @@ def get_config(config=None): return babel # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) + # values which we need to update into the dictionary retrieved. + default_values = conf.get_config_defaults(base, key_mangling=('-', '_'), + get_first_key=True, + recursive=True) - # XXX: T2665: we currently have no nice way for defaults under tag nodes, - # clean them out and add them manually :( - del default_values['interface'] - - # merge in remaining default values - babel = dict_merge(default_values, babel) + # merge in default values + babel = config_dict_merge(default_values, babel) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 0436abaf9..dab784662 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -17,12 +17,10 @@ import os from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_vrf from vyos.template import is_ipv6 from vyos.template import render_to_string -from vyos.validate import is_ipv6_link_local -from vyos.xml import defaults +from vyos.utils.network import is_ipv6_link_local from vyos import ConfigError from vyos import frr from vyos import airbag @@ -41,18 +39,7 @@ def get_config(config=None): if not conf.exists(base): return bfd - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary retrived. - # XXX: T2665: we currently have no nice way for defaults under tag - # nodes, thus we load the defaults "by hand" - default_values = defaults(base + ['peer']) - if 'peer' in bfd: - for peer in bfd['peer']: - bfd['peer'][peer] = dict_merge(default_values, bfd['peer'][peer]) - - if 'profile' in bfd: - for profile in bfd['profile']: - bfd['profile'][profile] = dict_merge(default_values, bfd['profile'][profile]) + bfd = conf.merge_defaults(bfd, recursive=True) return bfd diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index b23584bdb..00015023c 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -20,14 +20,16 @@ from sys import argv from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdict import node_changed from vyos.configverify import verify_prefix_list from vyos.configverify import verify_route_map from vyos.configverify import verify_vrf from vyos.template import is_ip from vyos.template import is_interface from vyos.template import render_to_string -from vyos.util import dict_search -from vyos.validate import is_addr_assigned +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_vrf +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import frr from vyos import airbag @@ -55,6 +57,12 @@ def get_config(config=None): get_first_key=True, no_tag_node_value_mangle=True) + # Remove per interface MPLS configuration - get a list if changed + # nodes under the interface tagNode + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + bgp['interface_removed'] = list(interfaces_removed) + # Assign the name of our VRF context. This MUST be done before the return # statement below, else on deletion we will delete the default instance # instead of the VRF instance. @@ -195,14 +203,21 @@ def verify_remote_as(peer_config, bgp_config): return None def verify_afi(peer_config, bgp_config): + # If address_family configured under neighboor if 'address_family' in peer_config: return True + # If address_family configured under peer-group + # if neighbor interface configured + peer_group_name = '' + if dict_search('interface.peer_group', peer_config): + peer_group_name = peer_config['interface']['peer_group'] + # if neighbor IP configured. if 'peer_group' in peer_config: peer_group_name = peer_config['peer_group'] + if peer_group_name: tmp = dict_search(f'peer_group.{peer_group_name}.address_family', bgp_config) if tmp: return True - return False def verify(bgp): @@ -231,6 +246,18 @@ def verify(bgp): if 'system_as' not in bgp: raise ConfigError('BGP system-as number must be defined!') + # Verify vrf on interface and bgp section + if 'interface' in bgp: + for interface in bgp['interface']: + error_msg = f'Interface "{interface}" belongs to different VRF instance' + tmp = get_interface_vrf(interface) + if 'vrf' in bgp: + if bgp['vrf'] != tmp: + vrf = bgp['vrf'] + raise ConfigError(f'{error_msg} "{vrf}"!') + elif tmp != 'default': + raise ConfigError(f'{error_msg} "{tmp}"!') + # Common verification for both peer-group and neighbor statements for neighbor in ['neighbor', 'peer_group']: # bail out early if there is no neighbor or peer-group statement @@ -448,6 +475,8 @@ def verify(bgp): if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): raise ConfigError( 'Command "import vrf" conflicts with "rd vpn export" command!') + if not dict_search('parameters.router_id', bgp): + Warning(f'BGP "router-id" is required when using "rd" and "route-target"!') if dict_search('route_target.vpn.both', afi_config): if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): @@ -520,6 +549,14 @@ def apply(bgp): vrf = ' vrf ' + bgp['vrf'] frr_cfg.load_configuration(bgp_daemon) + + # Remove interface specific config + for key in ['interface', 'interface_removed']: + if key not in bgp: + continue + for interface in bgp[key]: + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(f'^router bgp \d+{vrf}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_bgpd_config' in bgp: frr_cfg.add_before(frr.default_add_before, bgp['frr_bgpd_config']) diff --git a/src/conf_mode/protocols_failover.py b/src/conf_mode/protocols_failover.py index 85e984afe..e7e44db84 100755 --- a/src/conf_mode/protocols_failover.py +++ b/src/conf_mode/protocols_failover.py @@ -19,10 +19,8 @@ import json from pathlib import Path from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render -from vyos.util import call -from vyos.xml import defaults +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag @@ -42,15 +40,12 @@ def get_config(config=None): conf = Config() base = ['protocols', 'failover'] - failover = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + failover = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) # Set default values only if we set config - if failover.get('route'): - for route, route_config in failover.get('route').items(): - for next_hop, next_hop_config in route_config.get('next_hop').items(): - default_values = defaults(base + ['route']) - failover['route'][route]['next_hop'][next_hop] = dict_merge( - default_values['next_hop'], failover['route'][route]['next_hop'][next_hop]) + if failover.get('route') is not None: + failover = conf.merge_defaults(failover, recursive=True) return failover diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index 65cc2beba..f6097e282 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -21,7 +21,8 @@ from sys import exit from vyos import ConfigError from vyos.config import Config -from vyos.util import call, process_named_running +from vyos.utils.process import process_named_running +from vyos.utils.process import call from vyos.template import render from signal import SIGTERM diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index ecca87db0..e00c58ee4 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -25,10 +25,9 @@ from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_interface_exists from vyos.ifconfig import Interface -from vyos.util import dict_search -from vyos.util import get_interface_config +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config from vyos.template import render_to_string -from vyos.xml import defaults from vyos import ConfigError from vyos import frr from vyos import airbag @@ -64,19 +63,14 @@ def get_config(config=None): if interfaces_removed: isis['interface_removed'] = list(interfaces_removed) - # Bail out early if configuration tree does not exist + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. if not conf.exists(base): isis.update({'deleted' : ''}) return isis - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - # XXX: Note that we can not call defaults(base), as defaults does not work - # on an instance of a tag node. As we use the exact same CLI definition for - # both the non-vrf and vrf version this is absolutely safe! - default_values = defaults(base_path) # merge in default values - isis = dict_merge(default_values, isis) + isis = conf.merge_defaults(isis, recursive=True) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). @@ -254,7 +248,7 @@ def apply(isis): if key not in isis: continue for interface in isis[key]: - frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_isisd_config' in isis: frr_cfg.add_before(frr.default_add_before, isis['frr_isisd_config']) diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 73af6595b..177a43444 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -21,9 +21,9 @@ from sys import exit from glob import glob from vyos.config import Config from vyos.template import render_to_string -from vyos.util import dict_search -from vyos.util import read_file -from vyos.util import sysctl_write +from vyos.utils.dict import dict_search +from vyos.utils.file import read_file +from vyos.utils.system import sysctl_write from vyos.configverify import verify_interface_exists from vyos import ConfigError from vyos import frr diff --git a/src/conf_mode/protocols_nhrp.py b/src/conf_mode/protocols_nhrp.py index d28ced4fd..5ec0bc9e5 100755 --- a/src/conf_mode/protocols_nhrp.py +++ b/src/conf_mode/protocols_nhrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -19,8 +19,8 @@ import os from vyos.config import Config from vyos.configdict import node_changed from vyos.template import render -from vyos.util import process_named_running -from vyos.util import run +from vyos.utils.process import process_named_running +from vyos.utils.process import run from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index b73483470..cddd3765e 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -20,6 +20,7 @@ from sys import exit from sys import argv from vyos.config import Config +from vyos.config import config_dict_merge from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps @@ -27,9 +28,8 @@ from vyos.configverify import verify_route_map from vyos.configverify import verify_interface_exists from vyos.configverify import verify_access_list from vyos.template import render_to_string -from vyos.util import dict_search -from vyos.util import get_interface_config -from vyos.xml import defaults +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config from vyos import ConfigError from vyos import frr from vyos import airbag @@ -65,17 +65,15 @@ def get_config(config=None): if interfaces_removed: ospf['interface_removed'] = list(interfaces_removed) - # Bail out early if configuration tree does not exist + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. if not conf.exists(base): ospf.update({'deleted' : ''}) return ospf # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - # XXX: Note that we can not call defaults(base), as defaults does not work - # on an instance of a tag node. As we use the exact same CLI definition for - # both the non-vrf and vrf version this is absolutely safe! - default_values = defaults(base_path) + default_values = conf.get_config_defaults(**ospf.kwargs, recursive=True) # We have to cleanup the default dict, as default values could enable features # which are not explicitly enabled on the CLI. Example: default-information @@ -84,60 +82,27 @@ def get_config(config=None): # need to check this first and probably drop that key. if dict_search('default_information.originate', ospf) is None: del default_values['default_information'] - if dict_search('area.area_type.nssa', ospf) is None: - del default_values['area']['area_type']['nssa'] if 'mpls_te' not in ospf: del default_values['mpls_te'] + if 'graceful_restart' not in ospf: + del default_values['graceful_restart'] + for area_num in default_values.get('area', []): + if dict_search(f'area.{area_num}.area_type.nssa', ospf) is None: + del default_values['area'][area_num]['area_type']['nssa'] - for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static', 'table']: - # table is a tagNode thus we need to clean out all occurances for the - # default values and load them in later individually - if protocol == 'table': - del default_values['redistribute']['table'] - continue + for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']: if dict_search(f'redistribute.{protocol}', ospf) is None: del default_values['redistribute'][protocol] - # XXX: T2665: we currently have no nice way for defaults under tag nodes, - # clean them out and add them manually :( - del default_values['neighbor'] - del default_values['area']['virtual_link'] - del default_values['interface'] - - # merge in remaining default values - ospf = dict_merge(default_values, ospf) + for interface in ospf.get('interface', []): + # We need to reload the defaults on every pass b/c of + # hello-multiplier dependency on dead-interval + # If hello-multiplier is set, we need to remove the default from + # dead-interval. + if 'hello_multiplier' in ospf['interface'][interface]: + del default_values['interface'][interface]['dead_interval'] - if 'neighbor' in ospf: - default_values = defaults(base + ['neighbor']) - for neighbor in ospf['neighbor']: - ospf['neighbor'][neighbor] = dict_merge(default_values, ospf['neighbor'][neighbor]) - - if 'area' in ospf: - default_values = defaults(base + ['area', 'virtual-link']) - for area, area_config in ospf['area'].items(): - if 'virtual_link' in area_config: - for virtual_link in area_config['virtual_link']: - ospf['area'][area]['virtual_link'][virtual_link] = dict_merge( - default_values, ospf['area'][area]['virtual_link'][virtual_link]) - - if 'interface' in ospf: - for interface in ospf['interface']: - # We need to reload the defaults on every pass b/c of - # hello-multiplier dependency on dead-interval - default_values = defaults(base + ['interface']) - # If hello-multiplier is set, we need to remove the default from - # dead-interval. - if 'hello_multiplier' in ospf['interface'][interface]: - del default_values['dead_interval'] - - ospf['interface'][interface] = dict_merge(default_values, - ospf['interface'][interface]) - - if 'redistribute' in ospf and 'table' in ospf['redistribute']: - default_values = defaults(base + ['redistribute', 'table']) - for table in ospf['redistribute']['table']: - ospf['redistribute']['table'][table] = dict_merge(default_values, - ospf['redistribute']['table'][table]) + ospf = config_dict_merge(default_values, ospf) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). @@ -250,6 +215,13 @@ def verify(ospf): raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\ f'and no-php-flag configured at the same time.') + # Check route summarisation + if 'summary_address' in ospf: + for prefix, prefix_options in ospf['summary_address'].items(): + if {'tag', 'no_advertise'} <= set(prefix_options): + raise ConfigError(f'Can not set both "tag" and "no-advertise" for Type-5 '\ + f'and Type-7 route summarisation of "{prefix}"!') + return None def generate(ospf): @@ -278,7 +250,7 @@ def apply(ospf): if key not in ospf: continue for interface in ospf[key]: - frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) if 'frr_ospfd_config' in ospf: frr_cfg.add_before(frr.default_add_before, ospf['frr_ospfd_config']) diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index cb21bd83c..5b1adce30 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -20,6 +20,7 @@ from sys import exit from sys import argv from vyos.config import Config +from vyos.config import config_dict_merge from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps @@ -27,9 +28,8 @@ from vyos.configverify import verify_route_map from vyos.configverify import verify_interface_exists from vyos.template import render_to_string from vyos.ifconfig import Interface -from vyos.util import dict_search -from vyos.util import get_interface_config -from vyos.xml import defaults +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config from vyos import ConfigError from vyos import frr from vyos import airbag @@ -64,17 +64,16 @@ def get_config(config=None): if interfaces_removed: ospfv3['interface_removed'] = list(interfaces_removed) - # Bail out early if configuration tree does not exist + # Bail out early if configuration tree does no longer exist. this must + # be done after retrieving the list of interfaces to be removed. if not conf.exists(base): ospfv3.update({'deleted' : ''}) return ospfv3 # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - # XXX: Note that we can not call defaults(base), as defaults does not work - # on an instance of a tag node. As we use the exact same CLI definition for - # both the non-vrf and vrf version this is absolutely safe! - default_values = defaults(base_path) + default_values = conf.get_config_defaults(**ospfv3.kwargs, + recursive=True) # We have to cleanup the default dict, as default values could enable features # which are not explicitly enabled on the CLI. Example: default-information @@ -83,13 +82,13 @@ def get_config(config=None): # need to check this first and probably drop that key. if dict_search('default_information.originate', ospfv3) is None: del default_values['default_information'] + if 'graceful_restart' not in ospfv3: + del default_values['graceful_restart'] - # XXX: T2665: we currently have no nice way for defaults under tag nodes, - # clean them out and add them manually :( - del default_values['interface'] + default_values.pop('interface', {}) # merge in remaining default values - ospfv3 = dict_merge(default_values, ospfv3) + ospfv3 = config_dict_merge(default_values, ospfv3) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). @@ -168,7 +167,7 @@ def apply(ospfv3): if key not in ospfv3: continue for interface in ospfv3[key]: - frr_cfg.modify_section(f'^interface {interface}{vrf}', stop_pattern='^exit', remove_stop_mark=True) + frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True) if 'new_frr_config' in ospfv3: frr_cfg.add_before(frr.default_add_before, ospfv3['new_frr_config']) diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 78df9b6f8..0aaa0d2c6 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -21,7 +21,8 @@ from sys import exit from vyos.config import Config from vyos import ConfigError -from vyos.util import call, process_named_running +from vyos.utils.process import process_named_running +from vyos.utils.process import call from vyos.template import render from signal import SIGTERM diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index c78d90396..bd47dfd00 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -24,8 +24,7 @@ from vyos.configdict import node_changed from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.dict import dict_search from vyos.template import render_to_string from vyos import ConfigError from vyos import frr @@ -55,9 +54,7 @@ def get_config(config=None): # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - default_values = defaults(base) - # merge in remaining default values - rip = dict_merge(default_values, rip) + rip = conf.merge_defaults(rip, recursive=True) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). diff --git a/src/conf_mode/protocols_ripng.py b/src/conf_mode/protocols_ripng.py index 21ff710b3..dd1550033 100755 --- a/src/conf_mode/protocols_ripng.py +++ b/src/conf_mode/protocols_ripng.py @@ -23,8 +23,7 @@ from vyos.configdict import dict_merge from vyos.configverify import verify_common_route_maps from vyos.configverify import verify_access_list from vyos.configverify import verify_prefix_list -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.dict import dict_search from vyos.template import render_to_string from vyos import ConfigError from vyos import frr @@ -45,9 +44,7 @@ def get_config(config=None): # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - default_values = defaults(base) - # merge in remaining default values - ripng = dict_merge(default_values, ripng) + ripng = conf.merge_defaults(ripng, recursive=True) # We also need some additional information from the config, prefix-lists # and route-maps for instance. They will be used in verify(). diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py index 62ea9c878..05e876f3b 100755 --- a/src/conf_mode/protocols_rpki.py +++ b/src/conf_mode/protocols_rpki.py @@ -19,10 +19,8 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render_to_string -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import frr from vyos import airbag @@ -43,8 +41,7 @@ def get_config(config=None): # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - default_values = defaults(base) - rpki = dict_merge(default_values, rpki) + rpki = conf.merge_defaults(rpki, recursive=True) return rpki diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 7b6150696..5def8d645 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -47,7 +47,7 @@ def get_config(config=None): base_path = ['protocols', 'static'] # eqivalent of the C foo ? 'a' : 'b' statement base = vrf and ['vrf', 'name', vrf, 'protocols', 'static'] or base_path - static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) # Assign the name of our VRF context if vrf: static['vrf'] = vrf diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py index 6afdf31f3..7f6ae3680 100755 --- a/src/conf_mode/protocols_static_multicast.py +++ b/src/conf_mode/protocols_static_multicast.py @@ -21,7 +21,7 @@ from sys import exit from vyos import ConfigError from vyos.config import Config -from vyos.util import call +from vyos.utils.process import call from vyos.template import render from vyos import airbag diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py index 1be2c283f..ad4121a49 100755 --- a/src/conf_mode/qos.py +++ b/src/conf_mode/qos.py @@ -36,9 +36,8 @@ from vyos.qos import RateLimiter from vyos.qos import RoundRobin from vyos.qos import TrafficShaper from vyos.qos import TrafficShaperHFSC -from vyos.util import call -from vyos.util import dict_search_recursive -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.dict import dict_search_recursive from vyos import ConfigError from vyos import airbag airbag.enable() @@ -85,75 +84,43 @@ def get_config(config=None): get_first_key=True, no_tag_node_value_mangle=True) - if 'interface' in qos: - for ifname, if_conf in qos['interface'].items(): - if_node = Section.get_config_path(ifname) + for ifname in interfaces(): + if_node = Section.get_config_path(ifname) - if not if_node: - continue + if not if_node: + continue - path = f'interfaces {if_node}' - if conf.exists(f'{path} mirror') or conf.exists(f'{path} redirect'): - type_node = path.split(" ")[1] # return only interface type node - set_dependents(type_node, conf, ifname) - - if 'policy' in qos: - for policy in qos['policy']: - # when calling defaults() we need to use the real CLI node, thus we - # need a hyphen - policy_hyphen = policy.replace('_', '-') - - if policy in ['random_detect']: - for rd_name, rd_config in qos['policy'][policy].items(): - # There are eight precedence levels - ensure all are present - # to be filled later down with the appropriate default values - default_precedence = {'precedence' : { '0' : {}, '1' : {}, '2' : {}, '3' : {}, - '4' : {}, '5' : {}, '6' : {}, '7' : {} }} - qos['policy']['random_detect'][rd_name] = dict_merge( - default_precedence, qos['policy']['random_detect'][rd_name]) - - for p_name, p_config in qos['policy'][policy].items(): - default_values = defaults(base + ['policy', policy_hyphen]) - - if policy in ['priority_queue']: - if 'default' not in p_config: - raise ConfigError(f'QoS policy {p_name} misses "default" class!') - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if 'class' in default_values: - del default_values['class'] - if 'precedence' in default_values: - del default_values['precedence'] - - qos['policy'][policy][p_name] = dict_merge( - default_values, qos['policy'][policy][p_name]) - - # class is another tag node which requires individual handling - if 'class' in p_config: - default_values = defaults(base + ['policy', policy_hyphen, 'class']) - for p_class in p_config['class']: - qos['policy'][policy][p_name]['class'][p_class] = dict_merge( - default_values, qos['policy'][policy][p_name]['class'][p_class]) - - if 'precedence' in p_config: - default_values = defaults(base + ['policy', policy_hyphen, 'precedence']) - # precedence values are a bit more complex as they are calculated - # under specific circumstances - thus we need to iterate two times. - # first blend in the defaults from XML / CLI - for precedence in p_config['precedence']: - qos['policy'][policy][p_name]['precedence'][precedence] = dict_merge( - default_values, qos['policy'][policy][p_name]['precedence'][precedence]) - # second calculate defaults based on actual dictionary - for precedence in p_config['precedence']: - max_thr = int(qos['policy'][policy][p_name]['precedence'][precedence]['maximum_threshold']) - if 'minimum_threshold' not in qos['policy'][policy][p_name]['precedence'][precedence]: - qos['policy'][policy][p_name]['precedence'][precedence]['minimum_threshold'] = str( - int((9 + int(precedence)) * max_thr) // 18); - - if 'queue_limit' not in qos['policy'][policy][p_name]['precedence'][precedence]: - qos['policy'][policy][p_name]['precedence'][precedence]['queue_limit'] = \ - str(int(4 * max_thr)) + path = f'interfaces {if_node}' + if conf.exists(f'{path} mirror') or conf.exists(f'{path} redirect'): + type_node = path.split(" ")[1] # return only interface type node + set_dependents(type_node, conf, ifname.split(".")[0]) + + for policy in qos.get('policy', []): + if policy in ['random_detect']: + for rd_name in list(qos['policy'][policy]): + # There are eight precedence levels - ensure all are present + # to be filled later down with the appropriate default values + default_precedence = {'precedence' : { '0' : {}, '1' : {}, '2' : {}, '3' : {}, + '4' : {}, '5' : {}, '6' : {}, '7' : {} }} + qos['policy']['random_detect'][rd_name] = dict_merge( + default_precedence, qos['policy']['random_detect'][rd_name]) + + qos = conf.merge_defaults(qos, recursive=True) + + for policy in qos.get('policy', []): + for p_name, p_config in qos['policy'][policy].items(): + if 'precedence' in p_config: + # precedence settings are a bit more complex as they are + # calculated under specific circumstances: + for precedence in p_config['precedence']: + max_thr = int(qos['policy'][policy][p_name]['precedence'][precedence]['maximum_threshold']) + if 'minimum_threshold' not in qos['policy'][policy][p_name]['precedence'][precedence]: + qos['policy'][policy][p_name]['precedence'][precedence]['minimum_threshold'] = str( + int((9 + int(precedence)) * max_thr) // 18); + + if 'queue_limit' not in qos['policy'][policy][p_name]['precedence'][precedence]: + qos['policy'][policy][p_name]['precedence'][precedence]['queue_limit'] = \ + str(int(4 * max_thr)) return qos @@ -202,7 +169,9 @@ def verify(qos): queue_lim = int(precedence_config['queue_limit']) if queue_lim < max_tr: raise ConfigError(f'Policy "{policy}" uses queue-limit "{queue_lim}" < max-threshold "{max_tr}"!') - + if policy_type in ['priority_queue']: + if 'default' not in policy_config: + raise ConfigError(f'Policy {policy} misses "default" class!') if 'default' in policy_config: if 'bandwidth' not in policy_config['default'] and policy_type not in ['priority_queue', 'round_robin']: raise ConfigError('Bandwidth not defined for default traffic!') @@ -239,6 +208,8 @@ def apply(qos): call(f'tc qdisc del dev {interface} parent ffff:') call(f'tc qdisc del dev {interface} root') + call_dependents() + if not qos or 'interface' not in qos: return None @@ -259,8 +230,6 @@ def apply(qos): tmp = shaper_type(interface) tmp.update(shaper_config, direction) - call_dependents() - return None if __name__ == '__main__': diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py index 00b889a11..a8fce8e01 100755 --- a/src/conf_mode/salt-minion.py +++ b/src/conf_mode/salt-minion.py @@ -22,12 +22,10 @@ from urllib3 import PoolManager from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_interface_exists from vyos.template import render -from vyos.util import call -from vyos.util import chown -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.permission import chown from vyos import ConfigError from vyos import airbag @@ -55,8 +53,7 @@ def get_config(config=None): salt['id'] = gethostname() # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - default_values = defaults(base) - salt = dict_merge(default_values, salt) + salt = conf.merge_defaults(salt, recursive=True) if not conf.exists(base): return None diff --git a/src/conf_mode/service_config_sync.py b/src/conf_mode/service_config_sync.py new file mode 100755 index 000000000..4b8a7f6ee --- /dev/null +++ b/src/conf_mode/service_config_sync.py @@ -0,0 +1,105 @@ +#!/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 os +import json +from pathlib import Path + +from vyos.config import Config +from vyos import ConfigError +from vyos import airbag + +airbag.enable() + + +service_conf = Path(f'/run/config_sync_conf.conf') +post_commit_dir = '/run/scripts/commit/post-hooks.d' +post_commit_file_src = '/usr/libexec/vyos/vyos_config_sync.py' +post_commit_file = f'{post_commit_dir}/vyos_config_sync' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'config-sync'] + if not conf.exists(base): + return None + config = conf.get_config_dict(base, get_first_key=True, + with_recursive_defaults=True) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if not config: + return None + + if 'mode' not in config: + raise ConfigError(f'config-sync mode is mandatory!') + + for option in ['secondary', 'section']: + if option not in config: + raise ConfigError(f"config-sync '{option}' is not configured!") + + if 'address' not in config['secondary']: + raise ConfigError(f'secondary address is mandatory!') + if 'key' not in config['secondary']: + raise ConfigError(f'secondary key is mandatory!') + + +def generate(config): + if not config: + + if os.path.exists(post_commit_file): + os.unlink(post_commit_file) + + if service_conf.exists(): + service_conf.unlink() + + return None + + # Write configuration file + conf_json = json.dumps(config, indent=4) + service_conf.write_text(conf_json) + + # Create post commit dir + if not os.path.isdir(post_commit_dir): + os.makedirs(post_commit_dir) + + # Symlink from helpers to post-commit + if not os.path.exists(post_commit_file): + os.symlink(post_commit_file_src, post_commit_file) + + return None + + +def apply(config): + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py index 60eff6543..b112add3f 100755 --- a/src/conf_mode/service_console-server.py +++ b/src/conf_mode/service_console-server.py @@ -20,10 +20,8 @@ from sys import exit from psutil import process_iter from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render -from vyos.util import call -from vyos.xml import defaults +from vyos.utils.process import call from vyos import ConfigError config_file = '/run/conserver/conserver.cf' @@ -49,11 +47,7 @@ def get_config(config=None): # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - default_values = defaults(base + ['device']) - if 'device' in proxy: - for device in proxy['device']: - tmp = dict_merge(default_values, proxy['device'][device]) - proxy['device'][device] = tmp + proxy = conf.merge_defaults(proxy, recursive=True) return proxy diff --git a/src/conf_mode/service_event_handler.py b/src/conf_mode/service_event_handler.py index 5440d1056..5028ef52f 100755 --- a/src/conf_mode/service_event_handler.py +++ b/src/conf_mode/service_event_handler.py @@ -18,7 +18,8 @@ import json from pathlib import Path from vyos.config import Config -from vyos.util import call, dict_search +from vyos.utils.dict import dict_search +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py index c58f8db9a..276a71fcb 100755 --- a/src/conf_mode/service_ids_fastnetmon.py +++ b/src/conf_mode/service_ids_fastnetmon.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -19,10 +19,8 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render -from vyos.util import call -from vyos.xml import defaults +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() @@ -30,6 +28,7 @@ airbag.enable() config_file = r'/run/fastnetmon/fastnetmon.conf' networks_list = r'/run/fastnetmon/networks_list' excluded_networks_list = r'/run/fastnetmon/excluded_networks_list' +attack_dir = '/var/log/fastnetmon_attacks' def get_config(config=None): if config: @@ -40,11 +39,9 @@ def get_config(config=None): if not conf.exists(base): return None - fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - fastnetmon = dict_merge(default_values, fastnetmon) + fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return fastnetmon @@ -55,8 +52,11 @@ def verify(fastnetmon): if 'mode' not in fastnetmon: raise ConfigError('Specify operating mode!') - if 'listen_interface' not in fastnetmon: - raise ConfigError('Specify interface(s) for traffic capture') + if fastnetmon.get('mode') == 'mirror' and 'listen_interface' not in fastnetmon: + raise ConfigError("Incorrect settings for 'mode mirror': must specify interface(s) for traffic mirroring") + + if fastnetmon.get('mode') == 'sflow' and 'listen_address' not in fastnetmon.get('sflow', {}): + raise ConfigError("Incorrect settings for 'mode sflow': must specify sFlow 'listen-address'") if 'alert_script' in fastnetmon: if os.path.isfile(fastnetmon['alert_script']): @@ -74,6 +74,10 @@ def generate(fastnetmon): return None + # Create dir for log attack details + if not os.path.exists(attack_dir): + os.mkdir(attack_dir) + render(config_file, 'ids/fastnetmon.j2', fastnetmon) render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon) render(excluded_networks_list, 'ids/fastnetmon_excluded_networks_list.j2', fastnetmon) diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 95c72df47..b70e32373 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -24,8 +24,8 @@ from vyos.configdict import get_accel_dict from vyos.configverify import verify_accel_ppp_base_service from vyos.configverify import verify_interface_exists from vyos.template import render -from vyos.util import call -from vyos.util import dict_search +from vyos.utils.process import call +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py index 2383a53fb..a2c90b537 100755 --- a/src/conf_mode/service_mdns-repeater.py +++ b/src/conf_mode/service_mdns-repeater.py @@ -23,7 +23,7 @@ from netifaces import ifaddresses, interfaces, AF_INET from vyos.config import Config from vyos.ifconfig.vrrp import VRRP from vyos.template import render -from vyos.util import call +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 47510ce80..40eb13e23 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -22,15 +22,13 @@ from sys import exit from shutil import rmtree from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.ifconfig import Section from vyos.template import render -from vyos.util import call -from vyos.util import chown -from vyos.util import cmd -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.permission import chown +from vyos.utils.process import cmd from vyos import ConfigError from vyos import airbag airbag.enable() @@ -83,8 +81,7 @@ def get_config(config=None): # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - default_values = defaults(base) - monitoring = dict_merge(default_values, monitoring) + monitoring = conf.merge_defaults(monitoring, recursive=True) monitoring['custom_scripts_dir'] = custom_scripts_dir monitoring['hostname'] = get_hostname() diff --git a/src/conf_mode/service_monitoring_zabbix-agent.py b/src/conf_mode/service_monitoring_zabbix-agent.py new file mode 100755 index 000000000..98d8a32ca --- /dev/null +++ b/src/conf_mode/service_monitoring_zabbix-agent.py @@ -0,0 +1,98 @@ +#!/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 os + +from vyos.config import Config +from vyos.template import render +from vyos.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +service_name = 'zabbix-agent2' +service_conf = f'/run/zabbix/{service_name}.conf' +systemd_override = r'/run/systemd/system/zabbix-agent2.service.d/10-override.conf' + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['service', 'monitoring', 'zabbix-agent'] + + if not conf.exists(base): + return None + + config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + + # Cut the / from the end, /tmp/ => /tmp + if 'directory' in config and config['directory'].endswith('/'): + config['directory'] = config['directory'][:-1] + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if config is None: + return + + if 'server' not in config: + raise ConfigError('Server is required!') + + +def generate(config): + # bail out early - looks like removal from running config + if config is None: + # Remove old config and return + config_files = [service_conf, systemd_override] + for file in config_files: + if os.path.isfile(file): + os.unlink(file) + + return None + + # Write configuration file + render(service_conf, 'zabbix-agent/zabbix-agent.conf.j2', config) + render(systemd_override, 'zabbix-agent/10-override.conf.j2', config) + + return None + + +def apply(config): + call('systemctl daemon-reload') + if config: + call(f'systemctl restart {service_name}.service') + else: + call(f'systemctl stop {service_name}.service') + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index adeefaa37..aace267a7 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -24,8 +24,8 @@ from vyos.configdict import is_node_changed from vyos.configverify import verify_accel_ppp_base_service from vyos.configverify import verify_interface_exists from vyos.template import render -from vyos.util import call -from vyos.util import dict_search +from vyos.utils.process import call +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index 1dd973d67..dbb47de4e 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -19,10 +19,8 @@ import os from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render -from vyos.util import call -from vyos.xml import defaults +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() @@ -35,40 +33,9 @@ def get_config(config=None): else: conf = Config() base = ['service', 'router-advert'] - rtradv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_interface_values = defaults(base + ['interface']) - # we deal with prefix, route defaults later on - if 'prefix' in default_interface_values: - del default_interface_values['prefix'] - if 'route' in default_interface_values: - del default_interface_values['route'] - - default_prefix_values = defaults(base + ['interface', 'prefix']) - default_route_values = defaults(base + ['interface', 'route']) - - if 'interface' in rtradv: - for interface in rtradv['interface']: - rtradv['interface'][interface] = dict_merge( - default_interface_values, rtradv['interface'][interface]) - - if 'prefix' in rtradv['interface'][interface]: - for prefix in rtradv['interface'][interface]['prefix']: - rtradv['interface'][interface]['prefix'][prefix] = dict_merge( - default_prefix_values, rtradv['interface'][interface]['prefix'][prefix]) - - if 'route' in rtradv['interface'][interface]: - for route in rtradv['interface'][interface]['route']: - rtradv['interface'][interface]['route'][route] = dict_merge( - default_route_values, rtradv['interface'][interface]['route'][route]) - - if 'name_server' in rtradv['interface'][interface]: - # always use a list when dealing with nameservers - eases the template generation - if isinstance(rtradv['interface'][interface]['name_server'], str): - rtradv['interface'][interface]['name_server'] = [ - rtradv['interface'][interface]['name_server']] + rtradv = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return rtradv diff --git a/src/conf_mode/service_sla.py b/src/conf_mode/service_sla.py index b1e22f37b..ba5e645f0 100755 --- a/src/conf_mode/service_sla.py +++ b/src/conf_mode/service_sla.py @@ -19,10 +19,8 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render -from vyos.util import call -from vyos.xml import defaults +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() @@ -44,11 +42,9 @@ def get_config(config=None): if not conf.exists(base): return None - sla = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - sla = dict_merge(default_values, sla) + sla = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) # Ignore default XML values if config doesn't exists # Delete key from dict diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py index c798fd515..cf26bf9ce 100755 --- a/src/conf_mode/service_upnp.py +++ b/src/conf_mode/service_upnp.py @@ -23,12 +23,10 @@ from ipaddress import IPv4Network from ipaddress import IPv6Network from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.util import call +from vyos.utils.process import call from vyos.template import render from vyos.template import is_ipv4 from vyos.template import is_ipv6 -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -47,10 +45,7 @@ def get_config(config=None): if not upnpd: return None - if 'rule' in upnpd: - default_member_values = defaults(base + ['rule']) - for rule,rule_config in upnpd['rule'].items(): - upnpd['rule'][rule] = dict_merge(default_member_values, upnpd['rule'][rule]) + upnpd = conf.merge_defaults(upnpd, recursive=True) uuidgen = uuid.uuid1() upnpd.update({'uuid': uuidgen}) diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py index 658e496a6..12ae4135e 100755 --- a/src/conf_mode/service_webproxy.py +++ b/src/conf_mode/service_webproxy.py @@ -20,14 +20,13 @@ from shutil import rmtree from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.config import config_dict_merge from vyos.template import render -from vyos.util import call -from vyos.util import chmod_755 -from vyos.util import dict_search -from vyos.util import write_file -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.permission import chmod_755 +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.network import is_addr_assigned from vyos.base import Warning from vyos import ConfigError from vyos import airbag @@ -125,7 +124,8 @@ def get_config(config=None): get_first_key=True) # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - default_values = defaults(base) + default_values = conf.get_config_defaults(**proxy.kwargs, + recursive=True) # if no authentication method is supplied, no need to add defaults if not dict_search('authentication.method', proxy): @@ -138,16 +138,7 @@ def get_config(config=None): proxy['squidguard_conf'] = squidguard_config_file proxy['squidguard_db_dir'] = squidguard_db_dir - # XXX: T2665: blend in proper cache-peer default values later - default_values.pop('cache_peer') - proxy = dict_merge(default_values, proxy) - - # XXX: T2665: blend in proper cache-peer default values - if 'cache_peer' in proxy: - default_values = defaults(base + ['cache-peer']) - for peer in proxy['cache_peer']: - proxy['cache_peer'][peer] = dict_merge(default_values, - proxy['cache_peer'][peer]) + proxy = config_dict_merge(default_values, proxy) return proxy diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 9b7c04eb0..7882f8510 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -26,12 +26,11 @@ from vyos.snmpv3_hashgen import plaintext_to_md5 from vyos.snmpv3_hashgen import plaintext_to_sha1 from vyos.snmpv3_hashgen import random from vyos.template import render -from vyos.util import call -from vyos.util import chmod_755 -from vyos.util import dict_search -from vyos.validate import is_addr_assigned +from vyos.utils.process import call +from vyos.utils.permission import chmod_755 +from vyos.utils.dict import dict_search +from vyos.utils.network import is_addr_assigned from vyos.version import get_version_data -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -70,26 +69,9 @@ def get_config(config=None): # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - default_values = defaults(base) - - # We can not merge defaults for tagNodes - those need to be blended in - # per tagNode instance - if 'listen_address' in default_values: - del default_values['listen_address'] - if 'community' in default_values: - del default_values['community'] - if 'trap_target' in default_values: - del default_values['trap_target'] - if 'v3' in default_values: - del default_values['v3'] - snmp = dict_merge(default_values, snmp) + snmp = conf.merge_defaults(snmp, recursive=True) if 'listen_address' in snmp: - default_values = defaults(base + ['listen-address']) - for address in snmp['listen_address']: - snmp['listen_address'][address] = dict_merge( - default_values, snmp['listen_address'][address]) - # Always listen on localhost if an explicit address has been configured # This is a safety measure to not end up with invalid listen addresses # that are not configured on this system. See https://vyos.dev/T850 @@ -101,41 +83,6 @@ def get_config(config=None): tmp = {'::1': {'port': '161'}} snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) - if 'community' in snmp: - default_values = defaults(base + ['community']) - if 'network' in default_values: - # convert multiple default networks to list - default_values['network'] = default_values['network'].split() - for community in snmp['community']: - snmp['community'][community] = dict_merge( - default_values, snmp['community'][community]) - - if 'trap_target' in snmp: - default_values = defaults(base + ['trap-target']) - for trap in snmp['trap_target']: - snmp['trap_target'][trap] = dict_merge( - default_values, snmp['trap_target'][trap]) - - if 'v3' in snmp: - default_values = defaults(base + ['v3']) - # tagNodes need to be merged in individually later on - for tmp in ['user', 'group', 'trap_target']: - del default_values[tmp] - snmp['v3'] = dict_merge(default_values, snmp['v3']) - - for user_group in ['user', 'group']: - if user_group in snmp['v3']: - default_values = defaults(base + ['v3', user_group]) - for tmp in snmp['v3'][user_group]: - snmp['v3'][user_group][tmp] = dict_merge( - default_values, snmp['v3'][user_group][tmp]) - - if 'trap_target' in snmp['v3']: - default_values = defaults(base + ['v3', 'trap-target']) - for trap in snmp['v3']['trap_target']: - snmp['v3']['trap_target'][trap] = dict_merge( - default_values, snmp['v3']['trap_target'][trap]) - return snmp def verify(snmp): @@ -161,8 +108,12 @@ def verify(snmp): for address in snmp['listen_address']: # We only wan't to configure addresses that exist on the system. # Hint the user if they don't exist - if not is_addr_assigned(address): - Warning(f'SNMP listen address "{address}" not configured!') + if 'vrf' in snmp: + vrf_name = snmp['vrf'] + if not is_addr_assigned(address, vrf_name) and address not in ['::1','127.0.0.1']: + raise ConfigError(f'SNMP listen address "{address}" not configured in vrf "{vrf_name}"!') + elif not is_addr_assigned(address): + raise ConfigError(f'SNMP listen address "{address}" not configured in default vrf!') if 'trap_target' in snmp: for trap, trap_config in snmp['trap_target'].items(): diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 8de0617af..ee5e1eca2 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -21,12 +21,10 @@ from syslog import syslog from syslog import LOG_INFO from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf -from vyos.util import call +from vyos.utils.process import call from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -57,8 +55,8 @@ def get_config(config=None): # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. - default_values = defaults(base) - ssh = dict_merge(default_values, ssh) + ssh = conf.merge_defaults(ssh, recursive=True) + # pass config file path - used in override template ssh['config_file'] = config_file diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py index 95865c690..5e4e5ec28 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -20,11 +20,10 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_route_map from vyos.template import render_to_string -from vyos.util import call -from vyos.util import dict_search -from vyos.util import sysctl_write -from vyos.util import write_file -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.dict import dict_search +from vyos.utils.file import write_file +from vyos.utils.system import sysctl_write from vyos import ConfigError from vyos import frr from vyos import airbag @@ -37,11 +36,9 @@ def get_config(config=None): conf = Config() base = ['system', 'ip'] - opt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - opt = dict_merge(default_values, opt) + opt = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) # When working with FRR we need to know the corresponding address-family opt['afi'] = 'ip' @@ -64,8 +61,7 @@ def verify(opt): return def generate(opt): - if 'protocol' in opt: - opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) + opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) return def apply(opt): @@ -98,17 +94,37 @@ def apply(opt): value = '1' if (tmp != None) else '0' sysctl_write('net.ipv4.fib_multipath_hash_policy', value) - if 'protocol' in opt: - zebra_daemon = 'zebra' - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() - - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) - frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - if 'frr_zebra_config' in opt: - frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + # configure TCP options (defaults as of Linux 6.4) + tmp = dict_search('tcp.mss.probing', opt) + if tmp is None: + value = 0 + elif tmp == 'on-icmp-black-hole': + value = 1 + elif tmp == 'force': + value = 2 + else: + # Shouldn't happen + raise ValueError("TCP MSS probing is neither 'on-icmp-black-hole' nor 'force'!") + sysctl_write('net.ipv4.tcp_mtu_probing', value) + + tmp = dict_search('tcp.mss.base', opt) + value = '1024' if (tmp is None) else tmp + sysctl_write('net.ipv4.tcp_base_mss', value) + + tmp = dict_search('tcp.mss.floor', opt) + value = '48' if (tmp is None) else tmp + sysctl_write('net.ipv4.tcp_mtu_probe_floor', value) + + zebra_daemon = 'zebra' + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() + + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'ip protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') + if 'frr_zebra_config' in opt: + frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) + frr_cfg.commit_configuration(zebra_daemon) if __name__ == '__main__': try: diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index b6d3a79c3..e40ed38e2 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -21,10 +21,9 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_route_map from vyos.template import render_to_string -from vyos.util import dict_search -from vyos.util import sysctl_write -from vyos.util import write_file -from vyos.xml import defaults +from vyos.utils.dict import dict_search +from vyos.utils.system import sysctl_write +from vyos.utils.file import write_file from vyos import ConfigError from vyos import frr from vyos import airbag @@ -37,12 +36,9 @@ def get_config(config=None): conf = Config() base = ['system', 'ipv6'] - opt = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - opt = dict_merge(default_values, opt) + opt = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) # When working with FRR we need to know the corresponding address-family opt['afi'] = 'ipv6' @@ -65,8 +61,7 @@ def verify(opt): return def generate(opt): - if 'protocol' in opt: - opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) + opt['frr_zebra_config'] = render_to_string('frr/zebra.route-map.frr.j2', opt) return def apply(opt): @@ -98,17 +93,16 @@ def apply(opt): if name == 'accept_dad': write_file(os.path.join(root, name), value) - if 'protocol' in opt: - zebra_daemon = 'zebra' - # Save original configuration prior to starting any commit actions - frr_cfg = frr.FRRConfig() + zebra_daemon = 'zebra' + # Save original configuration prior to starting any commit actions + frr_cfg = frr.FRRConfig() - # The route-map used for the FIB (zebra) is part of the zebra daemon - frr_cfg.load_configuration(zebra_daemon) - frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') - if 'frr_zebra_config' in opt: - frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) - frr_cfg.commit_configuration(zebra_daemon) + # The route-map used for the FIB (zebra) is part of the zebra daemon + frr_cfg.load_configuration(zebra_daemon) + frr_cfg.modify_section(r'ipv6 protocol \w+ route-map [-a-zA-Z0-9.]+', stop_pattern='(\s|!)') + if 'frr_zebra_config' in opt: + frr_cfg.add_before(frr.default_add_before, opt['frr_zebra_config']) + frr_cfg.commit_configuration(zebra_daemon) if __name__ == '__main__': try: diff --git a/src/conf_mode/system-login-banner.py b/src/conf_mode/system-login-banner.py index a521c9834..65fa04417 100755 --- a/src/conf_mode/system-login-banner.py +++ b/src/conf_mode/system-login-banner.py @@ -18,7 +18,7 @@ from sys import exit from copy import deepcopy from vyos.config import Config -from vyos.util import write_file +from vyos.utils.file import write_file from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index fbb013cf3..02c97afaa 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -24,17 +24,16 @@ from sys import exit from time import sleep from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_vrf from vyos.defaults import directories from vyos.template import render from vyos.template import is_ipv4 -from vyos.util import cmd -from vyos.util import call, rc_cmd -from vyos.util import run -from vyos.util import DEVNULL -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.dict import dict_search +from vyos.utils.process import cmd +from vyos.utils.process import call +from vyos.utils.process import rc_cmd +from vyos.utils.process import run +from vyos.utils.process import DEVNULL from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,20 +41,38 @@ airbag.enable() autologout_file = "/etc/profile.d/autologout.sh" limits_file = "/etc/security/limits.d/10-vyos.conf" radius_config_file = "/etc/pam_radius_auth.conf" - +tacacs_pam_config_file = "/etc/tacplus_servers" +tacacs_nss_config_file = "/etc/tacplus_nss.conf" +nss_config_file = "/etc/nsswitch.conf" + +# Minimum UID used when adding system users +MIN_USER_UID: int = 1000 +# Maximim UID used when adding system users +MAX_USER_UID: int = 59999 # LOGIN_TIMEOUT from /etc/loign.defs minus 10 sec MAX_RADIUS_TIMEOUT: int = 50 # MAX_RADIUS_TIMEOUT divided by 2 sec (minimum recomended timeout) -MAX_RADIUS_COUNT: int = 25 +MAX_RADIUS_COUNT: int = 8 +# Maximum number of supported TACACS servers +MAX_TACACS_COUNT: int = 8 + +# List of local user accounts that must be preserved +SYSTEM_USER_SKIP_LIST: list = ['radius_user', 'radius_priv_user', 'tacacs0', 'tacacs1', + 'tacacs2', 'tacacs3', 'tacacs4', 'tacacs5', 'tacacs6', + 'tacacs7', 'tacacs8', 'tacacs9', 'tacacs10',' tacacs11', + 'tacacs12', 'tacacs13', 'tacacs14', 'tacacs15'] def get_local_users(): """Return list of dynamically allocated users (see Debian Policy Manual)""" local_users = [] for s_user in getpwall(): - uid = getpwnam(s_user.pw_name).pw_uid - if uid in range(1000, 29999): - if s_user.pw_name not in ['radius_user', 'radius_priv_user']: - local_users.append(s_user.pw_name) + if getpwnam(s_user.pw_name).pw_uid < MIN_USER_UID: + continue + if getpwnam(s_user.pw_name).pw_uid > MAX_USER_UID: + continue + if s_user.pw_name in SYSTEM_USER_SKIP_LIST: + continue + local_users.append(s_user.pw_name) return local_users @@ -74,7 +91,9 @@ def get_config(config=None): conf = Config() base = ['system', 'login'] login = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, get_first_key=True) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) # users no longer existing in the running configuration need to be deleted local_users = get_local_users() @@ -82,18 +101,9 @@ def get_config(config=None): if 'user' in login: cli_users = list(login['user']) - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - default_values = defaults(base + ['user']) - for user in login['user']: - login['user'][user] = dict_merge(default_values, login['user'][user]) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - default_values = defaults(base + ['radius', 'server']) - for server in dict_search('radius.server', login) or []: - login['radius']['server'][server] = dict_merge(default_values, - login['radius']['server'][server]) + # prune TACACS global defaults if not set by user + if login.from_defaults(['tacacs']): + del login['tacacs'] # create a list of all users, cli and users all_users = list(set(local_users + cli_users)) @@ -107,9 +117,13 @@ def get_config(config=None): def verify(login): if 'rm_users' in login: - cur_user = os.environ['SUDO_USER'] - if cur_user in login['rm_users']: - raise ConfigError(f'Attempting to delete current user: {cur_user}') + # This check is required as the script is also executed from vyos-router + # init script and there is no SUDO_USER environment variable available + # during system boot. + if 'SUDO_USER' in os.environ: + cur_user = os.environ['SUDO_USER'] + if cur_user in login['rm_users']: + raise ConfigError(f'Attempting to delete current user: {cur_user}') if 'user' in login: system_users = getpwall() @@ -117,7 +131,7 @@ def verify(login): # Linux system users range up until UID 1000, we can not create a # VyOS CLI user which already exists as system user for s_user in system_users: - if s_user.pw_name == user and s_user.pw_uid < 1000: + if s_user.pw_name == user and s_user.pw_uid < MIN_USER_UID: raise ConfigError(f'User "{user}" can not be created, conflict with local system account!') for pubkey, pubkey_options in (dict_search('authentication.public_keys', user_config) or {}).items(): @@ -126,6 +140,9 @@ def verify(login): if 'key' not in pubkey_options: raise ConfigError(f'Missing key for public-key "{pubkey}"!') + if {'radius', 'tacacs'} <= set(login): + raise ConfigError('Using both RADIUS and TACACS at the same time is not supported!') + # At lease one RADIUS server must not be disabled if 'radius' in login: if 'server' not in login['radius']: @@ -145,7 +162,7 @@ def verify(login): raise ConfigError('All RADIUS servers are disabled') if radius_servers_count > MAX_RADIUS_COUNT: - raise ConfigError('Number of RADIUS servers more than 25 ') + raise ConfigError(f'Number of RADIUS servers exceeded maximum of {MAX_RADIUS_COUNT}!') if sum_timeout > MAX_RADIUS_TIMEOUT: raise ConfigError('Sum of RADIUS servers timeouts ' @@ -165,6 +182,24 @@ def verify(login): if ipv6_count > 1: raise ConfigError('Only one IPv6 source-address can be set!') + if 'tacacs' in login: + tacacs_servers_count: int = 0 + fail = True + for server, server_config in dict_search('tacacs.server', login).items(): + if 'key' not in server_config: + raise ConfigError(f'TACACS server "{server}" requires key!') + if 'disable' not in server_config: + tacacs_servers_count += 1 + fail = False + + if fail: + raise ConfigError('All RADIUS servers are disabled') + + if tacacs_servers_count > MAX_TACACS_COUNT: + raise ConfigError(f'Number of TACACS servers exceeded maximum of {MAX_TACACS_COUNT}!') + + verify_vrf(login['tacacs']) + if 'max_login_session' in login and 'timeout' not in login: raise ConfigError('"login timeout" must be configured!') @@ -186,8 +221,8 @@ def generate(login): env['vyos_libexec_dir'] = directories['base'] # Set default commands for re-adding user with encrypted password - del_user_plain = f"system login user '{user}' authentication plaintext-password" - add_user_encrypt = f"system login user '{user}' authentication encrypted-password '{encrypted_password}'" + del_user_plain = f"system login user {user} authentication plaintext-password" + add_user_encrypt = f"system login user {user} authentication encrypted-password '{encrypted_password}'" lvl = env['VYATTA_EDIT_LEVEL'] # We're in config edit level, for example "edit system login" @@ -206,10 +241,10 @@ def generate(login): add_user_encrypt = add_user_encrypt[len(lvl):] add_user_encrypt = " ".join(add_user_encrypt) - call(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env) + ret, out = rc_cmd(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env) + if ret: raise ConfigError(out) ret, out = rc_cmd(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env) - if ret: - raise ConfigError(out) + if ret: raise ConfigError(out) else: try: if get_shadow_password(user) == dict_search('authentication.encrypted_password', user_config): @@ -223,6 +258,7 @@ def generate(login): except: pass + ### RADIUS based user authentication if 'radius' in login: render(radius_config_file, 'login/pam_radius_auth.conf.j2', login, permission=0o600, user='root', group='root') @@ -230,6 +266,24 @@ def generate(login): if os.path.isfile(radius_config_file): os.unlink(radius_config_file) + ### TACACS+ based user authentication + if 'tacacs' in login: + render(tacacs_pam_config_file, 'login/tacplus_servers.j2', login, + permission=0o644, user='root', group='root') + render(tacacs_nss_config_file, 'login/tacplus_nss.conf.j2', login, + permission=0o644, user='root', group='root') + else: + if os.path.isfile(tacacs_pam_config_file): + os.unlink(tacacs_pam_config_file) + if os.path.isfile(tacacs_nss_config_file): + os.unlink(tacacs_nss_config_file) + + + + # NSS must always be present on the system + render(nss_config_file, 'login/nsswitch.conf.j2', login, + permission=0o644, user='root', group='root') + # /etc/security/limits.d/10-vyos.conf if 'max_login_session' in login: render(limits_file, 'login/limits.j2', login, @@ -253,7 +307,7 @@ def apply(login): for user, user_config in login['user'].items(): # make new user using vyatta shell and make home directory (-m), # default group of 100 (users) - command = 'useradd --create-home --no-user-group' + command = 'useradd --create-home --no-user-group ' # check if user already exists: if user in get_local_users(): # update existing account @@ -317,44 +371,23 @@ def apply(login): # command until user is removed - userdel might return 8 as # SSH sessions are not all yet properly cleaned away, thus we # simply re-run the command until the account wen't away - while run(f'userdel --remove {user}', stderr=DEVNULL): + while run(f'userdel {user}', stderr=DEVNULL): sleep(0.250) except Exception as e: raise ConfigError(f'Deleting user "{user}" raised exception: {e}') - # - # RADIUS configuration - # - env = os.environ.copy() - env['DEBIAN_FRONTEND'] = 'noninteractive' - try: - if 'radius' in login: - # Enable RADIUS in PAM - cmd('pam-auth-update --package --enable radius', env=env) - # Make NSS system aware of RADIUS - # This fancy snipped was copied from old Vyatta code - command = "sed -i -e \'/\smapname/b\' \ - -e \'/^passwd:/s/\s\s*/&mapuid /\' \ - -e \'/^passwd:.*#/s/#.*/mapname &/\' \ - -e \'/^passwd:[^#]*$/s/$/ mapname &/\' \ - -e \'/^group:.*#/s/#.*/ mapname &/\' \ - -e \'/^group:[^#]*$/s/: */&mapname /\' \ - /etc/nsswitch.conf" - else: - # Disable RADIUS in PAM - cmd('pam-auth-update --package --remove radius', env=env) - # Drop RADIUS from NSS NSS system - # This fancy snipped was copied from old Vyatta code - command = "sed -i -e \'/^passwd:.*mapuid[ \t]/s/mapuid[ \t]//\' \ - -e \'/^passwd:.*[ \t]mapname/s/[ \t]mapname//\' \ - -e \'/^group:.*[ \t]mapname/s/[ \t]mapname//\' \ - -e \'s/[ \t]*$//\' \ - /etc/nsswitch.conf" - - cmd(command) - except Exception as e: - raise ConfigError(f'RADIUS configuration failed: {e}') + # Enable RADIUS in PAM configuration + pam_cmd = '--remove' + if 'radius' in login: + pam_cmd = '--enable' + cmd(f'pam-auth-update --package {pam_cmd} radius') + + # Enable/Disable TACACS in PAM configuration + pam_cmd = '--remove' + if 'tacacs' in login: + pam_cmd = '--enable' + cmd(f'pam-auth-update --package {pam_cmd} tacplus') return None diff --git a/src/conf_mode/system-logs.py b/src/conf_mode/system-logs.py index c71938a79..8ad4875d4 100755 --- a/src/conf_mode/system-logs.py +++ b/src/conf_mode/system-logs.py @@ -19,11 +19,9 @@ from sys import exit from vyos import ConfigError from vyos import airbag from vyos.config import Config -from vyos.configdict import dict_merge from vyos.logger import syslog from vyos.template import render -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.dict import dict_search airbag.enable() # path to logrotate configs @@ -38,11 +36,9 @@ def get_config(config=None): conf = Config() base = ['system', 'logs'] - default_values = defaults(base) - logs_config = conf.get_config_dict(base, - key_mangling=('-', '_'), - get_first_key=True) - logs_config = dict_merge(default_values, logs_config) + logs_config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return logs_config diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py index e6c7a0ed2..d92121b3d 100755 --- a/src/conf_mode/system-option.py +++ b/src/conf_mode/system-option.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# 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 @@ -21,14 +21,12 @@ from sys import exit from time import sleep from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_source_interface from vyos.template import render -from vyos.util import cmd -from vyos.util import is_systemd_service_running -from vyos.validate import is_addr_assigned -from vyos.validate import is_intf_addr_assigned -from vyos.xml import defaults +from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_running +from vyos.utils.network import is_addr_assigned +from vyos.utils.network import is_intf_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -36,6 +34,11 @@ airbag.enable() curlrc_config = r'/etc/curlrc' ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf' systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' +time_format_to_locale = { + '12-hour': 'en_US.UTF-8', + '24-hour': 'en_GB.UTF-8' +} + def get_config(config=None): if config: @@ -43,12 +46,9 @@ def get_config(config=None): else: conf = Config() base = ['system', 'option'] - options = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - options = dict_merge(default_values, options) + options = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return options @@ -143,6 +143,11 @@ def apply(options): else: cmd('systemctl disable root-partition-auto-resize.service') + # Time format 12|24-hour + if 'time_format' in options: + time_format = time_format_to_locale.get(options['time_format']) + cmd(f'localectl set-locale LC_TIME={time_format}') + if __name__ == '__main__': try: c = get_config() diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index cf34bad2e..07fbb0734 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -19,12 +19,10 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf -from vyos.util import call +from vyos.utils.process import call from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -46,59 +44,13 @@ def get_config(config=None): get_first_key=True, no_tag_node_value_mangle=True) syslog.update({ 'logrotate' : logrotate_conf }) + tmp = is_node_changed(conf, base + ['vrf']) if tmp: syslog.update({'restart_required': {}}) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - # XXX: some syslog default values can not be merged here (originating from - # a tagNode - remove and add them later per individual tagNode instance - if 'console' in default_values: - del default_values['console'] - for entity in ['global', 'user', 'host', 'file']: - if entity in default_values: - del default_values[entity] - - syslog = dict_merge(default_values, syslog) - - # XXX: add defaults for "console" tree - if 'console' in syslog and 'facility' in syslog['console']: - default_values = defaults(base + ['console', 'facility']) - for facility in syslog['console']['facility']: - syslog['console']['facility'][facility] = dict_merge(default_values, - syslog['console']['facility'][facility]) - - # XXX: add defaults for "host" tree - if 'host' in syslog: - default_values_host = defaults(base + ['host']) - if 'facility' in default_values_host: - del default_values_host['facility'] - default_values_facility = defaults(base + ['host', 'facility']) - - for host, host_config in syslog['host'].items(): - syslog['host'][host] = dict_merge(default_values_host, syslog['host'][host]) - if 'facility' in host_config: - for facility in host_config['facility']: - syslog['host'][host]['facility'][facility] = dict_merge(default_values_facility, - syslog['host'][host]['facility'][facility]) - - # XXX: add defaults for "user" tree - if 'user' in syslog: - default_values = defaults(base + ['user', 'facility']) - for user, user_config in syslog['user'].items(): - if 'facility' in user_config: - for facility in user_config['facility']: - syslog['user'][user]['facility'][facility] = dict_merge(default_values, - syslog['user'][user]['facility'][facility]) - - # XXX: add defaults for "file" tree - if 'file' in syslog: - default_values = defaults(base + ['file']) - for file, file_config in syslog['file'].items(): - for facility in file_config['facility']: - syslog['file'][file]['facility'][facility] = dict_merge(default_values, - syslog['file'][file]['facility'][facility]) + syslog = conf.merge_defaults(syslog, recursive=True) + if syslog.from_defaults(['global']): + del syslog['global'] return syslog diff --git a/src/conf_mode/system-timezone.py b/src/conf_mode/system-timezone.py index 3d98ba774..cd3d4b229 100755 --- a/src/conf_mode/system-timezone.py +++ b/src/conf_mode/system-timezone.py @@ -20,7 +20,7 @@ import os from copy import deepcopy from vyos.config import Config from vyos import ConfigError -from vyos.util import call +from vyos.utils.process import call from vyos import airbag airbag.enable() diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py index e922edc4e..ebf9a113b 100755 --- a/src/conf_mode/system_console.py +++ b/src/conf_mode/system_console.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -19,12 +19,10 @@ import re from pathlib import Path from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.util import call -from vyos.util import read_file -from vyos.util import write_file +from vyos.utils.process import call +from vyos.utils.file import read_file +from vyos.utils.file import write_file from vyos.template import render -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -45,16 +43,12 @@ def get_config(config=None): if 'device' not in console: return console - # convert CLI values to system values - default_values = defaults(base + ['device']) for device, device_config in console['device'].items(): if 'speed' not in device_config and device.startswith('hvc'): # XEN console has a different default console speed console['device'][device]['speed'] = 38400 - else: - # Merge in XML defaults - the proper way to do it - console['device'][device] = dict_merge(default_values, - console['device'][device]) + + console = conf.merge_defaults(console, recursive=True) return console diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py index 1af0055f6..fb252238a 100755 --- a/src/conf_mode/system_frr.py +++ b/src/conf_mode/system_frr.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -22,7 +22,9 @@ from vyos import airbag from vyos.config import Config from vyos.logger import syslog from vyos.template import render_to_string -from vyos.util import read_file, write_file, run +from vyos.utils.file import read_file +from vyos.utils.file import write_file +from vyos.utils.process import run airbag.enable() # path to daemons config and config status files diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py index 3341dd738..eb88224d1 100755 --- a/src/conf_mode/system_lcd.py +++ b/src/conf_mode/system_lcd.py @@ -19,8 +19,8 @@ import os from sys import exit from vyos.config import Config -from vyos.util import call -from vyos.util import find_device_file +from vyos.utils.process import call +from vyos.utils.system import find_device_file from vyos.template import render from vyos import ConfigError from vyos import airbag diff --git a/src/conf_mode/system_sflow.py b/src/conf_mode/system_sflow.py index a0c3fca7f..2df1bbb7a 100755 --- a/src/conf_mode/system_sflow.py +++ b/src/conf_mode/system_sflow.py @@ -19,11 +19,9 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge from vyos.template import render -from vyos.util import call -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,26 +40,9 @@ def get_config(config=None): if not conf.exists(base): return None - sflow = conf.get_config_dict(base, - key_mangling=('-', '_'), - get_first_key=True) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - - sflow = dict_merge(default_values, sflow) - - # Ignore default XML values if config doesn't exists - # Delete key from dict - if 'port' in sflow['server']: - del sflow['server']['port'] - - # Set default values per server - if 'server' in sflow: - for server in sflow['server']: - default_values = defaults(base + ['server']) - sflow['server'][server] = dict_merge(default_values, sflow['server'][server]) + sflow = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return sflow diff --git a/src/conf_mode/system_sysctl.py b/src/conf_mode/system_sysctl.py index 2e0004ffa..f6b02023d 100755 --- a/src/conf_mode/system_sysctl.py +++ b/src/conf_mode/system_sysctl.py @@ -20,7 +20,7 @@ from sys import exit from vyos.config import Config from vyos.template import render -from vyos.util import cmd +from vyos.utils.process import cmd from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/system_update_check.py b/src/conf_mode/system_update_check.py index 08ecfcb81..8d641a97d 100755 --- a/src/conf_mode/system_update_check.py +++ b/src/conf_mode/system_update_check.py @@ -22,7 +22,7 @@ from pathlib import Path from sys import exit from vyos.config import Config -from vyos.util import call +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index c5daccb7f..3ad346e2e 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -24,14 +24,12 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configverify import verify_vrf from vyos.template import render from vyos.template import is_ipv4 -from vyos.util import call -from vyos.util import chmod_755 -from vyos.validate import is_addr_assigned -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.permission import chmod_755 +from vyos.utils.network import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() @@ -48,11 +46,9 @@ def get_config(config=None): if not conf.exists(base): return None - tftpd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - tftpd = dict_merge(default_values, tftpd) + tftpd = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return tftpd def verify(tftpd): diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 63887b278..fa271cbdb 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -27,7 +27,7 @@ from vyos.base import Warning from vyos.config import Config from vyos.configdict import leaf_node_changed from vyos.configverify import verify_interface_exists -from vyos.configdict import dict_merge +from vyos.defaults import directories from vyos.ifconfig import Interface from vyos.pki import encode_public_key from vyos.pki import load_private_key @@ -39,12 +39,11 @@ from vyos.template import ip_from_cidr from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.template import render -from vyos.validate import is_ipv6_link_local -from vyos.util import call -from vyos.util import dict_search -from vyos.util import dict_search_args -from vyos.util import run -from vyos.xml import defaults +from vyos.utils.network import is_ipv6_link_local +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.process import call +from vyos.utils.process import run from vyos import ConfigError from vyos import airbag airbag.enable() @@ -69,7 +68,6 @@ KEY_PATH = f'{swanctl_dir}/private/' CA_PATH = f'{swanctl_dir}/x509ca/' CRL_PATH = f'{swanctl_dir}/x509crl/' -DHCP_BASE = '/var/lib/dhcp/dhclient' DHCP_HOOK_IFLIST = '/tmp/ipsec_dhcp_waiting' def get_config(config=None): @@ -84,88 +82,23 @@ def get_config(config=None): # retrieve common dictionary keys ipsec = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - # XXX: T2665: we must safely remove default values for tag nodes, those are - # added in a more fine grained way later on - del default_values['esp_group'] - del default_values['ike_group'] - del default_values['remote_access'] - del default_values['site_to_site'] - ipsec = dict_merge(default_values, ipsec) - - if 'esp_group' in ipsec: - default_values = defaults(base + ['esp-group']) - for group in ipsec['esp_group']: - ipsec['esp_group'][group] = dict_merge(default_values, - ipsec['esp_group'][group]) - if 'ike_group' in ipsec: - default_values = defaults(base + ['ike-group']) - # proposal is a tag node which may come with individual defaults per node - if 'proposal' in default_values: - del default_values['proposal'] - - for group in ipsec['ike_group']: - ipsec['ike_group'][group] = dict_merge(default_values, - ipsec['ike_group'][group]) - - if 'proposal' in ipsec['ike_group'][group]: - default_values = defaults(base + ['ike-group', 'proposal']) - for proposal in ipsec['ike_group'][group]['proposal']: - ipsec['ike_group'][group]['proposal'][proposal] = dict_merge(default_values, - ipsec['ike_group'][group]['proposal'][proposal]) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if dict_search('remote_access.connection', ipsec): - default_values = defaults(base + ['remote-access', 'connection']) - for rw in ipsec['remote_access']['connection']: - ipsec['remote_access']['connection'][rw] = dict_merge(default_values, - ipsec['remote_access']['connection'][rw]) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if dict_search('remote_access.radius.server', ipsec): - # Fist handle the "base" stuff like RADIUS timeout - default_values = defaults(base + ['remote-access', 'radius']) - if 'server' in default_values: - del default_values['server'] - ipsec['remote_access']['radius'] = dict_merge(default_values, - ipsec['remote_access']['radius']) - - # Take care about individual RADIUS servers implemented as tagNodes - this - # requires special treatment - default_values = defaults(base + ['remote-access', 'radius', 'server']) - for server in ipsec['remote_access']['radius']['server']: - ipsec['remote_access']['radius']['server'][server] = dict_merge(default_values, - ipsec['remote_access']['radius']['server'][server]) - - # XXX: T2665: we can not safely rely on the defaults() when there are - # tagNodes in place, it is better to blend in the defaults manually. - if dict_search('site_to_site.peer', ipsec): - default_values = defaults(base + ['site-to-site', 'peer']) - for peer in ipsec['site_to_site']['peer']: - ipsec['site_to_site']['peer'][peer] = dict_merge(default_values, - ipsec['site_to_site']['peer'][peer]) + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) ipsec['dhcp_no_address'] = {} ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface']) ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel']) ipsec['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) + no_tag_node_value_mangle=True, + get_first_key=True) tmp = conf.get_config_dict(l2tp_base, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True) + no_tag_node_value_mangle=True, + get_first_key=True) if tmp: - ipsec['l2tp'] = tmp - l2tp_defaults = defaults(l2tp_base) - ipsec['l2tp'] = dict_merge(l2tp_defaults, ipsec['l2tp']) + ipsec['l2tp'] = conf.merge_defaults(tmp, recursive=True) ipsec['l2tp_outside_address'] = conf.return_value(['vpn', 'l2tp', 'remote-access', 'outside-address']) ipsec['l2tp_ike_default'] = 'aes256-sha1-modp1024,3des-sha1-modp1024' ipsec['l2tp_esp_default'] = 'aes256-sha1,3des-sha1' @@ -433,8 +366,9 @@ def verify(ipsec): dhcp_interface = peer_conf['dhcp_interface'] verify_interface_exists(dhcp_interface) + dhcp_base = directories['isc_dhclient_dir'] - if not os.path.exists(f'{DHCP_BASE}_{dhcp_interface}.conf'): + if not os.path.exists(f'{dhcp_base}/dhclient_{dhcp_interface}.conf'): raise ConfigError(f"Invalid dhcp-interface on site-to-site peer {peer}") address = get_dhcp_address(dhcp_interface) @@ -455,7 +389,7 @@ def verify(ipsec): if dict_search('options.disable_route_autoinstall', ipsec) == None: - Warning('It\'s recommended to use ipsec vty with the next command\n[set vpn ipsec option disable-route-autoinstall]') + Warning('It\'s recommended to use ipsec vti with the next command\n[set vpn ipsec option disable-route-autoinstall]') if 'bind' in peer_conf['vti']: vti_interface = peer_conf['vti']['bind'] diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index ffac3b023..6232ce64a 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -26,10 +26,10 @@ from ipaddress import ip_network from vyos.config import Config from vyos.template import is_ipv4 from vyos.template import render -from vyos.util import call -from vyos.util import get_half_cpus -from vyos.util import check_port_availability -from vyos.util import is_listen_port_bind_service +from vyos.utils.process import call +from vyos.utils.system import get_half_cpus +from vyos.utils.network import check_port_availability +from vyos.utils.network import is_listen_port_bind_service from vyos import ConfigError from vyos import airbag diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index 83021a3e6..a039172c4 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -19,18 +19,16 @@ from sys import exit from vyos.base import Warning from vyos.config import Config -from vyos.configdict import dict_merge from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render -from vyos.util import call -from vyos.util import check_port_availability -from vyos.util import is_systemd_service_running -from vyos.util import is_listen_port_bind_service -from vyos.util import dict_search -from vyos.xml import defaults +from vyos.utils.process import call +from vyos.utils.network import check_port_availability +from vyos.utils.process import is_systemd_service_running +from vyos.utils.network import is_listen_port_bind_service +from vyos.utils.dict import dict_search from vyos import ConfigError -from crypt import crypt, mksalt, METHOD_SHA512 +from passlib.hash import sha512_crypt from time import sleep from vyos import airbag @@ -45,66 +43,7 @@ radius_servers = cfg_dir + '/radius_servers' # Generate hash from user cleartext password def get_hash(password): - return crypt(password, mksalt(METHOD_SHA512)) - - -def _default_dict_cleanup(origin: dict, default_values: dict) -> dict: - """ - https://vyos.dev/T2665 - Clear unnecessary key values in merged config by dict_merge function - :param origin: config - :type origin: dict - :param default_values: default values - :type default_values: dict - :return: merged dict - :rtype: dict - """ - if 'mode' in origin["authentication"] and "local" in \ - origin["authentication"]["mode"]: - del origin['authentication']['local_users']['username']['otp'] - if not origin["authentication"]["local_users"]["username"]: - raise ConfigError( - 'Openconnect authentication mode local requires at least one user') - default_ocserv_usr_values = \ - default_values['authentication']['local_users']['username']['otp'] - for user, params in origin['authentication']['local_users'][ - 'username'].items(): - # Not every configuration requires OTP settings - if origin['authentication']['local_users']['username'][user].get( - 'otp'): - origin['authentication']['local_users']['username'][user][ - 'otp'] = dict_merge(default_ocserv_usr_values, - origin['authentication'][ - 'local_users']['username'][user][ - 'otp']) - - if 'mode' in origin["authentication"] and "radius" in \ - origin["authentication"]["mode"]: - del origin['authentication']['radius']['server']['port'] - if not origin["authentication"]['radius']['server']: - raise ConfigError( - 'Openconnect authentication mode radius requires at least one RADIUS server') - default_values_radius_port = \ - default_values['authentication']['radius']['server']['port'] - for server, params in origin['authentication']['radius'][ - 'server'].items(): - if 'port' not in params: - params['port'] = default_values_radius_port - - if 'mode' in origin["accounting"] and "radius" in \ - origin["accounting"]["mode"]: - del origin['accounting']['radius']['server']['port'] - if not origin["accounting"]['radius']['server']: - raise ConfigError( - 'Openconnect accounting mode radius requires at least one RADIUS server') - default_values_radius_port = \ - default_values['accounting']['radius']['server']['port'] - for server, params in origin['accounting']['radius'][ - 'server'].items(): - if 'port' not in params: - params['port'] = default_values_radius_port - return origin - + return sha512_crypt.hash(password) def get_config(config=None): if config: @@ -115,16 +54,14 @@ def get_config(config=None): if not conf.exists(base): return None - ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - ocserv = dict_merge(default_values, ocserv) - # workaround a "know limitation" - https://vyos.dev/T2665 - ocserv = _default_dict_cleanup(ocserv, default_values) + ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + if ocserv: ocserv['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + no_tag_node_value_mangle=True, + get_first_key=True) return ocserv @@ -141,6 +78,8 @@ def verify(ocserv): # Check accounting if "accounting" in ocserv: if "mode" in ocserv["accounting"] and "radius" in ocserv["accounting"]["mode"]: + if not origin["accounting"]['radius']['server']: + raise ConfigError('Openconnect accounting mode radius requires at least one RADIUS server') if "authentication" not in ocserv or "mode" not in ocserv["authentication"]: raise ConfigError('Accounting depends on OpenConnect authentication configuration') elif "radius" not in ocserv["authentication"]["mode"]: @@ -149,9 +88,13 @@ def verify(ocserv): # Check authentication if "authentication" in ocserv: if "mode" in ocserv["authentication"]: - if "local" in ocserv["authentication"]["mode"]: - if "radius" in ocserv["authentication"]["mode"]: + if ("local" in ocserv["authentication"]["mode"] and + "radius" in ocserv["authentication"]["mode"]): raise ConfigError('OpenConnect authentication modes are mutually-exclusive, remove either local or radius from your configuration') + if "radius" in ocserv["authentication"]["mode"]: + if not ocserv["authentication"]['radius']['server']: + raise ConfigError('Openconnect authentication mode radius requires at least one RADIUS server') + if "local" in ocserv["authentication"]["mode"]: if not ocserv["authentication"]["local_users"]: raise ConfigError('openconnect mode local required at least one user') if not ocserv["authentication"]["local_users"]["username"]: diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index b9d18110a..d542f57fe 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -23,7 +23,8 @@ from sys import exit from vyos.config import Config from vyos.template import render -from vyos.util import call, get_half_cpus +from vyos.utils.system import get_half_cpus +from vyos.utils.process import call from vyos import ConfigError from vyos import airbag diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 2949ab290..e98d8385b 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -25,11 +25,11 @@ from vyos.configverify import verify_accel_ppp_base_service from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render -from vyos.util import call -from vyos.util import check_port_availability -from vyos.util import dict_search -from vyos.util import is_listen_port_bind_service -from vyos.util import write_file +from vyos.utils.process import call +from vyos.utils.network import check_port_availability +from vyos.utils.dict import dict_search +from vyos.utils.network import is_listen_port_bind_service +from vyos.utils.file import write_file from vyos import ConfigError from vyos import airbag airbag.enable() diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py new file mode 100755 index 000000000..82c2f236e --- /dev/null +++ b/src/conf_mode/vpp.py @@ -0,0 +1,207 @@ +#!/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 os +from psutil import virtual_memory + +from pathlib import Path +from re import search as re_search, MULTILINE as re_M + +from vyos.config import Config +from vyos.configdep import set_dependents, call_dependents +from vyos.configdict import node_changed +from vyos.ifconfig import Section +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.process import call +from vyos.utils.process import rc_cmd +from vyos.utils.system import sysctl_read +from vyos.utils.system import sysctl_apply +from vyos.template import render + +from vyos import ConfigError +from vyos import airbag +from vyos.vpp import VPPControl +from vyos.vpp import HostControl + +airbag.enable() + +service_name = 'vpp' +service_conf = Path(f'/run/vpp/{service_name}.conf') +systemd_override = '/run/systemd/system/vpp.service.d/10-override.conf' + +# Free memory required for VPP +# 2 GB for hugepages + 1 GB for other services +MIN_AVAILABLE_MEMORY: int = 3 * 1024**3 + + +def _get_pci_address_by_interface(iface) -> str: + rc, out = rc_cmd(f'ethtool -i {iface}') + # if ethtool command was successful + if rc == 0 and out: + regex_filter = r'^bus-info: (?P<address>\w+:\w+:\w+\.\w+)$' + re_obj = re_search(regex_filter, out, re_M) + # if bus-info with PCI address found + if re_obj: + address = re_obj.groupdict().get('address', '') + return address + # use VPP - maybe interface already attached to it + vpp_control = VPPControl(attempts=20, interval=500) + pci_addr = vpp_control.get_pci_addr(iface) + if pci_addr: + return pci_addr + # raise error if PCI address was not found + raise ConfigError(f'Cannot find PCI address for interface {iface}') + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['vpp'] + base_ethernet = ['interfaces', 'ethernet'] + + # find interfaces removed from VPP + removed_ifaces = [] + tmp = node_changed(conf, base + ['interface']) + if tmp: + for removed_iface in tmp: + pci_address: str = _get_pci_address_by_interface(removed_iface) + removed_ifaces.append({ + 'iface_name': removed_iface, + 'iface_pci_addr': pci_address + }) + # add an interface to a list of interfaces that need + # to be reinitialized after the commit + set_dependents('ethernet', conf, removed_iface) + + if not conf.exists(base): + return {'removed_ifaces': removed_ifaces} + + config = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, + get_first_key=True, + with_recursive_defaults=True) + + if 'interface' in config: + for iface, iface_config in config['interface'].items(): + # add an interface to a list of interfaces that need + # to be reinitialized after the commit + set_dependents('ethernet', conf, iface) + + # Get PCI address auto + if iface_config['pci'] == 'auto': + config['interface'][iface]['pci'] = _get_pci_address_by_interface(iface) + + config['other_interfaces'] = conf.get_config_dict(base_ethernet, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + if removed_ifaces: + config['removed_ifaces'] = removed_ifaces + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if not config or (len(config) == 1 and 'removed_ifaces' in config): + return None + + if 'interface' not in config: + raise ConfigError('"interface" is required but not set!') + + if 'cpu' in config: + if 'corelist_workers' in config['cpu'] and 'main_core' not in config[ + 'cpu']: + raise ConfigError('"cpu main-core" is required but not set!') + + memory_available: int = virtual_memory().available + if memory_available < MIN_AVAILABLE_MEMORY: + raise ConfigError( + 'Not enough free memory to start VPP:\n' + f'available: {round(memory_available / 1024**3, 1)}GB\n' + f'required: {round(MIN_AVAILABLE_MEMORY / 1024**3, 1)}GB') + + +def generate(config): + if not config or (len(config) == 1 and 'removed_ifaces' in config): + # Remove old config and return + service_conf.unlink(missing_ok=True) + return None + + render(service_conf, 'vpp/startup.conf.j2', config) + render(systemd_override, 'vpp/override.conf.j2', config) + + # apply default sysctl values from + # https://github.com/FDio/vpp/blob/v23.06/src/vpp/conf/80-vpp.conf + sysctl_config: dict[str, str] = { + 'vm.nr_hugepages': '1024', + 'vm.max_map_count': '3096', + 'vm.hugetlb_shm_group': '0', + 'kernel.shmmax': '2147483648' + } + # we do not want to reduce `kernel.shmmax` + kernel_shmnax_current: str = sysctl_read('kernel.shmmax') + if int(kernel_shmnax_current) > int(sysctl_config['kernel.shmmax']): + sysctl_config['kernel.shmmax'] = kernel_shmnax_current + + if not sysctl_apply(sysctl_config): + raise ConfigError('Cannot configure sysctl parameters for VPP') + + return None + + +def apply(config): + if not config or (len(config) == 1 and 'removed_ifaces' in config): + call(f'systemctl stop {service_name}.service') + else: + call('systemctl daemon-reload') + call(f'systemctl restart {service_name}.service') + + # Initialize interfaces removed from VPP + for iface in config.get('removed_ifaces', []): + host_control = HostControl() + # rescan PCI to use a proper driver + host_control.pci_rescan(iface['iface_pci_addr']) + # rename to the proper name + iface_new_name: str = host_control.get_eth_name(iface['iface_pci_addr']) + host_control.rename_iface(iface_new_name, iface['iface_name']) + + if 'interface' in config: + # connect to VPP + # must be performed multiple attempts because API is not available + # immediately after the service restart + vpp_control = VPPControl(attempts=20, interval=500) + for iface, _ in config['interface'].items(): + # Create lcp + if iface not in Section.interfaces(): + vpp_control.lcp_pair_add(iface, iface) + + # reinitialize interfaces, but not during the first boot + if boot_configuration_complete(): + call_dependents() + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 0b983293e..37625142c 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -26,13 +26,15 @@ from vyos.configverify import verify_route_map from vyos.ifconfig import Interface from vyos.template import render from vyos.template import render_to_string -from vyos.util import call -from vyos.util import cmd -from vyos.util import dict_search -from vyos.util import get_interface_config -from vyos.util import popen -from vyos.util import run -from vyos.util import sysctl_write +from vyos.utils.dict import dict_search +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_vrf_members +from vyos.utils.network import interface_exists +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import popen +from vyos.utils.process import run +from vyos.utils.system import sysctl_write from vyos import ConfigError from vyos import frr from vyos import airbag @@ -143,7 +145,7 @@ def verify(vrf): raise ConfigError(f'VRF "{name}" table id is mandatory!') # routing table id can't be changed - OS restriction - if os.path.isdir(f'/sys/class/net/{name}'): + if interface_exists(name): tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name))) if tmp and tmp != vrf_config['table']: raise ConfigError(f'VRF "{name}" table id modification not possible!') @@ -195,12 +197,23 @@ def apply(vrf): sysctl_write('net.ipv4.udp_l3mdev_accept', bind_all) for tmp in (dict_search('vrf_remove', vrf) or []): - if os.path.isdir(f'/sys/class/net/{tmp}'): - call(f'ip link delete dev {tmp}') + if interface_exists(tmp): + # T5492: deleting a VRF instance may leafe processes running + # (e.g. dhclient) as there is a depedency ordering issue in the CLI. + # We need to ensure that we stop the dhclient processes first so + # a proper DHCLP RELEASE message is sent + for interface in get_vrf_members(tmp): + vrf_iface = Interface(interface) + vrf_iface.set_dhcp(False) + vrf_iface.set_dhcpv6(False) + # Remove nftables conntrack zone map item nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{tmp}" }}' cmd(f'nft {nft_del_element}') + # Delete the VRF Kernel interface + call(f'ip link delete dev {tmp}') + if 'name' in vrf: # Separate VRFs in conntrack table # check if table already exists @@ -245,7 +258,7 @@ def apply(vrf): for name, config in vrf['name'].items(): table = config['table'] - if not os.path.isdir(f'/sys/class/net/{name}'): + if not interface_exists(name): # For each VRF apart from your default context create a VRF # interface with a separate routing table call(f'ip link add {name} type vrf table {table}') diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py index 9f33536e5..23b341079 100644 --- a/src/conf_mode/vrf_vni.py +++ b/src/conf_mode/vrf_vni.py @@ -19,7 +19,7 @@ from sys import exit from vyos.config import Config from vyos.template import render_to_string -from vyos.util import dict_search +from vyos.utils.dict import dict_search from vyos import ConfigError from vyos import frr from vyos import airbag diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/03-vyatta-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/03-vyos-dhclient-hook index 49bb18372..35721d009 100644 --- a/src/etc/dhcp/dhclient-exit-hooks.d/03-vyatta-dhclient-hook +++ b/src/etc/dhcp/dhclient-exit-hooks.d/03-vyos-dhclient-hook @@ -28,7 +28,8 @@ if [[ $reason =~ ^(REBOOT6|INIT6|EXPIRE6|RELEASE6|STOP6|INFORM6|BOUND6|REBIND6|D fi if [ "$RUN" = "yes" ]; then - LOG=/var/lib/dhcp/dhclient_"$interface"."$proto"lease + BASE_PATH=$(python3 -c "from vyos.defaults import directories; print(directories['isc_dhclient_dir'])") + LOG=${BASE_PATH}/dhclient_"$interface"."$proto"lease echo `date` > $LOG for i in reason interface new_expiry new_dhcp_lease_time medium \ diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook index 1f1926e17..c7a92fe26 100755 --- a/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook +++ b/src/etc/dhcp/dhclient-exit-hooks.d/99-ipsec-dhclient-hook @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. if [ "$reason" == "REBOOT" ] || [ "$reason" == "EXPIRE" ]; then - exit 0 + return 0 fi DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_waiting" @@ -24,22 +24,22 @@ if [ -f $DHCP_HOOK_IFLIST ] && [ "$reason" == "BOUND" ]; then if grep -qw $interface $DHCP_HOOK_IFLIST; then sudo rm $DHCP_HOOK_IFLIST sudo /usr/libexec/vyos/conf_mode/vpn_ipsec.py - exit 0 + return 0 fi fi if [ "$old_ip_address" == "$new_ip_address" ] && [ "$reason" == "BOUND" ]; then - exit 0 + return 0 fi python3 - <<PYEND import os import re -from vyos.util import call -from vyos.util import cmd -from vyos.util import read_file -from vyos.util import write_file +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.utils.file import write_file SWANCTL_CONF="/etc/swanctl/swanctl.conf" @@ -83,4 +83,4 @@ if __name__ == '__main__': call('sudo swanctl -q') exit(0) -PYEND
\ No newline at end of file +PYEND diff --git a/src/etc/ipsec.d/vti-up-down b/src/etc/ipsec.d/vti-up-down index 1ffb32955..9eb6fac48 100755 --- a/src/etc/ipsec.d/vti-up-down +++ b/src/etc/ipsec.d/vti-up-down @@ -25,9 +25,9 @@ from syslog import LOG_PID from syslog import LOG_INFO from vyos.configquery import ConfigTreeQuery -from vyos.util import call -from vyos.util import get_interface_config -from vyos.util import get_interface_address +from vyos.utils.process import call +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_interface_address if __name__ == '__main__': verb = os.getenv('PLUTO_VERB') diff --git a/src/etc/modprobe.d/openvpn.conf b/src/etc/modprobe.d/openvpn.conf new file mode 100644 index 000000000..a9259fea2 --- /dev/null +++ b/src/etc/modprobe.d/openvpn.conf @@ -0,0 +1 @@ +blacklist ovpn-dco-v2 diff --git a/src/etc/netplug/linkdown.d/dhclient b/src/etc/netplug/linkdown.d/dhclient deleted file mode 100755 index 555ff9134..000000000 --- a/src/etc/netplug/linkdown.d/dhclient +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/perl -# -# Module: dhclient -# -# **** License **** -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 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. -# -# A copy of the GNU General Public License is available as -# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution -# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'. -# You can also obtain it by writing to the Free Software Foundation, -# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# This code was originally developed by Vyatta, Inc. -# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc. -# All Rights Reserved. -# -# Author: Mohit Mehta -# Date: November 2008 -# Description: Script to release lease on link down -# -# **** End License **** -# - -use lib "/opt/vyatta/share/perl5/"; -use Vyatta::Config; -use Vyatta::Misc; - -use strict; -use warnings; - -sub stop_dhclient { - my $intf = shift; - my $dhcp_daemon = '/sbin/dhclient'; - my ($intf_config_file, $intf_process_id_file, $intf_leases_file) = Vyatta::Misc::generate_dhclient_intf_files($intf); - my $release_cmd = "sudo $dhcp_daemon -q -cf $intf_config_file -pf $intf_process_id_file -lf $intf_leases_file -r $intf 2> /dev/null;"; - $release_cmd .= "sudo rm -f $intf_process_id_file 2> /dev/null"; - system ($release_cmd); -} - - -# -# main -# - -my $dev=shift; - -# only do this if interface is configured to use dhcp for getting IP address -if (Vyatta::Misc::is_dhcp_enabled($dev, "outside_cli")) { - # do a dhcp lease release for interface - stop_dhclient($dev); -} - -exit 0; - -# end of file - diff --git a/src/etc/netplug/linkup.d/dhclient b/src/etc/netplug/linkup.d/dhclient deleted file mode 100755 index 8e50715fd..000000000 --- a/src/etc/netplug/linkup.d/dhclient +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/perl -# -# Module: dhclient -# -# **** License **** -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 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. -# -# A copy of the GNU General Public License is available as -# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution -# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'. -# You can also obtain it by writing to the Free Software Foundation, -# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# This code was originally developed by Vyatta, Inc. -# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc. -# All Rights Reserved. -# -# Author: Mohit Mehta -# Date: November 2008 -# Description: Script to renew lease on link up -# -# **** End License **** -# - -use lib "/opt/vyatta/share/perl5/"; -use Vyatta::Config; -use Vyatta::Misc; - -use strict; -use warnings; - -sub run_dhclient { - my $intf = shift; - my $dhcp_daemon = '/sbin/dhclient'; - my ($intf_config_file, $intf_process_id_file, $intf_leases_file) = Vyatta::Misc::generate_dhclient_intf_files($intf); - my $cmd = "sudo $dhcp_daemon -pf $intf_process_id_file -x $intf 2> /dev/null; sudo rm -f $intf_process_id_file 2> /dev/null;"; - $cmd .= "sudo $dhcp_daemon -q -nw -cf $intf_config_file -pf $intf_process_id_file -lf $intf_leases_file $intf 2> /dev/null &"; - system ($cmd); -} - -# -# main -# - -my $dev=shift; - -# only do this if interface is configured to use dhcp for getting IP address -if (Vyatta::Misc::is_dhcp_enabled($dev, "outside_cli")) { - # do a dhcp lease renew for interface - run_dhclient($dev); -} - -exit 0; - -# end of file - diff --git a/src/etc/netplug/linkup.d/vyos-python-helper b/src/etc/netplug/linkup.d/vyos-python-helper new file mode 100755 index 000000000..9c59c58ad --- /dev/null +++ b/src/etc/netplug/linkup.d/vyos-python-helper @@ -0,0 +1,4 @@ +#!/bin/sh +PYTHON3=$(which python3) +# Call the real python script and forward commandline arguments +$PYTHON3 /etc/netplug/vyos-netplug-dhcp-client "${@:1}" diff --git a/src/etc/netplug/netplug b/src/etc/netplug/netplug new file mode 100755 index 000000000..60b65e8c9 --- /dev/null +++ b/src/etc/netplug/netplug @@ -0,0 +1,41 @@ +#!/bin/sh +# +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +dev="$1" +action="$2" + +case "$action" in +in) + run-parts --arg $dev --arg in /etc/netplug/linkup.d + ;; +out) + run-parts --arg $dev --arg out /etc/netplug/linkdown.d + ;; + +# probe loads and initialises the driver for the interface and brings the +# interface into the "up" state, so that it can generate netlink(7) events. +# This interferes with "admin down" for an interface. Thus, commented out. An +# "admin up" is treated as a "link up" and thus, "link up" action is executed. +# To execute "link down" action on "admin down", run appropriate script in +# /etc/netplug/linkdown.d +#probe) +# ;; + +*) + exit 1 + ;; +esac diff --git a/src/etc/netplug/netplugd.conf b/src/etc/netplug/netplugd.conf new file mode 100644 index 000000000..7da3c67e8 --- /dev/null +++ b/src/etc/netplug/netplugd.conf @@ -0,0 +1,4 @@ +eth* +br* +bond* +wlan* diff --git a/src/etc/netplug/vyos-netplug-dhcp-client b/src/etc/netplug/vyos-netplug-dhcp-client new file mode 100755 index 000000000..55d15a163 --- /dev/null +++ b/src/etc/netplug/vyos-netplug-dhcp-client @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import sys + +from time import sleep + +from vyos.configquery import ConfigTreeQuery +from vyos.ifconfig import Section +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.commit import commit_in_progress +from vyos.utils.process import call +from vyos import airbag +airbag.enable() + +if len(sys.argv) < 3: + airbag.noteworthy("Must specify both interface and link status!") + sys.exit(1) + +if not boot_configuration_complete(): + airbag.noteworthy("System bootup not yet finished...") + sys.exit(1) + +while commit_in_progress(): + sleep(1) + +interface = sys.argv[1] +in_out = sys.argv[2] +config = ConfigTreeQuery() + +interface_path = ['interfaces'] + Section.get_config_path(interface).split() + +for _, interface_config in config.get_config_dict(interface_path).items(): + # Bail out early if we do not have an IP address configured + if 'address' not in interface_config: + continue + # Bail out early if interface ist administrative down + if 'disable' in interface_config: + continue + systemd_action = 'start' + if in_out == 'out': + systemd_action = 'stop' + # Start/Stop DHCP service + if 'dhcp' in interface_config['address']: + call(f'systemctl {systemd_action} dhclient@{interface}.service') + # Start/Stop DHCPv6 service + if 'dhcpv6' in interface_config['address']: + call(f'systemctl {systemd_action} dhcp6c@{interface}.service') diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py index 688c7af2a..f6f6d075c 100755 --- a/src/etc/opennhrp/opennhrp-script.py +++ b/src/etc/opennhrp/opennhrp-script.py @@ -23,8 +23,8 @@ from json import loads from pathlib import Path from vyos.logger import getLogger -from vyos.util import cmd -from vyos.util import process_named_running +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running NHRP_CONFIG: str = '/run/opennhrp/opennhrp.conf' diff --git a/src/etc/rsyslog.conf b/src/etc/rsyslog.conf index c28e9b537..9781f0835 100644 --- a/src/etc/rsyslog.conf +++ b/src/etc/rsyslog.conf @@ -52,8 +52,8 @@ $Umask 0022 # # Stop excessive logging of sudo # -:msg, contains, " pam_unix(sudo:session): session opened for user root(uid=0) by" ~ -:msg, contains, "pam_unix(sudo:session): session closed for user root" ~ +:msg, contains, " pam_unix(sudo:session): session opened for user root(uid=0) by" stop +:msg, contains, "pam_unix(sudo:session): session closed for user root" stop # # Include all config files in /etc/rsyslog.d/ diff --git a/src/etc/systemd/system-generators/vyos-generator b/src/etc/systemd/system-generators/vyos-generator new file mode 100755 index 000000000..34faab6a2 --- /dev/null +++ b/src/etc/systemd/system-generators/vyos-generator @@ -0,0 +1,94 @@ +#!/bin/sh +set -f + +LOG="" +DEBUG_LEVEL=1 +LOG_D="/run/vyos-router" +ENABLE="enabled" +DISABLE="disabled" +FOUND="found" +NOTFOUND="notfound" +RUN_ENABLED_FILE="$LOG_D/$ENABLE" +VYOS_SYSTEM_TARGET="/lib/systemd/system/vyos.target" +VYOS_TARGET_NAME="vyos.target" + +debug() { + local lvl="$1" + shift + [ "$lvl" -gt "$DEBUG_LEVEL" ] && return + if [ -z "$LOG" ]; then + local log="$LOG_D/${0##*/}.log" + { [ -d "$LOG_D" ] || mkdir -p "$LOG_D"; } && + { : > "$log"; } >/dev/null 2>&1 && LOG="$log" || + LOG="/dev/kmsg" + fi + echo "$@" >> "$LOG" +} + +default() { + _RET="$ENABLE" +} + +main() { + local normal_d="$1" early_d="$2" late_d="$3" + local target_name="multi-user.target" gen_d="$early_d" + local link_path="$gen_d/${target_name}.wants/${VYOS_TARGET_NAME}" + local ds="$NOTFOUND" + + debug 1 "$0 normal=$normal_d early=$early_d late=$late_d" + debug 2 "$0 $*" + + local search result="error" ret="" + for search in default; do + if $search; then + debug 1 "$search found $_RET" + [ "$_RET" = "$ENABLE" -o "$_RET" = "$DISABLE" ] && + result=$_RET && break + else + ret=$? + debug 0 "search $search returned $ret" + fi + done + + # enable AND ds=found == enable + # enable AND ds=notfound == disable + # disable || <any> == disabled + if [ "$result" = "$ENABLE" ]; then + if [ -e "$link_path" ]; then + debug 1 "already enabled: no change needed" + else + [ -d "${link_path%/*}" ] || mkdir -p "${link_path%/*}" || + debug 0 "failed to make dir $link_path" + if ln -snf "$VYOS_SYSTEM_TARGET" "$link_path"; then + debug 1 "enabled via $link_path -> $VYOS_SYSTEM_TARGET" + else + ret=$? + debug 0 "[$ret] enable failed:" \ + "ln $VYOS_SYSTEM_TARGET $link_path" + fi + fi + : > "$RUN_ENABLED_FILE" + elif [ "$result" = "$DISABLE" ]; then + if [ -f "$link_path" ]; then + if rm -f "$link_path"; then + debug 1 "disabled. removed existing $link_path" + else + ret=$? + debug 0 "[$ret] disable failed, remove $link_path" + fi + else + debug 1 "already disabled: no change needed [no $link_path]" + fi + if [ -e "$RUN_ENABLED_FILE" ]; then + rm -f "$RUN_ENABLED_FILE" + fi + else + debug 0 "unexpected result '$result' 'ds=$ds'" + ret=3 + fi + return $ret +} + +main "$@" + +# vi: ts=4 expandtab diff --git a/src/etc/systemd/system/ddclient.service.d/override.conf b/src/etc/systemd/system/ddclient.service.d/override.conf deleted file mode 100644 index 09d929d39..000000000 --- a/src/etc/systemd/system/ddclient.service.d/override.conf +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -After= -After=vyos-router.service - -[Service] -WorkingDirectory= -WorkingDirectory=/run/ddclient -PIDFile= -PIDFile=/run/ddclient/ddclient.pid -ExecStart= -ExecStart=/usr/bin/ddclient -cache /run/ddclient/ddclient.cache -pid /run/ddclient/ddclient.pid -file /run/ddclient/ddclient.conf diff --git a/src/etc/systemd/system/frr.service.d/override.conf b/src/etc/systemd/system/frr.service.d/override.conf index 2e4b6e295..094f83551 100644 --- a/src/etc/systemd/system/frr.service.d/override.conf +++ b/src/etc/systemd/system/frr.service.d/override.conf @@ -1,7 +1,3 @@ -[Unit] -Before= -Before=vyos-router.service - [Service] LimitNOFILE=4096 ExecStartPre=/bin/bash -c 'mkdir -p /run/frr/config; \ diff --git a/src/etc/systemd/system/getty@.service.d/aftervyos.conf b/src/etc/systemd/system/getty@.service.d/aftervyos.conf new file mode 100644 index 000000000..c5753900e --- /dev/null +++ b/src/etc/systemd/system/getty@.service.d/aftervyos.conf @@ -0,0 +1,3 @@ +[Service] +ExecStartPre=-/usr/libexec/vyos/init/vyos-config +StandardOutput=journal+console diff --git a/src/etc/systemd/system/serial-getty@.service.d/aftervyos.conf b/src/etc/systemd/system/serial-getty@.service.d/aftervyos.conf new file mode 100644 index 000000000..8ba42778d --- /dev/null +++ b/src/etc/systemd/system/serial-getty@.service.d/aftervyos.conf @@ -0,0 +1,3 @@ +[Service] +ExecStartPre=-/usr/libexec/vyos/init/vyos-config SERIAL +StandardOutput=journal+console diff --git a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py index d7eca5894..bb7515a90 100755 --- a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py +++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py @@ -4,7 +4,7 @@ import json import re import time -from vyos.util import cmd +from vyos.utils.process import cmd def get_nft_filter_chains(): diff --git a/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py b/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py index df4eed131..00f2f184c 100755 --- a/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py +++ b/src/etc/telegraf/custom_scripts/vyos_services_input_filter.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -17,7 +17,8 @@ import time from vyos.configquery import ConfigTreeQuery -from vyos.util import is_systemd_service_running, process_named_running +from vyos.utils.process import is_systemd_service_running +from vyos.utils.process import process_named_running # Availible services and prouceses # 1 - service diff --git a/src/etc/vmware-tools/scripts/resume-vm-default.d/ether-resume.py b/src/etc/vmware-tools/scripts/resume-vm-default.d/ether-resume.py index 4e7fb117c..7da57bca8 100755 --- a/src/etc/vmware-tools/scripts/resume-vm-default.d/ether-resume.py +++ b/src/etc/vmware-tools/scripts/resume-vm-default.d/ether-resume.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2021 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -17,9 +17,9 @@ import sys import syslog -from vyos.config import Config from vyos import ConfigError -from vyos.util import run +from vyos.config import Config +from vyos.utils.process import run def get_config(): c = Config() diff --git a/src/helpers/commit-confirm-notify.py b/src/helpers/commit-confirm-notify.py index eb7859ffa..8d7626c78 100755 --- a/src/helpers/commit-confirm-notify.py +++ b/src/helpers/commit-confirm-notify.py @@ -17,6 +17,7 @@ def notify(interval): if __name__ == "__main__": # Must be run as root to call wall(1) without a banner. if len(sys.argv) != 2 or os.getuid() != 0: + print('This script requires superuser privileges.', file=sys.stderr) exit(1) minutes = int(sys.argv[1]) # Drop the argument from the list so that the notification diff --git a/src/helpers/run-config-migration.py b/src/helpers/run-config-migration.py index cc7166c22..ce647ad0a 100755 --- a/src/helpers/run-config-migration.py +++ b/src/helpers/run-config-migration.py @@ -20,7 +20,7 @@ import sys import argparse import datetime -from vyos.util import cmd +from vyos.utils.process import cmd from vyos.migrator import Migrator, VirtualMigrator def main(): diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py index b9cc87bfa..01b06526d 100755 --- a/src/helpers/vyos-boot-config-loader.py +++ b/src/helpers/vyos-boot-config-loader.py @@ -26,7 +26,7 @@ from datetime import datetime from vyos.defaults import directories, config_status from vyos.configsession import ConfigSession, ConfigSessionError from vyos.configtree import ConfigTree -from vyos.util import cmd +from vyos.utils.process import cmd STATUS_FILE = config_status TRACE_FILE = '/tmp/boot-config-trace' diff --git a/src/helpers/vyos-check-wwan.py b/src/helpers/vyos-check-wwan.py index 2ff9a574f..334f08dd3 100755 --- a/src/helpers/vyos-check-wwan.py +++ b/src/helpers/vyos-check-wwan.py @@ -17,7 +17,7 @@ from vyos.configquery import VbashOpRun from vyos.configquery import ConfigTreeQuery -from vyos.util import is_wwan_connected +from vyos.utils.network import is_wwan_connected conf = ConfigTreeQuery() dict = conf.get_config_dict(['interfaces', 'wwan'], key_mangling=('-', '_'), diff --git a/src/helpers/vyos-domain-resolver.py b/src/helpers/vyos-domain-resolver.py index e31d9238e..7e2fe2462 100755 --- a/src/helpers/vyos-domain-resolver.py +++ b/src/helpers/vyos-domain-resolver.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -22,11 +22,11 @@ from vyos.configdict import dict_merge from vyos.configquery import ConfigTreeQuery from vyos.firewall import fqdn_config_parse from vyos.firewall import fqdn_resolve -from vyos.util import cmd -from vyos.util import commit_in_progress -from vyos.util import dict_search_args -from vyos.util import run -from vyos.xml import defaults +from vyos.utils.commit import commit_in_progress +from vyos.utils.dict import dict_search_args +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.xml_ref import get_defaults base = ['firewall'] timeout = 300 @@ -49,13 +49,7 @@ def get_config(conf): firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - default_values = defaults(base) - for tmp in ['name', 'ipv6_name']: - if tmp in default_values: - del default_values[tmp] - - if 'zone' in default_values: - del default_values['zone'] + default_values = get_defaults(base, get_first_key=True) firewall = dict_merge(default_values, firewall) diff --git a/src/helpers/vyos-failover.py b/src/helpers/vyos-failover.py index ce4cf8fa4..cc7610370 100755 --- a/src/helpers/vyos-failover.py +++ b/src/helpers/vyos-failover.py @@ -20,7 +20,7 @@ import subprocess import socket import time -from vyos.util import rc_cmd +from vyos.utils.process import rc_cmd from pathlib import Path from systemd import journal diff --git a/src/helpers/vyos-interface-rescan.py b/src/helpers/vyos-interface-rescan.py index 1ac1810e0..012357259 100755 --- a/src/helpers/vyos-interface-rescan.py +++ b/src/helpers/vyos-interface-rescan.py @@ -24,7 +24,7 @@ import netaddr from vyos.configtree import ConfigTree from vyos.defaults import directories -from vyos.util import get_cfg_group_id +from vyos.utils.permission import get_cfg_group_id debug = False diff --git a/src/helpers/vyos-merge-config.py b/src/helpers/vyos-merge-config.py index 14df2734b..8997705fe 100755 --- a/src/helpers/vyos-merge-config.py +++ b/src/helpers/vyos-merge-config.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2023 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 @@ -20,11 +20,12 @@ import os import tempfile import vyos.defaults import vyos.remote + from vyos.config import Config from vyos.configtree import ConfigTree from vyos.migrator import Migrator, VirtualMigrator -from vyos.util import cmd, DEVNULL - +from vyos.utils.process import cmd +from vyos.utils.process import DEVNULL if (len(sys.argv) < 2): print("Need config file name to merge.") diff --git a/src/helpers/vyos-save-config.py b/src/helpers/vyos-save-config.py new file mode 100755 index 000000000..2812155e8 --- /dev/null +++ b/src/helpers/vyos-save-config.py @@ -0,0 +1,55 @@ +#!/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 os +import re +import sys +from tempfile import NamedTemporaryFile + +from vyos.config import Config +from vyos.remote import urlc +from vyos.component_version import system_footer +from vyos.defaults import directories + +DEFAULT_CONFIG_PATH = os.path.join(directories['config'], 'config.boot') +remote_save = None + +if len(sys.argv) > 1: + save_file = sys.argv[1] +else: + save_file = DEFAULT_CONFIG_PATH + +if re.match(r'\w+:/', save_file): + try: + remote_save = urlc(save_file) + except ValueError as e: + sys.exit(e) + +config = Config() +ct = config.get_config_tree(effective=True) + +write_file = save_file if remote_save is None else NamedTemporaryFile(delete=False).name +with open(write_file, 'w') as f: + f.write(ct.to_string()) + f.write("\n") + f.write(system_footer()) + +if remote_save is not None: + try: + remote_save.upload(write_file) + finally: + os.remove(write_file) diff --git a/src/helpers/vyos-sudo.py b/src/helpers/vyos-sudo.py index 3e4c196d9..75dd7f29d 100755 --- a/src/helpers/vyos-sudo.py +++ b/src/helpers/vyos-sudo.py @@ -18,7 +18,7 @@ import os import sys -from vyos.util import is_admin +from vyos.utils.permission import is_admin if __name__ == '__main__': diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py new file mode 100755 index 000000000..7cfa8fe88 --- /dev/null +++ b/src/helpers/vyos_config_sync.py @@ -0,0 +1,192 @@ +#!/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 os +import json +import requests +import urllib3 +import logging +from typing import Optional, List, Union, Dict, Any + +from vyos.config import Config +from vyos.template import bracketize_ipv6 + + +CONFIG_FILE = '/run/config_sync_conf.conf' + +# Logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) +logger.name = os.path.basename(__file__) + +# API +API_HEADERS = {'Content-Type': 'application/json'} + + +def post_request(url: str, + data: str, + headers: Dict[str, str]) -> requests.Response: + """Sends a POST request to the specified URL + + Args: + url (str): The URL to send the POST request to. + data (Dict[str, Any]): The data to send with the POST request. + headers (Dict[str, str]): The headers to include with the POST request. + + Returns: + requests.Response: The response object representing the server's response to the request + """ + + response = requests.post(url, + data=data, + headers=headers, + verify=False, + timeout=timeout) + return response + + +def retrieve_config(section: str = None) -> Optional[Dict[str, Any]]: + """Retrieves the configuration from the local server. + + Args: + section: str: The section of the configuration to retrieve. Default is None. + + Returns: + Optional[Dict[str, Any]]: The retrieved configuration as a dictionary, or None if an error occurred. + """ + if section is None: + section = [] + else: + section = section.split() + + conf = Config() + config = conf.get_config_dict(section, get_first_key=True) + if config: + return config + return None + + +def set_remote_config( + address: str, + key: str, + op: str, + path: str = None, + section: Optional[str] = None) -> Optional[Dict[str, Any]]: + """Loads the VyOS configuration in JSON format to a remote host. + + Args: + address (str): The address of the remote host. + key (str): The key to use for loading the configuration. + path (Optional[str]): The path of the configuration. Default is None. + section (Optional[str]): The section of the configuration to load. Default is None. + + Returns: + Optional[Dict[str, Any]]: The response from the remote host as a dictionary, or None if an error occurred. + """ + + if path is None: + path = [] + else: + path = path.split() + headers = {'Content-Type': 'application/json'} + + # Disable the InsecureRequestWarning + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + url = f'https://{address}/configure-section' + data = json.dumps({ + 'op': mode, + 'path': path, + 'section': section, + 'key': key + }) + + try: + config = post_request(url, data, headers) + return config.json() + except requests.exceptions.RequestException as e: + print(f"An error occurred: {e}") + logger.error(f"An error occurred: {e}") + return None + + +def is_section_revised(section: str) -> bool: + from vyos.config_mgmt import is_node_revised + return is_node_revised([section]) + + +def config_sync(secondary_address: str, + secondary_key: str, + sections: List[str], + mode: str): + """Retrieve a config section from primary router in JSON format and send it to + secondary router + """ + # Config sync only if sections changed + if not any(map(is_section_revised, sections)): + return + + logger.info( + f"Config synchronization: Mode={mode}, Secondary={secondary_address}" + ) + + # Sync sections ("nat", "firewall", etc) + for section in sections: + config_json = retrieve_config(section=section) + # Check if config path deesn't exist, for example "set nat" + # we set empty value for config_json data + # As we cannot send to the remote host section "nat None" config + if not config_json: + config_json = "" + logger.debug( + f"Retrieved config for section '{section}': {config_json}") + set_config = set_remote_config(address=secondary_address, + key=secondary_key, + op=mode, + path=section, + section=config_json) + logger.debug(f"Set config for section '{section}': {set_config}") + + +if __name__ == '__main__': + # Read configuration from file + if not os.path.exists(CONFIG_FILE): + logger.error(f"Post-commit: No config file '{CONFIG_FILE}' exists") + exit(0) + + with open(CONFIG_FILE, 'r') as f: + config_data = f.read() + + config = json.loads(config_data) + + mode = config.get('mode') + secondary_address = config.get('secondary', {}).get('address') + secondary_address = bracketize_ipv6(secondary_address) + secondary_key = config.get('secondary', {}).get('key') + sections = config.get('section') + timeout = int(config.get('secondary', {}).get('timeout')) + + if not all([ + mode, secondary_address, secondary_key, sections + ]): + logger.error( + "Missing required configuration data for config synchronization.") + exit(0) + + config_sync(secondary_address, secondary_key, + sections, mode) diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name index 1798e92db..8c0992414 100755 --- a/src/helpers/vyos_net_name +++ b/src/helpers/vyos_net_name @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -13,8 +13,6 @@ # # 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 os import re @@ -26,7 +24,8 @@ from sys import argv from vyos.configtree import ConfigTree from vyos.defaults import directories -from vyos.util import cmd, boot_configuration_complete +from vyos.utils.process import cmd +from vyos.utils.boot import boot_configuration_complete from vyos.migrator import VirtualMigrator vyos_udev_dir = directories['vyos_udev_dir'] diff --git a/src/init/vyos-config b/src/init/vyos-config new file mode 100755 index 000000000..356427024 --- /dev/null +++ b/src/init/vyos-config @@ -0,0 +1,16 @@ +#!/bin/bash + +while [ ! -f /tmp/vyos-config-status ] +do + sleep 1 +done + +status=$(cat /tmp/vyos-config-status) + +if [ -z "$1" ]; then + if [ $status -ne 0 ]; then + echo "Configuration error" + else + echo "Configuration success" + fi +fi diff --git a/src/init/vyos-router b/src/init/vyos-router new file mode 100755 index 000000000..96f163213 --- /dev/null +++ b/src/init/vyos-router @@ -0,0 +1,445 @@ +#!/bin/bash +# Copyright (C) 2021 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/>. + +. /lib/lsb/init-functions + +: ${vyatta_env:=/etc/default/vyatta} +source $vyatta_env + +declare progname=${0##*/} +declare action=$1; shift + +declare -x BOOTFILE=$vyatta_sysconfdir/config/config.boot + +# If vyos-config= boot option is present, use that file instead +for x in $(cat /proc/cmdline); do + [[ $x = vyos-config=* ]] || continue + VYOS_CONFIG="${x#vyos-config=}" +done + +if [ ! -z "$VYOS_CONFIG" ]; then + if [ -r "$VYOS_CONFIG" ]; then + echo "Config selected manually: $VYOS_CONFIG" + declare -x BOOTFILE="$VYOS_CONFIG" + else + echo "WARNING: Could not read selected config file, using default!" + fi +fi + +declare -a subinit +declare -a all_subinits=( firewall ) + +if [ $# -gt 0 ] ; then + for s in $@ ; do + [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s + done +else + for s in ${all_subinits[@]} ; do + [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s + done +fi + +GROUP=vyattacfg + +# easy way to make empty file without any command +empty() +{ + >$1 +} + +# check if bootup of this portion is disabled +disabled () { + grep -q -w no-vyos-$1 /proc/cmdline +} + +# if necessary, provide initial config +init_bootfile () { + if [ ! -r $BOOTFILE ] ; then + if [ -f $vyatta_sysconfdir/config.boot.default ]; then + cp $vyatta_sysconfdir/config.boot.default $BOOTFILE + else + $vyos_libexec_dir/system-versions-foot.py > $BOOTFILE + fi + chgrp ${GROUP} $BOOTFILE + chmod 660 $BOOTFILE + fi +} + +# if necessary, migrate initial config +migrate_bootfile () +{ + if [ -x $vyos_libexec_dir/run-config-migration.py ]; then + log_progress_msg migrate + sg ${GROUP} -c "$vyos_libexec_dir/run-config-migration.py $BOOTFILE" + fi +} + +# load the initial config +load_bootfile () +{ + log_progress_msg configure + ( + if [ -f /etc/default/vyatta-load-boot ]; then + # build-specific environment for boot-time config loading + source /etc/default/vyatta-load-boot + fi + if [ -x $vyos_libexec_dir/vyos-boot-config-loader.py ]; then + sg ${GROUP} -c "$vyos_libexec_dir/vyos-boot-config-loader.py $BOOTFILE" + fi + ) +} + +# restore if missing pre-config script +restore_if_missing_preconfig_script () +{ + if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script ]; then + cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/ + chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script + chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script + fi +} + +# execute the pre-config script +run_preconfig_script () +{ + if [ -x $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script ]; then + $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script + fi +} + +# restore if missing post-config script +restore_if_missing_postconfig_script () +{ + if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script ]; then + cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/ + chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script + chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script + fi +} + +# execute the post-config scripts +run_postconfig_scripts () +{ + if [ -x $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script ]; then + $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script + fi + if [ -x $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script ]; then + $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script + fi +} + +run_postupgrade_script () +{ + if [ -f $vyatta_sysconfdir/config/.upgraded ]; then + # Run the system script + /usr/libexec/vyos/system/post-upgrade + + # Run user scripts + if [ -d $vyatta_sysconfdir/config/scripts/post-upgrade.d ]; then + run-parts $vyatta_sysconfdir/config/scripts/post-upgrade.d + fi + rm -f $vyatta_sysconfdir/config/.upgraded + fi +} + +# +# On image booted machines, we need to mount /boot from the image-specific +# boot directory so that kernel package installation will put the +# files in the right place. We also have to mount /boot/grub from the +# system-wide grub directory so that tools that edit the grub.cfg +# file will find it in the expected location. +# +bind_mount_boot () +{ + persist_path=$(/opt/vyatta/sbin/vyos-persistpath) + if [ $? == 0 ]; then + if [ -e $persist_path/boot ]; then + image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//') + + if [ -n "$image_name" ]; then + mount --bind $persist_path/boot/$image_name /boot + if [ $? -ne 0 ]; then + echo "Couldn't bind mount /boot" + fi + + if [ ! -d /boot/grub ]; then + mkdir /boot/grub + fi + + mount --bind $persist_path/boot/grub /boot/grub + if [ $? -ne 0 ]; then + echo "Couldn't bind mount /boot/grub" + fi + fi + fi + fi +} + +clear_or_override_config_files () +{ + for conf in snmp/snmpd.conf snmp/snmptrapd.conf snmp/snmp.conf \ + keepalived/keepalived.conf cron.d/vyos-crontab \ + ipvsadm.rules default/ipvsadm resolv.conf + do + if [ -s /etc/$conf ] ; then + empty /etc/$conf + chmod 0644 /etc/$conf + fi + done +} + +update_interface_config () +{ + if [ -d /run/udev/vyos ]; then + $vyos_libexec_dir/vyos-interface-rescan.py $BOOTFILE + fi +} + +cleanup_post_commit_hooks () { + # Remove links from the post-commit hooks directory. + # note that this approach only supports hooks that are "configured", + # i.e., it does not support hooks that need to always be present. + cpostdir=$(cli-shell-api getPostCommitHookDir) + # exclude commits hooks from vyatta-cfg + excluded="10vyatta-log-commit.pl 99vyos-user-postcommit-hooks" + if [ -d "$cpostdir" ]; then + for f in $cpostdir/*; do + if [[ ! $excluded =~ $(basename $f) ]]; then + rm -f $cpostdir/$(basename $f) + fi + done + fi +} + +# These are all the default security setting which are later +# overridden when configuration is read. These are the values the +# system defaults. +security_reset () +{ + # restore PAM back to virgin state (no radius/tacacs services) + pam-auth-update --package --remove radius + rm -f /etc/pam_radius_auth.conf + pam-auth-update --package --remove tacplus + rm -f /etc/tacplus_nss.conf /etc/tacplus_servers + + # Certain configuration files are re-generated by the configuration + # subsystem and must reside under /etc and can not easily be moved to /run. + # So on every boot we simply delete any remaining files and let the CLI + # regenearte them. + + # PPPoE + rm -f /etc/ppp/peers/pppoe* /etc/ppp/peers/wlm* + + # IPSec + rm -rf /etc/ipsec.conf /etc/ipsec.secrets + find /etc/swanctl -type f | xargs rm -f + + # limit cleanup + rm -f /etc/security/limits.d/10-vyos.conf + + # iproute2 cleanup + rm -f /etc/iproute2/rt_tables.d/vyos-*.conf + + # Container + rm -f /etc/containers/storage.conf /etc/containers/registries.conf /etc/containers/containers.conf + # Clean all networks and re-create them from our CLI + rm -f /etc/containers/networks/* + + # System Options (SSH/cURL) + rm -f /etc/ssh/ssh_config.d/*vyos*.conf + rm -f /etc/curlrc +} + +# XXX: T3885 - generate persistend DHCPv6 DUID (Type4 - UUID based) +gen_duid () +{ + DUID_FILE="/var/lib/dhcpv6/dhcp6c_duid" + UUID_FILE="/sys/class/dmi/id/product_uuid" + UUID_FILE_ALT="/sys/class/dmi/id/product_serial" + if [ ! -f ${UUID_FILE} ] && [ ! -f ${UUID_FILE_ALT} ]; then + return 1 + fi + + # DUID is based on the BIOS/EFI UUID. We omit additional - characters + if [ -f ${UUID_FILE} ]; then + UUID=$(cat ${UUID_FILE} | tr -d -) + fi + if [ -z ${UUID} ]; then + UUID=$(uuidgen --sha1 --namespace @dns --name $(cat ${UUID_FILE_ALT}) | tr -d -) + fi + # Add DUID type4 (UUID) information + DUID_TYPE="0004" + + # The length-information (as per RFC6355 UUID is 128 bits long) is in big-endian + # format - beware when porting to ARM64. The length field consists out of the + # UUID (128 bit + 16 bits DUID type) resulting in hex 12. + DUID_LEN="0012" + if [ "$(echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6 )" -eq 1 ]; then + # true on little-endian (x86) systems + DUID_LEN="1200" + fi + + for i in $(echo -n ${DUID_LEN}${DUID_TYPE}${UUID} | sed 's/../& /g'); do + echo -ne "\x$i" + done > ${DUID_FILE} +} + +start () +{ + # reset and clean config files + security_reset || log_failure_msg "security reset failed" + + # some legacy directories migrated over from old rl-system.init + mkdir -p /var/run/vyatta /var/log/vyatta + chgrp vyattacfg /var/run/vyatta /var/log/vyatta + chmod 775 /var/run/vyatta /var/log/vyatta + + log_daemon_msg "Waiting for NICs to settle down" + # On boot time udev migth take a long time to reorder nic's, this will ensure that + # all udev activity is completed and all nics presented at boot-time will have their + # final name before continuing with vyos-router initialization. + SECONDS=0 + udevadm settle + STATUS=$? + log_progress_msg "settled in ${SECONDS}sec." + log_end_msg ${STATUS} + + # mountpoint for bpf maps required by xdp + mount -t bpf none /sys/fs/bpf + + # Clear out Debian APT source config file + empty /etc/apt/sources.list + + # Generate DHCPv6 DUID + gen_duid || log_failure_msg "could not generate DUID" + + # Mount a temporary filesystem for container networks. + # Configuration should be loaded from VyOS cli. + cni_dir="/etc/cni/net.d" + [ ! -d ${cni_dir} ] && mkdir -p ${cni_dir} + mount -t tmpfs none ${cni_dir} + + # Init firewall + nfct helper add rpc inet tcp + nfct helper add rpc inet udp + nfct helper add tns inet tcp + nft -f /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules" + + rm -f /etc/hostname + ${vyos_conf_scripts_dir}/host_name.py || log_failure_msg "could not reset host-name" + systemctl start frr.service + + # As VyOS does not execute commands that are not present in the CLI we call + # the script by hand to have a single source for the login banner and MOTD + ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console" + ${vyos_conf_scripts_dir}/system-login.py || log_failure_msg "could not reset system login" + ${vyos_conf_scripts_dir}/system-login-banner.py || log_failure_msg "could not reset motd and issue files" + ${vyos_conf_scripts_dir}/system-option.py || log_failure_msg "could not reset system option files" + ${vyos_conf_scripts_dir}/conntrack.py || log_failure_msg "could not reset conntrack subsystem" + ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem" + + clear_or_override_config_files || log_failure_msg "could not reset config files" + + # enable some debugging before loading the configuration + if grep -q vyos-debug /proc/cmdline; then + log_action_begin_msg "Enable runtime debugging options" + touch /tmp/vyos.container.debug + touch /tmp/vyos.ifconfig.debug + touch /tmp/vyos.frr.debug + touch /tmp/vyos.container.debug + fi + + log_action_begin_msg "Mounting VyOS Config" + # ensure the vyatta_configdir supports a large number of inodes since + # the config hierarchy is often inode-bound (instead of size). + # impose a minimum and then scale up dynamically with the actual size + # of the system memory. + local tmem=$(sed -n 's/^MemTotal: \+\([0-9]\+\) kB$/\1/p' /proc/meminfo) + local tpages + local tmpfs_opts="nosuid,nodev,mode=775,nr_inodes=0" #automatically allocate inodes + mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} \ + && chgrp ${GROUP} ${vyatta_configdir} + log_action_end_msg $? + + disabled bootfile || init_bootfile + + cleanup_post_commit_hooks + + log_daemon_msg "Starting VyOS router" + disabled migrate || migrate_bootfile + + restore_if_missing_preconfig_script + + run_preconfig_script + + run_postupgrade_script + + update_interface_config + + for s in ${subinit[@]} ; do + if ! disabled $s; then + log_progress_msg $s + if ! ${vyatta_sbindir}/${s}.init start + then log_failure_msg + exit 1 + fi + fi + done + + bind_mount_boot + + disabled configure || load_bootfile + log_end_msg $? + + telinit q + chmod g-w,o-w / + + restore_if_missing_postconfig_script + + run_postconfig_scripts +} + +stop() +{ + local -i status=0 + log_daemon_msg "Stopping VyOS router" + for ((i=${#sub_inits[@]} - 1; i >= 0; i--)) ; do + s=${subinit[$i]} + log_progress_msg $s + ${vyatta_sbindir}/${s}.init stop + let status\|=$? + done + log_end_msg $status + log_action_begin_msg "Un-mounting VyOS Config" + umount ${vyatta_configdir} + log_action_end_msg $? + + systemctl stop frr.service +} + +case "$action" in + start) start ;; + stop) stop ;; + restart|force-reload) stop && start ;; + *) log_failure_msg "usage: $progname [ start|stop|restart ] [ subinit ... ]" ; + false ;; +esac + +exit $? + +# Local Variables: +# mode: shell-script +# sh-indentation: 4 +# End: diff --git a/src/migration-scripts/bgp/0-to-1 b/src/migration-scripts/bgp/0-to-1 index 5e9dffe1f..03c45107b 100755 --- a/src/migration-scripts/bgp/0-to-1 +++ b/src/migration-scripts/bgp/0-to-1 @@ -21,7 +21,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/bgp/1-to-2 b/src/migration-scripts/bgp/1-to-2 index e2d3fcd33..96b939b47 100755 --- a/src/migration-scripts/bgp/1-to-2 +++ b/src/migration-scripts/bgp/1-to-2 @@ -21,7 +21,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/bgp/2-to-3 b/src/migration-scripts/bgp/2-to-3 index 7ced0a3b0..34d321a96 100755 --- a/src/migration-scripts/bgp/2-to-3 +++ b/src/migration-scripts/bgp/2-to-3 @@ -21,7 +21,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/bgp/3-to-4 b/src/migration-scripts/bgp/3-to-4 index 0df2fbec4..894cdda2b 100755 --- a/src/migration-scripts/bgp/3-to-4 +++ b/src/migration-scripts/bgp/3-to-4 @@ -22,7 +22,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/config-management/0-to-1 b/src/migration-scripts/config-management/0-to-1 index 344359110..6528fd136 100755 --- a/src/migration-scripts/config-management/0-to-1 +++ b/src/migration-scripts/config-management/0-to-1 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/conntrack-sync/1-to-2 b/src/migration-scripts/conntrack-sync/1-to-2 index ebbd8c35a..a8e1007f3 100755 --- a/src/migration-scripts/conntrack-sync/1-to-2 +++ b/src/migration-scripts/conntrack-sync/1-to-2 @@ -23,7 +23,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/conntrack/1-to-2 b/src/migration-scripts/conntrack/1-to-2 index 4fc88a1ed..c4fe667fc 100755 --- a/src/migration-scripts/conntrack/1-to-2 +++ b/src/migration-scripts/conntrack/1-to-2 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/conntrack/2-to-3 b/src/migration-scripts/conntrack/2-to-3 index 8a8b43279..6fb457b7f 100755 --- a/src/migration-scripts/conntrack/2-to-3 +++ b/src/migration-scripts/conntrack/2-to-3 @@ -8,7 +8,7 @@ import sys from vyos.configtree import ConfigTree from vyos.version import get_version -if len(sys.argv) < 1: +if len(sys.argv) < 2: print('Must specify file name!') sys.exit(1) diff --git a/src/migration-scripts/container/0-to-1 b/src/migration-scripts/container/0-to-1 index d0461389b..6b282e082 100755 --- a/src/migration-scripts/container/0-to-1 +++ b/src/migration-scripts/container/0-to-1 @@ -21,9 +21,9 @@ import shutil import sys from vyos.configtree import ConfigTree -from vyos.util import call +from vyos.utils.process import call -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) @@ -39,12 +39,12 @@ config = ConfigTree(config_file) if config.exists(base): for container in config.list_nodes(base): # Stop any given container first - call(f'systemctl stop vyos-container-{container}.service') + call(f'sudo systemctl stop vyos-container-{container}.service') # Export container image for later re-import to new filesystem. We store # the backup on a real disk as a tmpfs (like /tmp) could probably lack # memory if a host has too many containers stored. image_name = config.return_value(base + [container, 'image']) - call(f'podman image save --quiet --output /root/{container}.tar --format oci-archive {image_name}') + call(f'sudo podman image save --quiet --output /root/{container}.tar --format oci-archive {image_name}') # No need to adjust the strage driver online (this is only used for testing and # debugging on a live system) - it is already overlay2 when the migration script @@ -66,10 +66,10 @@ if config.exists(base): # Export container image for later re-import to new filesystem image_name = config.return_value(base + [container, 'image']) image_path = f'/root/{container}.tar' - call(f'podman image load --quiet --input {image_path}') + call(f'sudo podman image load --quiet --input {image_path}') # Start any given container first - call(f'systemctl start vyos-container-{container}.service') + call(f'sudo systemctl start vyos-container-{container}.service') # Delete temporary container image if os.path.exists(image_path): diff --git a/src/migration-scripts/dhcp-relay/1-to-2 b/src/migration-scripts/dhcp-relay/1-to-2 index b72da1028..508bac6be 100755 --- a/src/migration-scripts/dhcp-relay/1-to-2 +++ b/src/migration-scripts/dhcp-relay/1-to-2 @@ -7,7 +7,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/dhcp-server/4-to-5 b/src/migration-scripts/dhcp-server/4-to-5 index 313b5279a..d15e0baf5 100755 --- a/src/migration-scripts/dhcp-server/4-to-5 +++ b/src/migration-scripts/dhcp-server/4-to-5 @@ -9,7 +9,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/dhcp-server/5-to-6 b/src/migration-scripts/dhcp-server/5-to-6 index aefe84737..f5c766a09 100755 --- a/src/migration-scripts/dhcp-server/5-to-6 +++ b/src/migration-scripts/dhcp-server/5-to-6 @@ -20,7 +20,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/dhcpv6-server/0-to-1 b/src/migration-scripts/dhcpv6-server/0-to-1 index 6f1150da1..deae1ca29 100755 --- a/src/migration-scripts/dhcpv6-server/0-to-1 +++ b/src/migration-scripts/dhcpv6-server/0-to-1 @@ -19,7 +19,7 @@ from sys import argv, exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/dns-dynamic/0-to-1 b/src/migration-scripts/dns-dynamic/0-to-1 new file mode 100755 index 000000000..d80e8d44a --- /dev/null +++ b/src/migration-scripts/dns-dynamic/0-to-1 @@ -0,0 +1,104 @@ +#!/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/>. + +# T5144: +# - migrate "service dns dynamic interface ..." +# to "service dns dynamic address ..." +# - migrate "service dns dynamic interface <interface> use-web ..." +# to "service dns dynamic address <address> web-options ..." +# - migrate "service dns dynamic interface <interface> rfc2136 <config> record ..." +# to "service dns dynamic address <address> rfc2136 <config> host-name ..." +# - migrate "service dns dynamic interface <interface> service <config> login ..." +# to "service dns dynamic address <address> service <config> username ..." +# - apply global 'ipv6-enable' to per <config> 'ip-version: ipv6' +# - apply service protocol mapping upfront, they are not 'auto-detected' anymore + +import sys +from vyos.configtree import ConfigTree + +service_protocol_mapping = { + 'afraid': 'freedns', + 'changeip': 'changeip', + 'cloudflare': 'cloudflare', + 'dnspark': 'dnspark', + 'dslreports': 'dslreports1', + 'dyndns': 'dyndns2', + 'easydns': 'easydns', + 'namecheap': 'namecheap', + 'noip': 'noip', + 'sitelutions': 'sitelutions', + 'zoneedit': 'zoneedit1' +} + +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) + +old_base_path = ['service', 'dns', 'dynamic', 'interface'] +new_base_path = ['service', 'dns', 'dynamic', 'address'] + +if not config.exists(old_base_path): + # Nothing to do + sys.exit(0) + +# Migrate "service dns dynamic interface" +# to "service dns dynamic address" +config.rename(old_base_path, new_base_path[-1]) + +for address in config.list_nodes(new_base_path): + # Migrate "service dns dynamic interface <interface> rfc2136 <config> record" + # to "service dns dynamic address <address> rfc2136 <config> host-name" + if config.exists(new_base_path + [address, 'rfc2136']): + for rfc_cfg in config.list_nodes(new_base_path + [address, 'rfc2136']): + if config.exists(new_base_path + [address, 'rfc2136', rfc_cfg, 'record']): + config.rename(new_base_path + [address, 'rfc2136', rfc_cfg, 'record'], 'host-name') + + # Migrate "service dns dynamic interface <interface> service <config> login" + # to "service dns dynamic address <address> service <config> username" + if config.exists(new_base_path + [address, 'service']): + for svc_cfg in config.list_nodes(new_base_path + [address, 'service']): + if config.exists(new_base_path + [address, 'service', svc_cfg, 'login']): + config.rename(new_base_path + [address, 'service', svc_cfg, 'login'], 'username') + # Apply global 'ipv6-enable' to per <config> 'ip-version: ipv6' + if config.exists(new_base_path + [address, 'ipv6-enable']): + config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'], + value='ipv6', replace=False) + config.delete(new_base_path + [address, 'ipv6-enable']) + # Apply service protocol mapping upfront, they are not 'auto-detected' anymore + if svc_cfg in service_protocol_mapping: + config.set(new_base_path + [address, 'service', svc_cfg, 'protocol'], + value=service_protocol_mapping.get(svc_cfg), replace=False) + + # Migrate "service dns dynamic interface <interface> use-web" + # to "service dns dynamic address <address> web-options" + # Also, rename <address> to 'web' literal for backward compatibility + if config.exists(new_base_path + [address, 'use-web']): + config.rename(new_base_path + [address], 'web') + config.rename(new_base_path + ['web', 'use-web'], 'web-options') + +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/dns-forwarding/0-to-1 b/src/migration-scripts/dns-forwarding/0-to-1 index 6e8720eef..7f4343652 100755 --- a/src/migration-scripts/dns-forwarding/0-to-1 +++ b/src/migration-scripts/dns-forwarding/0-to-1 @@ -22,7 +22,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2 index a8c930be7..7df2d47e2 100755 --- a/src/migration-scripts/dns-forwarding/1-to-2 +++ b/src/migration-scripts/dns-forwarding/1-to-2 @@ -25,7 +25,7 @@ from sys import argv, exit from vyos.ifconfig import Interface from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/dns-forwarding/2-to-3 b/src/migration-scripts/dns-forwarding/2-to-3 index 01e445b22..d7ff9e260 100755 --- a/src/migration-scripts/dns-forwarding/2-to-3 +++ b/src/migration-scripts/dns-forwarding/2-to-3 @@ -21,7 +21,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/dns-forwarding/3-to-4 b/src/migration-scripts/dns-forwarding/3-to-4 index 55165c2c5..3d5316ed4 100755 --- a/src/migration-scripts/dns-forwarding/3-to-4 +++ b/src/migration-scripts/dns-forwarding/3-to-4 @@ -20,7 +20,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11 new file mode 100755 index 000000000..716c5a240 --- /dev/null +++ b/src/migration-scripts/firewall/10-to-11 @@ -0,0 +1,374 @@ +#!/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/>. + +# T5160: Firewall re-writing + +# cli changes from: +# set firewall name <name> ... +# set firewall ipv6-name <name> ... +# To +# set firewall ipv4 name <name> +# set firewall ipv6 name <name> + +## Also from 'firewall interface' removed. +## in and out: + # set firewall interface <iface> [in|out] [name | ipv6-name] <name> + # To + # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> [inbound-interface | outboubd-interface] interface-name <iface> + # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> action jump + # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> jump-target <name> +## local: + # set firewall interface <iface> local [name | ipv6-name] <name> + # To + # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> inbound-interface interface-name <iface> + # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> action jump + # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> jump-target <name> + +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) + +### Migration of state policies +if config.exists(base + ['state-policy']): + for family in ['ipv4', 'ipv6']: + for hook in ['forward', 'input', 'output']: + for priority in ['filter']: + # Add default-action== accept for compatibility reasons: + config.set(base + [family, hook, priority, 'default-action'], value='accept') + position = 1 + for state in config.list_nodes(base + ['state-policy']): + action = config.return_value(base + ['state-policy', state, 'action']) + config.set(base + [family, hook, priority, 'rule']) + config.set_tag(base + [family, hook, priority, 'rule']) + config.set(base + [family, hook, priority, 'rule', position, 'state', state], value='enable') + config.set(base + [family, hook, priority, 'rule', position, 'action'], value=action) + position = position + 1 + config.delete(base + ['state-policy']) + +## migration of global options: +for option in ['all-ping', 'broadcast-ping', 'config-trap', 'ip-src-route', 'ipv6-receive-redirects', 'ipv6-src-route', 'log-martians', + 'receive-redirects', 'resolver-cache', 'resolver-internal', 'send-redirects', 'source-validation', 'syn-cookies', 'twa-hazards-protection']: + if config.exists(base + [option]): + if option != 'config-trap': + val = config.return_value(base + [option]) + config.set(base + ['global-options', option], value=val) + config.delete(base + [option]) + +### Migration of firewall name and ipv6-name +if config.exists(base + ['name']): + config.set(['firewall', 'ipv4', 'name']) + config.set_tag(['firewall', 'ipv4', 'name']) + + for ipv4name in config.list_nodes(base + ['name']): + config.copy(base + ['name', ipv4name], base + ['ipv4', 'name', ipv4name]) + config.delete(base + ['name']) + +if config.exists(base + ['ipv6-name']): + config.set(['firewall', 'ipv6', 'name']) + config.set_tag(['firewall', 'ipv6', 'name']) + + for ipv6name in config.list_nodes(base + ['ipv6-name']): + config.copy(base + ['ipv6-name', ipv6name], base + ['ipv6', 'name', ipv6name]) + config.delete(base + ['ipv6-name']) + +### Migration of firewall interface +if config.exists(base + ['interface']): + fwd_ipv4_rule = 5 + inp_ipv4_rule = 5 + fwd_ipv6_rule = 5 + inp_ipv6_rule = 5 + for iface in config.list_nodes(base + ['interface']): + for direction in ['in', 'out', 'local']: + if config.exists(base + ['interface', iface, direction]): + if config.exists(base + ['interface', iface, direction, 'name']): + target = config.return_value(base + ['interface', iface, direction, 'name']) + if direction == 'in': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv4', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) + fwd_ipv4_rule = fwd_ipv4_rule + 5 + elif direction == 'out': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv4', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv4_rule, 'outbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target) + fwd_ipv4_rule = fwd_ipv4_rule + 5 + else: + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv4', 'input', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [inp_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [inp_ipv4_rule, 'action'], value='jump') + config.set(new_base + [inp_ipv4_rule, 'jump-target'], value=target) + inp_ipv4_rule = inp_ipv4_rule + 5 + + if config.exists(base + ['interface', iface, direction, 'ipv6-name']): + target = config.return_value(base + ['interface', iface, direction, 'ipv6-name']) + if direction == 'in': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv6', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) + fwd_ipv6_rule = fwd_ipv6_rule + 5 + elif direction == 'out': + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept') + new_base = base + ['ipv6', 'forward', 'filter', 'rule'] + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [fwd_ipv6_rule, 'outbound-interface', 'interface-name'], value=iface) + config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump') + config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target) + fwd_ipv6_rule = fwd_ipv6_rule + 5 + else: + new_base = base + ['ipv6', 'input', 'filter', 'rule'] + # Add default-action== accept for compatibility reasons: + config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept') + config.set(new_base) + config.set_tag(new_base) + config.set(new_base + [inp_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface) + config.set(new_base + [inp_ipv6_rule, 'action'], value='jump') + config.set(new_base + [inp_ipv6_rule, 'jump-target'], value=target) + inp_ipv6_rule = inp_ipv6_rule + 5 + + 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()) +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/firewall/5-to-6 b/src/migration-scripts/firewall/5-to-6 index ccb86830a..e1eaea7a1 100755 --- a/src/migration-scripts/firewall/5-to-6 +++ b/src/migration-scripts/firewall/5-to-6 @@ -23,7 +23,7 @@ from sys import exit from vyos.configtree import ConfigTree from vyos.ifconfig import Section -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) @@ -46,12 +46,54 @@ for interface in config.list_nodes(base): if config.exists(base + [interface, 'adjust-mss']): section = Section.section(interface) tmp = config.return_value(base + [interface, 'adjust-mss']) - config.set(['interfaces', section, interface, 'ip', 'adjust-mss'], value=tmp) + + vlan = interface.split('.') + base_interface_path = ['interfaces', section, vlan[0]] + + if len(vlan) == 1: + # Normal interface, no VLAN + config.set(base_interface_path + ['ip', 'adjust-mss'], value=tmp) + elif len(vlan) == 2: + # Regular VIF or VIF-S interface - we need to check the config + vif = vlan[1] + if config.exists(base_interface_path + ['vif', vif]): + config.set(base_interface_path + ['vif', vif, 'ip', 'adjust-mss'], value=tmp) + elif config.exists(base_interface_path + ['vif-s', vif]): + config.set(base_interface_path + ['vif-s', vif, 'ip', 'adjust-mss'], value=tmp) + elif len(vlan) == 3: + # VIF-S interface with VIF-C subinterface + vif_s = vlan[1] + vif_c = vlan[2] + config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ip', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif-s']) + config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c']) if config.exists(base + [interface, 'adjust-mss6']): section = Section.section(interface) tmp = config.return_value(base + [interface, 'adjust-mss6']) - config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp) + + vlan = interface.split('.') + base_interface_path = ['interfaces', section, vlan[0]] + + if len(vlan) == 1: + # Normal interface, no VLAN + config.set(['interfaces', section, interface, 'ipv6', 'adjust-mss'], value=tmp) + elif len(vlan) == 2: + # Regular VIF or VIF-S interface - we need to check the config + vif = vlan[1] + if config.exists(base_interface_path + ['vif', vif]): + config.set(base_interface_path + ['vif', vif, 'ipv6', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif']) + elif config.exists(base_interface_path + ['vif-s', vif]): + config.set(base_interface_path + ['vif-s', vif, 'ipv6', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif-s']) + elif len(vlan) == 3: + # VIF-S interface with VIF-C subinterface + vif_s = vlan[1] + vif_c = vlan[2] + config.set(base_interface_path + ['vif-s', vif_s, 'vif-c', vif_c, 'ipv6', 'adjust-mss'], value=tmp) + config.set_tag(base_interface_path + ['vif-s']) + config.set_tag(base_interface_path + ['vif-s', vif_s, 'vif-c']) config.delete(['firewall', 'options']) diff --git a/src/migration-scripts/firewall/6-to-7 b/src/migration-scripts/firewall/6-to-7 index 626d6849f..9ad887acc 100755 --- a/src/migration-scripts/firewall/6-to-7 +++ b/src/migration-scripts/firewall/6-to-7 @@ -28,7 +28,7 @@ from sys import exit from vyos.configtree import ConfigTree from vyos.ifconfig import Section -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8 index ce527acf5..d06c3150a 100755 --- a/src/migration-scripts/firewall/7-to-8 +++ b/src/migration-scripts/firewall/7-to-8 @@ -25,7 +25,7 @@ from sys import exit from vyos.configtree import ConfigTree from vyos.ifconfig import Section -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/firewall/8-to-9 b/src/migration-scripts/firewall/8-to-9 index f7c1bb90d..d7647354a 100755 --- a/src/migration-scripts/firewall/8-to-9 +++ b/src/migration-scripts/firewall/8-to-9 @@ -28,7 +28,7 @@ from sys import exit from vyos.configtree import ConfigTree from vyos.ifconfig import Section -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/firewall/9-to-10 b/src/migration-scripts/firewall/9-to-10 index 6f67cc512..a70460718 100755 --- a/src/migration-scripts/firewall/9-to-10 +++ b/src/migration-scripts/firewall/9-to-10 @@ -28,7 +28,7 @@ from sys import exit from vyos.configtree import ConfigTree from vyos.ifconfig import Section -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/flow-accounting/0-to-1 b/src/migration-scripts/flow-accounting/0-to-1 index 72cce77b0..0f790fd9c 100755 --- a/src/migration-scripts/flow-accounting/0-to-1 +++ b/src/migration-scripts/flow-accounting/0-to-1 @@ -21,7 +21,7 @@ from sys import argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/https/2-to-3 b/src/migration-scripts/https/2-to-3 index fa29fdd18..2beba6d2b 100755 --- a/src/migration-scripts/https/2-to-3 +++ b/src/migration-scripts/https/2-to-3 @@ -25,7 +25,7 @@ from vyos.pki import create_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key -if (len(sys.argv) < 2): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/https/3-to-4 b/src/migration-scripts/https/3-to-4 index 5ee528b31..b3cfca201 100755 --- a/src/migration-scripts/https/3-to-4 +++ b/src/migration-scripts/https/3-to-4 @@ -20,7 +20,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 2): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/ids/0-to-1 b/src/migration-scripts/ids/0-to-1 index 9f08f7dc7..8b7850a1a 100755 --- a/src/migration-scripts/ids/0-to-1 +++ b/src/migration-scripts/ids/0-to-1 @@ -19,7 +19,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1 index c7f324661..25f6842eb 100755 --- a/src/migration-scripts/interfaces/0-to-1 +++ b/src/migration-scripts/interfaces/0-to-1 @@ -37,7 +37,7 @@ def migrate_bridge(config, tree, intf): if __name__ == '__main__': - if (len(sys.argv) < 1): + if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/interfaces/1-to-2 b/src/migration-scripts/interfaces/1-to-2 index c75404d85..c95623c2b 100755 --- a/src/migration-scripts/interfaces/1-to-2 +++ b/src/migration-scripts/interfaces/1-to-2 @@ -7,7 +7,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/interfaces/10-to-11 b/src/migration-scripts/interfaces/10-to-11 index 6b8e49ed9..cafaa3fa4 100755 --- a/src/migration-scripts/interfaces/10-to-11 +++ b/src/migration-scripts/interfaces/10-to-11 @@ -23,7 +23,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12 index 0dad24642..e9eb7f939 100755 --- a/src/migration-scripts/interfaces/11-to-12 +++ b/src/migration-scripts/interfaces/11-to-12 @@ -22,7 +22,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/12-to-13 b/src/migration-scripts/interfaces/12-to-13 index f866ca9a6..ef1d93903 100755 --- a/src/migration-scripts/interfaces/12-to-13 +++ b/src/migration-scripts/interfaces/12-to-13 @@ -24,7 +24,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/13-to-14 b/src/migration-scripts/interfaces/13-to-14 index 6e6439c36..b20d8b4db 100755 --- a/src/migration-scripts/interfaces/13-to-14 +++ b/src/migration-scripts/interfaces/13-to-14 @@ -21,7 +21,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/14-to-15 b/src/migration-scripts/interfaces/14-to-15 index c38db0bf8..e21251f86 100755 --- a/src/migration-scripts/interfaces/14-to-15 +++ b/src/migration-scripts/interfaces/14-to-15 @@ -20,7 +20,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/15-to-16 b/src/migration-scripts/interfaces/15-to-16 index 804c48be0..ae3441b9f 100755 --- a/src/migration-scripts/interfaces/15-to-16 +++ b/src/migration-scripts/interfaces/15-to-16 @@ -20,7 +20,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/16-to-17 b/src/migration-scripts/interfaces/16-to-17 index d123be06f..75f160686 100755 --- a/src/migration-scripts/interfaces/16-to-17 +++ b/src/migration-scripts/interfaces/16-to-17 @@ -21,7 +21,7 @@ import sys from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(sys.argv) < 1): + if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) @@ -35,7 +35,7 @@ if __name__ == '__main__': if not config.exists(base): # Nothing to do sys.exit(0) - + for interface in config.list_nodes(base): mirror_old_base = base + [interface, 'mirror'] if config.exists(mirror_old_base): @@ -43,7 +43,7 @@ if __name__ == '__main__': if config.exists(mirror_old_base): config.delete(mirror_old_base) config.set(mirror_old_base + ['ingress'],intf[0]) - + try: with open(file_name, 'w') as f: f.write(config.to_string()) diff --git a/src/migration-scripts/interfaces/17-to-18 b/src/migration-scripts/interfaces/17-to-18 index b8cb8c119..51486ac37 100755 --- a/src/migration-scripts/interfaces/17-to-18 +++ b/src/migration-scripts/interfaces/17-to-18 @@ -22,7 +22,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/18-to-19 b/src/migration-scripts/interfaces/18-to-19 index a12c4a6cd..c3209f250 100755 --- a/src/migration-scripts/interfaces/18-to-19 +++ b/src/migration-scripts/interfaces/18-to-19 @@ -41,7 +41,7 @@ def replace_nat_interfaces(config, old, new): if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/19-to-20 b/src/migration-scripts/interfaces/19-to-20 index e96663e54..05abae898 100755 --- a/src/migration-scripts/interfaces/19-to-20 +++ b/src/migration-scripts/interfaces/19-to-20 @@ -19,7 +19,7 @@ from sys import exit from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/2-to-3 b/src/migration-scripts/interfaces/2-to-3 index 68d41de39..15c3bc8be 100755 --- a/src/migration-scripts/interfaces/2-to-3 +++ b/src/migration-scripts/interfaces/2-to-3 @@ -7,7 +7,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/interfaces/20-to-21 b/src/migration-scripts/interfaces/20-to-21 index cb1c36882..14ad0fe4d 100755 --- a/src/migration-scripts/interfaces/20-to-21 +++ b/src/migration-scripts/interfaces/20-to-21 @@ -23,7 +23,7 @@ from sys import argv from vyos.ethtool import Ethtool from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/21-to-22 b/src/migration-scripts/interfaces/21-to-22 index 098102102..1838eb1c0 100755 --- a/src/migration-scripts/interfaces/21-to-22 +++ b/src/migration-scripts/interfaces/21-to-22 @@ -17,7 +17,7 @@ from sys import argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23 index 06e07572f..8b21fce51 100755 --- a/src/migration-scripts/interfaces/22-to-23 +++ b/src/migration-scripts/interfaces/22-to-23 @@ -75,7 +75,7 @@ def migrate_ripng(config, path, interface): config.delete(path[:-1]) if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/23-to-24 b/src/migration-scripts/interfaces/23-to-24 index d1ec2ad3e..8fd79ecc6 100755 --- a/src/migration-scripts/interfaces/23-to-24 +++ b/src/migration-scripts/interfaces/23-to-24 @@ -22,7 +22,7 @@ import sys from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(sys.argv) < 1): + if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/interfaces/24-to-25 b/src/migration-scripts/interfaces/24-to-25 index 4095f2a3e..9aa6ea5e3 100755 --- a/src/migration-scripts/interfaces/24-to-25 +++ b/src/migration-scripts/interfaces/24-to-25 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -19,6 +19,7 @@ import os import sys + from vyos.configtree import ConfigTree from vyos.pki import CERT_BEGIN from vyos.pki import load_certificate @@ -29,7 +30,7 @@ from vyos.pki import encode_certificate from vyos.pki import encode_dh_parameters from vyos.pki import encode_private_key from vyos.pki import verify_crl -from vyos.util import run +from vyos.utils.process import run def wrapped_pem_to_config_value(pem): out = [] @@ -52,7 +53,7 @@ def read_file_for_pki(config_auth_path): return output -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) @@ -241,7 +242,7 @@ if config.exists(base): config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) else: print(f'Failed to migrate private key on openvpn interface {interface}') - + config.delete(x509_base + ['key-file']) if config.exists(x509_base + ['dh-file']): @@ -276,7 +277,7 @@ base = ['interfaces', 'wireguard'] if config.exists(base): for interface in config.list_nodes(base): private_key_path = base + [interface, 'private-key'] - + key_file = 'default' if config.exists(private_key_path): key_file = config.return_value(private_key_path) @@ -375,7 +376,7 @@ if config.exists(base): config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) else: print(f'Failed to migrate private key on eapol config for interface {interface}') - + config.delete(x509_base + ['key-file']) try: diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26 index a8936235e..4967a29fa 100755 --- a/src/migration-scripts/interfaces/25-to-26 +++ b/src/migration-scripts/interfaces/25-to-26 @@ -22,7 +22,7 @@ from sys import argv from vyos.ethtool import Ethtool from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/26-to-27 b/src/migration-scripts/interfaces/26-to-27 index 949cc55b6..a0d043d11 100755 --- a/src/migration-scripts/interfaces/26-to-27 +++ b/src/migration-scripts/interfaces/26-to-27 @@ -22,7 +22,7 @@ from sys import argv from vyos.ethtool import Ethtool from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/27-to-28 b/src/migration-scripts/interfaces/27-to-28 index 6225d6414..ad5bfa653 100755 --- a/src/migration-scripts/interfaces/27-to-28 +++ b/src/migration-scripts/interfaces/27-to-28 @@ -22,7 +22,7 @@ from sys import argv from vyos.ethtool import Ethtool from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) @@ -37,7 +37,6 @@ if not config.exists(base): exit(0) for ifname in config.list_nodes(base): - print(ifname) multicast_base = base + [ifname, 'multicast'] if config.exists(multicast_base): tmp = config.return_value(multicast_base) diff --git a/src/migration-scripts/interfaces/28-to-29 b/src/migration-scripts/interfaces/28-to-29 new file mode 100755 index 000000000..acb6ee1fb --- /dev/null +++ b/src/migration-scripts/interfaces/28-to-29 @@ -0,0 +1,49 @@ +#!/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/>. + +# T5286: remove XDP support in favour of VPP + +from sys import argv + +from vyos.ethtool import Ethtool +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() + +supports_xdp = ['bonding', 'ethernet'] +config = ConfigTree(config_file) + +for if_type in supports_xdp: + base = ['interfaces', if_type] + if not config.exists(base): + continue + for interface in config.list_nodes(base): + if_base = base + [interface] + if config.exists(if_base + ['xdp']): + config.delete(if_base + ['xdp']) + +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/interfaces/29-to-30 b/src/migration-scripts/interfaces/29-to-30 new file mode 100755 index 000000000..97e1b329c --- /dev/null +++ b/src/migration-scripts/interfaces/29-to-30 @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021-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/>. +# +# Deletes Wireguard peers if they have the same public key as the router has. +import sys +from vyos.configtree import ConfigTree +from vyos.utils.network import is_wireguard_key_pair + +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) + base = ['interfaces', 'wireguard'] + if not config.exists(base): + # Nothing to do + sys.exit(0) + for interface in config.list_nodes(base): + private_key = config.return_value(base + [interface, 'private-key']) + interface_base = base + [interface] + if config.exists(interface_base + ['peer']): + for peer in config.list_nodes(interface_base + ['peer']): + peer_base = interface_base + ['peer', peer] + peer_public_key = config.return_value(peer_base + ['public-key']) + if config.exists(peer_base + ['public-key']): + if not config.exists(peer_base + ['disable']) \ + and is_wireguard_key_pair(private_key, peer_public_key): + config.set(peer_base + ['disable']) + + 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/interfaces/3-to-4 b/src/migration-scripts/interfaces/3-to-4 index e3bd25a68..c7fd7d01d 100755 --- a/src/migration-scripts/interfaces/3-to-4 +++ b/src/migration-scripts/interfaces/3-to-4 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/interfaces/4-to-5 b/src/migration-scripts/interfaces/4-to-5 index f645c5aeb..68d81e846 100755 --- a/src/migration-scripts/interfaces/4-to-5 +++ b/src/migration-scripts/interfaces/4-to-5 @@ -57,7 +57,7 @@ def migrate_dialer(config, tree, intf): if __name__ == '__main__': - if (len(sys.argv) < 1): + if len(sys.argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6 index ae79c1d1b..9d9a49c2d 100755 --- a/src/migration-scripts/interfaces/5-to-6 +++ b/src/migration-scripts/interfaces/5-to-6 @@ -98,7 +98,7 @@ def copy_rtradv(c, old_base, interface): c.delete(new_base + ['link-mtu']) if __name__ == '__main__': - if (len(sys.argv) < 1): + if len(sys.argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/6-to-7 b/src/migration-scripts/interfaces/6-to-7 index 220c7e601..49b853d90 100755 --- a/src/migration-scripts/interfaces/6-to-7 +++ b/src/migration-scripts/interfaces/6-to-7 @@ -20,7 +20,7 @@ import sys from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(sys.argv) < 1): + if len(sys.argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/7-to-8 b/src/migration-scripts/interfaces/7-to-8 index a4051301f..9343a48a8 100755 --- a/src/migration-scripts/interfaces/7-to-8 +++ b/src/migration-scripts/interfaces/7-to-8 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -21,7 +21,8 @@ import os from sys import exit, argv from vyos.configtree import ConfigTree -from vyos.util import chown, chmod_750 +from vyos.utils.permission import chown +from vyos.utils.permission import chmod_750 def migrate_default_keys(): kdir = r'/config/auth/wireguard' @@ -36,7 +37,7 @@ def migrate_default_keys(): os.rename(f'{kdir}/public.key', f'{location}/public.key') if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/8-to-9 b/src/migration-scripts/interfaces/8-to-9 index 2d1efd418..960962be7 100755 --- a/src/migration-scripts/interfaces/8-to-9 +++ b/src/migration-scripts/interfaces/8-to-9 @@ -22,7 +22,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/interfaces/9-to-10 b/src/migration-scripts/interfaces/9-to-10 index 4aa2c42b5..e9b8cb784 100755 --- a/src/migration-scripts/interfaces/9-to-10 +++ b/src/migration-scripts/interfaces/9-to-10 @@ -23,7 +23,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ipoe-server/0-to-1 b/src/migration-scripts/ipoe-server/0-to-1 index d768758ba..ac9d13abc 100755 --- a/src/migration-scripts/ipoe-server/0-to-1 +++ b/src/migration-scripts/ipoe-server/0-to-1 @@ -26,7 +26,7 @@ import sys from sys import argv, exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ipsec/10-to-11 b/src/migration-scripts/ipsec/10-to-11 index 0707a5e3c..509216267 100755 --- a/src/migration-scripts/ipsec/10-to-11 +++ b/src/migration-scripts/ipsec/10-to-11 @@ -20,7 +20,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ipsec/11-to-12 b/src/migration-scripts/ipsec/11-to-12 index 8bbde5efa..e34882c23 100755 --- a/src/migration-scripts/ipsec/11-to-12 +++ b/src/migration-scripts/ipsec/11-to-12 @@ -23,7 +23,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ipsec/4-to-5 b/src/migration-scripts/ipsec/4-to-5 index 4e959a7bf..772d05787 100755 --- a/src/migration-scripts/ipsec/4-to-5 +++ b/src/migration-scripts/ipsec/4-to-5 @@ -20,7 +20,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/ipsec/5-to-6 b/src/migration-scripts/ipsec/5-to-6 index 3a8b3926d..7d7c777c6 100755 --- a/src/migration-scripts/ipsec/5-to-6 +++ b/src/migration-scripts/ipsec/5-to-6 @@ -23,7 +23,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ipsec/6-to-7 b/src/migration-scripts/ipsec/6-to-7 index 788a87095..71fbbe8a1 100755 --- a/src/migration-scripts/ipsec/6-to-7 +++ b/src/migration-scripts/ipsec/6-to-7 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -27,9 +27,9 @@ from vyos.pki import load_crl from vyos.pki import load_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key -from vyos.util import run +from vyos.utils.process import run -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) @@ -127,7 +127,7 @@ if config.exists(ipsec_site_base): config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem)) else: print(f'Failed to migrate CRL on peer "{peer}"') - + config.delete(peer_x509_base + ['crl-file']) if config.exists(peer_x509_base + ['key', 'file']): @@ -157,7 +157,7 @@ if config.exists(ipsec_site_base): config.set(peer_x509_base + ['private-key-passphrase'], value=key_passphrase) else: print(f'Failed to migrate private key on peer "{peer}"') - + config.delete(peer_x509_base + ['key']) if changes_made: diff --git a/src/migration-scripts/ipsec/7-to-8 b/src/migration-scripts/ipsec/7-to-8 index 5d48b2875..e002db0b1 100755 --- a/src/migration-scripts/ipsec/7-to-8 +++ b/src/migration-scripts/ipsec/7-to-8 @@ -31,7 +31,7 @@ from vyos.pki import load_private_key from vyos.pki import encode_public_key from vyos.pki import encode_private_key -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ipsec/8-to-9 b/src/migration-scripts/ipsec/8-to-9 index eb44b6216..c08411f83 100755 --- a/src/migration-scripts/ipsec/8-to-9 +++ b/src/migration-scripts/ipsec/8-to-9 @@ -19,7 +19,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ipsec/9-to-10 b/src/migration-scripts/ipsec/9-to-10 index de366ef3b..a4a71d38e 100755 --- a/src/migration-scripts/ipsec/9-to-10 +++ b/src/migration-scripts/ipsec/9-to-10 @@ -24,7 +24,7 @@ from vyos.template import is_ipv4 from vyos.template import is_ipv6 -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/isis/0-to-1 b/src/migration-scripts/isis/0-to-1 index 93cbbbed5..0149c0c1f 100755 --- a/src/migration-scripts/isis/0-to-1 +++ b/src/migration-scripts/isis/0-to-1 @@ -21,7 +21,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) @@ -37,12 +37,9 @@ if not config.exists(base): # Nothing to do exit(0) -# Only one IS-IS process is supported, thus this operation is save -isis_base = base + config.list_nodes(base) - # We need a temporary copy of the config tmp_base = ['protocols', 'isis2'] -config.copy(isis_base, tmp_base) +config.copy(base, tmp_base) # Now it's save to delete the old configuration config.delete(base) diff --git a/src/migration-scripts/isis/1-to-2 b/src/migration-scripts/isis/1-to-2 index f914ea995..9c110bf2a 100755 --- a/src/migration-scripts/isis/1-to-2 +++ b/src/migration-scripts/isis/1-to-2 @@ -21,7 +21,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/isis/2-to-3 b/src/migration-scripts/isis/2-to-3 index 4490feb0a..78e3c1715 100755 --- a/src/migration-scripts/isis/2-to-3 +++ b/src/migration-scripts/isis/2-to-3 @@ -22,7 +22,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/l2tp/0-to-1 b/src/migration-scripts/l2tp/0-to-1 index 686ebc655..15d229822 100755 --- a/src/migration-scripts/l2tp/0-to-1 +++ b/src/migration-scripts/l2tp/0-to-1 @@ -8,7 +8,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/l2tp/1-to-2 b/src/migration-scripts/l2tp/1-to-2 index c46eba8f8..2ffb91c53 100755 --- a/src/migration-scripts/l2tp/1-to-2 +++ b/src/migration-scripts/l2tp/1-to-2 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/l2tp/2-to-3 b/src/migration-scripts/l2tp/2-to-3 index 3472ee3ed..b46b0f22e 100755 --- a/src/migration-scripts/l2tp/2-to-3 +++ b/src/migration-scripts/l2tp/2-to-3 @@ -23,7 +23,7 @@ import sys from sys import argv, exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/l2tp/3-to-4 b/src/migration-scripts/l2tp/3-to-4 index 18eabadec..8c2b909b7 100755 --- a/src/migration-scripts/l2tp/3-to-4 +++ b/src/migration-scripts/l2tp/3-to-4 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -27,9 +27,9 @@ from vyos.pki import load_crl from vyos.pki import load_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key -from vyos.util import run +from vyos.utils.process import run -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) @@ -156,7 +156,7 @@ if config.exists(x509_base + ['server-key-file']): config.set(x509_base + ['private-key-passphrase'], value=key_passphrase) else: print(f'Failed to migrate private key on l2tp remote-access config') - + config.delete(x509_base + ['server-key-file']) if config.exists(x509_base + ['server-key-password']): config.delete(x509_base + ['server-key-password']) diff --git a/src/migration-scripts/lldp/0-to-1 b/src/migration-scripts/lldp/0-to-1 index 5f66570e7..a936cbdfc 100755 --- a/src/migration-scripts/lldp/0-to-1 +++ b/src/migration-scripts/lldp/0-to-1 @@ -7,7 +7,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/monitoring/0-to-1 b/src/migration-scripts/monitoring/0-to-1 index 803cdb49c..384d22f8c 100755 --- a/src/migration-scripts/monitoring/0-to-1 +++ b/src/migration-scripts/monitoring/0-to-1 @@ -21,7 +21,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/nat/4-to-5 b/src/migration-scripts/nat/4-to-5 index b791996e2..ce215d455 100755 --- a/src/migration-scripts/nat/4-to-5 +++ b/src/migration-scripts/nat/4-to-5 @@ -20,7 +20,7 @@ from sys import argv,exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/nat66/0-to-1 b/src/migration-scripts/nat66/0-to-1 index 83b421926..444b2315f 100755 --- a/src/migration-scripts/nat66/0-to-1 +++ b/src/migration-scripts/nat66/0-to-1 @@ -17,7 +17,7 @@ from sys import argv,exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ntp/0-to-1 b/src/migration-scripts/ntp/0-to-1 index 294964580..cbce45b9b 100755 --- a/src/migration-scripts/ntp/0-to-1 +++ b/src/migration-scripts/ntp/0-to-1 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/ntp/1-to-2 b/src/migration-scripts/ntp/1-to-2 index d1e510e4c..fd1f15d91 100755 --- a/src/migration-scripts/ntp/1-to-2 +++ b/src/migration-scripts/ntp/1-to-2 @@ -20,7 +20,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/ntp/2-to-3 b/src/migration-scripts/ntp/2-to-3 new file mode 100755 index 000000000..a4351845e --- /dev/null +++ b/src/migration-scripts/ntp/2-to-3 @@ -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/>. + +# T5154: allow only one ip address per family for parameter 'listen-address' +# Allow only one interface for parameter 'interface' +# If more than one are specified, remove such entries + +import sys + +from vyos.configtree import ConfigTree +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 + +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) + +base_path = ['service', 'ntp'] +if not config.exists(base_path): + # Nothing to do + sys.exit(0) + +if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv4(addr)]) > 1): + for addr in config.return_values(base_path + ['listen-address']): + if is_ipv4(addr): + config.delete_value(base_path + ['listen-address'], addr) + +if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv6(addr)]) > 1): + for addr in config.return_values(base_path + ['listen-address']): + if is_ipv6(addr): + config.delete_value(base_path + ['listen-address'], addr) + +if config.exists(base_path + ['interface']): + if len(config.return_values(base_path + ['interface'])) > 1: + config.delete(base_path + ['interface']) + +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/openconnect/0-to-1 b/src/migration-scripts/openconnect/0-to-1 index 83cd09143..8be15fad1 100755 --- a/src/migration-scripts/openconnect/0-to-1 +++ b/src/migration-scripts/openconnect/0-to-1 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -26,9 +26,9 @@ from vyos.pki import load_crl from vyos.pki import load_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key -from vyos.util import run +from vyos.utils.process import run -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) @@ -125,7 +125,7 @@ if config.exists(x509_base + ['key-file']): config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) else: print(f'Failed to migrate private key on openconnect config') - + config.delete(x509_base + ['key-file']) try: diff --git a/src/migration-scripts/openconnect/1-to-2 b/src/migration-scripts/openconnect/1-to-2 index 7031fb252..7978aa56e 100755 --- a/src/migration-scripts/openconnect/1-to-2 +++ b/src/migration-scripts/openconnect/1-to-2 @@ -20,7 +20,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) @@ -39,13 +39,13 @@ if not config.exists(cfg_base): else: if config.exists(cfg_base + ['authentication', 'mode']): if config.return_value(cfg_base + ['authentication', 'mode']) == 'radius': - # if "mode value radius", change to "tag node mode + valueless node radius" - config.delete(cfg_base + ['authentication','mode', 'radius']) - config.set(cfg_base + ['authentication', 'mode', 'radius'], value=None, replace=True) - elif not config.exists(cfg_base + ['authentication', 'mode', 'local']): - # if "mode local", change to "tag node mode + node local value password" - config.delete(cfg_base + ['authentication', 'mode', 'local']) - config.set(cfg_base + ['authentication', 'mode', 'local'], value='password', replace=True) + # if "mode value radius", change to "mode + valueless node radius" + config.delete_value(cfg_base + ['authentication','mode'], 'radius') + config.set(cfg_base + ['authentication', 'mode', 'radius'], value=None) + elif config.return_value(cfg_base + ['authentication', 'mode']) == 'local': + # if "mode local", change to "mode + node local value password" + config.delete_value(cfg_base + ['authentication', 'mode'], 'local') + config.set(cfg_base + ['authentication', 'mode', 'local'], value='password') try: with open(file_name, 'w') as f: f.write(config.to_string()) diff --git a/src/migration-scripts/ospf/0-to-1 b/src/migration-scripts/ospf/0-to-1 index 678569d9e..8f02acada 100755 --- a/src/migration-scripts/ospf/0-to-1 +++ b/src/migration-scripts/ospf/0-to-1 @@ -37,7 +37,7 @@ def ospf_passive_migration(config, ospf_base): config.set(ospf_base + ['interface', interface, 'passive', 'disable']) config.delete(ospf_base + ['passive-interface-exclude']) -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ospf/1-to-2 b/src/migration-scripts/ospf/1-to-2 index a6beaf04e..ba9499c60 100755 --- a/src/migration-scripts/ospf/1-to-2 +++ b/src/migration-scripts/ospf/1-to-2 @@ -22,7 +22,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/policy/0-to-1 b/src/migration-scripts/policy/0-to-1 index 7134920ad..8508b734a 100755 --- a/src/migration-scripts/policy/0-to-1 +++ b/src/migration-scripts/policy/0-to-1 @@ -23,7 +23,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2 index eebbf9d41..c70490ce9 100755 --- a/src/migration-scripts/policy/1-to-2 +++ b/src/migration-scripts/policy/1-to-2 @@ -23,7 +23,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/policy/2-to-3 b/src/migration-scripts/policy/2-to-3 index 84cb1ff4a..8a62c8e6f 100755 --- a/src/migration-scripts/policy/2-to-3 +++ b/src/migration-scripts/policy/2-to-3 @@ -23,7 +23,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/policy/3-to-4 b/src/migration-scripts/policy/3-to-4 index bae30cffc..1ebb248b0 100755 --- a/src/migration-scripts/policy/3-to-4 +++ b/src/migration-scripts/policy/3-to-4 @@ -51,7 +51,7 @@ def community_migrate(config: ConfigTree, rule: list[str]) -> bool: :rtype: bool """ community_list = list((config.return_value(rule)).split(" ")) - config.delete(rule) + if 'none' in community_list: config.set(rule + ['none']) return False @@ -61,8 +61,10 @@ def community_migrate(config: ConfigTree, rule: list[str]) -> bool: community_action = 'add' community_list.remove('additive') for community in community_list: - config.set(rule + [community_action], value=community, - replace=False) + if len(community): + config.set(rule + [community_action], value=community, + replace=False) + config.delete(rule) if community_action == 'replace': return False else: @@ -96,7 +98,7 @@ def extcommunity_migrate(config: ConfigTree, rule: list[str]) -> None: config.set(rule + ['soo'], value=community, replace=False) -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/policy/4-to-5 b/src/migration-scripts/policy/4-to-5 index 33c9e6ade..f6f889c35 100755 --- a/src/migration-scripts/policy/4-to-5 +++ b/src/migration-scripts/policy/4-to-5 @@ -24,7 +24,7 @@ from sys import exit from vyos.configtree import ConfigTree from vyos.ifconfig import Section -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/pppoe-server/0-to-1 b/src/migration-scripts/pppoe-server/0-to-1 index 063c7eb56..4d36f8545 100755 --- a/src/migration-scripts/pppoe-server/0-to-1 +++ b/src/migration-scripts/pppoe-server/0-to-1 @@ -20,7 +20,7 @@ from sys import argv, exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/pppoe-server/1-to-2 b/src/migration-scripts/pppoe-server/1-to-2 index 902efb86b..c73899ca1 100755 --- a/src/migration-scripts/pppoe-server/1-to-2 +++ b/src/migration-scripts/pppoe-server/1-to-2 @@ -21,7 +21,7 @@ import os from sys import argv, exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/pppoe-server/2-to-3 b/src/migration-scripts/pppoe-server/2-to-3 index 7cae3b5bc..a7be060df 100755 --- a/src/migration-scripts/pppoe-server/2-to-3 +++ b/src/migration-scripts/pppoe-server/2-to-3 @@ -19,7 +19,7 @@ from sys import argv, exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/pppoe-server/3-to-4 b/src/migration-scripts/pppoe-server/3-to-4 index 5f9730a41..c07bbb1df 100755 --- a/src/migration-scripts/pppoe-server/3-to-4 +++ b/src/migration-scripts/pppoe-server/3-to-4 @@ -21,7 +21,7 @@ import os from sys import argv, exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/pppoe-server/4-to-5 b/src/migration-scripts/pppoe-server/4-to-5 index 05e9c17d6..5850db673 100755 --- a/src/migration-scripts/pppoe-server/4-to-5 +++ b/src/migration-scripts/pppoe-server/4-to-5 @@ -20,7 +20,7 @@ from vyos.configtree import ConfigTree from sys import argv from sys import exit -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/pppoe-server/5-to-6 b/src/migration-scripts/pppoe-server/5-to-6 index e4888f4db..e079ae684 100755 --- a/src/migration-scripts/pppoe-server/5-to-6 +++ b/src/migration-scripts/pppoe-server/5-to-6 @@ -20,7 +20,7 @@ from vyos.configtree import ConfigTree from sys import argv from sys import exit -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/pptp/0-to-1 b/src/migration-scripts/pptp/0-to-1 index d0c7a83b5..1b7697c11 100755 --- a/src/migration-scripts/pptp/0-to-1 +++ b/src/migration-scripts/pptp/0-to-1 @@ -8,7 +8,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/pptp/1-to-2 b/src/migration-scripts/pptp/1-to-2 index a13cc3a4f..99624dceb 100755 --- a/src/migration-scripts/pptp/1-to-2 +++ b/src/migration-scripts/pptp/1-to-2 @@ -21,7 +21,7 @@ from sys import argv, exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/qos/1-to-2 b/src/migration-scripts/qos/1-to-2 index 14d3a6e0a..cca32d06e 100755 --- a/src/migration-scripts/qos/1-to-2 +++ b/src/migration-scripts/qos/1-to-2 @@ -18,7 +18,7 @@ from sys import argv,exit from vyos.base import Warning from vyos.configtree import ConfigTree -from vyos.util import read_file +from vyos.utils.file import read_file def bandwidth_percent_to_val(interface, percent) -> int: speed = read_file(f'/sys/class/net/{interface}/speed') @@ -28,7 +28,7 @@ def bandwidth_percent_to_val(interface, percent) -> int: speed = int(speed) *1000000 # convert to MBit/s return speed * int(percent) // 100 # integer division -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/quagga/10-to-11 b/src/migration-scripts/quagga/10-to-11 index 04fc16f79..0ed4f5df6 100755 --- a/src/migration-scripts/quagga/10-to-11 +++ b/src/migration-scripts/quagga/10-to-11 @@ -22,7 +22,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/quagga/2-to-3 b/src/migration-scripts/quagga/2-to-3 index 4c1cd86a3..96b56da70 100755 --- a/src/migration-scripts/quagga/2-to-3 +++ b/src/migration-scripts/quagga/2-to-3 @@ -21,7 +21,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/quagga/3-to-4 b/src/migration-scripts/quagga/3-to-4 index be3528391..1e8c8e2f2 100755 --- a/src/migration-scripts/quagga/3-to-4 +++ b/src/migration-scripts/quagga/3-to-4 @@ -28,7 +28,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/quagga/4-to-5 b/src/migration-scripts/quagga/4-to-5 index f8c87ce8c..fcb496a9c 100755 --- a/src/migration-scripts/quagga/4-to-5 +++ b/src/migration-scripts/quagga/4-to-5 @@ -21,7 +21,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/quagga/5-to-6 b/src/migration-scripts/quagga/5-to-6 index a71851942..f075fc2e7 100755 --- a/src/migration-scripts/quagga/5-to-6 +++ b/src/migration-scripts/quagga/5-to-6 @@ -22,7 +22,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 2): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/quagga/6-to-7 b/src/migration-scripts/quagga/6-to-7 index 25cf5eebd..ed295a95c 100755 --- a/src/migration-scripts/quagga/6-to-7 +++ b/src/migration-scripts/quagga/6-to-7 @@ -23,7 +23,7 @@ from vyos.configtree import ConfigTree from vyos.template import is_ipv4 from vyos.template import is_ipv6 -if (len(argv) < 2): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/quagga/7-to-8 b/src/migration-scripts/quagga/7-to-8 index 15c44924f..8f11bf390 100755 --- a/src/migration-scripts/quagga/7-to-8 +++ b/src/migration-scripts/quagga/7-to-8 @@ -22,7 +22,7 @@ from sys import argv from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 2): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/quagga/8-to-9 b/src/migration-scripts/quagga/8-to-9 index 38507bd3d..0f683d5a1 100755 --- a/src/migration-scripts/quagga/8-to-9 +++ b/src/migration-scripts/quagga/8-to-9 @@ -84,7 +84,7 @@ def migrate_route(config, base, path, route_route6): config.rename(vrf_path, 'vrf') -if (len(argv) < 2): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/quagga/9-to-10 b/src/migration-scripts/quagga/9-to-10 index 249738822..3731762f7 100755 --- a/src/migration-scripts/quagga/9-to-10 +++ b/src/migration-scripts/quagga/9-to-10 @@ -21,7 +21,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 2): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/rip/0-to-1 b/src/migration-scripts/rip/0-to-1 index 60d510001..08a866374 100755 --- a/src/migration-scripts/rip/0-to-1 +++ b/src/migration-scripts/rip/0-to-1 @@ -22,7 +22,7 @@ from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/rpki/0-to-1 b/src/migration-scripts/rpki/0-to-1 index 5b4893205..a7b5d07d5 100755 --- a/src/migration-scripts/rpki/0-to-1 +++ b/src/migration-scripts/rpki/0-to-1 @@ -18,7 +18,7 @@ from sys import exit from sys import argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/salt/0-to-1 b/src/migration-scripts/salt/0-to-1 index 79053c056..481d9de8f 100755 --- a/src/migration-scripts/salt/0-to-1 +++ b/src/migration-scripts/salt/0-to-1 @@ -22,7 +22,7 @@ from sys import argv,exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/snmp/0-to-1 b/src/migration-scripts/snmp/0-to-1 index 096ba779d..b1e61b958 100755 --- a/src/migration-scripts/snmp/0-to-1 +++ b/src/migration-scripts/snmp/0-to-1 @@ -17,7 +17,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/snmp/1-to-2 b/src/migration-scripts/snmp/1-to-2 index 466a624e6..e02cd1aa1 100755 --- a/src/migration-scripts/snmp/1-to-2 +++ b/src/migration-scripts/snmp/1-to-2 @@ -43,7 +43,7 @@ def migrate_keys(config, path): config.set(config_path_priv, value=tmp) if __name__ == '__main__': - if (len(argv) < 1): + if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/snmp/2-to-3 b/src/migration-scripts/snmp/2-to-3 index 5f8d9c88d..30911aa27 100755 --- a/src/migration-scripts/snmp/2-to-3 +++ b/src/migration-scripts/snmp/2-to-3 @@ -28,7 +28,7 @@ from sys import exit from vyos.configtree import ConfigTree from vyos.ifconfig import Section -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/ssh/0-to-1 b/src/migration-scripts/ssh/0-to-1 index 91b832276..2595599ac 100755 --- a/src/migration-scripts/ssh/0-to-1 +++ b/src/migration-scripts/ssh/0-to-1 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/ssh/1-to-2 b/src/migration-scripts/ssh/1-to-2 index 31c40df16..79d65d7d4 100755 --- a/src/migration-scripts/ssh/1-to-2 +++ b/src/migration-scripts/ssh/1-to-2 @@ -21,7 +21,7 @@ from sys import argv,exit from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/sstp/0-to-1 b/src/migration-scripts/sstp/0-to-1 index dc65bdeab..e2fe1ea8f 100755 --- a/src/migration-scripts/sstp/0-to-1 +++ b/src/migration-scripts/sstp/0-to-1 @@ -28,7 +28,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/sstp/1-to-2 b/src/migration-scripts/sstp/1-to-2 index 94cb04831..f7ecbb6d4 100755 --- a/src/migration-scripts/sstp/1-to-2 +++ b/src/migration-scripts/sstp/1-to-2 @@ -25,7 +25,7 @@ from shutil import copy2 from stat import S_IRUSR, S_IWUSR, S_IRGRP, S_IROTH from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/sstp/2-to-3 b/src/migration-scripts/sstp/2-to-3 index 963b2ba4b..245db7ad6 100755 --- a/src/migration-scripts/sstp/2-to-3 +++ b/src/migration-scripts/sstp/2-to-3 @@ -21,7 +21,7 @@ from vyos.configtree import ConfigTree from sys import argv from sys import exit -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/sstp/3-to-4 b/src/migration-scripts/sstp/3-to-4 index 0568f043f..00ca7a52d 100755 --- a/src/migration-scripts/sstp/3-to-4 +++ b/src/migration-scripts/sstp/3-to-4 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -26,9 +26,9 @@ from vyos.pki import load_crl from vyos.pki import load_private_key from vyos.pki import encode_certificate from vyos.pki import encode_private_key -from vyos.util import run +from vyos.utils.process import run -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) @@ -125,7 +125,7 @@ if config.exists(x509_base + ['key-file']): config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem)) else: print(f'Failed to migrate private key on sstp config') - + config.delete(x509_base + ['key-file']) try: diff --git a/src/migration-scripts/system/10-to-11 b/src/migration-scripts/system/10-to-11 index 3c49f0d95..5d662af40 100755 --- a/src/migration-scripts/system/10-to-11 +++ b/src/migration-scripts/system/10-to-11 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/11-to-12 b/src/migration-scripts/system/11-to-12 index 9cddaa1a7..880ab56dc 100755 --- a/src/migration-scripts/system/11-to-12 +++ b/src/migration-scripts/system/11-to-12 @@ -8,7 +8,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/12-to-13 b/src/migration-scripts/system/12-to-13 index 36311a19d..e6c4e3802 100755 --- a/src/migration-scripts/system/12-to-13 +++ b/src/migration-scripts/system/12-to-13 @@ -8,7 +8,7 @@ import re from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/13-to-14 b/src/migration-scripts/system/13-to-14 index 5b068f4fc..1fa781869 100755 --- a/src/migration-scripts/system/13-to-14 +++ b/src/migration-scripts/system/13-to-14 @@ -12,10 +12,10 @@ import re import sys from vyos.configtree import ConfigTree -from vyos.util import cmd +from vyos.utils.process import cmd -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/14-to-15 b/src/migration-scripts/system/14-to-15 index c055dad1f..feaac37de 100755 --- a/src/migration-scripts/system/14-to-15 +++ b/src/migration-scripts/system/14-to-15 @@ -11,7 +11,7 @@ ipv6_blacklist_file = '/etc/modprobe.d/vyatta_blacklist_ipv6.conf' from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/15-to-16 b/src/migration-scripts/system/15-to-16 index 2491e3d0d..aa1c34032 100755 --- a/src/migration-scripts/system/15-to-16 +++ b/src/migration-scripts/system/15-to-16 @@ -7,7 +7,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/16-to-17 b/src/migration-scripts/system/16-to-17 index e70893d55..37e02611d 100755 --- a/src/migration-scripts/system/16-to-17 +++ b/src/migration-scripts/system/16-to-17 @@ -25,7 +25,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/17-to-18 b/src/migration-scripts/system/17-to-18 index 8f762c0e2..f6adebb06 100755 --- a/src/migration-scripts/system/17-to-18 +++ b/src/migration-scripts/system/17-to-18 @@ -22,7 +22,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/18-to-19 b/src/migration-scripts/system/18-to-19 index fd0e15d42..fad1d17a4 100755 --- a/src/migration-scripts/system/18-to-19 +++ b/src/migration-scripts/system/18-to-19 @@ -24,7 +24,7 @@ from sys import argv, exit from vyos.ifconfig import Interface from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) @@ -92,9 +92,6 @@ else: for intf in dhcp_interfaces: config.set(base + ['name-servers-dhcp'], value=intf, replace=False) - # delete old node - config.delete(base + ['disable-dhcp-nameservers']) - try: with open(file_name, 'w') as f: f.write(config.to_string()) diff --git a/src/migration-scripts/system/19-to-20 b/src/migration-scripts/system/19-to-20 index eb20fd8db..c04e6a5a6 100755 --- a/src/migration-scripts/system/19-to-20 +++ b/src/migration-scripts/system/19-to-20 @@ -21,7 +21,7 @@ import os from sys import exit, argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/system/20-to-21 b/src/migration-scripts/system/20-to-21 index 1728995de..4bcf4edab 100755 --- a/src/migration-scripts/system/20-to-21 +++ b/src/migration-scripts/system/20-to-21 @@ -21,7 +21,7 @@ import os from sys import argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/system/21-to-22 b/src/migration-scripts/system/21-to-22 index ad41be646..810b634ab 100755 --- a/src/migration-scripts/system/21-to-22 +++ b/src/migration-scripts/system/21-to-22 @@ -19,7 +19,7 @@ import os from sys import exit, argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/system/22-to-23 b/src/migration-scripts/system/22-to-23 index 7f832e48a..8ed198383 100755 --- a/src/migration-scripts/system/22-to-23 +++ b/src/migration-scripts/system/22-to-23 @@ -19,7 +19,7 @@ import os from sys import exit, argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/system/23-to-24 b/src/migration-scripts/system/23-to-24 index 97fe82462..fd68dbf22 100755 --- a/src/migration-scripts/system/23-to-24 +++ b/src/migration-scripts/system/23-to-24 @@ -22,7 +22,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree from vyos.template import is_ipv4 -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/system/24-to-25 b/src/migration-scripts/system/24-to-25 index c2f70689d..1c81a76e7 100755 --- a/src/migration-scripts/system/24-to-25 +++ b/src/migration-scripts/system/24-to-25 @@ -19,7 +19,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/system/25-to-26 b/src/migration-scripts/system/25-to-26 index 615274430..7bdf3be98 100755 --- a/src/migration-scripts/system/25-to-26 +++ b/src/migration-scripts/system/25-to-26 @@ -21,7 +21,7 @@ from sys import exit, argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/system/6-to-7 b/src/migration-scripts/system/6-to-7 index bf07abf3a..d24521134 100755 --- a/src/migration-scripts/system/6-to-7 +++ b/src/migration-scripts/system/6-to-7 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/7-to-8 b/src/migration-scripts/system/7-to-8 index 4cbb21f17..5d084d2bf 100755 --- a/src/migration-scripts/system/7-to-8 +++ b/src/migration-scripts/system/7-to-8 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/system/8-to-9 b/src/migration-scripts/system/8-to-9 index db3fefdea..e3bb2bca8 100755 --- a/src/migration-scripts/system/8-to-9 +++ b/src/migration-scripts/system/8-to-9 @@ -6,7 +6,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/vrf/0-to-1 b/src/migration-scripts/vrf/0-to-1 index 5df751113..8187138d9 100755 --- a/src/migration-scripts/vrf/0-to-1 +++ b/src/migration-scripts/vrf/0-to-1 @@ -20,7 +20,7 @@ from sys import argv from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 2): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/vrf/1-to-2 b/src/migration-scripts/vrf/1-to-2 index 9bc704e02..52d4c2c7b 100755 --- a/src/migration-scripts/vrf/1-to-2 +++ b/src/migration-scripts/vrf/1-to-2 @@ -20,7 +20,7 @@ from sys import argv from sys import exit from vyos.configtree import ConfigTree -if (len(argv) < 2): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/vrf/2-to-3 b/src/migration-scripts/vrf/2-to-3 index 8e0f97141..d45b185ee 100755 --- a/src/migration-scripts/vrf/2-to-3 +++ b/src/migration-scripts/vrf/2-to-3 @@ -69,7 +69,7 @@ def _search_tables(config_commands, table_num): return table_items -if (len(argv) < 2): +if len(argv) < 2: print("Must specify file name!") exit(1) diff --git a/src/migration-scripts/vrrp/1-to-2 b/src/migration-scripts/vrrp/1-to-2 index b2e61dd38..dba5af81c 100755 --- a/src/migration-scripts/vrrp/1-to-2 +++ b/src/migration-scripts/vrrp/1-to-2 @@ -21,7 +21,7 @@ import sys from vyos.configtree import ConfigTree -if (len(sys.argv) < 1): +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/migration-scripts/vrrp/2-to-3 b/src/migration-scripts/vrrp/2-to-3 index 1151ae18c..ed583b489 100755 --- a/src/migration-scripts/vrrp/2-to-3 +++ b/src/migration-scripts/vrrp/2-to-3 @@ -19,7 +19,7 @@ from sys import argv from vyos.configtree import ConfigTree -if (len(argv) < 1): +if len(argv) < 2: print('Must specify file name!') exit(1) diff --git a/src/migration-scripts/vrrp/3-to-4 b/src/migration-scripts/vrrp/3-to-4 new file mode 100755 index 000000000..e5d93578c --- /dev/null +++ b/src/migration-scripts/vrrp/3-to-4 @@ -0,0 +1,51 @@ +#!/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/>. + +from sys import argv +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 = ['high-availability', 'virtual-server'] +config = ConfigTree(config_file) + +if not config.exists(base): + # Nothing to do + exit(0) + +if config.exists(base): + for vs in config.list_nodes(base): + vs_base = base + [vs] + + # If the fwmark is used, the address is not required + if not config.exists(vs_base + ['fwmark']): + # add option: 'virtual-server <tag> address x.x.x.x' + config.set(vs_base + ['address'], value=vs) + + +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/webproxy/1-to-2 b/src/migration-scripts/webproxy/1-to-2 index 070ff356d..03f357878 100755 --- a/src/migration-scripts/webproxy/1-to-2 +++ b/src/migration-scripts/webproxy/1-to-2 @@ -7,7 +7,7 @@ import sys from vyos.configtree import ConfigTree -if len(sys.argv) < 1: +if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) diff --git a/src/op_mode/accelppp.py b/src/op_mode/accelppp.py index 00de45fc8..67ce786d0 100755 --- a/src/op_mode/accelppp.py +++ b/src/op_mode/accelppp.py @@ -21,7 +21,7 @@ import vyos.accel_ppp import vyos.opmode from vyos.configquery import ConfigTreeQuery -from vyos.util import rc_cmd +from vyos.utils.process import rc_cmd accel_dict = { diff --git a/src/op_mode/bgp.py b/src/op_mode/bgp.py index af9ea788b..096113cb4 100755 --- a/src/op_mode/bgp.py +++ b/src/op_mode/bgp.py @@ -81,7 +81,7 @@ ArgFamily = typing.Literal['inet', 'inet6', 'l2vpn'] ArgFamilyModifier = typing.Literal['unicast', 'labeled_unicast', 'multicast', 'vpn', 'flowspec'] def show_summary(raw: bool): - from vyos.util import cmd + from vyos.utils.process import cmd if raw: from json import loads @@ -96,7 +96,7 @@ def show_summary(raw: bool): return output def show_neighbors(raw: bool): - from vyos.util import cmd + from vyos.utils.process import cmd from vyos.utils.dict import dict_to_list if raw: @@ -129,7 +129,7 @@ def show(raw: bool, frr_command = frr_command_template.render(kwargs) frr_command = re.sub(r'\s+', ' ', frr_command) - from vyos.util import cmd + from vyos.utils.process import cmd output = cmd(f"vtysh -c '{frr_command}'") if raw: diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py index d6098c158..185db4f20 100755 --- a/src/op_mode/bridge.py +++ b/src/op_mode/bridge.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -22,12 +22,13 @@ import typing from sys import exit from tabulate import tabulate -from vyos.util import cmd, rc_cmd -from vyos.util import dict_search +from vyos.utils.process import cmd +from vyos.utils.process import rc_cmd +from vyos.utils.process import call +from vyos.utils.dict import dict_search import vyos.opmode - def _get_json_data(): """ Get bridge data format JSON @@ -44,11 +45,14 @@ def _get_raw_data_summary(): return data_dict -def _get_raw_data_vlan(): +def _get_raw_data_vlan(tunnel:bool=False): """ :returns dict """ - json_data = cmd('bridge --json --compressvlans vlan show') + show = 'show' + if tunnel: + show = 'tunnel' + json_data = cmd(f'bridge --json --compressvlans vlan {show}') data_dict = json.loads(json_data) return data_dict @@ -128,13 +132,38 @@ def _get_formatted_output_vlan(data): if vlan_entry.get('vlanEnd'): vlan_end = vlan_entry.get('vlanEnd') vlan = f'{vlan}-{vlan_end}' - flags = ', '.join(vlan_entry.get('flags')).lower() + flags_raw = vlan_entry.get('flags') + flags = ', '.join(flags_raw if isinstance(flags_raw,list) else "").lower() data_entries.append([interface, vlan, flags]) - headers = ["Interface", "Vlan", "Flags"] + headers = ["Interface", "VLAN", "Flags"] output = tabulate(data_entries, headers) return output +def _get_formatted_output_vlan_tunnel(data): + data_entries = [] + for entry in data: + interface = entry.get('ifname') + first = True + for tunnel_entry in entry.get('tunnels'): + vlan = tunnel_entry.get('vlan') + vni = tunnel_entry.get('tunid') + if first: + data_entries.append([interface, vlan, vni]) + first = False + else: + # Group by VXLAN interface only - no need to repeat + # VXLAN interface name for every VLAN <-> VNI mapping + # + # Interface VLAN VNI + # ----------- ------ ----- + # vxlan0 100 100 + # 200 200 + data_entries.append(['', vlan, vni]) + + headers = ["Interface", "VLAN", "VNI"] + output = tabulate(data_entries, headers) + return output def _get_formatted_output_fdb(data): data_entries = [] @@ -163,6 +192,23 @@ def _get_formatted_output_mdb(data): output = tabulate(data_entries, headers) return output +def _get_bridge_detail(iface): + """Get interface detail statistics""" + return call(f'vtysh -c "show interface {iface}"') + +def _get_bridge_detail_nexthop_group(iface): + """Get interface detail nexthop_group statistics""" + return call(f'vtysh -c "show interface {iface} nexthop-group"') + +def _get_bridge_detail_nexthop_group_raw(iface): + out = cmd(f'vtysh -c "show interface {iface} nexthop-group"') + return out + +def _get_bridge_detail_raw(iface): + """Get interface detail json statistics""" + data = cmd(f'vtysh -c "show interface {iface} json"') + data_dict = json.loads(data) + return data_dict def show(raw: bool): bridge_data = _get_raw_data_summary() @@ -172,12 +218,15 @@ def show(raw: bool): return _get_formatted_output_summary(bridge_data) -def show_vlan(raw: bool): - bridge_vlan = _get_raw_data_vlan() +def show_vlan(raw: bool, tunnel: typing.Optional[bool]): + bridge_vlan = _get_raw_data_vlan(tunnel) if raw: return bridge_vlan else: - return _get_formatted_output_vlan(bridge_vlan) + if tunnel: + return _get_formatted_output_vlan_tunnel(bridge_vlan) + else: + return _get_formatted_output_vlan(bridge_vlan) def show_fdb(raw: bool, interface: str): @@ -195,6 +244,17 @@ def show_mdb(raw: bool, interface: str): else: return _get_formatted_output_mdb(mdb_data) +def show_detail(raw: bool, nexthop_group: typing.Optional[bool], interface: str): + if raw: + if nexthop_group: + return _get_bridge_detail_nexthop_group_raw(interface) + else: + return _get_bridge_detail_raw(interface) + else: + if nexthop_group: + return _get_bridge_detail_nexthop_group(interface) + else: + return _get_bridge_detail(interface) if __name__ == '__main__': try: diff --git a/src/op_mode/clear_conntrack.py b/src/op_mode/clear_conntrack.py index 423694187..fec7cf144 100755 --- a/src/op_mode/clear_conntrack.py +++ b/src/op_mode/clear_conntrack.py @@ -16,8 +16,9 @@ import sys -from vyos.util import ask_yes_no -from vyos.util import cmd, DEVNULL +from vyos.utils.io import ask_yes_no +from vyos.utils.process import cmd +from vyos.utils.process import DEVNULL if not ask_yes_no("This will clear all currently tracked and expected connections. Continue?"): sys.exit(1) diff --git a/src/op_mode/clear_dhcp_lease.py b/src/op_mode/clear_dhcp_lease.py index 250dbcce1..f372d3af0 100755 --- a/src/op_mode/clear_dhcp_lease.py +++ b/src/op_mode/clear_dhcp_lease.py @@ -7,9 +7,9 @@ from isc_dhcp_leases import Lease from isc_dhcp_leases import IscDhcpLeases from vyos.configquery import ConfigTreeQuery -from vyos.util import ask_yes_no -from vyos.util import call -from vyos.util import commit_in_progress +from vyos.utils.io import ask_yes_no +from vyos.utils.process import call +from vyos.utils.commit import commit_in_progress config = ConfigTreeQuery() diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py index d39e88bf3..89f929be7 100755 --- a/src/op_mode/connect_disconnect.py +++ b/src/op_mode/connect_disconnect.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -19,10 +19,10 @@ import argparse from psutil import process_iter -from vyos.util import call -from vyos.util import commit_in_progress -from vyos.util import DEVNULL -from vyos.util import is_wwan_connected +from vyos.utils.process import call +from vyos.utils.commit import commit_in_progress +from vyos.utils.network import is_wwan_connected +from vyos.utils.process import DEVNULL def check_ppp_interface(interface): if not os.path.isfile(f'/etc/ppp/peers/{interface}'): diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py index ea7c4c208..cf8adf795 100755 --- a/src/op_mode/conntrack.py +++ b/src/op_mode/conntrack.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -19,8 +19,8 @@ import typing import xmltodict from tabulate import tabulate -from vyos.util import cmd -from vyos.util import run +from vyos.utils.process import cmd +from vyos.utils.process import run import vyos.opmode diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py index c3345a936..a38688e45 100755 --- a/src/op_mode/conntrack_sync.py +++ b/src/op_mode/conntrack_sync.py @@ -24,10 +24,10 @@ import vyos.opmode from argparse import ArgumentParser from vyos.configquery import CliShellApiConfigQuery from vyos.configquery import ConfigTreeQuery -from vyos.util import call -from vyos.util import commit_in_progress -from vyos.util import cmd -from vyos.util import run +from vyos.utils.commit import commit_in_progress +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import run from vyos.template import render_to_string conntrackd_bin = '/usr/sbin/conntrackd' diff --git a/src/op_mode/container.py b/src/op_mode/container.py index d48766a0c..5a022d0c0 100755 --- a/src/op_mode/container.py +++ b/src/op_mode/container.py @@ -19,7 +19,7 @@ import sys from sys import exit -from vyos.util import cmd +from vyos.utils.process import cmd import vyos.opmode @@ -36,14 +36,14 @@ def _get_raw_data(command: str) -> list: return data def add_image(name: str): - from vyos.util import rc_cmd + from vyos.utils.process import rc_cmd rc, output = rc_cmd(f'podman image pull {name}') if rc != 0: raise vyos.opmode.InternalError(output) def delete_image(name: str): - from vyos.util import rc_cmd + from vyos.utils.process import rc_cmd rc, output = rc_cmd(f'podman image rm --force {name}') if rc != 0: @@ -77,13 +77,13 @@ def show_network(raw: bool): def restart(name: str): - from vyos.util import rc_cmd + from vyos.utils.process import rc_cmd rc, output = rc_cmd(f'systemctl restart vyos-container-{name}.service') if rc != 0: print(output) return None - print(f'Container name "{name}" restarted!') + print(f'Container "{name}" restarted!') return output diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py index fe7f252ba..77f38992b 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -14,10 +14,12 @@ # 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 os import sys import typing from datetime import datetime +from glob import glob from ipaddress import ip_address from isc_dhcp_leases import IscDhcpLeases from tabulate import tabulate @@ -27,9 +29,12 @@ import vyos.opmode from vyos.base import Warning from vyos.configquery import ConfigTreeQuery -from vyos.util import cmd -from vyos.util import dict_search -from vyos.util import is_systemd_service_running +from vyos.utils.dict import dict_search +from vyos.utils.file import read_file +from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_running + +time_string = "%a %b %d %H:%M:%S %Z %Y" config = ConfigTreeQuery() lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] @@ -287,6 +292,97 @@ def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str], return _get_formatted_server_leases(lease_data, family=family) +def _get_raw_client_leases(family='inet', interface=None): + from time import mktime + from datetime import datetime + from vyos.defaults import directories + from vyos.utils.network import get_interface_vrf + + lease_dir = directories['isc_dhclient_dir'] + lease_files = [] + lease_data = [] + + if interface: + tmp = f'{lease_dir}/dhclient_{interface}.lease' + if os.path.exists(tmp): + lease_files.append(tmp) + else: + # All DHCP leases + lease_files = glob(f'{lease_dir}/dhclient_*.lease') + + for lease in lease_files: + tmp = {} + with open(lease, 'r') as f: + for line in f.readlines(): + line = line.rstrip() + if 'last_update' not in tmp: + # ISC dhcp client contains least_update timestamp in human readable + # format this makes less sense for an API and also the expiry + # timestamp is provided in UNIX time. Convert string (e.g. Sun Jul + # 30 18:13:44 CEST 2023) to UNIX time (1690733624) + tmp.update({'last_update' : int(mktime(datetime.strptime(line, time_string).timetuple()))}) + continue + + k, v = line.split('=') + tmp.update({k : v.replace("'", "")}) + + if 'interface' in tmp: + vrf = get_interface_vrf(tmp['interface']) + if vrf: tmp.update({'vrf' : vrf}) + + lease_data.append(tmp) + + return lease_data + +def _get_formatted_client_leases(lease_data, family): + from time import localtime + from time import strftime + + from vyos.utils.network import is_intf_addr_assigned + + data_entries = [] + for lease in lease_data: + if not lease.get('new_ip_address'): + continue + data_entries.append(["Interface", lease['interface']]) + if 'new_ip_address' in lease: + tmp = '[Active]' if is_intf_addr_assigned(lease['interface'], lease['new_ip_address']) else '[Inactive]' + data_entries.append(["IP address", lease['new_ip_address'], tmp]) + if 'new_subnet_mask' in lease: + data_entries.append(["Subnet Mask", lease['new_subnet_mask']]) + if 'new_domain_name' in lease: + data_entries.append(["Domain Name", lease['new_domain_name']]) + if 'new_routers' in lease: + data_entries.append(["Router", lease['new_routers']]) + if 'new_domain_name_servers' in lease: + data_entries.append(["Name Server", lease['new_domain_name_servers']]) + if 'new_dhcp_server_identifier' in lease: + data_entries.append(["DHCP Server", lease['new_dhcp_server_identifier']]) + if 'new_dhcp_lease_time' in lease: + data_entries.append(["DHCP Server", lease['new_dhcp_lease_time']]) + if 'vrf' in lease: + data_entries.append(["VRF", lease['vrf']]) + if 'last_update' in lease: + tmp = strftime(time_string, localtime(int(lease['last_update']))) + data_entries.append(["Last Update", tmp]) + if 'new_expiry' in lease: + tmp = strftime(time_string, localtime(int(lease['new_expiry']))) + data_entries.append(["Expiry", tmp]) + + # Add empty marker + data_entries.append(['']) + + output = tabulate(data_entries, tablefmt='plain') + + return output + +def show_client_leases(raw: bool, family: ArgFamily, interface: typing.Optional[str]): + lease_data = _get_raw_client_leases(family=family, interface=interface) + if raw: + return lease_data + else: + return _get_formatted_client_leases(lease_data, family=family) + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) diff --git a/src/op_mode/dns.py b/src/op_mode/dns.py index f8863c530..2168aef89 100755 --- a/src/op_mode/dns.py +++ b/src/op_mode/dns.py @@ -20,7 +20,7 @@ import sys from tabulate import tabulate from vyos.configquery import ConfigTreeQuery -from vyos.util import cmd +from vyos.utils.process import cmd import vyos.opmode diff --git a/src/op_mode/dynamic_dns.py b/src/op_mode/dns_dynamic.py index d41a74db3..12aa5494a 100755 --- a/src/op_mode/dynamic_dns.py +++ b/src/op_mode/dns_dynamic.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2020 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -22,7 +22,7 @@ from tabulate import tabulate from vyos.config import Config from vyos.template import is_ipv4, is_ipv6 -from vyos.util import call +from vyos.utils.process import call cache_file = r'/run/ddclient/ddclient.cache' diff --git a/src/op_mode/dns_forwarding_reset.py b/src/op_mode/dns_forwarding_reset.py index bfc640a26..55e20918f 100755 --- a/src/op_mode/dns_forwarding_reset.py +++ b/src/op_mode/dns_forwarding_reset.py @@ -25,7 +25,7 @@ import argparse from sys import exit from vyos.config import Config -from vyos.util import call +from vyos.utils.process import call PDNS_CMD='/usr/bin/rec_control --socket-dir=/run/powerdns' diff --git a/src/op_mode/dns_forwarding_statistics.py b/src/op_mode/dns_forwarding_statistics.py index d79b6c024..32b5c76a7 100755 --- a/src/op_mode/dns_forwarding_statistics.py +++ b/src/op_mode/dns_forwarding_statistics.py @@ -4,7 +4,7 @@ import jinja2 from sys import exit from vyos.config import Config -from vyos.util import cmd +from vyos.utils.process import cmd PDNS_CMD='/usr/bin/rec_control --socket-dir=/run/powerdns' diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py index 46bda5f7e..581710b31 100755 --- a/src/op_mode/firewall.py +++ b/src/op_mode/firewall.py @@ -21,65 +21,31 @@ import re import tabulate from vyos.config import Config -from vyos.util import cmd -from vyos.util import dict_search_args +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search_args -def get_firewall_interfaces(firewall, name=None, ipv6=False): - directions = ['in', 'out', 'local'] - - if 'interface' in firewall: - for ifname, if_conf in firewall['interface'].items(): - for direction in directions: - if direction not in if_conf: - continue - - fw_conf = if_conf[direction] - name_str = f'({ifname},{direction})' - - if 'name' in fw_conf: - fw_name = fw_conf['name'] - - if not name: - firewall['name'][fw_name]['interface'].append(name_str) - elif not ipv6 and name == fw_name: - firewall['interface'].append(name_str) - - if 'ipv6_name' in fw_conf: - fw_name = fw_conf['ipv6_name'] - - if not name: - firewall['ipv6_name'][fw_name]['interface'].append(name_str) - elif ipv6 and name == fw_name: - firewall['interface'].append(name_str) - - return firewall - -def get_config_firewall(conf, name=None, ipv6=False, interfaces=True): +def get_config_firewall(conf, hook=None, priority=None, ipv6=False): config_path = ['firewall'] - if name: - config_path += ['ipv6-name' if ipv6 else 'name', name] + if hook: + config_path += ['ipv6' if ipv6 else 'ipv4', hook] + if priority: + config_path += [priority] firewall = conf.get_config_dict(config_path, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - if firewall and interfaces: - if name: - firewall['interface'] = {} - else: - if 'name' in firewall: - for fw_name, name_conf in firewall['name'].items(): - name_conf['interface'] = [] - if 'ipv6_name' in firewall: - for fw_name, name_conf in firewall['ipv6_name'].items(): - name_conf['interface'] = [] - - get_firewall_interfaces(firewall, name, ipv6) return firewall -def get_nftables_details(name, ipv6=False): +def get_nftables_details(hook, priority, ipv6=False): suffix = '6' if ipv6 else '' + aux = 'IPV6_' if ipv6 else '' name_prefix = 'NAME6_' if ipv6 else 'NAME_' - command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{name}' + if hook == 'name' or hook == 'ipv6-name': + command = f'sudo nft list chain ip{suffix} vyos_filter {name_prefix}{priority}' + else: + up_hook = hook.upper() + command = f'sudo nft list chain ip{suffix} vyos_filter VYOS_{aux}{up_hook}_{priority}' + try: results = cmd(command) except: @@ -87,7 +53,7 @@ def get_nftables_details(name, ipv6=False): out = {} for line in results.split('\n'): - comment_search = re.search(rf'{name}[\- ](\d+|default-action)', line) + comment_search = re.search(rf'{priority}[\- ](\d+|default-action)', line) if not comment_search: continue @@ -102,18 +68,15 @@ def get_nftables_details(name, ipv6=False): out[rule_id] = rule return out -def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None): +def output_firewall_name(hook, priority, firewall_conf, ipv6=False, single_rule_id=None): ip_str = 'IPv6' if ipv6 else 'IPv4' - print(f'\n---------------------------------\n{ip_str} Firewall "{name}"\n') - - if name_conf['interface']: - print('Active on: {0}\n'.format(" ".join(name_conf['interface']))) + print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {priority}"\n') - details = get_nftables_details(name, ipv6) + details = get_nftables_details(hook, priority, ipv6) rows = [] - if 'rule' in name_conf: - for rule_id, rule_conf in name_conf['rule'].items(): + if 'rule' in firewall_conf: + for rule_id, rule_conf in firewall_conf['rule'].items(): if single_rule_id and rule_id != single_rule_id: continue @@ -128,8 +91,8 @@ def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None): row.append(rule_details['conditions']) rows.append(row) - if 'default_action' in name_conf and not single_rule_id: - row = ['default', name_conf['default_action'], 'all'] + if 'default_action' in firewall_conf and not single_rule_id: + row = ['default', firewall_conf['default_action'], 'all'] if 'default-action' in details: rule_details = details['default-action'] row.append(rule_details.get('packets', 0)) @@ -140,26 +103,56 @@ def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None): header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions'] print(tabulate.tabulate(rows, header) + '\n') -def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id=None): +def output_firewall_name_statistics(hook, prior, prior_conf, ipv6=False, single_rule_id=None): ip_str = 'IPv6' if ipv6 else 'IPv4' - print(f'\n---------------------------------\n{ip_str} Firewall "{name}"\n') + print(f'\n---------------------------------\n{ip_str} Firewall "{hook} {prior}"\n') - if name_conf['interface']: - print('Active on: {0}\n'.format(" ".join(name_conf['interface']))) - - details = get_nftables_details(name, ipv6) + details = get_nftables_details(hook, prior, ipv6) rows = [] - if 'rule' in name_conf: - for rule_id, rule_conf in name_conf['rule'].items(): + if 'rule' in prior_conf: + for rule_id, rule_conf in prior_conf['rule'].items(): if single_rule_id and rule_id != single_rule_id: continue if 'disable' in rule_conf: continue - source_addr = dict_search_args(rule_conf, 'source', 'address') or '0.0.0.0/0' - dest_addr = dict_search_args(rule_conf, 'destination', 'address') or '0.0.0.0/0' + # Get source + source_addr = dict_search_args(rule_conf, 'source', 'address') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'address_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'network_group') + if not source_addr: + source_addr = dict_search_args(rule_conf, 'source', 'group', 'domain_group') + if not source_addr: + source_addr = '::/0' if ipv6 else '0.0.0.0/0' + + # Get destination + dest_addr = dict_search_args(rule_conf, 'destination', 'address') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'address_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'network_group') + if not dest_addr: + dest_addr = dict_search_args(rule_conf, 'destination', 'group', 'domain_group') + if not dest_addr: + dest_addr = '::/0' if ipv6 else '0.0.0.0/0' + + # Get inbound interface + iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_name') + if not iiface: + iiface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group') + if not iiface: + iiface = 'any' + + # Get outbound interface + oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_name') + if not oiface: + oiface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group') + if not oiface: + oiface = 'any' row = [rule_id] if rule_id in details: @@ -172,9 +165,11 @@ def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id= row.append(rule_conf['action']) row.append(source_addr) row.append(dest_addr) + row.append(iiface) + row.append(oiface) rows.append(row) - if 'default_action' in name_conf and not single_rule_id: + if 'default_action' in prior_conf and not single_rule_id: row = ['default'] if 'default-action' in details: rule_details = details['default-action'] @@ -183,13 +178,13 @@ def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id= else: row.append('0') row.append('0') - row.append(name_conf['default_action']) + row.append(prior_conf['default_action']) row.append('0.0.0.0/0') # Source row.append('0.0.0.0/0') # Dest rows.append(row) if rows: - header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination'] + header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination', 'Inbound-Interface', 'Outbound-interface'] print(tabulate.tabulate(rows, header) + '\n') def show_firewall(): @@ -201,52 +196,102 @@ def show_firewall(): if not firewall: return - if 'name' in firewall: - for name, name_conf in firewall['name'].items(): - output_firewall_name(name, name_conf, ipv6=False) + if 'ipv4' in firewall: + for hook, hook_conf in firewall['ipv4'].items(): + for prior, prior_conf in firewall['ipv4'][hook].items(): + output_firewall_name(hook, prior, prior_conf, ipv6=False) + + if 'ipv6' in firewall: + for hook, hook_conf in firewall['ipv6'].items(): + for prior, prior_conf in firewall['ipv6'][hook].items(): + output_firewall_name(hook, prior, prior_conf, ipv6=True) - if 'ipv6_name' in firewall: - for name, name_conf in firewall['ipv6_name'].items(): - output_firewall_name(name, name_conf, ipv6=True) +def show_firewall_family(family): + print(f'Rulesets {family} Information') -def show_firewall_name(name, ipv6=False): + conf = Config() + firewall = get_config_firewall(conf) + + if not firewall: + return + + for hook, hook_conf in firewall[family].items(): + for prior, prior_conf in firewall[family][hook].items(): + if family == 'ipv6': + output_firewall_name(hook, prior, prior_conf, ipv6=True) + else: + output_firewall_name(hook, prior, prior_conf, ipv6=False) + +def show_firewall_name(hook, priority, ipv6=False): print('Ruleset Information') conf = Config() - firewall = get_config_firewall(conf, name, ipv6) + firewall = get_config_firewall(conf, hook, priority, ipv6) if firewall: - output_firewall_name(name, firewall, ipv6) + output_firewall_name(hook, priority, firewall, ipv6) -def show_firewall_rule(name, rule_id, ipv6=False): +def show_firewall_rule(hook, priority, rule_id, ipv6=False): print('Rule Information') conf = Config() - firewall = get_config_firewall(conf, name, ipv6) + firewall = get_config_firewall(conf, hook, priority, ipv6) if firewall: - output_firewall_name(name, firewall, ipv6, rule_id) + output_firewall_name(hook, priority, firewall, ipv6, rule_id) def show_firewall_group(name=None): conf = Config() - firewall = get_config_firewall(conf, interfaces=False) + firewall = get_config_firewall(conf) if 'group' not in firewall: return def find_references(group_type, group_name): out = [] - for name_type in ['name', 'ipv6_name']: - if name_type not in firewall: - continue - for name, name_conf in firewall[name_type].items(): - if 'rule' not in name_conf: - continue - for rule_id, rule_conf in name_conf['rule'].items(): - source_group = dict_search_args(rule_conf, 'source', 'group', group_type) - dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) - if source_group and group_name == source_group: - out.append(f'{name}-{rule_id}') - elif dest_group and group_name == dest_group: - out.append(f'{name}-{rule_id}') + family = [] + if group_type in ['address_group', 'network_group']: + family = ['ipv4'] + elif group_type == 'ipv6_address_group': + family = ['ipv6'] + group_type = 'address_group' + elif group_type == 'ipv6_network_group': + family = ['ipv6'] + group_type = 'network_group' + else: + family = ['ipv4', 'ipv6'] + + for item in family: + for name_type in ['name', 'ipv6_name', 'forward', 'input', 'output']: + if item in firewall: + if name_type not in firewall[item]: + continue + for priority, priority_conf in firewall[item][name_type].items(): + if priority not in firewall[item][name_type]: + continue + for rule_id, rule_conf in priority_conf['rule'].items(): + source_group = dict_search_args(rule_conf, 'source', 'group', group_type) + dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type) + in_interface = dict_search_args(rule_conf, 'inbound_interface', 'interface_group') + out_interface = dict_search_args(rule_conf, 'outbound_interface', 'interface_group') + if source_group: + if source_group[0] == "!": + source_group = source_group[1:] + if group_name == source_group: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if dest_group: + if dest_group[0] == "!": + dest_group = dest_group[1:] + if group_name == dest_group: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if in_interface: + if in_interface[0] == "!": + in_interface = in_interface[1:] + if group_name == in_interface: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') + if out_interface: + if out_interface[0] == "!": + out_interface = out_interface[1:] + if group_name == out_interface: + out.append(f'{item}-{name_type}-{priority}-{rule_id}') return out header = ['Name', 'Type', 'References', 'Members'] @@ -267,6 +312,8 @@ def show_firewall_group(name=None): row.append("\n".join(sorted(group_conf['mac_address']))) elif 'port' in group_conf: row.append("\n".join(sorted(group_conf['port']))) + elif 'interface' in group_conf: + row.append("\n".join(sorted(group_conf['interface']))) else: row.append('N/A') rows.append(row) @@ -284,28 +331,28 @@ def show_summary(): if not firewall: return - header = ['Ruleset Name', 'Description', 'References'] + header = ['Ruleset Hook', 'Ruleset Priority', 'Description', 'References'] v4_out = [] v6_out = [] - if 'name' in firewall: - for name, name_conf in firewall['name'].items(): - description = name_conf.get('description', '') - interfaces = ", ".join(name_conf['interface']) - v4_out.append([name, description, interfaces]) + if 'ipv4' in firewall: + for hook, hook_conf in firewall['ipv4'].items(): + for prior, prior_conf in firewall['ipv4'][hook].items(): + description = prior_conf.get('description', '') + v4_out.append([hook, prior, description]) - if 'ipv6_name' in firewall: - for name, name_conf in firewall['ipv6_name'].items(): - description = name_conf.get('description', '') - interfaces = ", ".join(name_conf['interface']) - v6_out.append([name, description, interfaces or 'N/A']) + if 'ipv6' in firewall: + for hook, hook_conf in firewall['ipv6'].items(): + for prior, prior_conf in firewall['ipv6'][hook].items(): + description = prior_conf.get('description', '') + v6_out.append([hook, prior, description]) if v6_out: - print('\nIPv6 name:\n') + print('\nIPv6 Ruleset:\n') print(tabulate.tabulate(v6_out, header) + '\n') if v4_out: - print('\nIPv4 name:\n') + print('\nIPv4 Ruleset:\n') print(tabulate.tabulate(v4_out, header) + '\n') show_firewall_group() @@ -319,18 +366,23 @@ def show_statistics(): if not firewall: return - if 'name' in firewall: - for name, name_conf in firewall['name'].items(): - output_firewall_name_statistics(name, name_conf, ipv6=False) + if 'ipv4' in firewall: + for hook, hook_conf in firewall['ipv4'].items(): + for prior, prior_conf in firewall['ipv4'][hook].items(): + output_firewall_name_statistics(hook,prior, prior_conf, ipv6=False) - if 'ipv6_name' in firewall: - for name, name_conf in firewall['ipv6_name'].items(): - output_firewall_name_statistics(name, name_conf, ipv6=True) + if 'ipv6' in firewall: + for hook, hook_conf in firewall['ipv6'].items(): + for prior, prior_conf in firewall['ipv6'][hook].items(): + output_firewall_name_statistics(hook,prior, prior_conf, ipv6=True) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--action', help='Action', required=False) parser.add_argument('--name', help='Firewall name', required=False, action='store', nargs='?', default='') + parser.add_argument('--family', help='IP family', required=False, action='store', nargs='?', default='') + parser.add_argument('--hook', help='Firewall hook', required=False, action='store', nargs='?', default='') + parser.add_argument('--priority', help='Firewall priority', required=False, action='store', nargs='?', default='') parser.add_argument('--rule', help='Firewall Rule ID', required=False) parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true') @@ -338,11 +390,13 @@ if __name__ == '__main__': if args.action == 'show': if not args.rule: - show_firewall_name(args.name, args.ipv6) + show_firewall_name(args.hook, args.priority, args.ipv6) else: - show_firewall_rule(args.name, args.rule, args.ipv6) + show_firewall_rule(args.hook, args.priority, args.rule, args.ipv6) elif args.action == 'show_all': show_firewall() + elif args.action == 'show_family': + show_firewall_family(args.family) elif args.action == 'show_group': show_firewall_group(args.name) elif args.action == 'show_statistics': diff --git a/src/op_mode/flow_accounting_op.py b/src/op_mode/flow_accounting_op.py index 514143cd7..497ccafdf 100755 --- a/src/op_mode/flow_accounting_op.py +++ b/src/op_mode/flow_accounting_op.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -13,18 +13,18 @@ # # 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 sys import argparse import re import ipaddress import os.path + from tabulate import tabulate from json import loads -from vyos.util import cmd -from vyos.util import commit_in_progress -from vyos.util import run +from vyos.utils.commit import commit_in_progress +from vyos.utils.process import cmd +from vyos.utils.process import run from vyos.logger import syslog # some default values diff --git a/src/op_mode/format_disk.py b/src/op_mode/format_disk.py index b3ba44e87..31ceb196a 100755 --- a/src/op_mode/format_disk.py +++ b/src/op_mode/format_disk.py @@ -20,10 +20,10 @@ import re from datetime import datetime -from vyos.util import ask_yes_no -from vyos.util import call -from vyos.util import cmd -from vyos.util import DEVNULL +from vyos.utils.io import ask_yes_no +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import DEVNULL def list_disks(): disks = set() diff --git a/src/op_mode/generate_interfaces_debug_archive.py b/src/op_mode/generate_interfaces_debug_archive.py index f5767080a..3059aad23 100755 --- a/src/op_mode/generate_interfaces_debug_archive.py +++ b/src/op_mode/generate_interfaces_debug_archive.py @@ -20,7 +20,7 @@ from shutil import rmtree from socket import gethostname from sys import exit from tarfile import open as tar_open -from vyos.util import rc_cmd +from vyos.utils.process import rc_cmd import os # define a list of commands that needs to be executed diff --git a/src/op_mode/generate_ipsec_debug_archive.py b/src/op_mode/generate_ipsec_debug_archive.py index 1422559a8..60195d48b 100755 --- a/src/op_mode/generate_ipsec_debug_archive.py +++ b/src/op_mode/generate_ipsec_debug_archive.py @@ -20,7 +20,7 @@ from shutil import rmtree from socket import gethostname from sys import exit from tarfile import open as tar_open -from vyos.util import rc_cmd +from vyos.utils.process import rc_cmd # define a list of commands that needs to be executed CMD_LIST: list[str] = [ diff --git a/src/op_mode/generate_openconnect_otp_key.py b/src/op_mode/generate_openconnect_otp_key.py index 363bcf3ea..99b67d261 100755 --- a/src/op_mode/generate_openconnect_otp_key.py +++ b/src/op_mode/generate_openconnect_otp_key.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -17,7 +17,7 @@ import argparse import os -from vyos.util import popen +from vyos.utils.process import popen from secrets import token_hex from base64 import b32encode diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py index 0628e6135..cec370a07 100755 --- a/src/op_mode/generate_ovpn_client_file.py +++ b/src/op_mode/generate_ovpn_client_file.py @@ -22,7 +22,7 @@ from textwrap import fill from vyos.configquery import ConfigTreeQuery from vyos.ifconfig import Section -from vyos.util import cmd +from vyos.utils.process import cmd client_config = """ diff --git a/src/op_mode/generate_ssh_server_key.py b/src/op_mode/generate_ssh_server_key.py index 43e94048d..d6063c43c 100755 --- a/src/op_mode/generate_ssh_server_key.py +++ b/src/op_mode/generate_ssh_server_key.py @@ -15,9 +15,9 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sys import exit -from vyos.util import ask_yes_no -from vyos.util import cmd -from vyos.util import commit_in_progress +from vyos.utils.io import ask_yes_no +from vyos.utils.process import cmd +from vyos.utils.commit import commit_in_progress if not ask_yes_no('Do you really want to remove the existing SSH host keys?'): exit(0) diff --git a/src/op_mode/generate_system_login_user.py b/src/op_mode/generate_system_login_user.py index 8f8827b1b..1b328eae0 100755 --- a/src/op_mode/generate_system_login_user.py +++ b/src/op_mode/generate_system_login_user.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -17,7 +17,7 @@ import argparse import os -from vyos.util import popen +from vyos.utils.process import popen from secrets import token_hex from base64 import b32encode diff --git a/src/op_mode/igmp-proxy.py b/src/op_mode/igmp-proxy.py index 0086c9aa6..709e25915 100755 --- a/src/op_mode/igmp-proxy.py +++ b/src/op_mode/igmp-proxy.py @@ -28,16 +28,14 @@ import tabulate import vyos.config import vyos.opmode -from vyos.util import bytes_to_human, print_error +from vyos.utils.convert import bytes_to_human +from vyos.utils.io import print_error +from vyos.utils.process import process_named_running def _is_configured(): """Check if IGMP proxy is configured""" return vyos.config.Config().exists_effective('protocols igmp-proxy') -def _is_running(): - """Check if IGMP proxy is currently running""" - return not vyos.util.run('ps -C igmpproxy') - def _kernel_to_ip(addr): """ Convert any given address from Linux kernel to a proper, IPv4 address @@ -84,7 +82,7 @@ def show_interface(raw: bool): if not _is_configured(): print_error('IGMP proxy is not configured.') sys.exit(0) -if not _is_running(): +if not process_named_running('igmpproxy'): print_error('IGMP proxy is not running.') sys.exit(0) diff --git a/src/op_mode/ikev2_profile_generator.py b/src/op_mode/ikev2_profile_generator.py index a22f04c45..5454cc0ce 100755 --- a/src/op_mode/ikev2_profile_generator.py +++ b/src/op_mode/ikev2_profile_generator.py @@ -24,7 +24,7 @@ from cryptography.x509.oid import NameOID from vyos.configquery import ConfigTreeQuery from vyos.pki import load_certificate from vyos.template import render_to_string -from vyos.util import ask_input +from vyos.utils.io import ask_input # Apple profiles only support one IKE/ESP encryption cipher and hash, whereas # VyOS comes with a multitude of different proposals for a connection. diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py index f38b95a71..782e178c6 100755 --- a/src/op_mode/interfaces.py +++ b/src/op_mode/interfaces.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -28,7 +28,9 @@ import vyos.opmode from vyos.ifconfig import Section from vyos.ifconfig import Interface from vyos.ifconfig import VRRP -from vyos.util import cmd, rc_cmd, call +from vyos.utils.process import cmd +from vyos.utils.process import rc_cmd +from vyos.utils.process import call def catch_broken_pipe(func): def wrapped(*args, **kwargs): @@ -384,7 +386,7 @@ def _format_show_counters(data: list): rx_errors = entry.get('rx_over_errors') tx_errors = entry.get('tx_carrier_errors') data_entries.append([interface, rx_packets, rx_bytes, tx_packets, tx_bytes, rx_dropped, tx_dropped, rx_errors, tx_errors]) - + headers = ['Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes', 'Rx Dropped', 'Tx Dropped', 'Rx Errors', 'Tx Errors'] output = tabulate(data_entries, headers, numalign="left") print (output) diff --git a/src/op_mode/ipoe-control.py b/src/op_mode/ipoe-control.py index 7111498b2..0f33beca7 100755 --- a/src/op_mode/ipoe-control.py +++ b/src/op_mode/ipoe-control.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -18,7 +18,8 @@ import sys import argparse from vyos.config import Config -from vyos.util import popen, run +from vyos.utils.process import popen +from vyos.utils.process import run cmd_dict = { 'cmd_base' : '/usr/bin/accel-cmd -p 2002 ', diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index 823bd039d..57d3cfed9 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -21,9 +21,9 @@ from hurry import filesize from re import split as re_split from tabulate import tabulate -from vyos.util import convert_data -from vyos.util import seconds_to_human -from vyos.util import cmd +from vyos.utils.convert import convert_data +from vyos.utils.convert import seconds_to_human +from vyos.utils.process import cmd from vyos.configquery import ConfigTreeQuery import vyos.opmode diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py index 1a1b94783..c287b8fa6 100755 --- a/src/op_mode/lldp.py +++ b/src/op_mode/lldp.py @@ -22,8 +22,8 @@ import typing from tabulate import tabulate from vyos.configquery import ConfigTreeQuery -from vyos.util import cmd -from vyos.util import dict_search +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search import vyos.opmode unconf_message = 'LLDP is not configured' diff --git a/src/op_mode/log.py b/src/op_mode/log.py index b0abd6191..797ba5a88 100755 --- a/src/op_mode/log.py +++ b/src/op_mode/log.py @@ -21,7 +21,7 @@ import typing from jinja2 import Template -from vyos.util import rc_cmd +from vyos.utils.process import rc_cmd import vyos.opmode diff --git a/src/op_mode/memory.py b/src/op_mode/memory.py index 7666de646..eb530035b 100755 --- a/src/op_mode/memory.py +++ b/src/op_mode/memory.py @@ -54,7 +54,7 @@ def _get_raw_data(): return mem_data def _get_formatted_output(mem): - from vyos.util import bytes_to_human + from vyos.utils.convert import bytes_to_human # For human-readable outputs, we convert bytes to more convenient units # (100M, 1.3G...) diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index c92795745..71a40c0e1 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -25,8 +25,8 @@ from tabulate import tabulate import vyos.opmode from vyos.configquery import ConfigTreeQuery -from vyos.util import cmd -from vyos.util import dict_search +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search base = 'nat' unconf_message = 'NAT is not configured' diff --git a/src/op_mode/neighbor.py b/src/op_mode/neighbor.py index b329ea280..8b3c45c7c 100755 --- a/src/op_mode/neighbor.py +++ b/src/op_mode/neighbor.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -31,17 +31,14 @@ import sys import typing import vyos.opmode +from vyos.utils.network import interface_exists ArgFamily = typing.Literal['inet', 'inet6'] ArgState = typing.Literal['reachable', 'stale', 'failed', 'permanent'] -def interface_exists(interface): - import os - return os.path.exists(f'/sys/class/net/{interface}') - def get_raw_data(family, interface=None, state=None): from json import loads - from vyos.util import cmd + from vyos.utils.process import cmd if interface: if not interface_exists(interface): @@ -102,7 +99,7 @@ def show(raw: bool, family: ArgFamily, interface: typing.Optional[str], return format_neighbors(data, interface) def reset(family: ArgFamily, interface: typing.Optional[str], address: typing.Optional[str]): - from vyos.util import run + from vyos.utils.process import run if address and interface: raise ValueError("interface and address parameters are mutually exclusive") @@ -114,7 +111,6 @@ def reset(family: ArgFamily, interface: typing.Optional[str], address: typing.Op # Flush an entire neighbor table run(f"""ip --family {family} neighbor flush""") - if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) diff --git a/src/op_mode/nhrp.py b/src/op_mode/nhrp.py index 5ff91a59c..e66f33079 100755 --- a/src/op_mode/nhrp.py +++ b/src/op_mode/nhrp.py @@ -18,9 +18,9 @@ import sys import tabulate import vyos.opmode -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import colon_separated_to_dict +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.dict import colon_separated_to_dict def _get_formatted_output(output_dict: dict) -> str: diff --git a/src/op_mode/openconnect-control.py b/src/op_mode/openconnect-control.py index 20c50e779..b70d4fa16 100755 --- a/src/op_mode/openconnect-control.py +++ b/src/op_mode/openconnect-control.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -18,12 +18,13 @@ import sys import argparse import json -from vyos.config import Config -from vyos.util import popen -from vyos.util import run -from vyos.util import DEVNULL from tabulate import tabulate +from vyos.config import Config +from vyos.utils.process import popen +from vyos.utils.process import run +from vyos.utils.process import DEVNULL + occtl = '/usr/bin/occtl' occtl_socket = '/run/ocserv/occtl.socket' diff --git a/src/op_mode/openconnect.py b/src/op_mode/openconnect.py index b21890728..cfa0678a7 100755 --- a/src/op_mode/openconnect.py +++ b/src/op_mode/openconnect.py @@ -19,7 +19,7 @@ import json from tabulate import tabulate from vyos.configquery import ConfigTreeQuery -from vyos.util import rc_cmd +from vyos.utils.process import rc_cmd import vyos.opmode diff --git a/src/op_mode/openvpn.py b/src/op_mode/openvpn.py index d9ae965c5..fd9d2db92 100755 --- a/src/op_mode/openvpn.py +++ b/src/op_mode/openvpn.py @@ -23,10 +23,10 @@ import typing from tabulate import tabulate import vyos.opmode -from vyos.util import bytes_to_human -from vyos.util import commit_in_progress -from vyos.util import call -from vyos.util import rc_cmd +from vyos.utils.convert import bytes_to_human +from vyos.utils.commit import commit_in_progress +from vyos.utils.process import call +from vyos.utils.process import rc_cmd from vyos.config import Config ArgMode = typing.Literal['client', 'server', 'site_to_site'] diff --git a/src/op_mode/ping.py b/src/op_mode/ping.py index 610e63cb3..f1d87a118 100755 --- a/src/op_mode/ping.py +++ b/src/op_mode/ping.py @@ -18,7 +18,7 @@ import os import sys import socket import ipaddress -from vyos.util import get_all_vrfs +from vyos.utils.network import get_all_vrfs from vyos.ifconfig import Section @@ -90,6 +90,16 @@ options = { 'type': '<seconds>', 'help': 'Number of seconds to wait between requests' }, + 'ipv4': { + 'ping': '{command} -4', + 'type': 'noarg', + 'help': 'Use IPv4 only' + }, + 'ipv6': { + 'ping': '{command} -6', + 'type': 'noarg', + 'help': 'Use IPv6 only' + }, 'mark': { 'ping': '{command} -m {value}', 'type': '<fwmark>', diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py index b054690b0..35c7ce0e2 100755 --- a/src/op_mode/pki.py +++ b/src/op_mode/pki.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -25,33 +25,31 @@ from cryptography import x509 from cryptography.x509.oid import ExtendedKeyUsageOID from vyos.config import Config -from vyos.configquery import ConfigTreeQuery -from vyos.configdict import dict_merge from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters +from vyos.pki import get_certificate_fingerprint from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list from vyos.pki import create_private_key from vyos.pki import create_dh_parameters from vyos.pki import load_certificate, load_certificate_request, load_private_key from vyos.pki import load_crl, load_dh_parameters, load_public_key from vyos.pki import verify_certificate -from vyos.xml import defaults -from vyos.util import ask_input, ask_yes_no -from vyos.util import cmd -from vyos.util import install_into_config +from vyos.utils.io import ask_input +from vyos.utils.io import ask_yes_no +from vyos.utils.misc import install_into_config +from vyos.utils.process import cmd CERT_REQ_END = '-----END CERTIFICATE REQUEST-----' auth_dir = '/config/auth' # Helper Functions -conf = ConfigTreeQuery() +conf = Config() def get_default_values(): # Fetch default x509 values base = ['pki', 'x509', 'default'] x509_defaults = conf.get_config_dict(base, key_mangling=('-', '_'), + no_tag_node_value_mangle=True, get_first_key=True, - no_tag_node_value_mangle=True) - default_values = defaults(base) - x509_defaults = dict_merge(default_values, x509_defaults) + with_recursive_defaults=True) return x509_defaults @@ -190,7 +188,7 @@ def install_ssh_key(name, public_key, private_key, passphrase=None): def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None, prompt=True): # Show/install conf commands for key-pair - + config_paths = [] if public_key: @@ -840,7 +838,7 @@ def import_openvpn_secret(name, path): install_openvpn_key(name, key_data, key_version) # Show functions -def show_certificate_authority(name=None): +def show_certificate_authority(name=None, pem=False): headers = ['Name', 'Subject', 'Issuer CN', 'Issued', 'Expiry', 'Private Key', 'Parent'] data = [] certs = get_config_ca_certificate() @@ -852,6 +850,11 @@ def show_certificate_authority(name=None): continue cert = load_certificate(cert_dict['certificate']) + + if name and pem: + print(encode_certificate(cert)) + return + parent_ca_name = get_certificate_ca(cert, certs) cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0] @@ -867,7 +870,7 @@ def show_certificate_authority(name=None): print("Certificate Authorities:") print(tabulate.tabulate(data, headers)) -def show_certificate(name=None): +def show_certificate(name=None, pem=False): headers = ['Name', 'Type', 'Subject CN', 'Issuer CN', 'Issued', 'Expiry', 'Revoked', 'Private Key', 'CA Present'] data = [] certs = get_config_certificate() @@ -885,6 +888,10 @@ def show_certificate(name=None): if not cert: continue + if name and pem: + print(encode_certificate(cert)) + return + ca_name = get_certificate_ca(cert, ca_certs) cert_subject_cn = cert.subject.rfc4514_string().split(",")[0] cert_issuer_cn = cert.issuer.rfc4514_string().split(",")[0] @@ -906,7 +913,13 @@ def show_certificate(name=None): print("Certificates:") print(tabulate.tabulate(data, headers)) -def show_crl(name=None): +def show_certificate_fingerprint(name, hash): + cert = get_config_certificate(name=name) + cert = load_certificate(cert['certificate']) + + print(get_certificate_fingerprint(cert, hash)) + +def show_crl(name=None, pem=False): headers = ['CA Name', 'Updated', 'Revokes'] data = [] certs = get_config_ca_certificate() @@ -927,9 +940,16 @@ def show_crl(name=None): if not crl: continue + if name and pem: + print(encode_certificate(crl)) + continue + certs = get_revoked_by_serial_numbers([revoked.serial_number for revoked in crl]) data.append([cert_name, crl.last_update, ", ".join(certs)]) + if name and pem: + return + print("Certificate Revocation Lists:") print(tabulate.tabulate(data, headers)) @@ -943,6 +963,8 @@ if __name__ == '__main__': parser.add_argument('--crl', help='Certificate Revocation List', required=False) parser.add_argument('--sign', help='Sign certificate with specified CA', required=False) parser.add_argument('--self-sign', help='Self-sign the certificate', action='store_true') + parser.add_argument('--pem', help='Output using PEM encoding', action='store_true') + parser.add_argument('--fingerprint', help='Show fingerprint and exit', action='store') # SSH parser.add_argument('--ssh', help='SSH Key', required=False) @@ -1032,16 +1054,19 @@ if __name__ == '__main__': if not conf.exists(['pki', 'ca', ca_name]): print(f'CA "{ca_name}" does not exist!') exit(1) - show_certificate_authority(ca_name) + show_certificate_authority(ca_name, args.pem) elif args.certificate: cert_name = None if args.certificate == 'all' else args.certificate if cert_name: if not conf.exists(['pki', 'certificate', cert_name]): print(f'Certificate "{cert_name}" does not exist!') exit(1) - show_certificate(None if args.certificate == 'all' else args.certificate) + if args.fingerprint is None: + show_certificate(None if args.certificate == 'all' else args.certificate, args.pem) + else: + show_certificate_fingerprint(args.certificate, args.fingerprint) elif args.crl: - show_crl(None if args.crl == 'all' else args.crl) + show_crl(None if args.crl == 'all' else args.crl, args.pem) else: show_certificate_authority() show_certificate() diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py index 5953786f3..eff99de7f 100755 --- a/src/op_mode/policy_route.py +++ b/src/op_mode/policy_route.py @@ -19,8 +19,8 @@ import re import tabulate from vyos.config import Config -from vyos.util import cmd -from vyos.util import dict_search_args +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search_args def get_config_policy(conf, name=None, ipv6=False): config_path = ['policy'] @@ -61,8 +61,10 @@ def output_policy_route(name, route_conf, ipv6=False, single_rule_id=None): ip_str = 'IPv6' if ipv6 else 'IPv4' print(f'\n---------------------------------\n{ip_str} Policy Route "{name}"\n') - if route_conf['interface']: + if route_conf.get('interface'): print('Active on: {0}\n'.format(" ".join(route_conf['interface']))) + else: + print('Inactive - Not applied to any interfaces\n') details = get_nftables_details(name, ipv6) rows = [] diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py index fd4f86d88..3ac5991b4 100755 --- a/src/op_mode/powerctrl.py +++ b/src/op_mode/powerctrl.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# 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 @@ -22,7 +22,11 @@ from datetime import datetime, timedelta, time as type_time, date as type_date from sys import exit from time import time -from vyos.util import ask_yes_no, cmd, call, run, STDOUT +from vyos.utils.io import ask_yes_no +from vyos.utils.process import call +from vyos.utils.process import cmd +from vyos.utils.process import run +from vyos.utils.process import STDOUT systemd_sched_file = "/run/systemd/shutdown/scheduled" @@ -102,8 +106,19 @@ def cancel_shutdown(): else: print("Reboot or poweroff is not scheduled") +def check_unsaved_config(): + from vyos.config_mgmt import unsaved_commits + from vyos.utils.boot import boot_configuration_success + + if unsaved_commits() and boot_configuration_success(): + print("Warning: there are unsaved configuration changes!") + print("Run 'save' command if you do not want to lose those changes after reboot/shutdown.") + else: + pass def execute_shutdown(time, reboot=True, ask=True): + check_unsaved_config() + action = "reboot" if reboot else "poweroff" if not ask: if not ask_yes_no(f"Are you sure you want to {action} this system?"): diff --git a/src/op_mode/ppp-server-ctrl.py b/src/op_mode/ppp-server-ctrl.py index e93963fdd..2bae5b32a 100755 --- a/src/op_mode/ppp-server-ctrl.py +++ b/src/op_mode/ppp-server-ctrl.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# 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 @@ -18,7 +18,8 @@ import sys import argparse from vyos.config import Config -from vyos.util import popen, DEVNULL +from vyos.utils.process import popen +from vyos.utils.process import DEVNULL cmd_dict = { 'cmd_base' : '/usr/bin/accel-cmd -p {} ', diff --git a/src/op_mode/reset_openvpn.py b/src/op_mode/reset_openvpn.py index efbf65083..cef5299da 100755 --- a/src/op_mode/reset_openvpn.py +++ b/src/op_mode/reset_openvpn.py @@ -16,8 +16,8 @@ import os from sys import argv, exit -from vyos.util import call -from vyos.util import commit_in_progress +from vyos.utils.process import call +from vyos.utils.commit import commit_in_progress if __name__ == '__main__': if (len(argv) < 1): diff --git a/src/op_mode/reset_vpn.py b/src/op_mode/reset_vpn.py index 46195d6cd..61d7c8c81 100755 --- a/src/op_mode/reset_vpn.py +++ b/src/op_mode/reset_vpn.py @@ -13,10 +13,11 @@ # # 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 sys import typing -from vyos.util import run +from vyos.utils.process import run import vyos.opmode @@ -29,7 +30,6 @@ cmd_dict = { } } - def reset_conn(protocol: str, username: typing.Optional[str] = None, interface: typing.Optional[str] = None): if protocol in cmd_dict['vpn_types']: diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py index 9203c009f..3ead97f4c 100755 --- a/src/op_mode/restart_dhcp_relay.py +++ b/src/op_mode/restart_dhcp_relay.py @@ -23,8 +23,8 @@ import argparse import os import vyos.config -from vyos.util import call -from vyos.util import commit_in_progress +from vyos.utils.process import call +from vyos.utils.commit import commit_in_progress parser = argparse.ArgumentParser() diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py index 680d9f8cc..5cce377eb 100755 --- a/src/op_mode/restart_frr.py +++ b/src/op_mode/restart_frr.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2021 VyOS maintainers and contributors +# 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 @@ -23,10 +23,10 @@ from logging.handlers import SysLogHandler from shutil import rmtree from vyos.base import Warning -from vyos.util import call -from vyos.util import ask_yes_no -from vyos.util import process_named_running -from vyos.util import makedir +from vyos.utils.io import ask_yes_no +from vyos.utils.file import makedir +from vyos.utils.process import call +from vyos.utils.process import process_named_running # some default values watchfrr = '/usr/lib/frr/watchfrr.sh' diff --git a/src/op_mode/reverseproxy.py b/src/op_mode/reverseproxy.py new file mode 100755 index 000000000..44ffd7a37 --- /dev/null +++ b/src/op_mode/reverseproxy.py @@ -0,0 +1,239 @@ +#!/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 json +import socket +import sys +import typing + +from sys import exit +from tabulate import tabulate +from vyos.configquery import ConfigTreeQuery + +import vyos.opmode + +socket_path = '/run/haproxy/admin.sock' +timeout = 5 + + +def _execute_haproxy_command(command): + """Execute a command on the HAProxy UNIX socket and retrieve the response. + + Args: + command (str): The command to be executed. + + Returns: + str: The response received from the HAProxy UNIX socket. + + Raises: + socket.error: If there is an error while connecting or communicating with the socket. + + Finally: + Closes the socket connection. + + Notes: + - HAProxy expects a newline character at the end of the command. + - The socket connection is established using the HAProxy UNIX socket. + - The response from the socket is received and decoded. + + Example: + response = _execute_haproxy_command('show stat') + print(response) + """ + try: + # HAProxy expects new line for command + command = f'{command}\n' + + # Connect to the HAProxy UNIX socket + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(socket_path) + + # Set the socket timeout + sock.settimeout(timeout) + + # Send the command + sock.sendall(command.encode()) + + # Receive and decode the response + response = b'' + while True: + data = sock.recv(4096) + if not data: + break + response += data + response = response.decode() + + return (response) + + except socket.error as e: + print(f"Error: {e}") + + finally: + # Close the socket + sock.close() + + +def _convert_seconds(seconds): + """Convert seconds to days, hours, minutes, and seconds. + + Args: + seconds (int): The number of seconds to convert. + + Returns: + tuple: A tuple containing the number of days, hours, minutes, and seconds. + """ + minutes = seconds // 60 + hours = minutes // 60 + days = hours // 24 + + return days, hours % 24, minutes % 60, seconds % 60 + + +def _last_change_format(seconds): + """Format the time components into a string representation. + + Args: + seconds (int): The total number of seconds. + + Returns: + str: The formatted time string with days, hours, minutes, and seconds. + + Examples: + >>> _last_change_format(1434) + '23m54s' + >>> _last_change_format(93734) + '1d0h23m54s' + >>> _last_change_format(85434) + '23h23m54s' + """ + days, hours, minutes, seconds = _convert_seconds(seconds) + time_format = "" + + if days: + time_format += f"{days}d" + if hours: + time_format += f"{hours}h" + if minutes: + time_format += f"{minutes}m" + if seconds: + time_format += f"{seconds}s" + + return time_format + + +def _get_json_data(): + """Get haproxy data format JSON""" + return _execute_haproxy_command('show stat json') + + +def _get_raw_data(): + """Retrieve raw data from JSON and organize it into a dictionary. + + Returns: + dict: A dictionary containing the organized data categorized + into frontend, backend, and server. + """ + + data = json.loads(_get_json_data()) + lb_dict = {'frontend': [], 'backend': [], 'server': []} + + for key in data: + frontend = [] + backend = [] + server = [] + for entry in key: + obj_type = entry['objType'].lower() + position = entry['field']['pos'] + name = entry['field']['name'] + value = entry['value']['value'] + + dict_entry = {'pos': position, 'name': {name: value}} + + if obj_type == 'frontend': + frontend.append(dict_entry) + elif obj_type == 'backend': + backend.append(dict_entry) + elif obj_type == 'server': + server.append(dict_entry) + + if len(frontend) > 0: + lb_dict['frontend'].append(frontend) + if len(backend) > 0: + lb_dict['backend'].append(backend) + if len(server) > 0: + lb_dict['server'].append(server) + + return lb_dict + + +def _get_formatted_output(data): + """ + Format the data into a tabulated output. + + Args: + data (dict): The data to be formatted. + + Returns: + str: The tabulated output representing the formatted data. + """ + table = [] + headers = [ + "Proxy name", "Role", "Status", "Req rate", "Resp time", "Last change" + ] + + for key in data: + for item in data[key]: + row = [None] * len(headers) + + for element in item: + if 'pxname' in element['name']: + row[0] = element['name']['pxname'] + elif 'svname' in element['name']: + row[1] = element['name']['svname'] + elif 'status' in element['name']: + row[2] = element['name']['status'] + elif 'req_rate' in element['name']: + row[3] = element['name']['req_rate'] + elif 'rtime' in element['name']: + row[4] = f"{element['name']['rtime']} ms" + elif 'lastchg' in element['name']: + row[5] = _last_change_format(element['name']['lastchg']) + table.append(row) + + out = tabulate(table, headers, numalign="left") + return out + + +def show(raw: bool): + config = ConfigTreeQuery() + if not config.exists('load-balancing reverse-proxy'): + raise vyos.opmode.UnconfiguredSubsystem('Reverse-proxy is not configured') + + data = _get_raw_data() + if raw: + return data + else: + return _get_formatted_output(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/route.py b/src/op_mode/route.py index d6d6b7d6f..4aa57dbf4 100755 --- a/src/op_mode/route.py +++ b/src/op_mode/route.py @@ -57,7 +57,7 @@ frr_command_template = Template(""" ArgFamily = typing.Literal['inet', 'inet6'] def show_summary(raw: bool, family: ArgFamily, table: typing.Optional[int], vrf: typing.Optional[str]): - from vyos.util import cmd + from vyos.utils.process import cmd if family == 'inet': family_cmd = 'ip' @@ -119,7 +119,7 @@ def show(raw: bool, frr_command = frr_command_template.render(kwargs) frr_command = re.sub(r'\s+', ' ', frr_command) - from vyos.util import cmd + from vyos.utils.process import cmd output = cmd(f"vtysh -c '{frr_command}'") if raw: diff --git a/src/op_mode/sflow.py b/src/op_mode/sflow.py index 88f70d6bd..dca7f44cb 100755 --- a/src/op_mode/sflow.py +++ b/src/op_mode/sflow.py @@ -20,7 +20,7 @@ import sys from tabulate import tabulate from vyos.configquery import ConfigTreeQuery -from vyos.util import cmd +from vyos.utils.process import cmd import vyos.opmode diff --git a/src/op_mode/show-bond.py b/src/op_mode/show-bond.py index edf7847fc..f676e0841 100755 --- a/src/op_mode/show-bond.py +++ b/src/op_mode/show-bond.py @@ -19,7 +19,7 @@ import jinja2 from argparse import ArgumentParser from vyos.ifconfig import Section from vyos.ifconfig import BondIf -from vyos.util import read_file +from vyos.utils.file import read_file from sys import exit diff --git a/src/op_mode/show_acceleration.py b/src/op_mode/show_acceleration.py index 48c31d4d9..1c4831f1d 100755 --- a/src/op_mode/show_acceleration.py +++ b/src/op_mode/show_acceleration.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# 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 @@ -20,8 +20,8 @@ import re import argparse from vyos.config import Config -from vyos.util import popen -from vyos.util import call +from vyos.utils.process import call +from vyos.utils.process import popen def detect_qat_dev(): output, err = popen('lspci -nn', decode='utf-8') diff --git a/src/op_mode/show_ntp.sh b/src/op_mode/show_ntp.sh index 85f8eda15..4b59b801e 100755 --- a/src/op_mode/show_ntp.sh +++ b/src/op_mode/show_ntp.sh @@ -18,7 +18,7 @@ if ! ps -C chronyd &>/dev/null; then fi PID=$(pgrep chronyd | head -n1) -VRF_NAME=$(ip vrf identify ) +VRF_NAME=$(ip vrf identify ${PID}) if [ ! -z ${VRF_NAME} ]; then VRF_CMD="sudo ip vrf exec ${VRF_NAME}" diff --git a/src/op_mode/show_openconnect_otp.py b/src/op_mode/show_openconnect_otp.py index 88982c50b..3771fb385 100755 --- a/src/op_mode/show_openconnect_otp.py +++ b/src/op_mode/show_openconnect_otp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2017, 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2017-2023 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 @@ -17,12 +17,11 @@ import argparse import os +from base64 import b32encode from vyos.config import Config -from vyos.xml import defaults -from vyos.configdict import dict_merge -from vyos.util import popen -from base64 import b32encode +from vyos.utils.dict import dict_search_args +from vyos.utils.process import popen otp_file = '/run/ocserv/users.oath' @@ -33,7 +32,7 @@ def check_uname_otp(username): config = Config() base_key = ['vpn', 'openconnect', 'authentication', 'local-users', 'username', username, 'otp', 'key'] if not config.exists(base_key): - return None + return False return True def get_otp_ocserv(username): @@ -41,21 +40,21 @@ def get_otp_ocserv(username): base = ['vpn', 'openconnect'] if not config.exists(base): return None - ocserv = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - default_values = defaults(base) - ocserv = dict_merge(default_values, ocserv) - # workaround a "know limitation" - https://vyos.dev/T2665 - del ocserv['authentication']['local_users']['username']['otp'] - if not ocserv["authentication"]["local_users"]["username"]: + + ocserv = config.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) + + user_path = ['authentication', 'local_users', 'username'] + users = dict_search_args(ocserv, *user_path) + + if users is None: return None - default_ocserv_usr_values = default_values['authentication']['local_users']['username']['otp'] - for user, params in ocserv['authentication']['local_users']['username'].items(): - # Not every configuration requires OTP settings - if ocserv['authentication']['local_users']['username'][user].get('otp'): - ocserv['authentication']['local_users']['username'][user]['otp'] = dict_merge(default_ocserv_usr_values, ocserv['authentication']['local_users']['username'][user]['otp']) - result = ocserv['authentication']['local_users']['username'][username] + + # function is called conditionally, if check_uname_otp true, so username + # exists + result = users[username] + return result def display_otp_ocserv(username, params, info): @@ -101,8 +100,7 @@ if __name__ == '__main__': parser.add_argument('--info', action="store", type=str, default='full', help='Wich information to display') args = parser.parse_args() - check_otp = check_uname_otp(args.user) - if check_otp: + if check_uname_otp(args.user): user_otp_params = get_otp_ocserv(args.user) display_otp_ocserv(args.user, user_otp_params, args.info) else: diff --git a/src/op_mode/show_openvpn_mfa.py b/src/op_mode/show_openvpn_mfa.py index 1ab54600c..100c42154 100755 --- a/src/op_mode/show_openvpn_mfa.py +++ b/src/op_mode/show_openvpn_mfa.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 - -# Copyright 2017, 2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# Copyright 2017-2023 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 @@ -20,7 +20,7 @@ import socket import urllib.parse import argparse -from vyos.util import popen +from vyos.utils.process import popen otp_file = '/config/auth/openvpn/{interface}-otp-secrets' diff --git a/src/op_mode/show_sensors.py b/src/op_mode/show_sensors.py index 6ae477647..5e3084fe9 100755 --- a/src/op_mode/show_sensors.py +++ b/src/op_mode/show_sensors.py @@ -1,9 +1,25 @@ #!/usr/bin/env python3 +# +# Copyright 2017-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. import re import sys -from vyos.util import popen -from vyos.util import DEVNULL +from vyos.utils.process import popen +from vyos.utils.process import DEVNULL + output,retcode = popen("sensors --no-adapter", stderr=DEVNULL) if retcode == 0: print (output) @@ -23,5 +39,3 @@ else: print ("No sensors found") sys.exit(1) - - diff --git a/src/op_mode/show_techsupport_report.py b/src/op_mode/show_techsupport_report.py index 782004144..53144fd52 100644 --- a/src/op_mode/show_techsupport_report.py +++ b/src/op_mode/show_techsupport_report.py @@ -17,7 +17,7 @@ import os from typing import List -from vyos.util import rc_cmd +from vyos.utils.process import rc_cmd from vyos.ifconfig import Section from vyos.ifconfig import Interface @@ -91,7 +91,8 @@ def show_package_repository_config() -> None: def show_user_startup_scripts() -> None: """Prints the user startup scripts.""" - execute_command('cat /config/scripts/vyos-postconfig-bootup.script', 'User Startup Scripts') + execute_command('cat /config/scripts/vyos-preconfig-bootup.script', 'User Startup Scripts (Preconfig)') + execute_command('cat /config/scripts/vyos-postconfig-bootup.script', 'User Startup Scripts (Postconfig)') def show_frr_config() -> None: diff --git a/src/op_mode/show_virtual_server.py b/src/op_mode/show_virtual_server.py index 377180dec..7880edc97 100755 --- a/src/op_mode/show_virtual_server.py +++ b/src/op_mode/show_virtual_server.py @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from vyos.configquery import ConfigTreeQuery -from vyos.util import call +from vyos.utils.process import call def is_configured(): """ Check if high-availability virtual-server is configured """ diff --git a/src/op_mode/show_wireless.py b/src/op_mode/show_wireless.py index 19ab6771c..340163057 100755 --- a/src/op_mode/show_wireless.py +++ b/src/op_mode/show_wireless.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# 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 @@ -21,7 +21,7 @@ from sys import exit from copy import deepcopy from vyos.config import Config -from vyos.util import popen +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'") diff --git a/src/op_mode/show_wwan.py b/src/op_mode/show_wwan.py index eb601a456..bd97bb0e5 100755 --- a/src/op_mode/show_wwan.py +++ b/src/op_mode/show_wwan.py @@ -18,7 +18,7 @@ import argparse from sys import exit from vyos.configquery import ConfigTreeQuery -from vyos.util import cmd +from vyos.utils.process import cmd parser = argparse.ArgumentParser() parser.add_argument("--model", help="Get module model", action="store_true") diff --git a/src/op_mode/show_xdp_stats.sh b/src/op_mode/show_xdp_stats.sh deleted file mode 100755 index a4ef33107..000000000 --- a/src/op_mode/show_xdp_stats.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -if cli-shell-api existsEffective interfaces $1 $2 xdp; then - /usr/sbin/xdp_stats --dev "$2" -else - echo "XDP not enabled on $2" -fi diff --git a/src/op_mode/snmp.py b/src/op_mode/snmp.py index 5fae67881..43f5d9e0a 100755 --- a/src/op_mode/snmp.py +++ b/src/op_mode/snmp.py @@ -24,7 +24,7 @@ import sys import argparse from vyos.config import Config -from vyos.util import call +from vyos.utils.process import call config_file_daemon = r'/etc/snmp/snmpd.conf' diff --git a/src/op_mode/snmp_ifmib.py b/src/op_mode/snmp_ifmib.py index 2479936bd..c71febac9 100755 --- a/src/op_mode/snmp_ifmib.py +++ b/src/op_mode/snmp_ifmib.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -24,7 +24,7 @@ import argparse import netifaces from vyos.config import Config -from vyos.util import popen +from vyos.utils.process import popen parser = argparse.ArgumentParser(description='Retrieve SNMP interfaces information') parser.add_argument('--ifindex', action='store', nargs='?', const='all', help='Show interface index') diff --git a/src/op_mode/storage.py b/src/op_mode/storage.py index d16e271bd..6bc3d3a2d 100755 --- a/src/op_mode/storage.py +++ b/src/op_mode/storage.py @@ -18,7 +18,7 @@ import sys import vyos.opmode -from vyos.util import cmd +from vyos.utils.process import cmd # FIY: As of coreutils from Debian Buster and Bullseye, # the outpt looks like this: @@ -43,7 +43,7 @@ def _get_system_storage(only_persistent=False): def _get_raw_data(): from re import sub as re_sub - from vyos.util import human_to_bytes + from vyos.utils.convert import human_to_bytes out = _get_system_storage(only_persistent=True) lines = out.splitlines() diff --git a/src/op_mode/traceroute.py b/src/op_mode/traceroute.py index 6c7030ea0..2f0edf53a 100755 --- a/src/op_mode/traceroute.py +++ b/src/op_mode/traceroute.py @@ -18,7 +18,7 @@ import os import sys import socket import ipaddress -from vyos.util import get_all_vrfs +from vyos.utils.network import get_all_vrfs from vyos.ifconfig import Section diff --git a/src/op_mode/uptime.py b/src/op_mode/uptime.py index 2ebe6783b..d6adf6f4d 100755 --- a/src/op_mode/uptime.py +++ b/src/op_mode/uptime.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-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 as @@ -20,7 +20,7 @@ import vyos.opmode def _get_uptime_seconds(): from re import search - from vyos.util import read_file + from vyos.utils.file import read_file data = read_file("/proc/uptime") seconds = search("([0-9\.]+)\s", data).group(1) @@ -29,7 +29,7 @@ def _get_uptime_seconds(): def _get_load_averages(): from re import search - from vyos.util import cmd + from vyos.utils.process import cmd from vyos.cpu import get_core_count data = cmd("uptime") @@ -45,7 +45,7 @@ def _get_load_averages(): return res def _get_raw_data(): - from vyos.util import seconds_to_human + from vyos.utils.convert import seconds_to_human res = {} res["uptime_seconds"] = _get_uptime_seconds() diff --git a/src/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py index 4b44c5c15..069c12069 100755 --- a/src/op_mode/vpn_ike_sa.py +++ b/src/op_mode/vpn_ike_sa.py @@ -19,7 +19,7 @@ import re import sys import vici -from vyos.util import process_named_running +from vyos.utils.process import process_named_running ike_sa_peer_prefix = """\ Peer ID / IP Local ID / IP @@ -39,8 +39,6 @@ def ike_sa(peer, nat): peers = [] for conn in sas: for name, sa in conn.items(): - if peer and not name.startswith('peer_' + peer): - continue if name.startswith('peer_') and name in peers: continue if nat and 'nat-local' not in sa: @@ -70,7 +68,7 @@ if __name__ == '__main__': args = parser.parse_args() - if not process_named_running('charon'): + if not process_named_running('charon-systemd'): print("IPsec Process NOT Running") sys.exit(0) diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py index b81d1693e..ef89e605f 100755 --- a/src/op_mode/vpn_ipsec.py +++ b/src/op_mode/vpn_ipsec.py @@ -17,7 +17,7 @@ import re import argparse -from vyos.util import call +from vyos.utils.process import call SWANCTL_CONF = '/etc/swanctl/swanctl.conf' diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py index a9a416761..51032a4b5 100755 --- a/src/op_mode/vrf.py +++ b/src/op_mode/vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -20,11 +20,11 @@ import sys import typing from tabulate import tabulate -from vyos.util import cmd +from vyos.utils.network import get_vrf_members +from vyos.utils.process import cmd import vyos.opmode - def _get_raw_data(name=None): """ If vrf name is not set - get all VRFs @@ -45,21 +45,6 @@ def _get_raw_data(name=None): return data -def _get_vrf_members(vrf: str) -> list: - """ - Get list of interface VRF members - :param vrf: str - :return: list - """ - output = cmd(f'ip --json --brief link show master {vrf}') - answer = json.loads(output) - interfaces = [] - for data in answer: - if 'ifname' in data: - interfaces.append(data.get('ifname')) - return interfaces if len(interfaces) > 0 else ['n/a'] - - def _get_formatted_output(raw_data): data_entries = [] for vrf in raw_data: @@ -67,7 +52,9 @@ def _get_formatted_output(raw_data): state = vrf.get('operstate').lower() hw_address = vrf.get('address') flags = ','.join(vrf.get('flags')).lower() - members = ','.join(_get_vrf_members(name)) + tmp = get_vrf_members(name) + if tmp: members = ','.join(get_vrf_members(name)) + else: members = 'n/a' data_entries.append([name, state, hw_address, flags, members]) headers = ["Name", "State", "MAC address", "Flags", "Interfaces"] diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py index dab146d28..b3ab55cc3 100755 --- a/src/op_mode/vrrp.py +++ b/src/op_mode/vrrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -13,7 +13,6 @@ # # 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 sys import time @@ -21,12 +20,10 @@ import argparse import json import tabulate -import vyos.util - from vyos.configquery import ConfigTreeQuery from vyos.ifconfig.vrrp import VRRP -from vyos.ifconfig.vrrp import VRRPError, VRRPNoData - +from vyos.ifconfig.vrrp import VRRPError +from vyos.ifconfig.vrrp import VRRPNoData parser = argparse.ArgumentParser() group = parser.add_mutually_exclusive_group() @@ -44,7 +41,7 @@ def is_configured(): return True # Exit early if VRRP is dead or not configured -if is_configured() == False: +if is_configured() == False: print('VRRP not configured!') exit(0) if not VRRP.is_running(): diff --git a/src/op_mode/vyos-op-cmd-wrapper.sh b/src/op_mode/vyos-op-cmd-wrapper.sh new file mode 100755 index 000000000..a89211b2b --- /dev/null +++ b/src/op_mode/vyos-op-cmd-wrapper.sh @@ -0,0 +1,6 @@ +#!/bin/vbash +shopt -s expand_aliases +source /etc/default/vyatta +source /etc/bash_completion.d/vyatta-op +_vyatta_op_init +_vyatta_op_run "$@" diff --git a/src/op_mode/webproxy_update_blacklist.sh b/src/op_mode/webproxy_update_blacklist.sh index 4fb9a54c6..05ea86f9e 100755 --- a/src/op_mode/webproxy_update_blacklist.sh +++ b/src/op_mode/webproxy_update_blacklist.sh @@ -45,6 +45,9 @@ do --auto-update-blacklist) auto="yes" ;; + --vrf) + vrf="yes" + ;; (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;; (*) break;; esac @@ -76,7 +79,11 @@ fi if [[ -n $update ]] && [[ $update -eq "yes" ]]; then tmp_blacklists='/tmp/blacklists.gz' - curl -o $tmp_blacklists $blacklist_url + if [[ -n $vrf ]] && [[ $vrf -eq "yes" ]]; then + sudo ip vrf exec $1 curl -o $tmp_blacklists $blacklist_url + else + curl -o $tmp_blacklists $blacklist_url + fi if [ $? -ne 0 ]; then echo "Unable to download [$blacklist_url]!" exit 1 diff --git a/src/op_mode/wireguard_client.py b/src/op_mode/wireguard_client.py index 76c1ff7d1..04d8ce28c 100755 --- a/src/op_mode/wireguard_client.py +++ b/src/op_mode/wireguard_client.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -23,8 +23,8 @@ from ipaddress import ip_interface from vyos.ifconfig import Section from vyos.template import is_ipv4 from vyos.template import is_ipv6 -from vyos.util import cmd -from vyos.util import popen +from vyos.utils.process import cmd +from vyos.utils.process import popen if os.geteuid() != 0: exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.") diff --git a/src/op_mode/zone.py b/src/op_mode/zone.py index f326215b1..17ce90396 100755 --- a/src/op_mode/zone.py +++ b/src/op_mode/zone.py @@ -19,8 +19,8 @@ import vyos.opmode import tabulate from vyos.configquery import ConfigTreeQuery -from vyos.util import dict_search_args -from vyos.util import dict_search +from vyos.utils.dict import dict_search_args +from vyos.utils.dict import dict_search def get_config_zone(conf, name=None): diff --git a/src/pam-configs/radius b/src/pam-configs/radius index aaae6aeb0..08247f77c 100644 --- a/src/pam-configs/radius +++ b/src/pam-configs/radius @@ -1,20 +1,17 @@ Name: RADIUS authentication -Default: yes +Default: no Priority: 257 Auth-Type: Primary Auth: - [default=ignore success=1] pam_succeed_if.so uid eq 1000 quiet - [default=ignore success=ignore] pam_succeed_if.so uid eq 1001 quiet + [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet [authinfo_unavail=ignore success=end default=ignore] pam_radius_auth.so Account-Type: Primary Account: - [default=ignore success=1] pam_succeed_if.so uid eq 1000 quiet - [default=ignore success=ignore] pam_succeed_if.so uid eq 1001 quiet + [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet [authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_radius_auth.so Session-Type: Additional Session: - [default=ignore success=1] pam_succeed_if.so uid eq 1000 quiet - [default=ignore success=ignore] pam_succeed_if.so uid eq 1001 quiet + [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet [authinfo_unavail=ignore success=ok default=ignore] pam_radius_auth.so diff --git a/src/pam-configs/tacplus b/src/pam-configs/tacplus new file mode 100644 index 000000000..66a1eaa4c --- /dev/null +++ b/src/pam-configs/tacplus @@ -0,0 +1,17 @@ +Name: TACACS+ authentication +Default: no +Priority: 257 +Auth-Type: Primary +Auth: + [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet + [authinfo_unavail=ignore success=end auth_err=bad default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login + +Account-Type: Primary +Account: + [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet + [authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login + +Session-Type: Additional +Session: + [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet + [authinfo_unavail=ignore success=ok default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login diff --git a/src/services/api/graphql/generate/schema_from_op_mode.py b/src/services/api/graphql/generate/schema_from_op_mode.py index 229ccf90f..ab7cb691f 100755 --- a/src/services/api/graphql/generate/schema_from_op_mode.py +++ b/src/services/api/graphql/generate/schema_from_op_mode.py @@ -27,7 +27,7 @@ from jinja2 import Template from vyos.defaults import directories from vyos.opmode import _is_op_mode_function_name as is_op_mode_function_name from vyos.opmode import _get_literal_values as get_literal_values -from vyos.util import load_as_module +from vyos.utils.system import load_as_module if __package__ is None or __package__ == '': sys.path.append(os.path.join(directories['services'], 'api')) from graphql.libs.op_mode import is_show_function_name diff --git a/src/services/api/graphql/libs/op_mode.py b/src/services/api/graphql/libs/op_mode.py index e91d8bd0f..5022f7d4e 100644 --- a/src/services/api/graphql/libs/op_mode.py +++ b/src/services/api/graphql/libs/op_mode.py @@ -20,7 +20,7 @@ from typing import Union, Tuple, Optional from humps import decamelize from vyos.defaults import directories -from vyos.util import load_as_module +from vyos.utils.system import load_as_module from vyos.opmode import _normalize_field_names from vyos.opmode import _is_literal_type, _get_literal_values diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 48c9135e2..355182b26 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -13,8 +13,6 @@ # # 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 os import sys @@ -28,8 +26,9 @@ import zmq from contextlib import contextmanager from vyos.defaults import directories -from vyos.util import boot_configuration_complete -from vyos.configsource import ConfigSourceString, ConfigSourceError +from vyos.utils.boot import boot_configuration_complete +from vyos.configsource import ConfigSourceString +from vyos.configsource import ConfigSourceError from vyos.config import Config from vyos import ConfigError diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 894f9e24d..e34a4b740 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# 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 @@ -241,9 +241,14 @@ import traceback import re import logging import zmq + from voluptuous import Schema, MultipleInvalid, Required, Any from collections import OrderedDict -from vyos.util import popen, chown, chmod_755, makedir, process_named_running +from vyos.utils.file import makedir +from vyos.utils.permission import chown +from vyos.utils.permission import chmod_755 +from vyos.utils.process import popen +from vyos.utils.process import process_named_running from vyos.template import render debug = True diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index acaa383b4..66e80ced5 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -1,6 +1,6 @@ #!/usr/share/vyos-http-api-tools/bin/python3 # -# Copyright (C) 2019-2021 VyOS maintainers and contributors +# 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 @@ -91,26 +91,20 @@ def success(data): class ApiModel(BaseModel): key: StrictStr -class BaseConfigureModel(BaseModel): +class BasePathModel(BaseModel): op: StrictStr path: List[StrictStr] - value: StrictStr = None - @validator("path", pre=True, always=True) + @validator("path") def check_non_empty(cls, path): - assert len(path) > 0 + if not len(path) > 0: + raise ValueError('path must be non-empty') return path -class ConfigureModel(ApiModel): - op: StrictStr - path: List[StrictStr] +class BaseConfigureModel(BasePathModel): value: StrictStr = None - @validator("path", pre=True, always=True) - def check_non_empty(cls, path): - assert len(path) > 0 - return path - +class ConfigureModel(ApiModel, BaseConfigureModel): class Config: schema_extra = { "example": { @@ -131,6 +125,15 @@ class ConfigureListModel(ApiModel): } } +class BaseConfigSectionModel(BasePathModel): + section: Dict + +class ConfigSectionModel(ApiModel, BaseConfigSectionModel): + pass + +class ConfigSectionListModel(ApiModel): + commands: List[BaseConfigSectionModel] + class RetrieveModel(ApiModel): op: StrictStr path: List[StrictStr] @@ -258,18 +261,15 @@ def auth_required(data: ApiModel): # the explicit validation may be dropped, if desired, in favor of native # validation by FastAPI/Pydantic, as is used for application/json requests class MultipartRequest(Request): - ERR_MISSING_KEY = False - ERR_MISSING_DATA = False - ERR_NOT_JSON = False - ERR_NOT_DICT = False - ERR_NO_OP = False - ERR_NO_PATH = False - ERR_EMPTY_PATH = False - ERR_PATH_NOT_LIST = False - ERR_VALUE_NOT_STRING = False - ERR_PATH_NOT_LIST_OF_STR = False - offending_command = {} - exception = None + _form_err = () + @property + def form_err(self): + return self._form_err + + @form_err.setter + def form_err(self, val): + if not self._form_err: + self._form_err = val @property def orig_headers(self): @@ -308,19 +308,20 @@ class MultipartRequest(Request): form_data = await self.form() if form_data: + endpoint = self.url.path logger.debug("processing form data") for k, v in form_data.multi_items(): forms[k] = v if 'data' not in forms: - self.ERR_MISSING_DATA = True + self.form_err = (422, "Non-empty data field is required") + return self._body else: try: tmp = json.loads(forms['data']) except json.JSONDecodeError as e: - self.ERR_NOT_JSON = True - self.exception = e - tmp = {} + self.form_err = (400, f'Failed to parse JSON: {e}') + return self._body if isinstance(tmp, list): merge['commands'] = tmp else: @@ -334,29 +335,40 @@ class MultipartRequest(Request): for c in cmds: if not isinstance(c, dict): - self.ERR_NOT_DICT = True - self.offending_command = c - elif 'op' not in c: - self.ERR_NO_OP = True - self.offending_command = c - elif 'path' not in c: - self.ERR_NO_PATH = True - self.offending_command = c - elif not c['path']: - self.ERR_EMPTY_PATH = True - self.offending_command = c - elif not isinstance(c['path'], list): - self.ERR_PATH_NOT_LIST = True - self.offending_command = c - elif not all(isinstance(el, str) for el in c['path']): - self.ERR_PATH_NOT_LIST_OF_STR = True - self.offending_command = c - elif 'value' in c and not isinstance(c['value'], str): - self.ERR_VALUE_NOT_STRING = True - self.offending_command = c + self.form_err = (400, + f"Malformed command '{c}': any command must be JSON of dict") + return self._body + if 'op' not in c: + self.form_err = (400, + f"Malformed command '{c}': missing 'op' field") + if endpoint not in ('/config-file', '/container-image', + '/image'): + if 'path' not in c: + self.form_err = (400, + f"Malformed command '{c}': missing 'path' field") + elif not isinstance(c['path'], list): + self.form_err = (400, + f"Malformed command '{c}': 'path' field must be a list") + elif not all(isinstance(el, str) for el in c['path']): + self.form_err = (400, + f"Malformed command '{0}': 'path' field must be a list of strings") + if endpoint in ('/configure'): + if not c['path']: + self.form_err = (400, + f"Malformed command '{c}': 'path' list must be non-empty") + if 'value' in c and not isinstance(c['value'], str): + self.form_err = (400, + f"Malformed command '{c}': 'value' field must be a string") + if endpoint in ('/configure-section'): + if 'section' not in c: + self.form_err = (400, + f"Malformed command '{c}': missing 'section' field") + elif not isinstance(c['section'], dict): + self.form_err = (400, + f"Malformed command '{c}': 'section' field must be JSON of dict") if 'key' not in forms and 'key' not in merge: - self.ERR_MISSING_KEY = True + self.form_err = (401, "Valid API key is required") if 'key' in forms and 'key' not in merge: merge['key'] = forms['key'] @@ -372,40 +384,14 @@ class MultipartRoute(APIRoute): async def custom_route_handler(request: Request) -> Response: request = MultipartRequest(request.scope, request.receive) - endpoint = request.url.path try: response: Response = await original_route_handler(request) except HTTPException as e: return error(e.status_code, e.detail) except Exception as e: - if request.ERR_MISSING_KEY: - return error(401, "Valid API key is required") - if request.ERR_MISSING_DATA: - return error(422, "Non-empty data field is required") - if request.ERR_NOT_JSON: - return error(400, "Failed to parse JSON: {0}".format(request.exception)) - if endpoint == '/configure': - if request.ERR_NOT_DICT: - return error(400, "Malformed command \"{0}\": any command must be a dict".format(json.dumps(request.offending_command))) - if request.ERR_NO_OP: - return error(400, "Malformed command \"{0}\": missing \"op\" field".format(json.dumps(request.offending_command))) - if request.ERR_NO_PATH: - return error(400, "Malformed command \"{0}\": missing \"path\" field".format(json.dumps(request.offending_command))) - if request.ERR_EMPTY_PATH: - return error(400, "Malformed command \"{0}\": empty path".format(json.dumps(request.offending_command))) - if request.ERR_PATH_NOT_LIST: - return error(400, "Malformed command \"{0}\": \"path\" field must be a list".format(json.dumps(request.offending_command))) - if request.ERR_VALUE_NOT_STRING: - return error(400, "Malformed command \"{0}\": \"value\" field must be a string".format(json.dumps(request.offending_command))) - if request.ERR_PATH_NOT_LIST_OF_STR: - return error(400, "Malformed command \"{0}\": \"path\" field must be a list of strings".format(json.dumps(request.offending_command))) - if endpoint in ('/retrieve','/generate','/show','/reset'): - if request.ERR_NO_OP or request.ERR_NO_PATH: - return error(400, "Missing required field. \"op\" and \"path\" fields are required") - if endpoint in ('/config-file', '/image', '/container-image'): - if request.ERR_NO_OP: - return error(400, "Missing required field \"op\"") - + form_err = request.form_err + if form_err: + return error(*form_err) raise e return response @@ -424,12 +410,15 @@ app.router.route_class = MultipartRoute async def validation_exception_handler(request, exc): return error(400, str(exc.errors()[0])) -@app.post('/configure') -async def configure_op(data: Union[ConfigureModel, ConfigureListModel]): +def _configure_op(data: Union[ConfigureModel, ConfigureListModel, + ConfigSectionModel, ConfigSectionListModel], + request: Request): session = app.state.vyos_session env = session.get_session_env() config = vyos.config.Config(session_env=env) + endpoint = request.url.path + # Allow users to pass just one command if not isinstance(data, ConfigureListModel): data = [data] @@ -442,33 +431,44 @@ async def configure_op(data: Union[ConfigureModel, ConfigureListModel]): lock.acquire() status = 200 + msg = None error_msg = None try: for c in data: op = c.op path = c.path - if c.value: - value = c.value - else: - value = "" - - # For vyos.configsession calls that have no separate value arguments, - # and for type checking too - cfg_path = " ".join(path + [value]).strip() - - if op == 'set': - # XXX: it would be nice to do a strict check for "path already exists", - # but there's probably no way to do that - session.set(path, value=value) - elif op == 'delete': - if app.state.vyos_strict and not config.exists(cfg_path): - raise ConfigSessionError("Cannot delete [{0}]: path/value does not exist".format(cfg_path)) - session.delete(path, value=value) - elif op == 'comment': - session.comment(path, value=value) - else: - raise ConfigSessionError("\"{0}\" is not a valid operation".format(op)) + if isinstance(c, BaseConfigureModel): + if c.value: + value = c.value + else: + value = "" + # For vyos.configsession calls that have no separate value arguments, + # and for type checking too + cfg_path = " ".join(path + [value]).strip() + + elif isinstance(c, BaseConfigSectionModel): + section = c.section + + if isinstance(c, BaseConfigureModel): + if op == 'set': + session.set(path, value=value) + elif op == 'delete': + if app.state.vyos_strict and not config.exists(cfg_path): + raise ConfigSessionError(f"Cannot delete [{cfg_path}]: path/value does not exist") + session.delete(path, value=value) + elif op == 'comment': + session.comment(path, value=value) + else: + raise ConfigSessionError(f"'{op}' is not a valid operation") + + elif isinstance(c, BaseConfigSectionModel): + if op == 'set': + session.set_section(path, section) + elif op == 'load': + session.load_section(path, section) + else: + raise ConfigSessionError(f"'{op}' is not a valid operation") # end for session.commit() logger.info(f"Configuration modified via HTTP API using key '{app.state.vyos_id}'") @@ -491,7 +491,19 @@ async def configure_op(data: Union[ConfigureModel, ConfigureListModel]): if status != 200: return error(status, error_msg) - return success(None) + return success(msg) + +@app.post('/configure') +def configure_op(data: Union[ConfigureModel, + ConfigureListModel], + request: Request): + return _configure_op(data, request) + +@app.post('/configure-section') +def configure_section_op(data: Union[ConfigSectionModel, + ConfigSectionListModel], + request: Request): + return _configure_op(data, request) @app.post("/retrieve") async def retrieve_op(data: RetrieveModel): @@ -524,9 +536,9 @@ async def retrieve_op(data: RetrieveModel): elif config_format == 'raw': pass else: - return error(400, "\"{0}\" is not a valid config format".format(config_format)) + return error(400, f"'{config_format}' is not a valid config format") else: - return error(400, "\"{0}\" is not a valid operation".format(op)) + return error(400, f"'{op}' is not a valid operation") except ConfigSessionError as e: return error(400, str(e)) except Exception as e: @@ -556,7 +568,7 @@ def config_file_op(data: ConfigFileModel): res = session.migrate_and_load_config(path) res = session.commit() else: - return error(400, "\"{0}\" is not a valid operation".format(op)) + return error(400, f"'{op}' is not a valid operation") except ConfigSessionError as e: return error(400, str(e)) except Exception as e: @@ -585,7 +597,7 @@ def image_op(data: ImageModel): return error(400, "Missing required field \"name\"") res = session.remove_image(name) else: - return error(400, "\"{0}\" is not a valid operation".format(op)) + return error(400, f"'{op}' is not a valid operation") except ConfigSessionError as e: return error(400, str(e)) except Exception as e: @@ -616,7 +628,7 @@ def image_op(data: ContainerImageModel): elif op == 'show': res = session.show_container_image() else: - return error(400, "\"{0}\" is not a valid operation".format(op)) + return error(400, f"'{op}' is not a valid operation") except ConfigSessionError as e: return error(400, str(e)) except Exception as e: @@ -636,7 +648,7 @@ def generate_op(data: GenerateModel): if op == 'generate': res = session.generate(path) else: - return error(400, "\"{0}\" is not a valid operation".format(op)) + return error(400, f"'{op}' is not a valid operation") except ConfigSessionError as e: return error(400, str(e)) except Exception as e: @@ -656,7 +668,7 @@ def show_op(data: ShowModel): if op == 'show': res = session.show(path) else: - return error(400, "\"{0}\" is not a valid operation".format(op)) + return error(400, f"'{op}' is not a valid operation") except ConfigSessionError as e: return error(400, str(e)) except Exception as e: @@ -676,7 +688,7 @@ def reset_op(data: ResetModel): if op == 'reset': res = session.reset(path) else: - return error(400, "\"{0}\" is not a valid operation".format(op)) + return error(400, f"'{op}' is not a valid operation") except ConfigSessionError as e: return error(400, str(e)) except Exception as e: diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 864ee8419..5e19bdbad 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -28,9 +28,9 @@ from logging.handlers import SysLogHandler from vyos.ifconfig.vrrp import VRRP from vyos.configquery import ConfigTreeQuery -from vyos.util import cmd -from vyos.util import dict_search -from vyos.util import commit_in_progress +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search +from vyos.utils.commit import commit_in_progress # configure logging logger = logging.getLogger(__name__) diff --git a/src/system/vyos-event-handler.py b/src/system/vyos-event-handler.py index 1c85380bc..74112ec91 100755 --- a/src/system/vyos-event-handler.py +++ b/src/system/vyos-event-handler.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-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 @@ -18,6 +18,7 @@ import argparse import json import re import select + from copy import deepcopy from os import getpid, environ from pathlib import Path @@ -25,13 +26,13 @@ from signal import signal, SIGTERM, SIGINT from sys import exit from systemd import journal -from vyos.util import run, dict_search +from vyos.utils.dict import dict_search +from vyos.utils.process import run # Identify this script my_pid = getpid() my_name = Path(__file__).stem - # handle termination signal def handle_signal(signal_type, frame): if signal_type == SIGTERM: diff --git a/src/system/vyos-system-update-check.py b/src/system/vyos-system-update-check.py index c9597721b..c874f1e2c 100755 --- a/src/system/vyos-system-update-check.py +++ b/src/system/vyos-system-update-check.py @@ -22,7 +22,7 @@ from pathlib import Path from sys import exit from time import sleep -from vyos.util import call +from vyos.utils.process import call import vyos.version diff --git a/src/systemd/dhclient@.service b/src/systemd/dhclient@.service index 23cd4cfc3..099f7ed52 100644 --- a/src/systemd/dhclient@.service +++ b/src/systemd/dhclient@.service @@ -1,18 +1,17 @@ [Unit] Description=DHCP client on %i Documentation=man:dhclient(8) -ConditionPathExists=/var/lib/dhcp/dhclient_%i.conf -ConditionPathExists=/var/lib/dhcp/dhclient_%i.options +StartLimitIntervalSec=0 After=vyos-router.service [Service] -WorkingDirectory=/var/lib/dhcp Type=exec -EnvironmentFile=-/var/lib/dhcp/dhclient_%i.options -PIDFile=/var/lib/dhcp/dhclient_%i.pid -ExecStart=/sbin/dhclient -4 $DHCLIENT_OPTS -ExecStop=/sbin/dhclient -4 $DHCLIENT_OPTS -r +ExecStart=/sbin/dhclient -4 -d $DHCLIENT_OPTS +ExecStop=/sbin/dhclient -4 -r $DHCLIENT_OPTS Restart=always +RestartPreventExitStatus= +RestartSec=10 +RuntimeDirectoryPreserve=yes TimeoutStopSec=20 SendSIGKILL=true FinalKillSignal=SIGABRT diff --git a/src/systemd/dhcp6c@.service b/src/systemd/dhcp6c@.service index 9a97ee261..f634bd944 100644 --- a/src/systemd/dhcp6c@.service +++ b/src/systemd/dhcp6c@.service @@ -1,17 +1,19 @@ [Unit] Description=WIDE DHCPv6 client on %i Documentation=man:dhcp6c(8) man:dhcp6c.conf(5) -ConditionPathExists=/run/dhcp6c/dhcp6c.%i.conf -After=vyos-router.service StartLimitIntervalSec=0 +After=vyos-router.service [Service] -WorkingDirectory=/run/dhcp6c Type=forking +WorkingDirectory=/run/dhcp6c +EnvironmentFile=-/run/dhcp6c/dhcp6c.%i.options PIDFile=/run/dhcp6c/dhcp6c.%i.pid -ExecStart=/usr/sbin/dhcp6c -D -k /run/dhcp6c/dhcp6c.%i.sock -c /run/dhcp6c/dhcp6c.%i.conf -p /run/dhcp6c/dhcp6c.%i.pid %i -Restart=on-failure -RestartSec=20 +ExecStart=/usr/sbin/dhcp6c $DHCP6C_OPTS +Restart=always +RestartPreventExitStatus= +RestartSec=10 +RuntimeDirectoryPreserve=yes [Install] WantedBy=multi-user.target diff --git a/src/systemd/isc-dhcp-relay6.service b/src/systemd/isc-dhcp-relay6.service index 30037e013..a365ae4b3 100644 --- a/src/systemd/isc-dhcp-relay6.service +++ b/src/systemd/isc-dhcp-relay6.service @@ -5,7 +5,7 @@ Wants=network-online.target RequiresMountsFor=/run ConditionPathExists=/run/dhcp-relay/dhcrelay6.conf After=vyos-router.service - +StartLimitIntervalSec=0 [Service] Type=forking WorkingDirectory=/run/dhcp-relay @@ -15,6 +15,6 @@ EnvironmentFile=/run/dhcp-relay/dhcrelay6.conf PIDFile=/run/dhcp-relay/dhcrelay6.pid ExecStart=/usr/sbin/dhcrelay -6 -pf /run/dhcp-relay/dhcrelay6.pid $OPTIONS Restart=always - +RestartSec=10 [Install] WantedBy=multi-user.target diff --git a/src/systemd/vyos-router.service b/src/systemd/vyos-router.service new file mode 100644 index 000000000..6f683cebb --- /dev/null +++ b/src/systemd/vyos-router.service @@ -0,0 +1,19 @@ +[Unit] +Description=VyOS Router +After=systemd-journald-dev-log.socket time-sync.target local-fs.target cloud-config.service +Requires=frr.service +Conflicts=shutdown.target +Before=systemd-user-sessions.service + +[Service] +Type=simple +Restart=no +TimeoutSec=20min +KillMode=process +RemainAfterExit=yes +ExecStart=/usr/libexec/vyos/init/vyos-router start +ExecStop=/usr/libexec/vyos/init/vyos-router stop +StandardOutput=journal+console + +[Install] +WantedBy=vyos.target diff --git a/src/systemd/vyos.target b/src/systemd/vyos.target new file mode 100644 index 000000000..47c91c1cc --- /dev/null +++ b/src/systemd/vyos.target @@ -0,0 +1,3 @@ +[Unit] +Description=VyOS target +After=multi-user.target diff --git a/src/tests/test_configverify.py b/src/tests/test_configverify.py index 6fb43ece2..15ccdf13d 100644 --- a/src/tests/test_configverify.py +++ b/src/tests/test_configverify.py @@ -16,7 +16,7 @@ from unittest import TestCase from vyos.configverify import verify_diffie_hellman_length -from vyos.util import cmd +from vyos.utils.process import cmd dh_file = '/tmp/dh.pem' diff --git a/src/tests/test_dict_search.py b/src/tests/test_dict_search.py index 1028437b2..2435d89c7 100644 --- a/src/tests/test_dict_search.py +++ b/src/tests/test_dict_search.py @@ -15,8 +15,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from unittest import TestCase -from vyos.util import dict_search -from vyos.util import dict_search_recursive +from vyos.utils.dict import dict_search +from vyos.utils.dict import dict_search_recursive data = { 'string': 'fooo', diff --git a/src/tests/test_find_device_file.py b/src/tests/test_find_device_file.py index 43c80dc76..f18043d65 100755 --- a/src/tests/test_find_device_file.py +++ b/src/tests/test_find_device_file.py @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from unittest import TestCase -from vyos.util import find_device_file +from vyos.utils.system import find_device_file class TestDeviceFile(TestCase): """ used to find USB devices on target """ diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py index cb843ff09..ba50d06cc 100644 --- a/src/tests/test_initial_setup.py +++ b/src/tests/test_initial_setup.py @@ -21,14 +21,16 @@ import vyos.configtree import vyos.initialsetup as vis from unittest import TestCase -from vyos import xml +from vyos.xml_ref import definition +from vyos.xml_ref.pkg_cache.vyos_1x_cache import reference class TestInitialSetup(TestCase): def setUp(self): with open('tests/data/config.boot.default', 'r') as f: config_string = f.read() self.config = vyos.configtree.ConfigTree(config_string) - self.xml = xml.load_configuration() + self.xml = definition.Xml() + self.xml.define(reference) def test_set_user_password(self): vis.set_user_password(self.config, 'vyos', 'vyosvyos') diff --git a/src/tests/test_util.py b/src/tests/test_utils.py index 473052bef..9ae329ced 100644 --- a/src/tests/test_util.py +++ b/src/tests/test_utils.py @@ -15,14 +15,14 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from unittest import TestCase -from vyos.util import * - -class TestVyOSUtil(TestCase): - def test_key_mangline(self): +class TestVyOSUtils(TestCase): + def test_key_mangling(self): + from vyos.utils.dict import mangle_dict_keys data = {"foo-bar": {"baz-quux": None}} expected_data = {"foo_bar": {"baz_quux": None}} new_data = mangle_dict_keys(data, '-', '_') self.assertEqual(new_data, expected_data) def test_sysctl_read(self): + from vyos.utils.system import sysctl_read self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1') diff --git a/src/tests/test_utils_network.py b/src/tests/test_utils_network.py new file mode 100644 index 000000000..5a6dc2586 --- /dev/null +++ b/src/tests/test_utils_network.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020-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 vyos.utils.network +from unittest import TestCase + +class TestVyOSUtilsNetwork(TestCase): + def setUp(self): + pass + + def test_is_addr_assigned(self): + self.assertTrue(vyos.utils.network.is_addr_assigned('127.0.0.1')) + self.assertTrue(vyos.utils.network.is_addr_assigned('::1')) + self.assertFalse(vyos.utils.network.is_addr_assigned('127.251.255.123')) + + def test_is_ipv6_link_local(self): + self.assertFalse(vyos.utils.network.is_ipv6_link_local('169.254.0.1')) + self.assertTrue(vyos.utils.network.is_ipv6_link_local('fe80::')) + self.assertTrue(vyos.utils.network.is_ipv6_link_local('fe80::affe:1')) + self.assertTrue(vyos.utils.network.is_ipv6_link_local('fe80::affe:1%eth0')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('2001:db8::')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('2001:db8::%eth0')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('VyOS')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('::1')) + self.assertFalse(vyos.utils.network.is_ipv6_link_local('::1%lo')) + + def test_is_ipv6_link_local(self): + self.assertTrue(vyos.utils.network.is_loopback_addr('127.0.0.1')) + self.assertTrue(vyos.utils.network.is_loopback_addr('127.0.1.1')) + self.assertTrue(vyos.utils.network.is_loopback_addr('127.1.1.1')) + self.assertTrue(vyos.utils.network.is_loopback_addr('::1')) + + self.assertFalse(vyos.utils.network.is_loopback_addr('::2')) + self.assertFalse(vyos.utils.network.is_loopback_addr('192.0.2.1')) + + + diff --git a/src/tests/test_validate.py b/src/tests/test_validate.py deleted file mode 100644 index 68a257d25..000000000 --- a/src/tests/test_validate.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 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 vyos.validate -from unittest import TestCase - -class TestVyOSValidate(TestCase): - def setUp(self): - pass - - def test_is_addr_assigned(self): - self.assertTrue(vyos.validate.is_addr_assigned('127.0.0.1')) - self.assertTrue(vyos.validate.is_addr_assigned('::1')) - self.assertFalse(vyos.validate.is_addr_assigned('127.251.255.123')) - - def test_is_ipv6_link_local(self): - self.assertFalse(vyos.validate.is_ipv6_link_local('169.254.0.1')) - self.assertTrue(vyos.validate.is_ipv6_link_local('fe80::')) - self.assertTrue(vyos.validate.is_ipv6_link_local('fe80::affe:1')) - self.assertTrue(vyos.validate.is_ipv6_link_local('fe80::affe:1%eth0')) - self.assertFalse(vyos.validate.is_ipv6_link_local('2001:db8::')) - self.assertFalse(vyos.validate.is_ipv6_link_local('2001:db8::%eth0')) - self.assertFalse(vyos.validate.is_ipv6_link_local('VyOS')) - self.assertFalse(vyos.validate.is_ipv6_link_local('::1')) - self.assertFalse(vyos.validate.is_ipv6_link_local('::1%lo')) - - def test_is_ipv6_link_local(self): - self.assertTrue(vyos.validate.is_loopback_addr('127.0.0.1')) - self.assertTrue(vyos.validate.is_loopback_addr('127.0.1.1')) - self.assertTrue(vyos.validate.is_loopback_addr('127.1.1.1')) - self.assertTrue(vyos.validate.is_loopback_addr('::1')) - - self.assertFalse(vyos.validate.is_loopback_addr('::2')) - self.assertFalse(vyos.validate.is_loopback_addr('192.0.2.1')) - - - diff --git a/src/validators/bgp-extended-community b/src/validators/bgp-extended-community index b69ae3449..d66665519 100755 --- a/src/validators/bgp-extended-community +++ b/src/validators/bgp-extended-community @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2019-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-2023 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 @@ -28,28 +28,27 @@ if __name__ == '__main__': parser: ArgumentParser = ArgumentParser() parser.add_argument('community', type=str) args = parser.parse_args() - community: str = args.community - if community.count(':') != 1: - print("Invalid community format") - exit(1) - try: - # try to extract community parts from an argument - comm_left: str = community.split(':')[0] - comm_right: int = int(community.split(':')[1]) - - # check if left part is an IPv4 address - if is_ipv4(comm_left) and 0 <= comm_right <= COMM_MAX_2_OCTET: - exit() - # check if a left part is a number - if 0 <= int(comm_left) <= COMM_MAX_2_OCTET \ - and 0 <= comm_right <= COMM_MAX_4_OCTET: - exit() - - except Exception: - # fail if something was wrong - print("Invalid community format") - exit(1) - - # fail if none of validators catched the value - print("Invalid community format") - exit(1)
\ No newline at end of file + + for community in args.community.split(): + if community.count(':') != 1: + print("Invalid community format") + exit(1) + try: + # try to extract community parts from an argument + comm_left: str = community.split(':')[0] + comm_right: int = int(community.split(':')[1]) + + # check if left part is an IPv4 address + if is_ipv4(comm_left) and 0 <= comm_right <= COMM_MAX_2_OCTET: + continue + # check if a left part is a number + if 0 <= int(comm_left) <= COMM_MAX_2_OCTET \ + and 0 <= comm_right <= COMM_MAX_4_OCTET: + continue + + raise Exception() + + except Exception: + # fail if something was wrong + print("Invalid community format") + exit(1)
\ No newline at end of file diff --git a/src/validators/ipv6-link-local b/src/validators/ipv6-link-local index 05e693b77..6ac3ea710 100755 --- a/src/validators/ipv6-link-local +++ b/src/validators/ipv6-link-local @@ -1,7 +1,7 @@ #!/usr/bin/python3 import sys -from vyos.validate import is_ipv6_link_local +from vyos.utils.network import is_ipv6_link_local if __name__ == '__main__': if len(sys.argv)>1: diff --git a/src/validators/port-multi b/src/validators/port-multi index bd6f0ef60..ed6ff6849 100755 --- a/src/validators/port-multi +++ b/src/validators/port-multi @@ -4,7 +4,7 @@ from sys import argv from sys import exit import re -from vyos.util import read_file +from vyos.utils.file import read_file services_file = '/etc/services' diff --git a/src/validators/port-range b/src/validators/port-range index 5468000a7..526c639ad 100755 --- a/src/validators/port-range +++ b/src/validators/port-range @@ -3,7 +3,7 @@ import sys import re -from vyos.util import read_file +from vyos.utils.file import read_file services_file = '/etc/services' diff --git a/src/validators/script b/src/validators/script index 4ffdeb2a0..eb176d23b 100755 --- a/src/validators/script +++ b/src/validators/script @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2021 VyOS maintainers and contributors +# Copyright (C) 2018-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 @@ -19,7 +19,7 @@ import os import sys import shlex -import vyos.util +from vyos.utils.file import file_is_persistent if __name__ == '__main__': if len(sys.argv) < 2: @@ -35,7 +35,7 @@ if __name__ == '__main__': sys.exit(f'File {script} is not an executable file') # File outside the config dir is just a warning - if not vyos.util.file_is_persistent(script): + if not file_is_persistent(script): sys.exit(0)( f'Warning: file {script} is outside the "/config" directory\n' 'It will not be automatically migrated to a new image on system update' diff --git a/src/validators/timezone b/src/validators/timezone index 107571181..e55af8d2a 100755 --- a/src/validators/timezone +++ b/src/validators/timezone @@ -17,7 +17,7 @@ import argparse import sys -from vyos.util import cmd +from vyos.utils.process import cmd if __name__ == '__main__': diff --git a/src/xdp/.gitignore b/src/xdp/.gitignore deleted file mode 100644 index 2c931cf47..000000000 --- a/src/xdp/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -*.o -*.ll -xdp_loader -xdp_prog_user -xdp_stats diff --git a/src/xdp/Makefile b/src/xdp/Makefile deleted file mode 100644 index 0b5a6eaa0..000000000 --- a/src/xdp/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) - -XDP_TARGETS := xdp_prog_kern -USER_TARGETS := xdp_prog_user - -COMMON_DIR = common - -COPY_LOADER := xdp_loader -COPY_STATS := xdp_stats -EXTRA_DEPS := $(COMMON_DIR)/parsing_helpers.h - -include $(COMMON_DIR)/common.mk diff --git a/src/xdp/common/Makefile b/src/xdp/common/Makefile deleted file mode 100644 index 2502027e9..000000000 --- a/src/xdp/common/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0) -CC := gcc - -all: common_params.o common_user_bpf_xdp.o common_libbpf.o - -CFLAGS := -g -Wall - -CFLAGS += -I../include/ -# TODO: Do we need to make libbpf from this make file too? - -common_params.o: common_params.c common_params.h - $(CC) $(CFLAGS) -c -o $@ $< - -common_user_bpf_xdp.o: common_user_bpf_xdp.c common_user_bpf_xdp.h - $(CC) $(CFLAGS) -c -o $@ $< - -common_libbpf.o: common_libbpf.c common_libbpf.h - $(CC) $(CFLAGS) -c -o $@ $< - -.PHONY: clean - -clean: - rm -f *.o diff --git a/src/xdp/common/README.org b/src/xdp/common/README.org deleted file mode 100644 index 561fdbced..000000000 --- a/src/xdp/common/README.org +++ /dev/null @@ -1,8 +0,0 @@ -# -*- fill-column: 76; -*- -#+TITLE: Common files -#+OPTIONS: ^:nil - -This directory contains code that is common between the different -assignments. This reduce code duplication in each tutorial assignment, and -allow us to hideaway code that is irrelevant or have been seen/introduced in -earlier assignments. diff --git a/src/xdp/common/common.mk b/src/xdp/common/common.mk deleted file mode 100644 index ffb86a65c..000000000 --- a/src/xdp/common/common.mk +++ /dev/null @@ -1,103 +0,0 @@ -# Common Makefile parts for BPF-building with libbpf -# -------------------------------------------------- -# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) -# -# This file should be included from your Makefile like: -# COMMON_DIR = ../common/ -# include $(COMMON_DIR)/common.mk -# -# It is expected that you define the variables: -# XDP_TARGETS and USER_TARGETS -# as a space-separated list -# -LLC ?= llc -CLANG ?= clang -CC ?= gcc - -XDP_C = ${XDP_TARGETS:=.c} -XDP_OBJ = ${XDP_C:.c=.o} -USER_C := ${USER_TARGETS:=.c} -USER_OBJ := ${USER_C:.c=.o} - -# Expect this is defined by including Makefile, but define if not -COMMON_DIR ?= ../common/ - -COPY_LOADER ?= -LOADER_DIR ?= $(COMMON_DIR)/../utils - -# Extend if including Makefile already added some -COMMON_OBJS += $(COMMON_DIR)/common_params.o $(COMMON_DIR)/common_user_bpf_xdp.o - -# Create expansions for dependencies -COMMON_H := ${COMMON_OBJS:.o=.h} - -EXTRA_DEPS += - -# BPF-prog kern and userspace shares struct via header file: -KERN_USER_H ?= $(wildcard common_kern_user.h) - -CFLAGS ?= -g -I../include/ -BPF_CFLAGS ?= -I../include/ - -LIBS = -lbpf -lelf $(USER_LIBS) - -all: llvm-check $(USER_TARGETS) $(XDP_OBJ) $(COPY_LOADER) $(COPY_STATS) - -.PHONY: clean $(CLANG) $(LLC) - -clean: - $(MAKE) -C $(COMMON_DIR) clean - rm -f $(USER_TARGETS) $(XDP_OBJ) $(USER_OBJ) $(COPY_LOADER) $(COPY_STATS) - rm -f *.ll - rm -f *~ - -ifdef COPY_LOADER -$(COPY_LOADER): $(LOADER_DIR)/${COPY_LOADER:=.c} $(COMMON_H) - make -C $(LOADER_DIR) $(COPY_LOADER) - cp $(LOADER_DIR)/$(COPY_LOADER) $(COPY_LOADER) -endif - -ifdef COPY_STATS -$(COPY_STATS): $(LOADER_DIR)/${COPY_STATS:=.c} $(COMMON_H) - make -C $(LOADER_DIR) $(COPY_STATS) - cp $(LOADER_DIR)/$(COPY_STATS) $(COPY_STATS) -# Needing xdp_stats imply depending on header files: -EXTRA_DEPS += $(COMMON_DIR)/xdp_stats_kern.h $(COMMON_DIR)/xdp_stats_kern_user.h -endif - -# For build dependency on this file, if it gets updated -COMMON_MK = $(COMMON_DIR)/common.mk - -llvm-check: $(CLANG) $(LLC) - @for TOOL in $^ ; do \ - if [ ! $$(command -v $${TOOL} 2>/dev/null) ]; then \ - echo "*** ERROR: Cannot find tool $${TOOL}" ;\ - exit 1; \ - else true; fi; \ - done - -# Create dependency: detect if C-file change and touch H-file, to trigger -# target $(COMMON_OBJS) -$(COMMON_H): %.h: %.c - touch $@ - -# Detect if any of common obj changed and create dependency on .h-files -$(COMMON_OBJS): %.o: %.h - make -C $(COMMON_DIR) - -$(USER_TARGETS): %: %.c Makefile $(COMMON_MK) $(COMMON_OBJS) $(KERN_USER_H) $(EXTRA_DEPS) - $(CC) -Wall $(CFLAGS) $(LDFLAGS) -o $@ $(COMMON_OBJS) \ - $< $(LIBS) - -$(XDP_OBJ): %.o: %.c Makefile $(COMMON_MK) $(KERN_USER_H) $(EXTRA_DEPS) - $(CLANG) -S \ - -target bpf \ - -D __BPF_TRACING__ \ - $(BPF_CFLAGS) \ - -Wall \ - -Wno-unused-value \ - -Wno-pointer-sign \ - -Wno-compare-distinct-pointer-types \ - -Werror \ - -O2 -emit-llvm -c -g -o ${@:.o=.ll} $< - $(LLC) -march=bpf -filetype=obj -o $@ ${@:.o=.ll} diff --git a/src/xdp/common/common_defines.h b/src/xdp/common/common_defines.h deleted file mode 100644 index 2986d2d67..000000000 --- a/src/xdp/common/common_defines.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef __COMMON_DEFINES_H -#define __COMMON_DEFINES_H - -#include <net/if.h> -#include <linux/types.h> -#include <stdbool.h> - -struct config { - __u32 xdp_flags; - int ifindex; - char *ifname; - char ifname_buf[IF_NAMESIZE]; - int redirect_ifindex; - char *redirect_ifname; - char redirect_ifname_buf[IF_NAMESIZE]; - bool do_unload; - bool reuse_maps; - char pin_dir[512]; - char filename[512]; - char progsec[32]; - char src_mac[18]; - char dest_mac[18]; - __u16 xsk_bind_flags; - int xsk_if_queue; - bool xsk_poll_mode; -}; - -/* Defined in common_params.o */ -extern int verbose; - -/* Exit return codes */ -#define EXIT_OK 0 /* == EXIT_SUCCESS (stdlib.h) man exit(3) */ -#define EXIT_FAIL 1 /* == EXIT_FAILURE (stdlib.h) man exit(3) */ -#define EXIT_FAIL_OPTION 2 -#define EXIT_FAIL_XDP 30 -#define EXIT_FAIL_BPF 40 - -#endif /* __COMMON_DEFINES_H */ diff --git a/src/xdp/common/common_libbpf.c b/src/xdp/common/common_libbpf.c deleted file mode 100644 index 443ca4c66..000000000 --- a/src/xdp/common/common_libbpf.c +++ /dev/null @@ -1,161 +0,0 @@ -/* Common function that with time should be moved to libbpf */ - -#include <errno.h> -#include <string.h> - -#include <bpf/bpf.h> -#include <bpf/libbpf.h> - -#include "common_libbpf.h" - -/* From: include/linux/err.h */ -#define MAX_ERRNO 4095 -#define IS_ERR_VALUE(x) ((x) >= (unsigned long)-MAX_ERRNO) -static inline bool IS_ERR_OR_NULL(const void *ptr) -{ - return (!ptr) || IS_ERR_VALUE((unsigned long)ptr); -} - -#define pr_warning printf - -/* As close as possible to libbpf bpf_prog_load_xattr(), with the - * difference of handling pinned maps. - */ -int bpf_prog_load_xattr_maps(const struct bpf_prog_load_attr_maps *attr, - struct bpf_object **pobj, int *prog_fd) -{ - struct bpf_program *prog, *first_prog = NULL; - enum bpf_attach_type expected_attach_type; - enum bpf_prog_type prog_type; - struct bpf_object *obj; - struct bpf_map *map; - int err; - int i; - - if (!attr) - return -EINVAL; - if (!attr->file) - return -EINVAL; - - obj = bpf_object__open_file(attr->file, NULL); - - if (libbpf_get_error(obj)) - return -EINVAL; - - prog = bpf_object__next_program(obj, NULL); - bpf_program__set_type(prog, attr->prog_type); - - bpf_object__for_each_program(prog, obj) { - /* - * If type is not specified, try to guess it based on - * section name. - */ - prog_type = attr->prog_type; - // Was: prog->prog_ifindex = attr->ifindex; - bpf_program__set_ifindex(prog, attr->ifindex); - - expected_attach_type = attr->expected_attach_type; -#if 0 /* Use internal libbpf variables */ - if (prog_type == BPF_PROG_TYPE_UNSPEC) { - err = bpf_program__identify_section(prog, &prog_type, - &expected_attach_type); - if (err < 0) { - bpf_object__close(obj); - return -EINVAL; - } - } -#endif - - bpf_program__set_type(prog, prog_type); - bpf_program__set_expected_attach_type(prog, - expected_attach_type); - - if (!first_prog) - first_prog = prog; - } - - /* Reset attr->pinned_maps.map_fd to identify successful file load */ - for (i = 0; i < attr->nr_pinned_maps; i++) - attr->pinned_maps[i].map_fd = -1; - - bpf_map__for_each(map, obj) { - const char* mapname = bpf_map__name(map); - - if (bpf_map__type(map) != BPF_MAP_TYPE_PERF_EVENT_ARRAY) - bpf_map__set_ifindex(map, attr->ifindex); - /* Was: map->map_ifindex = attr->ifindex; */ - - for (i = 0; i < attr->nr_pinned_maps; i++) { - struct bpf_pinned_map *pin_map = &attr->pinned_maps[i]; - int fd; - - if (strcmp(mapname, pin_map->name) != 0) - continue; - - /* Matched, try opening pinned file */ - fd = bpf_obj_get(pin_map->filename); - if (fd > 0) { - /* Use FD from pinned map as replacement */ - bpf_map__reuse_fd(map, fd); - /* TODO: Might want to set internal map "name" - * if opened pinned map didn't, to allow - * bpf_object__find_map_fd_by_name() to work. - */ - pin_map->map_fd = fd; - continue; - } - /* Could not open pinned filename map, then this prog - * should then pin the map, BUT this can only happen - * after bpf_object__load(). - */ - } - } - - if (!first_prog) { - pr_warning("object file doesn't contain bpf program\n"); - bpf_object__close(obj); - return -ENOENT; - } - - err = bpf_object__load(obj); - if (err) { - bpf_object__close(obj); - return -EINVAL; - } - - /* Pin the maps that were not loaded via pinned filename */ - bpf_map__for_each(map, obj) { - const char* mapname = bpf_map__name(map); - - for (i = 0; i < attr->nr_pinned_maps; i++) { - struct bpf_pinned_map *pin_map = &attr->pinned_maps[i]; - int err; - - if (strcmp(mapname, pin_map->name) != 0) - continue; - - /* Matched, check if map is already loaded */ - if (pin_map->map_fd != -1) - continue; - - /* Needs to be pinned */ - err = bpf_map__pin(map, pin_map->filename); - if (err) - continue; - pin_map->map_fd = bpf_map__fd(map); - } - } - - /* Help user if requested map name that doesn't exist */ - for (i = 0; i < attr->nr_pinned_maps; i++) { - struct bpf_pinned_map *pin_map = &attr->pinned_maps[i]; - - if (pin_map->map_fd < 0) - pr_warning("%s() requested mapname:%s not seen\n", - __func__, pin_map->name); - } - - *pobj = obj; - *prog_fd = bpf_program__fd(first_prog); - return 0; -} diff --git a/src/xdp/common/common_libbpf.h b/src/xdp/common/common_libbpf.h deleted file mode 100644 index 4754bd8ca..000000000 --- a/src/xdp/common/common_libbpf.h +++ /dev/null @@ -1,24 +0,0 @@ -/* Common function that with time should be moved to libbpf */ -#ifndef __COMMON_LIBBPF_H -#define __COMMON_LIBBPF_H - -struct bpf_pinned_map { - const char *name; - const char *filename; - int map_fd; -}; - -/* bpf_prog_load_attr extended */ -struct bpf_prog_load_attr_maps { - const char *file; - enum bpf_prog_type prog_type; - enum bpf_attach_type expected_attach_type; - int ifindex; - int nr_pinned_maps; - struct bpf_pinned_map *pinned_maps; -}; - -int bpf_prog_load_xattr_maps(const struct bpf_prog_load_attr_maps *attr, - struct bpf_object **pobj, int *prog_fd); - -#endif /* __COMMON_LIBBPF_H */ diff --git a/src/xdp/common/common_params.c b/src/xdp/common/common_params.c deleted file mode 100644 index 642d56d92..000000000 --- a/src/xdp/common/common_params.c +++ /dev/null @@ -1,197 +0,0 @@ -#include <stddef.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <stdbool.h> -#include <getopt.h> -#include <errno.h> - -#include <net/if.h> -#include <linux/if_link.h> /* XDP_FLAGS_* depend on kernel-headers installed */ -#include <linux/if_xdp.h> - -#include "common_params.h" - -int verbose = 1; - -#define BUFSIZE 30 - -void _print_options(const struct option_wrapper *long_options, bool required) -{ - int i, pos; - char buf[BUFSIZE]; - - for (i = 0; long_options[i].option.name != 0; i++) { - if (long_options[i].required != required) - continue; - - if (long_options[i].option.val > 64) /* ord('A') = 65 */ - printf(" -%c,", long_options[i].option.val); - else - printf(" "); - pos = snprintf(buf, BUFSIZE, " --%s", long_options[i].option.name); - if (long_options[i].metavar) - snprintf(&buf[pos], BUFSIZE-pos, " %s", long_options[i].metavar); - printf("%-22s", buf); - printf(" %s", long_options[i].help); - printf("\n"); - } -} - -void usage(const char *prog_name, const char *doc, - const struct option_wrapper *long_options, bool full) -{ - printf("Usage: %s [options]\n", prog_name); - - if (!full) { - printf("Use --help (or -h) to see full option list.\n"); - return; - } - - printf("\nDOCUMENTATION:\n %s\n", doc); - printf("Required options:\n"); - _print_options(long_options, true); - printf("\n"); - printf("Other options:\n"); - _print_options(long_options, false); - printf("\n"); -} - -int option_wrappers_to_options(const struct option_wrapper *wrapper, - struct option **options) -{ - int i, num; - struct option *new_options; - for (i = 0; wrapper[i].option.name != 0; i++) {} - num = i; - - new_options = malloc(sizeof(struct option) * num); - if (!new_options) - return -1; - for (i = 0; i < num; i++) { - memcpy(&new_options[i], &wrapper[i], sizeof(struct option)); - } - - *options = new_options; - return 0; -} - -void parse_cmdline_args(int argc, char **argv, - const struct option_wrapper *options_wrapper, - struct config *cfg, const char *doc) -{ - struct option *long_options; - bool full_help = false; - int longindex = 0; - char *dest; - int opt; - - if (option_wrappers_to_options(options_wrapper, &long_options)) { - fprintf(stderr, "Unable to malloc()\n"); - exit(EXIT_FAIL_OPTION); - } - - /* Parse commands line args */ - while ((opt = getopt_long(argc, argv, "hd:r:L:R:ASNFUMQ:czpq", - long_options, &longindex)) != -1) { - switch (opt) { - case 'd': - if (strlen(optarg) >= IF_NAMESIZE) { - fprintf(stderr, "ERR: --dev name too long\n"); - goto error; - } - cfg->ifname = (char *)&cfg->ifname_buf; - strncpy(cfg->ifname, optarg, IF_NAMESIZE); - cfg->ifindex = if_nametoindex(cfg->ifname); - if (cfg->ifindex == 0) { - fprintf(stderr, - "ERR: --dev name unknown err(%d):%s\n", - errno, strerror(errno)); - goto error; - } - break; - case 'r': - if (strlen(optarg) >= IF_NAMESIZE) { - fprintf(stderr, "ERR: --redirect-dev name too long\n"); - goto error; - } - cfg->redirect_ifname = (char *)&cfg->redirect_ifname_buf; - strncpy(cfg->redirect_ifname, optarg, IF_NAMESIZE); - cfg->redirect_ifindex = if_nametoindex(cfg->redirect_ifname); - if (cfg->redirect_ifindex == 0) { - fprintf(stderr, - "ERR: --redirect-dev name unknown err(%d):%s\n", - errno, strerror(errno)); - goto error; - } - break; - case 'A': - cfg->xdp_flags &= ~XDP_FLAGS_MODES; /* Clear flags */ - break; - case 'S': - cfg->xdp_flags &= ~XDP_FLAGS_MODES; /* Clear flags */ - cfg->xdp_flags |= XDP_FLAGS_SKB_MODE; /* Set flag */ - cfg->xsk_bind_flags &= XDP_ZEROCOPY; - cfg->xsk_bind_flags |= XDP_COPY; - break; - case 'N': - cfg->xdp_flags &= ~XDP_FLAGS_MODES; /* Clear flags */ - cfg->xdp_flags |= XDP_FLAGS_DRV_MODE; /* Set flag */ - break; - case 3: /* --offload-mode */ - cfg->xdp_flags &= ~XDP_FLAGS_MODES; /* Clear flags */ - cfg->xdp_flags |= XDP_FLAGS_HW_MODE; /* Set flag */ - break; - case 'F': - cfg->xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST; - break; - case 'M': - cfg->reuse_maps = true; - break; - case 'U': - cfg->do_unload = true; - break; - case 'p': - cfg->xsk_poll_mode = true; - break; - case 'q': - verbose = false; - break; - case 'Q': - cfg->xsk_if_queue = atoi(optarg); - break; - case 1: /* --filename */ - dest = (char *)&cfg->filename; - strncpy(dest, optarg, sizeof(cfg->filename)); - break; - case 2: /* --progsec */ - dest = (char *)&cfg->progsec; - strncpy(dest, optarg, sizeof(cfg->progsec)); - break; - case 'L': /* --src-mac */ - dest = (char *)&cfg->src_mac; - strncpy(dest, optarg, sizeof(cfg->src_mac)); - break; - case 'R': /* --dest-mac */ - dest = (char *)&cfg->dest_mac; - strncpy(dest, optarg, sizeof(cfg->dest_mac)); - case 'c': - cfg->xsk_bind_flags &= XDP_ZEROCOPY; - cfg->xsk_bind_flags |= XDP_COPY; - break; - case 'z': - cfg->xsk_bind_flags &= XDP_COPY; - cfg->xsk_bind_flags |= XDP_ZEROCOPY; - break; - case 'h': - full_help = true; - /* fall-through */ - error: - default: - usage(argv[0], doc, options_wrapper, full_help); - free(long_options); - exit(EXIT_FAIL_OPTION); - } - } - free(long_options); -} diff --git a/src/xdp/common/common_params.h b/src/xdp/common/common_params.h deleted file mode 100644 index 6f64c82e3..000000000 --- a/src/xdp/common/common_params.h +++ /dev/null @@ -1,22 +0,0 @@ -/* This common_user.h is used by userspace programs */ -#ifndef __COMMON_PARAMS_H -#define __COMMON_PARAMS_H - -#include <getopt.h> -#include "common_defines.h" - -struct option_wrapper { - struct option option; - char *help; - char *metavar; - bool required; -}; - -void usage(const char *prog_name, const char *doc, - const struct option_wrapper *long_options, bool full); - -void parse_cmdline_args(int argc, char **argv, - const struct option_wrapper *long_options, - struct config *cfg, const char *doc); - -#endif /* __COMMON_PARAMS_H */ diff --git a/src/xdp/common/common_user_bpf_xdp.c b/src/xdp/common/common_user_bpf_xdp.c deleted file mode 100644 index 524f08c9d..000000000 --- a/src/xdp/common/common_user_bpf_xdp.c +++ /dev/null @@ -1,389 +0,0 @@ -#include <bpf/libbpf.h> /* bpf_get_link_xdp_id + bpf_set_link_xdp_id */ -#include <string.h> /* strerror */ -#include <net/if.h> /* IF_NAMESIZE */ -#include <stdlib.h> /* exit(3) */ -#include <errno.h> - -#include <bpf/bpf.h> -#include <bpf/libbpf.h> - -#include <linux/if_link.h> /* Need XDP flags */ -#include <linux/err.h> - -#include "common_defines.h" - -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif - -int xdp_link_attach(int ifindex, __u32 xdp_flags, int prog_fd) -{ - int err; - - /* libbpf provide the XDP net_device link-level hook attach helper */ - err = bpf_xdp_attach(ifindex, prog_fd, xdp_flags, NULL); - if (err == -EEXIST && !(xdp_flags & XDP_FLAGS_UPDATE_IF_NOEXIST)) { - /* Force mode didn't work, probably because a program of the - * opposite type is loaded. Let's unload that and try loading - * again. - */ - - __u32 old_flags = xdp_flags; - - xdp_flags &= ~XDP_FLAGS_MODES; - xdp_flags |= (old_flags & XDP_FLAGS_SKB_MODE) ? XDP_FLAGS_DRV_MODE : XDP_FLAGS_SKB_MODE; - err = bpf_xdp_detach(ifindex, xdp_flags, NULL); - if (!err) - err = bpf_xdp_attach(ifindex, prog_fd, old_flags, NULL); - } - if (err < 0) { - fprintf(stderr, "ERR: " - "ifindex(%d) link set xdp fd failed (%d): %s\n", - ifindex, -err, strerror(-err)); - - switch (-err) { - case EBUSY: - case EEXIST: - fprintf(stderr, "Hint: XDP already loaded on device" - " use --force to swap/replace\n"); - break; - case EOPNOTSUPP: - fprintf(stderr, "Hint: Native-XDP not supported" - " use --skb-mode or --auto-mode\n"); - break; - default: - break; - } - return EXIT_FAIL_XDP; - } - - return EXIT_OK; -} - -int xdp_link_detach(int ifindex, __u32 xdp_flags, __u32 expected_prog_id) -{ - __u32 curr_prog_id; - int err; - - err = bpf_xdp_query_id(ifindex, xdp_flags, &curr_prog_id); - if (err) { - fprintf(stderr, "ERR: get link xdp id failed (err=%d): %s\n", - -err, strerror(-err)); - return EXIT_FAIL_XDP; - } - - if (!curr_prog_id) { - if (verbose) - printf("INFO: %s() no curr XDP prog on ifindex:%d\n", - __func__, ifindex); - return EXIT_OK; - } - - if (expected_prog_id && curr_prog_id != expected_prog_id) { - fprintf(stderr, "ERR: %s() " - "expected prog ID(%d) no match(%d), not removing\n", - __func__, expected_prog_id, curr_prog_id); - return EXIT_FAIL; - } - - if ((err = bpf_xdp_detach(ifindex, xdp_flags, NULL)) < 0) { - fprintf(stderr, "ERR: %s() link set xdp failed (err=%d): %s\n", - __func__, err, strerror(-err)); - return EXIT_FAIL_XDP; - } - - if (verbose) - printf("INFO: %s() removed XDP prog ID:%d on ifindex:%d\n", - __func__, curr_prog_id, ifindex); - - return EXIT_OK; -} - -struct bpf_object *load_bpf_object_file(const char *filename, int ifindex) -{ - int first_prog_fd = -1; - struct bpf_object *obj; - int err; - - /* This struct allow us to set ifindex, this features is used for - * hardware offloading XDP programs (note this sets libbpf - * bpf_program->prog_ifindex and foreach bpf_map->map_ifindex). - */ - struct bpf_program *prog; - obj = bpf_object__open_file(filename, NULL); - - if (libbpf_get_error(obj)) - return NULL; - - prog = bpf_object__next_program(obj, NULL); - bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); - bpf_program__set_ifindex(prog, ifindex); - - /* Use libbpf for extracting BPF byte-code from BPF-ELF object, and - * loading this into the kernel via bpf-syscall - */ - err = bpf_object__load(obj); - if (err) { - fprintf(stderr, "ERR: loading BPF-OBJ file(%s) (%d): %s\n", - filename, err, strerror(-err)); - return NULL; - } - - first_prog_fd = bpf_program__fd(prog); - - /* Notice how a pointer to a libbpf bpf_object is returned */ - return obj; -} - -static struct bpf_object *open_bpf_object(const char *file, int ifindex) -{ - int err; - struct bpf_object *obj; - struct bpf_map *map; - struct bpf_program *prog, *first_prog = NULL; - - obj = bpf_object__open_file(file, NULL); - - if (libbpf_get_error(obj)) - return NULL; - - prog = bpf_object__next_program(obj, NULL); - bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); - - err = bpf_object__load(obj); - if (IS_ERR_OR_NULL(obj)) { - err = -PTR_ERR(obj); - fprintf(stderr, "ERR: opening BPF-OBJ file(%s) (%d): %s\n", - file, err, strerror(-err)); - return NULL; - } - - bpf_object__for_each_program(prog, obj) { - bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); - bpf_program__set_ifindex(prog, ifindex); - if (!first_prog) - first_prog = prog; - } - - bpf_object__for_each_map(map, obj) { - if (bpf_map__type(map) != BPF_MAP_TYPE_PERF_EVENT_ARRAY) - bpf_map__set_ifindex(map, ifindex); - } - - if (!first_prog) { - fprintf(stderr, "ERR: file %s contains no programs\n", file); - return NULL; - } - - return obj; -} - -static int reuse_maps(struct bpf_object *obj, const char *path) -{ - struct bpf_map *map; - - if (!obj) - return -ENOENT; - - if (!path) - return -EINVAL; - - bpf_object__for_each_map(map, obj) { - int len, err; - int pinned_map_fd; - char buf[PATH_MAX]; - - len = snprintf(buf, PATH_MAX, "%s/%s", path, bpf_map__name(map)); - if (len < 0) { - return -EINVAL; - } else if (len >= PATH_MAX) { - return -ENAMETOOLONG; - } - - pinned_map_fd = bpf_obj_get(buf); - if (pinned_map_fd < 0) - return pinned_map_fd; - - err = bpf_map__reuse_fd(map, pinned_map_fd); - if (err) - return err; - } - - return 0; -} - -struct bpf_object *load_bpf_object_file_reuse_maps(const char *file, - int ifindex, - const char *pin_dir) -{ - int err; - struct bpf_object *obj; - - obj = open_bpf_object(file, ifindex); - if (!obj) { - fprintf(stderr, "ERR: failed to open object %s\n", file); - return NULL; - } - - err = reuse_maps(obj, pin_dir); - if (err) { - fprintf(stderr, "ERR: failed to reuse maps for object %s, pin_dir=%s\n", - file, pin_dir); - return NULL; - } - - err = bpf_object__load(obj); - if (err) { - fprintf(stderr, "ERR: loading BPF-OBJ file(%s) (%d): %s\n", - file, err, strerror(-err)); - return NULL; - } - - return obj; -} - -struct bpf_object *load_bpf_and_xdp_attach(struct config *cfg) -{ - struct bpf_program *bpf_prog; - struct bpf_object *bpf_obj; - int offload_ifindex = 0; - int prog_fd = -1; - int err; - - /* If flags indicate hardware offload, supply ifindex */ - if (cfg->xdp_flags & XDP_FLAGS_HW_MODE) - offload_ifindex = cfg->ifindex; - - /* Load the BPF-ELF object file and get back libbpf bpf_object */ - if (cfg->reuse_maps) - bpf_obj = load_bpf_object_file_reuse_maps(cfg->filename, - offload_ifindex, - cfg->pin_dir); - else - bpf_obj = load_bpf_object_file(cfg->filename, offload_ifindex); - if (!bpf_obj) { - fprintf(stderr, "ERR: loading file: %s\n", cfg->filename); - exit(EXIT_FAIL_BPF); - } - /* At this point: All XDP/BPF programs from the cfg->filename have been - * loaded into the kernel, and evaluated by the verifier. Only one of - * these gets attached to XDP hook, the others will get freed once this - * process exit. - */ - - if (cfg->progsec[0]) - /* Find a matching BPF prog section name */ - bpf_prog = bpf_object__find_program_by_name(bpf_obj, cfg->progsec); - else - /* Find the first program */ - bpf_prog = bpf_object__next_program(bpf_obj, NULL); - - if (!bpf_prog) { - fprintf(stderr, "ERR: couldn't find a program in ELF section '%s'\n", cfg->progsec); - exit(EXIT_FAIL_BPF); - } - - strncpy(cfg->progsec, bpf_program__section_name(bpf_prog), sizeof(cfg->progsec)); - - prog_fd = bpf_program__fd(bpf_prog); - if (prog_fd <= 0) { - fprintf(stderr, "ERR: bpf_program__fd failed\n"); - exit(EXIT_FAIL_BPF); - } - - /* At this point: BPF-progs are (only) loaded by the kernel, and prog_fd - * is our select file-descriptor handle. Next step is attaching this FD - * to a kernel hook point, in this case XDP net_device link-level hook. - */ - err = xdp_link_attach(cfg->ifindex, cfg->xdp_flags, prog_fd); - if (err) - exit(err); - - return bpf_obj; -} - -#define XDP_UNKNOWN XDP_REDIRECT + 1 -#ifndef XDP_ACTION_MAX -#define XDP_ACTION_MAX (XDP_UNKNOWN + 1) -#endif - -static const char *xdp_action_names[XDP_ACTION_MAX] = { - [XDP_ABORTED] = "XDP_ABORTED", - [XDP_DROP] = "XDP_DROP", - [XDP_PASS] = "XDP_PASS", - [XDP_TX] = "XDP_TX", - [XDP_REDIRECT] = "XDP_REDIRECT", - [XDP_UNKNOWN] = "XDP_UNKNOWN", -}; - -const char *action2str(__u32 action) -{ - if (action < XDP_ACTION_MAX) - return xdp_action_names[action]; - return NULL; -} - -int check_map_fd_info(const struct bpf_map_info *info, - const struct bpf_map_info *exp) -{ - if (exp->key_size && exp->key_size != info->key_size) { - fprintf(stderr, "ERR: %s() " - "Map key size(%d) mismatch expected size(%d)\n", - __func__, info->key_size, exp->key_size); - return EXIT_FAIL; - } - if (exp->value_size && exp->value_size != info->value_size) { - fprintf(stderr, "ERR: %s() " - "Map value size(%d) mismatch expected size(%d)\n", - __func__, info->value_size, exp->value_size); - return EXIT_FAIL; - } - if (exp->max_entries && exp->max_entries != info->max_entries) { - fprintf(stderr, "ERR: %s() " - "Map max_entries(%d) mismatch expected size(%d)\n", - __func__, info->max_entries, exp->max_entries); - return EXIT_FAIL; - } - if (exp->type && exp->type != info->type) { - fprintf(stderr, "ERR: %s() " - "Map type(%d) mismatch expected type(%d)\n", - __func__, info->type, exp->type); - return EXIT_FAIL; - } - - return 0; -} - -int open_bpf_map_file(const char *pin_dir, - const char *mapname, - struct bpf_map_info *info) -{ - char filename[PATH_MAX]; - int err, len, fd; - __u32 info_len = sizeof(*info); - - len = snprintf(filename, PATH_MAX, "%s/%s", pin_dir, mapname); - if (len < 0) { - fprintf(stderr, "ERR: constructing full mapname path\n"); - return -1; - } - - fd = bpf_obj_get(filename); - if (fd < 0) { - fprintf(stderr, - "WARN: Failed to open bpf map file:%s err(%d):%s\n", - filename, errno, strerror(errno)); - return fd; - } - - if (info) { - err = bpf_obj_get_info_by_fd(fd, info, &info_len); - if (err) { - fprintf(stderr, "ERR: %s() can't get info - %s\n", - __func__, strerror(errno)); - return EXIT_FAIL_BPF; - } - } - - return fd; -} diff --git a/src/xdp/common/common_user_bpf_xdp.h b/src/xdp/common/common_user_bpf_xdp.h deleted file mode 100644 index 4f8aa51d4..000000000 --- a/src/xdp/common/common_user_bpf_xdp.h +++ /dev/null @@ -1,20 +0,0 @@ -/* Common BPF/XDP functions used by userspace side programs */ -#ifndef __COMMON_USER_BPF_XDP_H -#define __COMMON_USER_BPF_XDP_H - -int xdp_link_attach(int ifindex, __u32 xdp_flags, int prog_fd); -int xdp_link_detach(int ifindex, __u32 xdp_flags, __u32 expected_prog_id); - -struct bpf_object *load_bpf_object_file(const char *filename, int ifindex); -struct bpf_object *load_bpf_and_xdp_attach(struct config *cfg); - -const char *action2str(__u32 action); - -int check_map_fd_info(const struct bpf_map_info *info, - const struct bpf_map_info *exp); - -int open_bpf_map_file(const char *pin_dir, - const char *mapname, - struct bpf_map_info *info); - -#endif /* __COMMON_USER_BPF_XDP_H */ diff --git a/src/xdp/common/parsing_helpers.h b/src/xdp/common/parsing_helpers.h deleted file mode 100644 index c0837e88f..000000000 --- a/src/xdp/common/parsing_helpers.h +++ /dev/null @@ -1,244 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */ -/* - * This file contains parsing functions that are used in the packetXX XDP - * programs. The functions are marked as __always_inline, and fully defined in - * this header file to be included in the BPF program. - * - * Each helper parses a packet header, including doing bounds checking, and - * returns the type of its contents if successful, and -1 otherwise. - * - * For Ethernet and IP headers, the content type is the type of the payload - * (h_proto for Ethernet, nexthdr for IPv6), for ICMP it is the ICMP type field. - * All return values are in host byte order. - * - * The versions of the functions included here are slightly expanded versions of - * the functions in the packet01 lesson. For instance, the Ethernet header - * parsing has support for parsing VLAN tags. - */ - -#ifndef __PARSING_HELPERS_H -#define __PARSING_HELPERS_H - -#include <stddef.h> -#include <linux/if_ether.h> -#include <linux/if_packet.h> -#include <linux/ip.h> -#include <linux/ipv6.h> -#include <linux/icmp.h> -#include <linux/icmpv6.h> -#include <linux/udp.h> -#include <linux/tcp.h> - -/* Header cursor to keep track of current parsing position */ -struct hdr_cursor { - void *pos; -}; - -/* - * struct vlan_hdr - vlan header - * @h_vlan_TCI: priority and VLAN ID - * @h_vlan_encapsulated_proto: packet type ID or len - */ -struct vlan_hdr { - __be16 h_vlan_TCI; - __be16 h_vlan_encapsulated_proto; -}; - -/* - * Struct icmphdr_common represents the common part of the icmphdr and icmp6hdr - * structures. - */ -struct icmphdr_common { - __u8 type; - __u8 code; - __sum16 cksum; -}; - -/* Allow users of header file to redefine VLAN max depth */ -#ifndef VLAN_MAX_DEPTH -#define VLAN_MAX_DEPTH 4 -#endif - -static __always_inline int proto_is_vlan(__u16 h_proto) -{ - return !!(h_proto == bpf_htons(ETH_P_8021Q) || - h_proto == bpf_htons(ETH_P_8021AD)); -} - -/* Notice, parse_ethhdr() will skip VLAN tags, by advancing nh->pos and returns - * next header EtherType, BUT the ethhdr pointer supplied still points to the - * Ethernet header. Thus, caller can look at eth->h_proto to see if this was a - * VLAN tagged packet. - */ -static __always_inline int parse_ethhdr(struct hdr_cursor *nh, void *data_end, - struct ethhdr **ethhdr) -{ - struct ethhdr *eth = nh->pos; - int hdrsize = sizeof(*eth); - struct vlan_hdr *vlh; - __u16 h_proto; - int i; - - /* Byte-count bounds check; check if current pointer + size of header - * is after data_end. - */ - if (nh->pos + hdrsize > data_end) - return -1; - - nh->pos += hdrsize; - *ethhdr = eth; - vlh = nh->pos; - h_proto = eth->h_proto; - - /* Use loop unrolling to avoid the verifier restriction on loops; - * support up to VLAN_MAX_DEPTH layers of VLAN encapsulation. - */ - #pragma unroll - for (i = 0; i < VLAN_MAX_DEPTH; i++) { - if (!proto_is_vlan(h_proto)) - break; - - if (vlh + 1 > data_end) - break; - - h_proto = vlh->h_vlan_encapsulated_proto; - vlh++; - } - - nh->pos = vlh; - return h_proto; /* network-byte-order */ -} - -static __always_inline int parse_ip6hdr(struct hdr_cursor *nh, - void *data_end, - struct ipv6hdr **ip6hdr) -{ - struct ipv6hdr *ip6h = nh->pos; - - /* Pointer-arithmetic bounds check; pointer +1 points to after end of - * thing being pointed to. We will be using this style in the remainder - * of the tutorial. - */ - if (ip6h + 1 > data_end) - return -1; - - nh->pos = ip6h + 1; - *ip6hdr = ip6h; - - return ip6h->nexthdr; -} - -static __always_inline int parse_iphdr(struct hdr_cursor *nh, - void *data_end, - struct iphdr **iphdr) -{ - struct iphdr *iph = nh->pos; - int hdrsize; - - if (iph + 1 > data_end) - return -1; - - hdrsize = iph->ihl * 4; - - /* Variable-length IPv4 header, need to use byte-based arithmetic */ - if (nh->pos + hdrsize > data_end) - return -1; - - nh->pos += hdrsize; - *iphdr = iph; - - return iph->protocol; -} - -static __always_inline int parse_icmp6hdr(struct hdr_cursor *nh, - void *data_end, - struct icmp6hdr **icmp6hdr) -{ - struct icmp6hdr *icmp6h = nh->pos; - - if (icmp6h + 1 > data_end) - return -1; - - nh->pos = icmp6h + 1; - *icmp6hdr = icmp6h; - - return icmp6h->icmp6_type; -} - -static __always_inline int parse_icmphdr(struct hdr_cursor *nh, - void *data_end, - struct icmphdr **icmphdr) -{ - struct icmphdr *icmph = nh->pos; - - if (icmph + 1 > data_end) - return -1; - - nh->pos = icmph + 1; - *icmphdr = icmph; - - return icmph->type; -} - -static __always_inline int parse_icmphdr_common(struct hdr_cursor *nh, - void *data_end, - struct icmphdr_common **icmphdr) -{ - struct icmphdr_common *h = nh->pos; - - if (h + 1 > data_end) - return -1; - - nh->pos = h + 1; - *icmphdr = h; - - return h->type; -} - -/* - * parse_udphdr: parse the udp header and return the length of the udp payload - */ -static __always_inline int parse_udphdr(struct hdr_cursor *nh, - void *data_end, - struct udphdr **udphdr) -{ - int len; - struct udphdr *h = nh->pos; - - if (h + 1 > data_end) - return -1; - - nh->pos = h + 1; - *udphdr = h; - - len = bpf_ntohs(h->len) - sizeof(struct udphdr); - if (len < 0) - return -1; - - return len; -} - -/* - * parse_tcphdr: parse and return the length of the tcp header - */ -static __always_inline int parse_tcphdr(struct hdr_cursor *nh, - void *data_end, - struct tcphdr **tcphdr) -{ - int len; - struct tcphdr *h = nh->pos; - - if (h + 1 > data_end) - return -1; - - len = h->doff * 4; - if ((void *) h + len > data_end) - return -1; - - nh->pos = h + 1; - *tcphdr = h; - - return len; -} - -#endif /* __PARSING_HELPERS_H */ diff --git a/src/xdp/common/rewrite_helpers.h b/src/xdp/common/rewrite_helpers.h deleted file mode 100644 index a5d3e671d..000000000 --- a/src/xdp/common/rewrite_helpers.h +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */ -/* - * This file contains functions that are used in the packetXX XDP programs to - * manipulate on packets data. The functions are marked as __always_inline, and - * fully defined in this header file to be included in the BPF program. - */ - -#ifndef __REWRITE_HELPERS_H -#define __REWRITE_HELPERS_H - -#include <linux/bpf.h> -#include <linux/ip.h> -#include <linux/ipv6.h> -#include <linux/if_ether.h> - -#include <bpf/bpf_helpers.h> -#include <bpf/bpf_endian.h> - -/* Pops the outermost VLAN tag off the packet. Returns the popped VLAN ID on - * success or negative errno on failure. - */ -static __always_inline int vlan_tag_pop(struct xdp_md *ctx, struct ethhdr *eth) -{ - void *data_end = (void *)(long)ctx->data_end; - struct ethhdr eth_cpy; - struct vlan_hdr *vlh; - __be16 h_proto; - int vlid; - - if (!proto_is_vlan(eth->h_proto)) - return -1; - - /* Careful with the parenthesis here */ - vlh = (void *)(eth + 1); - - /* Still need to do bounds checking */ - if (vlh + 1 > data_end) - return -1; - - /* Save vlan ID for returning, h_proto for updating Ethernet header */ - vlid = bpf_ntohs(vlh->h_vlan_TCI); - h_proto = vlh->h_vlan_encapsulated_proto; - - /* Make a copy of the outer Ethernet header before we cut it off */ - __builtin_memcpy(ð_cpy, eth, sizeof(eth_cpy)); - - /* Actually adjust the head pointer */ - if (bpf_xdp_adjust_head(ctx, (int)sizeof(*vlh))) - return -1; - - /* Need to re-evaluate data *and* data_end and do new bounds checking - * after adjusting head - */ - eth = (void *)(long)ctx->data; - data_end = (void *)(long)ctx->data_end; - if (eth + 1 > data_end) - return -1; - - /* Copy back the old Ethernet header and update the proto type */ - __builtin_memcpy(eth, ð_cpy, sizeof(*eth)); - eth->h_proto = h_proto; - - return vlid; -} - -/* Pushes a new VLAN tag after the Ethernet header. Returns 0 on success, - * -1 on failure. - */ -static __always_inline int vlan_tag_push(struct xdp_md *ctx, - struct ethhdr *eth, int vlid) -{ - void *data_end = (void *)(long)ctx->data_end; - struct ethhdr eth_cpy; - struct vlan_hdr *vlh; - - /* First copy the original Ethernet header */ - __builtin_memcpy(ð_cpy, eth, sizeof(eth_cpy)); - - /* Then add space in front of the packet */ - if (bpf_xdp_adjust_head(ctx, 0 - (int)sizeof(*vlh))) - return -1; - - /* Need to re-evaluate data_end and data after head adjustment, and - * bounds check, even though we know there is enough space (as we - * increased it). - */ - data_end = (void *)(long)ctx->data_end; - eth = (void *)(long)ctx->data; - - if (eth + 1 > data_end) - return -1; - - /* Copy back Ethernet header in the right place, populate VLAN tag with - * ID and proto, and set outer Ethernet header to VLAN type. - */ - __builtin_memcpy(eth, ð_cpy, sizeof(*eth)); - - vlh = (void *)(eth + 1); - - if (vlh + 1 > data_end) - return -1; - - vlh->h_vlan_TCI = bpf_htons(vlid); - vlh->h_vlan_encapsulated_proto = eth->h_proto; - - eth->h_proto = bpf_htons(ETH_P_8021Q); - return 0; -} - -/* - * Swaps destination and source MAC addresses inside an Ethernet header - */ -static __always_inline void swap_src_dst_mac(struct ethhdr *eth) -{ - __u8 h_tmp[ETH_ALEN]; - - __builtin_memcpy(h_tmp, eth->h_source, ETH_ALEN); - __builtin_memcpy(eth->h_source, eth->h_dest, ETH_ALEN); - __builtin_memcpy(eth->h_dest, h_tmp, ETH_ALEN); -} - -/* - * Swaps destination and source IPv6 addresses inside an IPv6 header - */ -static __always_inline void swap_src_dst_ipv6(struct ipv6hdr *ipv6) -{ - struct in6_addr tmp = ipv6->saddr; - - ipv6->saddr = ipv6->daddr; - ipv6->daddr = tmp; -} - -/* - * Swaps destination and source IPv4 addresses inside an IPv4 header - */ -static __always_inline void swap_src_dst_ipv4(struct iphdr *iphdr) -{ - __be32 tmp = iphdr->saddr; - - iphdr->saddr = iphdr->daddr; - iphdr->daddr = tmp; -} - -#endif /* __REWRITE_HELPERS_H */ diff --git a/src/xdp/common/xdp_stats_kern.h b/src/xdp/common/xdp_stats_kern.h deleted file mode 100644 index c061a149d..000000000 --- a/src/xdp/common/xdp_stats_kern.h +++ /dev/null @@ -1,44 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/* Used *ONLY* by BPF-prog running kernel side. */ -#ifndef __XDP_STATS_KERN_H -#define __XDP_STATS_KERN_H - -/* Data record type 'struct datarec' is defined in common/xdp_stats_kern_user.h, - * programs using this header must first include that file. - */ -#ifndef __XDP_STATS_KERN_USER_H -#warning "You forgot to #include <../common/xdp_stats_kern_user.h>" -#include <../common/xdp_stats_kern_user.h> -#endif - -/* Keeps stats per (enum) xdp_action */ -struct { - __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); - __type(key, __u32); - __type(value, struct datarec); - __uint(max_entries, XDP_ACTION_MAX); -} xdp_stats_map SEC(".maps"); - -static __always_inline -__u32 xdp_stats_record_action(struct xdp_md *ctx, __u32 action) -{ - if (action >= XDP_ACTION_MAX) - return XDP_ABORTED; - - /* Lookup in kernel BPF-side return pointer to actual data record */ - struct datarec *rec = bpf_map_lookup_elem(&xdp_stats_map, &action); - if (!rec) - return XDP_ABORTED; - - /* BPF_MAP_TYPE_PERCPU_ARRAY returns a data record specific to current - * CPU and XDP hooks runs under Softirq, which makes it safe to update - * without atomic operations. - */ - rec->rx_packets++; - rec->rx_bytes += (ctx->data_end - ctx->data); - - return action; -} - -#endif /* __XDP_STATS_KERN_H */ diff --git a/src/xdp/common/xdp_stats_kern_user.h b/src/xdp/common/xdp_stats_kern_user.h deleted file mode 100644 index d7b8d05e6..000000000 --- a/src/xdp/common/xdp_stats_kern_user.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/* Used by BPF-prog kernel side BPF-progs and userspace programs, - * for sharing xdp_stats common struct and DEFINEs. - */ -#ifndef __XDP_STATS_KERN_USER_H -#define __XDP_STATS_KERN_USER_H - -/* This is the data record stored in the map */ -struct datarec { - __u64 rx_packets; - __u64 rx_bytes; -}; - -#ifndef XDP_ACTION_MAX -#define XDP_ACTION_MAX (XDP_REDIRECT + 1) -#endif - -#endif /* __XDP_STATS_KERN_USER_H */ diff --git a/src/xdp/include/bpf_endian.h b/src/xdp/include/bpf_endian.h deleted file mode 100644 index 2b0ede3d5..000000000 --- a/src/xdp/include/bpf_endian.h +++ /dev/null @@ -1,58 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* Copied from $(LINUX)/tools/testing/selftests/bpf/bpf_endian.h */ -#ifndef __BPF_ENDIAN__ -#define __BPF_ENDIAN__ - -#include <linux/swab.h> - -/* LLVM's BPF target selects the endianness of the CPU - * it compiles on, or the user specifies (bpfel/bpfeb), - * respectively. The used __BYTE_ORDER__ is defined by - * the compiler, we cannot rely on __BYTE_ORDER from - * libc headers, since it doesn't reflect the actual - * requested byte order. - * - * Note, LLVM's BPF target has different __builtin_bswapX() - * semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE - * in bpfel and bpfeb case, which means below, that we map - * to cpu_to_be16(). We could use it unconditionally in BPF - * case, but better not rely on it, so that this header here - * can be used from application and BPF program side, which - * use different targets. - */ -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -# define __bpf_ntohs(x)__builtin_bswap16(x) -# define __bpf_htons(x)__builtin_bswap16(x) -# define __bpf_constant_ntohs(x)___constant_swab16(x) -# define __bpf_constant_htons(x)___constant_swab16(x) -# define __bpf_ntohl(x)__builtin_bswap32(x) -# define __bpf_htonl(x)__builtin_bswap32(x) -# define __bpf_constant_ntohl(x)___constant_swab32(x) -# define __bpf_constant_htonl(x)___constant_swab32(x) -#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -# define __bpf_ntohs(x)(x) -# define __bpf_htons(x)(x) -# define __bpf_constant_ntohs(x)(x) -# define __bpf_constant_htons(x)(x) -# define __bpf_ntohl(x)(x) -# define __bpf_htonl(x)(x) -# define __bpf_constant_ntohl(x)(x) -# define __bpf_constant_htonl(x)(x) -#else -# error "Fix your compiler's __BYTE_ORDER__?!" -#endif - -#define bpf_htons(x)\ - (__builtin_constant_p(x) ?\ - __bpf_constant_htons(x) : __bpf_htons(x)) -#define bpf_ntohs(x)\ - (__builtin_constant_p(x) ?\ - __bpf_constant_ntohs(x) : __bpf_ntohs(x)) -#define bpf_htonl(x)\ - (__builtin_constant_p(x) ?\ - __bpf_constant_htonl(x) : __bpf_htonl(x)) -#define bpf_ntohl(x)\ - (__builtin_constant_p(x) ?\ - __bpf_constant_ntohl(x) : __bpf_ntohl(x)) - -#endif /* __BPF_ENDIAN__ */ diff --git a/src/xdp/include/bpf_legacy.h b/src/xdp/include/bpf_legacy.h deleted file mode 100644 index 8dfa168cd..000000000 --- a/src/xdp/include/bpf_legacy.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ -#ifndef __BPF_LEGACY__ -#define __BPF_LEGACY__ - -/* - * legacy bpf_map_def with extra fields supported only by bpf_load(), do not - * use outside of samples/bpf - */ -struct bpf_map_def_legacy { - unsigned int type; - unsigned int key_size; - unsigned int value_size; - unsigned int max_entries; - unsigned int map_flags; - unsigned int inner_map_idx; - unsigned int numa_node; -}; - -#define BPF_ANNOTATE_KV_PAIR(name, type_key, type_val) \ - struct ____btf_map_##name { \ - type_key key; \ - type_val value; \ - }; \ - struct ____btf_map_##name \ - __attribute__ ((section(".maps." #name), used)) \ - ____btf_map_##name = { } - -/* llvm builtin functions that eBPF C program may use to - * emit BPF_LD_ABS and BPF_LD_IND instructions - */ -unsigned long long load_byte(void *skb, - unsigned long long off) asm("llvm.bpf.load.byte"); -unsigned long long load_half(void *skb, - unsigned long long off) asm("llvm.bpf.load.half"); -unsigned long long load_word(void *skb, - unsigned long long off) asm("llvm.bpf.load.word"); - -#endif diff --git a/src/xdp/include/bpf_util.h b/src/xdp/include/bpf_util.h deleted file mode 100644 index e74b33e9d..000000000 --- a/src/xdp/include/bpf_util.h +++ /dev/null @@ -1,61 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* Copied from $(LINUX)/tools/testing/selftests/bpf/bpf_util.h */ -#ifndef __BPF_UTIL__ -#define __BPF_UTIL__ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> - -static inline unsigned int bpf_num_possible_cpus(void) -{ - static const char *fcpu = "/sys/devices/system/cpu/possible"; - unsigned int start, end, possible_cpus = 0; - char buff[128]; - FILE *fp; - int n; - - fp = fopen(fcpu, "r"); - if (!fp) { - printf("Failed to open %s: '%s'!\n", fcpu, strerror(errno)); - exit(1); - } - - while (fgets(buff, sizeof(buff), fp)) { - n = sscanf(buff, "%u-%u", &start, &end); - if (n == 0) { - printf("Failed to retrieve # possible CPUs!\n"); - exit(1); - } else if (n == 1) { - end = start; - } - possible_cpus = start == 0 ? end + 1 : 0; - break; - } - fclose(fp); - - return possible_cpus; -} - -#define __bpf_percpu_val_align __attribute__((__aligned__(8))) - -#define BPF_DECLARE_PERCPU(type, name) \ - struct { type v; /* padding */ } __bpf_percpu_val_align \ - name[bpf_num_possible_cpus()] -#define bpf_percpu(name, cpu) name[(cpu)].v - -#ifndef ARRAY_SIZE -# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) -#endif - -#ifndef sizeof_field -#define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER)) -#endif - -#ifndef offsetofend -#define offsetofend(TYPE, MEMBER) \ - (offsetof(TYPE, MEMBER) + sizeof_field(TYPE, MEMBER)) -#endif - -#endif /* __BPF_UTIL__ */ diff --git a/src/xdp/include/jhash.h b/src/xdp/include/jhash.h deleted file mode 100644 index b81a0d0eb..000000000 --- a/src/xdp/include/jhash.h +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef _LINUX_JHASH_H -#define _LINUX_JHASH_H - -/* Copied from $(LINUX)/include/linux/jhash.h (kernel 4.18) */ - -/* jhash.h: Jenkins hash support. - * - * Copyright (C) 2006. Bob Jenkins (bob_jenkins@burtleburtle.net) - * - * http://burtleburtle.net/bob/hash/ - * - * These are the credits from Bob's sources: - * - * lookup3.c, by Bob Jenkins, May 2006, Public Domain. - * - * These are functions for producing 32-bit hashes for hash table lookup. - * hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() - * are externally useful functions. Routines to test the hash are included - * if SELF_TEST is defined. You can use this free for any purpose. It's in - * the public domain. It has no warranty. - * - * Copyright (C) 2009-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) - */ - -static inline __u32 rol32(__u32 word, unsigned int shift) -{ - return (word << shift) | (word >> ((-shift) & 31)); -} - -/* copy paste of jhash from kernel sources (include/linux/jhash.h) to make sure - * LLVM can compile it into valid sequence of BPF instructions - */ -#define __jhash_mix(a, b, c) \ -{ \ - a -= c; a ^= rol32(c, 4); c += b; \ - b -= a; b ^= rol32(a, 6); a += c; \ - c -= b; c ^= rol32(b, 8); b += a; \ - a -= c; a ^= rol32(c, 16); c += b; \ - b -= a; b ^= rol32(a, 19); a += c; \ - c -= b; c ^= rol32(b, 4); b += a; \ -} - -#define __jhash_final(a, b, c) \ -{ \ - c ^= b; c -= rol32(b, 14); \ - a ^= c; a -= rol32(c, 11); \ - b ^= a; b -= rol32(a, 25); \ - c ^= b; c -= rol32(b, 16); \ - a ^= c; a -= rol32(c, 4); \ - b ^= a; b -= rol32(a, 14); \ - c ^= b; c -= rol32(b, 24); \ -} - -#define JHASH_INITVAL 0xdeadbeef - -typedef unsigned int u32; - -/* jhash - hash an arbitrary key - * @k: sequence of bytes as key - * @length: the length of the key - * @initval: the previous hash, or an arbitray value - * - * The generic version, hashes an arbitrary sequence of bytes. - * No alignment or length assumptions are made about the input key. - * - * Returns the hash value of the key. The result depends on endianness. - */ -static inline u32 jhash(const void *key, u32 length, u32 initval) -{ - u32 a, b, c; - const unsigned char *k = key; - - /* Set up the internal state */ - a = b = c = JHASH_INITVAL + length + initval; - - /* All but the last block: affect some 32 bits of (a,b,c) */ - while (length > 12) { - a += *(u32 *)(k); - b += *(u32 *)(k + 4); - c += *(u32 *)(k + 8); - __jhash_mix(a, b, c); - length -= 12; - k += 12; - } - /* Last block: affect all 32 bits of (c) */ - switch (length) { - case 12: c += (u32)k[11]<<24; /* fall through */ - case 11: c += (u32)k[10]<<16; /* fall through */ - case 10: c += (u32)k[9]<<8; /* fall through */ - case 9: c += k[8]; /* fall through */ - case 8: b += (u32)k[7]<<24; /* fall through */ - case 7: b += (u32)k[6]<<16; /* fall through */ - case 6: b += (u32)k[5]<<8; /* fall through */ - case 5: b += k[4]; /* fall through */ - case 4: a += (u32)k[3]<<24; /* fall through */ - case 3: a += (u32)k[2]<<16; /* fall through */ - case 2: a += (u32)k[1]<<8; /* fall through */ - case 1: a += k[0]; - __jhash_final(a, b, c); - case 0: /* Nothing left to add */ - break; - } - - return c; -} - -/* jhash2 - hash an array of u32's - * @k: the key which must be an array of u32's - * @length: the number of u32's in the key - * @initval: the previous hash, or an arbitray value - * - * Returns the hash value of the key. - */ -static inline u32 jhash2(const u32 *k, u32 length, u32 initval) -{ - u32 a, b, c; - - /* Set up the internal state */ - a = b = c = JHASH_INITVAL + (length<<2) + initval; - - /* Handle most of the key */ - while (length > 3) { - a += k[0]; - b += k[1]; - c += k[2]; - __jhash_mix(a, b, c); - length -= 3; - k += 3; - } - - /* Handle the last 3 u32's */ - switch (length) { - case 3: c += k[2]; /* fall through */ - case 2: b += k[1]; /* fall through */ - case 1: a += k[0]; - __jhash_final(a, b, c); - case 0: /* Nothing left to add */ - break; - } - - return c; -} - - -/* __jhash_nwords - hash exactly 3, 2 or 1 word(s) */ -static inline u32 __jhash_nwords(u32 a, u32 b, u32 c, u32 initval) -{ - a += initval; - b += initval; - c += initval; - - __jhash_final(a, b, c); - - return c; -} - -static inline u32 jhash_3words(u32 a, u32 b, u32 c, u32 initval) -{ - return __jhash_nwords(a, b, c, initval + JHASH_INITVAL + (3 << 2)); -} - -static inline u32 jhash_2words(u32 a, u32 b, u32 initval) -{ - return __jhash_nwords(a, b, 0, initval + JHASH_INITVAL + (2 << 2)); -} - -static inline u32 jhash_1word(u32 a, u32 initval) -{ - return __jhash_nwords(a, 0, 0, initval + JHASH_INITVAL + (1 << 2)); -} - -#endif /* _LINUX_JHASH_H */ diff --git a/src/xdp/include/linux/bpf.h b/src/xdp/include/linux/bpf.h deleted file mode 100644 index 161a93809..000000000 --- a/src/xdp/include/linux/bpf.h +++ /dev/null @@ -1,3278 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 2 of the GNU General Public - * License as published by the Free Software Foundation. - */ -#ifndef _UAPI__LINUX_BPF_H__ -#define _UAPI__LINUX_BPF_H__ - -#include <linux/types.h> -#include <linux/bpf_common.h> - -/* Extended instruction set based on top of classic BPF */ - -/* instruction classes */ -#define BPF_JMP32 0x06 /* jmp mode in word width */ -#define BPF_ALU64 0x07 /* alu mode in double word width */ - -/* ld/ldx fields */ -#define BPF_DW 0x18 /* double word (64-bit) */ -#define BPF_XADD 0xc0 /* exclusive add */ - -/* alu/jmp fields */ -#define BPF_MOV 0xb0 /* mov reg to reg */ -#define BPF_ARSH 0xc0 /* sign extending arithmetic shift right */ - -/* change endianness of a register */ -#define BPF_END 0xd0 /* flags for endianness conversion: */ -#define BPF_TO_LE 0x00 /* convert to little-endian */ -#define BPF_TO_BE 0x08 /* convert to big-endian */ -#define BPF_FROM_LE BPF_TO_LE -#define BPF_FROM_BE BPF_TO_BE - -/* jmp encodings */ -#define BPF_JNE 0x50 /* jump != */ -#define BPF_JLT 0xa0 /* LT is unsigned, '<' */ -#define BPF_JLE 0xb0 /* LE is unsigned, '<=' */ -#define BPF_JSGT 0x60 /* SGT is signed '>', GT in x86 */ -#define BPF_JSGE 0x70 /* SGE is signed '>=', GE in x86 */ -#define BPF_JSLT 0xc0 /* SLT is signed, '<' */ -#define BPF_JSLE 0xd0 /* SLE is signed, '<=' */ -#define BPF_CALL 0x80 /* function call */ -#define BPF_EXIT 0x90 /* function return */ - -/* Register numbers */ -enum { - BPF_REG_0 = 0, - BPF_REG_1, - BPF_REG_2, - BPF_REG_3, - BPF_REG_4, - BPF_REG_5, - BPF_REG_6, - BPF_REG_7, - BPF_REG_8, - BPF_REG_9, - BPF_REG_10, - __MAX_BPF_REG, -}; - -/* BPF has 10 general purpose 64-bit registers and stack frame. */ -#define MAX_BPF_REG __MAX_BPF_REG - -struct bpf_insn { - __u8 code; /* opcode */ - __u8 dst_reg:4; /* dest register */ - __u8 src_reg:4; /* source register */ - __s16 off; /* signed offset */ - __s32 imm; /* signed immediate constant */ -}; - -/* Key of an a BPF_MAP_TYPE_LPM_TRIE entry */ -struct bpf_lpm_trie_key { - __u32 prefixlen; /* up to 32 for AF_INET, 128 for AF_INET6 */ - __u8 data[0]; /* Arbitrary size */ -}; - -struct bpf_cgroup_storage_key { - __u64 cgroup_inode_id; /* cgroup inode id */ - __u32 attach_type; /* program attach type */ -}; - -/* BPF syscall commands, see bpf(2) man-page for details. */ -enum bpf_cmd { - BPF_MAP_CREATE, - BPF_MAP_LOOKUP_ELEM, - BPF_MAP_UPDATE_ELEM, - BPF_MAP_DELETE_ELEM, - BPF_MAP_GET_NEXT_KEY, - BPF_PROG_LOAD, - BPF_OBJ_PIN, - BPF_OBJ_GET, - BPF_PROG_ATTACH, - BPF_PROG_DETACH, - BPF_PROG_TEST_RUN, - BPF_PROG_GET_NEXT_ID, - BPF_MAP_GET_NEXT_ID, - BPF_PROG_GET_FD_BY_ID, - BPF_MAP_GET_FD_BY_ID, - BPF_OBJ_GET_INFO_BY_FD, - BPF_PROG_QUERY, - BPF_RAW_TRACEPOINT_OPEN, - BPF_BTF_LOAD, - BPF_BTF_GET_FD_BY_ID, - BPF_TASK_FD_QUERY, - BPF_MAP_LOOKUP_AND_DELETE_ELEM, -}; - -enum bpf_map_type { - BPF_MAP_TYPE_UNSPEC, - BPF_MAP_TYPE_HASH, - BPF_MAP_TYPE_ARRAY, - BPF_MAP_TYPE_PROG_ARRAY, - BPF_MAP_TYPE_PERF_EVENT_ARRAY, - BPF_MAP_TYPE_PERCPU_HASH, - BPF_MAP_TYPE_PERCPU_ARRAY, - BPF_MAP_TYPE_STACK_TRACE, - BPF_MAP_TYPE_CGROUP_ARRAY, - BPF_MAP_TYPE_LRU_HASH, - BPF_MAP_TYPE_LRU_PERCPU_HASH, - BPF_MAP_TYPE_LPM_TRIE, - BPF_MAP_TYPE_ARRAY_OF_MAPS, - BPF_MAP_TYPE_HASH_OF_MAPS, - BPF_MAP_TYPE_DEVMAP, - BPF_MAP_TYPE_SOCKMAP, - BPF_MAP_TYPE_CPUMAP, - BPF_MAP_TYPE_XSKMAP, - BPF_MAP_TYPE_SOCKHASH, - BPF_MAP_TYPE_CGROUP_STORAGE, - BPF_MAP_TYPE_REUSEPORT_SOCKARRAY, - BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE, - BPF_MAP_TYPE_QUEUE, - BPF_MAP_TYPE_STACK, -}; - -/* Note that tracing related programs such as - * BPF_PROG_TYPE_{KPROBE,TRACEPOINT,PERF_EVENT,RAW_TRACEPOINT} - * are not subject to a stable API since kernel internal data - * structures can change from release to release and may - * therefore break existing tracing BPF programs. Tracing BPF - * programs correspond to /a/ specific kernel which is to be - * analyzed, and not /a/ specific kernel /and/ all future ones. - */ -enum bpf_prog_type { - BPF_PROG_TYPE_UNSPEC, - BPF_PROG_TYPE_SOCKET_FILTER, - BPF_PROG_TYPE_KPROBE, - BPF_PROG_TYPE_SCHED_CLS, - BPF_PROG_TYPE_SCHED_ACT, - BPF_PROG_TYPE_TRACEPOINT, - BPF_PROG_TYPE_XDP, - BPF_PROG_TYPE_PERF_EVENT, - BPF_PROG_TYPE_CGROUP_SKB, - BPF_PROG_TYPE_CGROUP_SOCK, - BPF_PROG_TYPE_LWT_IN, - BPF_PROG_TYPE_LWT_OUT, - BPF_PROG_TYPE_LWT_XMIT, - BPF_PROG_TYPE_SOCK_OPS, - BPF_PROG_TYPE_SK_SKB, - BPF_PROG_TYPE_CGROUP_DEVICE, - BPF_PROG_TYPE_SK_MSG, - BPF_PROG_TYPE_RAW_TRACEPOINT, - BPF_PROG_TYPE_CGROUP_SOCK_ADDR, - BPF_PROG_TYPE_LWT_SEG6LOCAL, - BPF_PROG_TYPE_LIRC_MODE2, - BPF_PROG_TYPE_SK_REUSEPORT, - BPF_PROG_TYPE_FLOW_DISSECTOR, -}; - -enum bpf_attach_type { - BPF_CGROUP_INET_INGRESS, - BPF_CGROUP_INET_EGRESS, - BPF_CGROUP_INET_SOCK_CREATE, - BPF_CGROUP_SOCK_OPS, - BPF_SK_SKB_STREAM_PARSER, - BPF_SK_SKB_STREAM_VERDICT, - BPF_CGROUP_DEVICE, - BPF_SK_MSG_VERDICT, - BPF_CGROUP_INET4_BIND, - BPF_CGROUP_INET6_BIND, - BPF_CGROUP_INET4_CONNECT, - BPF_CGROUP_INET6_CONNECT, - BPF_CGROUP_INET4_POST_BIND, - BPF_CGROUP_INET6_POST_BIND, - BPF_CGROUP_UDP4_SENDMSG, - BPF_CGROUP_UDP6_SENDMSG, - BPF_LIRC_MODE2, - BPF_FLOW_DISSECTOR, - __MAX_BPF_ATTACH_TYPE -}; - -#define MAX_BPF_ATTACH_TYPE __MAX_BPF_ATTACH_TYPE - -/* cgroup-bpf attach flags used in BPF_PROG_ATTACH command - * - * NONE(default): No further bpf programs allowed in the subtree. - * - * BPF_F_ALLOW_OVERRIDE: If a sub-cgroup installs some bpf program, - * the program in this cgroup yields to sub-cgroup program. - * - * BPF_F_ALLOW_MULTI: If a sub-cgroup installs some bpf program, - * that cgroup program gets run in addition to the program in this cgroup. - * - * Only one program is allowed to be attached to a cgroup with - * NONE or BPF_F_ALLOW_OVERRIDE flag. - * Attaching another program on top of NONE or BPF_F_ALLOW_OVERRIDE will - * release old program and attach the new one. Attach flags has to match. - * - * Multiple programs are allowed to be attached to a cgroup with - * BPF_F_ALLOW_MULTI flag. They are executed in FIFO order - * (those that were attached first, run first) - * The programs of sub-cgroup are executed first, then programs of - * this cgroup and then programs of parent cgroup. - * When children program makes decision (like picking TCP CA or sock bind) - * parent program has a chance to override it. - * - * A cgroup with MULTI or OVERRIDE flag allows any attach flags in sub-cgroups. - * A cgroup with NONE doesn't allow any programs in sub-cgroups. - * Ex1: - * cgrp1 (MULTI progs A, B) -> - * cgrp2 (OVERRIDE prog C) -> - * cgrp3 (MULTI prog D) -> - * cgrp4 (OVERRIDE prog E) -> - * cgrp5 (NONE prog F) - * the event in cgrp5 triggers execution of F,D,A,B in that order. - * if prog F is detached, the execution is E,D,A,B - * if prog F and D are detached, the execution is E,A,B - * if prog F, E and D are detached, the execution is C,A,B - * - * All eligible programs are executed regardless of return code from - * earlier programs. - */ -#define BPF_F_ALLOW_OVERRIDE (1U << 0) -#define BPF_F_ALLOW_MULTI (1U << 1) - -/* If BPF_F_STRICT_ALIGNMENT is used in BPF_PROG_LOAD command, the - * verifier will perform strict alignment checking as if the kernel - * has been built with CONFIG_EFFICIENT_UNALIGNED_ACCESS not set, - * and NET_IP_ALIGN defined to 2. - */ -#define BPF_F_STRICT_ALIGNMENT (1U << 0) - -/* If BPF_F_ANY_ALIGNMENT is used in BPF_PROF_LOAD command, the - * verifier will allow any alignment whatsoever. On platforms - * with strict alignment requirements for loads ands stores (such - * as sparc and mips) the verifier validates that all loads and - * stores provably follow this requirement. This flag turns that - * checking and enforcement off. - * - * It is mostly used for testing when we want to validate the - * context and memory access aspects of the verifier, but because - * of an unaligned access the alignment check would trigger before - * the one we are interested in. - */ -#define BPF_F_ANY_ALIGNMENT (1U << 1) - -/* when bpf_ldimm64->src_reg == BPF_PSEUDO_MAP_FD, bpf_ldimm64->imm == fd */ -#define BPF_PSEUDO_MAP_FD 1 - -/* when bpf_call->src_reg == BPF_PSEUDO_CALL, bpf_call->imm == pc-relative - * offset to another bpf function - */ -#define BPF_PSEUDO_CALL 1 - -/* flags for BPF_MAP_UPDATE_ELEM command */ -#define BPF_ANY 0 /* create new element or update existing */ -#define BPF_NOEXIST 1 /* create new element if it didn't exist */ -#define BPF_EXIST 2 /* update existing element */ -#define BPF_F_LOCK 4 /* spin_lock-ed map_lookup/map_update */ - -/* flags for BPF_MAP_CREATE command */ -#define BPF_F_NO_PREALLOC (1U << 0) -/* Instead of having one common LRU list in the - * BPF_MAP_TYPE_LRU_[PERCPU_]HASH map, use a percpu LRU list - * which can scale and perform better. - * Note, the LRU nodes (including free nodes) cannot be moved - * across different LRU lists. - */ -#define BPF_F_NO_COMMON_LRU (1U << 1) -/* Specify numa node during map creation */ -#define BPF_F_NUMA_NODE (1U << 2) - -#define BPF_OBJ_NAME_LEN 16U - -/* Flags for accessing BPF object */ -#define BPF_F_RDONLY (1U << 3) -#define BPF_F_WRONLY (1U << 4) - -/* Flag for stack_map, store build_id+offset instead of pointer */ -#define BPF_F_STACK_BUILD_ID (1U << 5) - -/* Zero-initialize hash function seed. This should only be used for testing. */ -#define BPF_F_ZERO_SEED (1U << 6) - -/* flags for BPF_PROG_QUERY */ -#define BPF_F_QUERY_EFFECTIVE (1U << 0) - -enum bpf_stack_build_id_status { - /* user space need an empty entry to identify end of a trace */ - BPF_STACK_BUILD_ID_EMPTY = 0, - /* with valid build_id and offset */ - BPF_STACK_BUILD_ID_VALID = 1, - /* couldn't get build_id, fallback to ip */ - BPF_STACK_BUILD_ID_IP = 2, -}; - -#define BPF_BUILD_ID_SIZE 20 -struct bpf_stack_build_id { - __s32 status; - unsigned char build_id[BPF_BUILD_ID_SIZE]; - union { - __u64 offset; - __u64 ip; - }; -}; - -union bpf_attr { - struct { /* anonymous struct used by BPF_MAP_CREATE command */ - __u32 map_type; /* one of enum bpf_map_type */ - __u32 key_size; /* size of key in bytes */ - __u32 value_size; /* size of value in bytes */ - __u32 max_entries; /* max number of entries in a map */ - __u32 map_flags; /* BPF_MAP_CREATE related - * flags defined above. - */ - __u32 inner_map_fd; /* fd pointing to the inner map */ - __u32 numa_node; /* numa node (effective only if - * BPF_F_NUMA_NODE is set). - */ - char map_name[BPF_OBJ_NAME_LEN]; - __u32 map_ifindex; /* ifindex of netdev to create on */ - __u32 btf_fd; /* fd pointing to a BTF type data */ - __u32 btf_key_type_id; /* BTF type_id of the key */ - __u32 btf_value_type_id; /* BTF type_id of the value */ - }; - - struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */ - __u32 map_fd; - __aligned_u64 key; - union { - __aligned_u64 value; - __aligned_u64 next_key; - }; - __u64 flags; - }; - - struct { /* anonymous struct used by BPF_PROG_LOAD command */ - __u32 prog_type; /* one of enum bpf_prog_type */ - __u32 insn_cnt; - __aligned_u64 insns; - __aligned_u64 license; - __u32 log_level; /* verbosity level of verifier */ - __u32 log_size; /* size of user buffer */ - __aligned_u64 log_buf; /* user supplied buffer */ - __u32 kern_version; /* not used */ - __u32 prog_flags; - char prog_name[BPF_OBJ_NAME_LEN]; - __u32 prog_ifindex; /* ifindex of netdev to prep for */ - /* For some prog types expected attach type must be known at - * load time to verify attach type specific parts of prog - * (context accesses, allowed helpers, etc). - */ - __u32 expected_attach_type; - __u32 prog_btf_fd; /* fd pointing to BTF type data */ - __u32 func_info_rec_size; /* userspace bpf_func_info size */ - __aligned_u64 func_info; /* func info */ - __u32 func_info_cnt; /* number of bpf_func_info records */ - __u32 line_info_rec_size; /* userspace bpf_line_info size */ - __aligned_u64 line_info; /* line info */ - __u32 line_info_cnt; /* number of bpf_line_info records */ - }; - - struct { /* anonymous struct used by BPF_OBJ_* commands */ - __aligned_u64 pathname; - __u32 bpf_fd; - __u32 file_flags; - }; - - struct { /* anonymous struct used by BPF_PROG_ATTACH/DETACH commands */ - __u32 target_fd; /* container object to attach to */ - __u32 attach_bpf_fd; /* eBPF program to attach */ - __u32 attach_type; - __u32 attach_flags; - }; - - struct { /* anonymous struct used by BPF_PROG_TEST_RUN command */ - __u32 prog_fd; - __u32 retval; - __u32 data_size_in; /* input: len of data_in */ - __u32 data_size_out; /* input/output: len of data_out - * returns ENOSPC if data_out - * is too small. - */ - __aligned_u64 data_in; - __aligned_u64 data_out; - __u32 repeat; - __u32 duration; - } test; - - struct { /* anonymous struct used by BPF_*_GET_*_ID */ - union { - __u32 start_id; - __u32 prog_id; - __u32 map_id; - __u32 btf_id; - }; - __u32 next_id; - __u32 open_flags; - }; - - struct { /* anonymous struct used by BPF_OBJ_GET_INFO_BY_FD */ - __u32 bpf_fd; - __u32 info_len; - __aligned_u64 info; - } info; - - struct { /* anonymous struct used by BPF_PROG_QUERY command */ - __u32 target_fd; /* container object to query */ - __u32 attach_type; - __u32 query_flags; - __u32 attach_flags; - __aligned_u64 prog_ids; - __u32 prog_cnt; - } query; - - struct { - __u64 name; - __u32 prog_fd; - } raw_tracepoint; - - struct { /* anonymous struct for BPF_BTF_LOAD */ - __aligned_u64 btf; - __aligned_u64 btf_log_buf; - __u32 btf_size; - __u32 btf_log_size; - __u32 btf_log_level; - }; - - struct { - __u32 pid; /* input: pid */ - __u32 fd; /* input: fd */ - __u32 flags; /* input: flags */ - __u32 buf_len; /* input/output: buf len */ - __aligned_u64 buf; /* input/output: - * tp_name for tracepoint - * symbol for kprobe - * filename for uprobe - */ - __u32 prog_id; /* output: prod_id */ - __u32 fd_type; /* output: BPF_FD_TYPE_* */ - __u64 probe_offset; /* output: probe_offset */ - __u64 probe_addr; /* output: probe_addr */ - } task_fd_query; -} __attribute__((aligned(8))); - -/* The description below is an attempt at providing documentation to eBPF - * developers about the multiple available eBPF helper functions. It can be - * parsed and used to produce a manual page. The workflow is the following, - * and requires the rst2man utility: - * - * $ ./scripts/bpf_helpers_doc.py \ - * --filename include/uapi/linux/bpf.h > /tmp/bpf-helpers.rst - * $ rst2man /tmp/bpf-helpers.rst > /tmp/bpf-helpers.7 - * $ man /tmp/bpf-helpers.7 - * - * Note that in order to produce this external documentation, some RST - * formatting is used in the descriptions to get "bold" and "italics" in - * manual pages. Also note that the few trailing white spaces are - * intentional, removing them would break paragraphs for rst2man. - * - * Start of BPF helper function descriptions: - * - * void *bpf_map_lookup_elem(struct bpf_map *map, const void *key) - * Description - * Perform a lookup in *map* for an entry associated to *key*. - * Return - * Map value associated to *key*, or **NULL** if no entry was - * found. - * - * int bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags) - * Description - * Add or update the value of the entry associated to *key* in - * *map* with *value*. *flags* is one of: - * - * **BPF_NOEXIST** - * The entry for *key* must not exist in the map. - * **BPF_EXIST** - * The entry for *key* must already exist in the map. - * **BPF_ANY** - * No condition on the existence of the entry for *key*. - * - * Flag value **BPF_NOEXIST** cannot be used for maps of types - * **BPF_MAP_TYPE_ARRAY** or **BPF_MAP_TYPE_PERCPU_ARRAY** (all - * elements always exist), the helper would return an error. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_map_delete_elem(struct bpf_map *map, const void *key) - * Description - * Delete entry with *key* from *map*. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_probe_read(void *dst, u32 size, const void *src) - * Description - * For tracing programs, safely attempt to read *size* bytes from - * address *src* and store the data in *dst*. - * Return - * 0 on success, or a negative error in case of failure. - * - * u64 bpf_ktime_get_ns(void) - * Description - * Return the time elapsed since system boot, in nanoseconds. - * Return - * Current *ktime*. - * - * int bpf_trace_printk(const char *fmt, u32 fmt_size, ...) - * Description - * This helper is a "printk()-like" facility for debugging. It - * prints a message defined by format *fmt* (of size *fmt_size*) - * to file *\/sys/kernel/debug/tracing/trace* from DebugFS, if - * available. It can take up to three additional **u64** - * arguments (as an eBPF helpers, the total number of arguments is - * limited to five). - * - * Each time the helper is called, it appends a line to the trace. - * The format of the trace is customizable, and the exact output - * one will get depends on the options set in - * *\/sys/kernel/debug/tracing/trace_options* (see also the - * *README* file under the same directory). However, it usually - * defaults to something like: - * - * :: - * - * telnet-470 [001] .N.. 419421.045894: 0x00000001: <formatted msg> - * - * In the above: - * - * * ``telnet`` is the name of the current task. - * * ``470`` is the PID of the current task. - * * ``001`` is the CPU number on which the task is - * running. - * * In ``.N..``, each character refers to a set of - * options (whether irqs are enabled, scheduling - * options, whether hard/softirqs are running, level of - * preempt_disabled respectively). **N** means that - * **TIF_NEED_RESCHED** and **PREEMPT_NEED_RESCHED** - * are set. - * * ``419421.045894`` is a timestamp. - * * ``0x00000001`` is a fake value used by BPF for the - * instruction pointer register. - * * ``<formatted msg>`` is the message formatted with - * *fmt*. - * - * The conversion specifiers supported by *fmt* are similar, but - * more limited than for printk(). They are **%d**, **%i**, - * **%u**, **%x**, **%ld**, **%li**, **%lu**, **%lx**, **%lld**, - * **%lli**, **%llu**, **%llx**, **%p**, **%s**. No modifier (size - * of field, padding with zeroes, etc.) is available, and the - * helper will return **-EINVAL** (but print nothing) if it - * encounters an unknown specifier. - * - * Also, note that **bpf_trace_printk**\ () is slow, and should - * only be used for debugging purposes. For this reason, a notice - * bloc (spanning several lines) is printed to kernel logs and - * states that the helper should not be used "for production use" - * the first time this helper is used (or more precisely, when - * **trace_printk**\ () buffers are allocated). For passing values - * to user space, perf events should be preferred. - * Return - * The number of bytes written to the buffer, or a negative error - * in case of failure. - * - * u32 bpf_get_prandom_u32(void) - * Description - * Get a pseudo-random number. - * - * From a security point of view, this helper uses its own - * pseudo-random internal state, and cannot be used to infer the - * seed of other random functions in the kernel. However, it is - * essential to note that the generator used by the helper is not - * cryptographically secure. - * Return - * A random 32-bit unsigned value. - * - * u32 bpf_get_smp_processor_id(void) - * Description - * Get the SMP (symmetric multiprocessing) processor id. Note that - * all programs run with preemption disabled, which means that the - * SMP processor id is stable during all the execution of the - * program. - * Return - * The SMP id of the processor running the program. - * - * int bpf_skb_store_bytes(struct sk_buff *skb, u32 offset, const void *from, u32 len, u64 flags) - * Description - * Store *len* bytes from address *from* into the packet - * associated to *skb*, at *offset*. *flags* are a combination of - * **BPF_F_RECOMPUTE_CSUM** (automatically recompute the - * checksum for the packet after storing the bytes) and - * **BPF_F_INVALIDATE_HASH** (set *skb*\ **->hash**, *skb*\ - * **->swhash** and *skb*\ **->l4hash** to 0). - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_l3_csum_replace(struct sk_buff *skb, u32 offset, u64 from, u64 to, u64 size) - * Description - * Recompute the layer 3 (e.g. IP) checksum for the packet - * associated to *skb*. Computation is incremental, so the helper - * must know the former value of the header field that was - * modified (*from*), the new value of this field (*to*), and the - * number of bytes (2 or 4) for this field, stored in *size*. - * Alternatively, it is possible to store the difference between - * the previous and the new values of the header field in *to*, by - * setting *from* and *size* to 0. For both methods, *offset* - * indicates the location of the IP checksum within the packet. - * - * This helper works in combination with **bpf_csum_diff**\ (), - * which does not update the checksum in-place, but offers more - * flexibility and can handle sizes larger than 2 or 4 for the - * checksum to update. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_l4_csum_replace(struct sk_buff *skb, u32 offset, u64 from, u64 to, u64 flags) - * Description - * Recompute the layer 4 (e.g. TCP, UDP or ICMP) checksum for the - * packet associated to *skb*. Computation is incremental, so the - * helper must know the former value of the header field that was - * modified (*from*), the new value of this field (*to*), and the - * number of bytes (2 or 4) for this field, stored on the lowest - * four bits of *flags*. Alternatively, it is possible to store - * the difference between the previous and the new values of the - * header field in *to*, by setting *from* and the four lowest - * bits of *flags* to 0. For both methods, *offset* indicates the - * location of the IP checksum within the packet. In addition to - * the size of the field, *flags* can be added (bitwise OR) actual - * flags. With **BPF_F_MARK_MANGLED_0**, a null checksum is left - * untouched (unless **BPF_F_MARK_ENFORCE** is added as well), and - * for updates resulting in a null checksum the value is set to - * **CSUM_MANGLED_0** instead. Flag **BPF_F_PSEUDO_HDR** indicates - * the checksum is to be computed against a pseudo-header. - * - * This helper works in combination with **bpf_csum_diff**\ (), - * which does not update the checksum in-place, but offers more - * flexibility and can handle sizes larger than 2 or 4 for the - * checksum to update. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_tail_call(void *ctx, struct bpf_map *prog_array_map, u32 index) - * Description - * This special helper is used to trigger a "tail call", or in - * other words, to jump into another eBPF program. The same stack - * frame is used (but values on stack and in registers for the - * caller are not accessible to the callee). This mechanism allows - * for program chaining, either for raising the maximum number of - * available eBPF instructions, or to execute given programs in - * conditional blocks. For security reasons, there is an upper - * limit to the number of successive tail calls that can be - * performed. - * - * Upon call of this helper, the program attempts to jump into a - * program referenced at index *index* in *prog_array_map*, a - * special map of type **BPF_MAP_TYPE_PROG_ARRAY**, and passes - * *ctx*, a pointer to the context. - * - * If the call succeeds, the kernel immediately runs the first - * instruction of the new program. This is not a function call, - * and it never returns to the previous program. If the call - * fails, then the helper has no effect, and the caller continues - * to run its subsequent instructions. A call can fail if the - * destination program for the jump does not exist (i.e. *index* - * is superior to the number of entries in *prog_array_map*), or - * if the maximum number of tail calls has been reached for this - * chain of programs. This limit is defined in the kernel by the - * macro **MAX_TAIL_CALL_CNT** (not accessible to user space), - * which is currently set to 32. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_clone_redirect(struct sk_buff *skb, u32 ifindex, u64 flags) - * Description - * Clone and redirect the packet associated to *skb* to another - * net device of index *ifindex*. Both ingress and egress - * interfaces can be used for redirection. The **BPF_F_INGRESS** - * value in *flags* is used to make the distinction (ingress path - * is selected if the flag is present, egress path otherwise). - * This is the only flag supported for now. - * - * In comparison with **bpf_redirect**\ () helper, - * **bpf_clone_redirect**\ () has the associated cost of - * duplicating the packet buffer, but this can be executed out of - * the eBPF program. Conversely, **bpf_redirect**\ () is more - * efficient, but it is handled through an action code where the - * redirection happens only after the eBPF program has returned. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * u64 bpf_get_current_pid_tgid(void) - * Return - * A 64-bit integer containing the current tgid and pid, and - * created as such: - * *current_task*\ **->tgid << 32 \|** - * *current_task*\ **->pid**. - * - * u64 bpf_get_current_uid_gid(void) - * Return - * A 64-bit integer containing the current GID and UID, and - * created as such: *current_gid* **<< 32 \|** *current_uid*. - * - * int bpf_get_current_comm(char *buf, u32 size_of_buf) - * Description - * Copy the **comm** attribute of the current task into *buf* of - * *size_of_buf*. The **comm** attribute contains the name of - * the executable (excluding the path) for the current task. The - * *size_of_buf* must be strictly positive. On success, the - * helper makes sure that the *buf* is NUL-terminated. On failure, - * it is filled with zeroes. - * Return - * 0 on success, or a negative error in case of failure. - * - * u32 bpf_get_cgroup_classid(struct sk_buff *skb) - * Description - * Retrieve the classid for the current task, i.e. for the net_cls - * cgroup to which *skb* belongs. - * - * This helper can be used on TC egress path, but not on ingress. - * - * The net_cls cgroup provides an interface to tag network packets - * based on a user-provided identifier for all traffic coming from - * the tasks belonging to the related cgroup. See also the related - * kernel documentation, available from the Linux sources in file - * *Documentation/cgroup-v1/net_cls.txt*. - * - * The Linux kernel has two versions for cgroups: there are - * cgroups v1 and cgroups v2. Both are available to users, who can - * use a mixture of them, but note that the net_cls cgroup is for - * cgroup v1 only. This makes it incompatible with BPF programs - * run on cgroups, which is a cgroup-v2-only feature (a socket can - * only hold data for one version of cgroups at a time). - * - * This helper is only available is the kernel was compiled with - * the **CONFIG_CGROUP_NET_CLASSID** configuration option set to - * "**y**" or to "**m**". - * Return - * The classid, or 0 for the default unconfigured classid. - * - * int bpf_skb_vlan_push(struct sk_buff *skb, __be16 vlan_proto, u16 vlan_tci) - * Description - * Push a *vlan_tci* (VLAN tag control information) of protocol - * *vlan_proto* to the packet associated to *skb*, then update - * the checksum. Note that if *vlan_proto* is different from - * **ETH_P_8021Q** and **ETH_P_8021AD**, it is considered to - * be **ETH_P_8021Q**. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_vlan_pop(struct sk_buff *skb) - * Description - * Pop a VLAN header from the packet associated to *skb*. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_get_tunnel_key(struct sk_buff *skb, struct bpf_tunnel_key *key, u32 size, u64 flags) - * Description - * Get tunnel metadata. This helper takes a pointer *key* to an - * empty **struct bpf_tunnel_key** of **size**, that will be - * filled with tunnel metadata for the packet associated to *skb*. - * The *flags* can be set to **BPF_F_TUNINFO_IPV6**, which - * indicates that the tunnel is based on IPv6 protocol instead of - * IPv4. - * - * The **struct bpf_tunnel_key** is an object that generalizes the - * principal parameters used by various tunneling protocols into a - * single struct. This way, it can be used to easily make a - * decision based on the contents of the encapsulation header, - * "summarized" in this struct. In particular, it holds the IP - * address of the remote end (IPv4 or IPv6, depending on the case) - * in *key*\ **->remote_ipv4** or *key*\ **->remote_ipv6**. Also, - * this struct exposes the *key*\ **->tunnel_id**, which is - * generally mapped to a VNI (Virtual Network Identifier), making - * it programmable together with the **bpf_skb_set_tunnel_key**\ - * () helper. - * - * Let's imagine that the following code is part of a program - * attached to the TC ingress interface, on one end of a GRE - * tunnel, and is supposed to filter out all messages coming from - * remote ends with IPv4 address other than 10.0.0.1: - * - * :: - * - * int ret; - * struct bpf_tunnel_key key = {}; - * - * ret = bpf_skb_get_tunnel_key(skb, &key, sizeof(key), 0); - * if (ret < 0) - * return TC_ACT_SHOT; // drop packet - * - * if (key.remote_ipv4 != 0x0a000001) - * return TC_ACT_SHOT; // drop packet - * - * return TC_ACT_OK; // accept packet - * - * This interface can also be used with all encapsulation devices - * that can operate in "collect metadata" mode: instead of having - * one network device per specific configuration, the "collect - * metadata" mode only requires a single device where the - * configuration can be extracted from this helper. - * - * This can be used together with various tunnels such as VXLan, - * Geneve, GRE or IP in IP (IPIP). - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_set_tunnel_key(struct sk_buff *skb, struct bpf_tunnel_key *key, u32 size, u64 flags) - * Description - * Populate tunnel metadata for packet associated to *skb.* The - * tunnel metadata is set to the contents of *key*, of *size*. The - * *flags* can be set to a combination of the following values: - * - * **BPF_F_TUNINFO_IPV6** - * Indicate that the tunnel is based on IPv6 protocol - * instead of IPv4. - * **BPF_F_ZERO_CSUM_TX** - * For IPv4 packets, add a flag to tunnel metadata - * indicating that checksum computation should be skipped - * and checksum set to zeroes. - * **BPF_F_DONT_FRAGMENT** - * Add a flag to tunnel metadata indicating that the - * packet should not be fragmented. - * **BPF_F_SEQ_NUMBER** - * Add a flag to tunnel metadata indicating that a - * sequence number should be added to tunnel header before - * sending the packet. This flag was added for GRE - * encapsulation, but might be used with other protocols - * as well in the future. - * - * Here is a typical usage on the transmit path: - * - * :: - * - * struct bpf_tunnel_key key; - * populate key ... - * bpf_skb_set_tunnel_key(skb, &key, sizeof(key), 0); - * bpf_clone_redirect(skb, vxlan_dev_ifindex, 0); - * - * See also the description of the **bpf_skb_get_tunnel_key**\ () - * helper for additional information. - * Return - * 0 on success, or a negative error in case of failure. - * - * u64 bpf_perf_event_read(struct bpf_map *map, u64 flags) - * Description - * Read the value of a perf event counter. This helper relies on a - * *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. The nature of - * the perf event counter is selected when *map* is updated with - * perf event file descriptors. The *map* is an array whose size - * is the number of available CPUs, and each cell contains a value - * relative to one CPU. The value to retrieve is indicated by - * *flags*, that contains the index of the CPU to look up, masked - * with **BPF_F_INDEX_MASK**. Alternatively, *flags* can be set to - * **BPF_F_CURRENT_CPU** to indicate that the value for the - * current CPU should be retrieved. - * - * Note that before Linux 4.13, only hardware perf event can be - * retrieved. - * - * Also, be aware that the newer helper - * **bpf_perf_event_read_value**\ () is recommended over - * **bpf_perf_event_read**\ () in general. The latter has some ABI - * quirks where error and counter value are used as a return code - * (which is wrong to do since ranges may overlap). This issue is - * fixed with **bpf_perf_event_read_value**\ (), which at the same - * time provides more features over the **bpf_perf_event_read**\ - * () interface. Please refer to the description of - * **bpf_perf_event_read_value**\ () for details. - * Return - * The value of the perf event counter read from the map, or a - * negative error code in case of failure. - * - * int bpf_redirect(u32 ifindex, u64 flags) - * Description - * Redirect the packet to another net device of index *ifindex*. - * This helper is somewhat similar to **bpf_clone_redirect**\ - * (), except that the packet is not cloned, which provides - * increased performance. - * - * Except for XDP, both ingress and egress interfaces can be used - * for redirection. The **BPF_F_INGRESS** value in *flags* is used - * to make the distinction (ingress path is selected if the flag - * is present, egress path otherwise). Currently, XDP only - * supports redirection to the egress interface, and accepts no - * flag at all. - * - * The same effect can be attained with the more generic - * **bpf_redirect_map**\ (), which requires specific maps to be - * used but offers better performance. - * Return - * For XDP, the helper returns **XDP_REDIRECT** on success or - * **XDP_ABORTED** on error. For other program types, the values - * are **TC_ACT_REDIRECT** on success or **TC_ACT_SHOT** on - * error. - * - * u32 bpf_get_route_realm(struct sk_buff *skb) - * Description - * Retrieve the realm or the route, that is to say the - * **tclassid** field of the destination for the *skb*. The - * indentifier retrieved is a user-provided tag, similar to the - * one used with the net_cls cgroup (see description for - * **bpf_get_cgroup_classid**\ () helper), but here this tag is - * held by a route (a destination entry), not by a task. - * - * Retrieving this identifier works with the clsact TC egress hook - * (see also **tc-bpf(8)**), or alternatively on conventional - * classful egress qdiscs, but not on TC ingress path. In case of - * clsact TC egress hook, this has the advantage that, internally, - * the destination entry has not been dropped yet in the transmit - * path. Therefore, the destination entry does not need to be - * artificially held via **netif_keep_dst**\ () for a classful - * qdisc until the *skb* is freed. - * - * This helper is available only if the kernel was compiled with - * **CONFIG_IP_ROUTE_CLASSID** configuration option. - * Return - * The realm of the route for the packet associated to *skb*, or 0 - * if none was found. - * - * int bpf_perf_event_output(struct pt_reg *ctx, struct bpf_map *map, u64 flags, void *data, u64 size) - * Description - * Write raw *data* blob into a special BPF perf event held by - * *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf - * event must have the following attributes: **PERF_SAMPLE_RAW** - * as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and - * **PERF_COUNT_SW_BPF_OUTPUT** as **config**. - * - * The *flags* are used to indicate the index in *map* for which - * the value must be put, masked with **BPF_F_INDEX_MASK**. - * Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU** - * to indicate that the index of the current CPU core should be - * used. - * - * The value to write, of *size*, is passed through eBPF stack and - * pointed by *data*. - * - * The context of the program *ctx* needs also be passed to the - * helper. - * - * On user space, a program willing to read the values needs to - * call **perf_event_open**\ () on the perf event (either for - * one or for all CPUs) and to store the file descriptor into the - * *map*. This must be done before the eBPF program can send data - * into it. An example is available in file - * *samples/bpf/trace_output_user.c* in the Linux kernel source - * tree (the eBPF program counterpart is in - * *samples/bpf/trace_output_kern.c*). - * - * **bpf_perf_event_output**\ () achieves better performance - * than **bpf_trace_printk**\ () for sharing data with user - * space, and is much better suitable for streaming data from eBPF - * programs. - * - * Note that this helper is not restricted to tracing use cases - * and can be used with programs attached to TC or XDP as well, - * where it allows for passing data to user space listeners. Data - * can be: - * - * * Only custom structs, - * * Only the packet payload, or - * * A combination of both. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_load_bytes(const struct sk_buff *skb, u32 offset, void *to, u32 len) - * Description - * This helper was provided as an easy way to load data from a - * packet. It can be used to load *len* bytes from *offset* from - * the packet associated to *skb*, into the buffer pointed by - * *to*. - * - * Since Linux 4.7, usage of this helper has mostly been replaced - * by "direct packet access", enabling packet data to be - * manipulated with *skb*\ **->data** and *skb*\ **->data_end** - * pointing respectively to the first byte of packet data and to - * the byte after the last byte of packet data. However, it - * remains useful if one wishes to read large quantities of data - * at once from a packet into the eBPF stack. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_get_stackid(struct pt_reg *ctx, struct bpf_map *map, u64 flags) - * Description - * Walk a user or a kernel stack and return its id. To achieve - * this, the helper needs *ctx*, which is a pointer to the context - * on which the tracing program is executed, and a pointer to a - * *map* of type **BPF_MAP_TYPE_STACK_TRACE**. - * - * The last argument, *flags*, holds the number of stack frames to - * skip (from 0 to 255), masked with - * **BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set - * a combination of the following flags: - * - * **BPF_F_USER_STACK** - * Collect a user space stack instead of a kernel stack. - * **BPF_F_FAST_STACK_CMP** - * Compare stacks by hash only. - * **BPF_F_REUSE_STACKID** - * If two different stacks hash into the same *stackid*, - * discard the old one. - * - * The stack id retrieved is a 32 bit long integer handle which - * can be further combined with other data (including other stack - * ids) and used as a key into maps. This can be useful for - * generating a variety of graphs (such as flame graphs or off-cpu - * graphs). - * - * For walking a stack, this helper is an improvement over - * **bpf_probe_read**\ (), which can be used with unrolled loops - * but is not efficient and consumes a lot of eBPF instructions. - * Instead, **bpf_get_stackid**\ () can collect up to - * **PERF_MAX_STACK_DEPTH** both kernel and user frames. Note that - * this limit can be controlled with the **sysctl** program, and - * that it should be manually increased in order to profile long - * user stacks (such as stacks for Java programs). To do so, use: - * - * :: - * - * # sysctl kernel.perf_event_max_stack=<new value> - * Return - * The positive or null stack id on success, or a negative error - * in case of failure. - * - * s64 bpf_csum_diff(__be32 *from, u32 from_size, __be32 *to, u32 to_size, __wsum seed) - * Description - * Compute a checksum difference, from the raw buffer pointed by - * *from*, of length *from_size* (that must be a multiple of 4), - * towards the raw buffer pointed by *to*, of size *to_size* - * (same remark). An optional *seed* can be added to the value - * (this can be cascaded, the seed may come from a previous call - * to the helper). - * - * This is flexible enough to be used in several ways: - * - * * With *from_size* == 0, *to_size* > 0 and *seed* set to - * checksum, it can be used when pushing new data. - * * With *from_size* > 0, *to_size* == 0 and *seed* set to - * checksum, it can be used when removing data from a packet. - * * With *from_size* > 0, *to_size* > 0 and *seed* set to 0, it - * can be used to compute a diff. Note that *from_size* and - * *to_size* do not need to be equal. - * - * This helper can be used in combination with - * **bpf_l3_csum_replace**\ () and **bpf_l4_csum_replace**\ (), to - * which one can feed in the difference computed with - * **bpf_csum_diff**\ (). - * Return - * The checksum result, or a negative error code in case of - * failure. - * - * int bpf_skb_get_tunnel_opt(struct sk_buff *skb, u8 *opt, u32 size) - * Description - * Retrieve tunnel options metadata for the packet associated to - * *skb*, and store the raw tunnel option data to the buffer *opt* - * of *size*. - * - * This helper can be used with encapsulation devices that can - * operate in "collect metadata" mode (please refer to the related - * note in the description of **bpf_skb_get_tunnel_key**\ () for - * more details). A particular example where this can be used is - * in combination with the Geneve encapsulation protocol, where it - * allows for pushing (with **bpf_skb_get_tunnel_opt**\ () helper) - * and retrieving arbitrary TLVs (Type-Length-Value headers) from - * the eBPF program. This allows for full customization of these - * headers. - * Return - * The size of the option data retrieved. - * - * int bpf_skb_set_tunnel_opt(struct sk_buff *skb, u8 *opt, u32 size) - * Description - * Set tunnel options metadata for the packet associated to *skb* - * to the option data contained in the raw buffer *opt* of *size*. - * - * See also the description of the **bpf_skb_get_tunnel_opt**\ () - * helper for additional information. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_change_proto(struct sk_buff *skb, __be16 proto, u64 flags) - * Description - * Change the protocol of the *skb* to *proto*. Currently - * supported are transition from IPv4 to IPv6, and from IPv6 to - * IPv4. The helper takes care of the groundwork for the - * transition, including resizing the socket buffer. The eBPF - * program is expected to fill the new headers, if any, via - * **skb_store_bytes**\ () and to recompute the checksums with - * **bpf_l3_csum_replace**\ () and **bpf_l4_csum_replace**\ - * (). The main case for this helper is to perform NAT64 - * operations out of an eBPF program. - * - * Internally, the GSO type is marked as dodgy so that headers are - * checked and segments are recalculated by the GSO/GRO engine. - * The size for GSO target is adapted as well. - * - * All values for *flags* are reserved for future usage, and must - * be left at zero. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_change_type(struct sk_buff *skb, u32 type) - * Description - * Change the packet type for the packet associated to *skb*. This - * comes down to setting *skb*\ **->pkt_type** to *type*, except - * the eBPF program does not have a write access to *skb*\ - * **->pkt_type** beside this helper. Using a helper here allows - * for graceful handling of errors. - * - * The major use case is to change incoming *skb*s to - * **PACKET_HOST** in a programmatic way instead of having to - * recirculate via **redirect**\ (..., **BPF_F_INGRESS**), for - * example. - * - * Note that *type* only allows certain values. At this time, they - * are: - * - * **PACKET_HOST** - * Packet is for us. - * **PACKET_BROADCAST** - * Send packet to all. - * **PACKET_MULTICAST** - * Send packet to group. - * **PACKET_OTHERHOST** - * Send packet to someone else. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_under_cgroup(struct sk_buff *skb, struct bpf_map *map, u32 index) - * Description - * Check whether *skb* is a descendant of the cgroup2 held by - * *map* of type **BPF_MAP_TYPE_CGROUP_ARRAY**, at *index*. - * Return - * The return value depends on the result of the test, and can be: - * - * * 0, if the *skb* failed the cgroup2 descendant test. - * * 1, if the *skb* succeeded the cgroup2 descendant test. - * * A negative error code, if an error occurred. - * - * u32 bpf_get_hash_recalc(struct sk_buff *skb) - * Description - * Retrieve the hash of the packet, *skb*\ **->hash**. If it is - * not set, in particular if the hash was cleared due to mangling, - * recompute this hash. Later accesses to the hash can be done - * directly with *skb*\ **->hash**. - * - * Calling **bpf_set_hash_invalid**\ (), changing a packet - * prototype with **bpf_skb_change_proto**\ (), or calling - * **bpf_skb_store_bytes**\ () with the - * **BPF_F_INVALIDATE_HASH** are actions susceptible to clear - * the hash and to trigger a new computation for the next call to - * **bpf_get_hash_recalc**\ (). - * Return - * The 32-bit hash. - * - * u64 bpf_get_current_task(void) - * Return - * A pointer to the current task struct. - * - * int bpf_probe_write_user(void *dst, const void *src, u32 len) - * Description - * Attempt in a safe way to write *len* bytes from the buffer - * *src* to *dst* in memory. It only works for threads that are in - * user context, and *dst* must be a valid user space address. - * - * This helper should not be used to implement any kind of - * security mechanism because of TOC-TOU attacks, but rather to - * debug, divert, and manipulate execution of semi-cooperative - * processes. - * - * Keep in mind that this feature is meant for experiments, and it - * has a risk of crashing the system and running programs. - * Therefore, when an eBPF program using this helper is attached, - * a warning including PID and process name is printed to kernel - * logs. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_current_task_under_cgroup(struct bpf_map *map, u32 index) - * Description - * Check whether the probe is being run is the context of a given - * subset of the cgroup2 hierarchy. The cgroup2 to test is held by - * *map* of type **BPF_MAP_TYPE_CGROUP_ARRAY**, at *index*. - * Return - * The return value depends on the result of the test, and can be: - * - * * 0, if the *skb* task belongs to the cgroup2. - * * 1, if the *skb* task does not belong to the cgroup2. - * * A negative error code, if an error occurred. - * - * int bpf_skb_change_tail(struct sk_buff *skb, u32 len, u64 flags) - * Description - * Resize (trim or grow) the packet associated to *skb* to the - * new *len*. The *flags* are reserved for future usage, and must - * be left at zero. - * - * The basic idea is that the helper performs the needed work to - * change the size of the packet, then the eBPF program rewrites - * the rest via helpers like **bpf_skb_store_bytes**\ (), - * **bpf_l3_csum_replace**\ (), **bpf_l3_csum_replace**\ () - * and others. This helper is a slow path utility intended for - * replies with control messages. And because it is targeted for - * slow path, the helper itself can afford to be slow: it - * implicitly linearizes, unclones and drops offloads from the - * *skb*. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_pull_data(struct sk_buff *skb, u32 len) - * Description - * Pull in non-linear data in case the *skb* is non-linear and not - * all of *len* are part of the linear section. Make *len* bytes - * from *skb* readable and writable. If a zero value is passed for - * *len*, then the whole length of the *skb* is pulled. - * - * This helper is only needed for reading and writing with direct - * packet access. - * - * For direct packet access, testing that offsets to access - * are within packet boundaries (test on *skb*\ **->data_end**) is - * susceptible to fail if offsets are invalid, or if the requested - * data is in non-linear parts of the *skb*. On failure the - * program can just bail out, or in the case of a non-linear - * buffer, use a helper to make the data available. The - * **bpf_skb_load_bytes**\ () helper is a first solution to access - * the data. Another one consists in using **bpf_skb_pull_data** - * to pull in once the non-linear parts, then retesting and - * eventually access the data. - * - * At the same time, this also makes sure the *skb* is uncloned, - * which is a necessary condition for direct write. As this needs - * to be an invariant for the write part only, the verifier - * detects writes and adds a prologue that is calling - * **bpf_skb_pull_data()** to effectively unclone the *skb* from - * the very beginning in case it is indeed cloned. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * s64 bpf_csum_update(struct sk_buff *skb, __wsum csum) - * Description - * Add the checksum *csum* into *skb*\ **->csum** in case the - * driver has supplied a checksum for the entire packet into that - * field. Return an error otherwise. This helper is intended to be - * used in combination with **bpf_csum_diff**\ (), in particular - * when the checksum needs to be updated after data has been - * written into the packet through direct packet access. - * Return - * The checksum on success, or a negative error code in case of - * failure. - * - * void bpf_set_hash_invalid(struct sk_buff *skb) - * Description - * Invalidate the current *skb*\ **->hash**. It can be used after - * mangling on headers through direct packet access, in order to - * indicate that the hash is outdated and to trigger a - * recalculation the next time the kernel tries to access this - * hash or when the **bpf_get_hash_recalc**\ () helper is called. - * - * int bpf_get_numa_node_id(void) - * Description - * Return the id of the current NUMA node. The primary use case - * for this helper is the selection of sockets for the local NUMA - * node, when the program is attached to sockets using the - * **SO_ATTACH_REUSEPORT_EBPF** option (see also **socket(7)**), - * but the helper is also available to other eBPF program types, - * similarly to **bpf_get_smp_processor_id**\ (). - * Return - * The id of current NUMA node. - * - * int bpf_skb_change_head(struct sk_buff *skb, u32 len, u64 flags) - * Description - * Grows headroom of packet associated to *skb* and adjusts the - * offset of the MAC header accordingly, adding *len* bytes of - * space. It automatically extends and reallocates memory as - * required. - * - * This helper can be used on a layer 3 *skb* to push a MAC header - * for redirection into a layer 2 device. - * - * All values for *flags* are reserved for future usage, and must - * be left at zero. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_xdp_adjust_head(struct xdp_buff *xdp_md, int delta) - * Description - * Adjust (move) *xdp_md*\ **->data** by *delta* bytes. Note that - * it is possible to use a negative value for *delta*. This helper - * can be used to prepare the packet for pushing or popping - * headers. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr) - * Description - * Copy a NUL terminated string from an unsafe address - * *unsafe_ptr* to *dst*. The *size* should include the - * terminating NUL byte. In case the string length is smaller than - * *size*, the target is not padded with further NUL bytes. If the - * string length is larger than *size*, just *size*-1 bytes are - * copied and the last byte is set to NUL. - * - * On success, the length of the copied string is returned. This - * makes this helper useful in tracing programs for reading - * strings, and more importantly to get its length at runtime. See - * the following snippet: - * - * :: - * - * SEC("kprobe/sys_open") - * void bpf_sys_open(struct pt_regs *ctx) - * { - * char buf[PATHLEN]; // PATHLEN is defined to 256 - * int res = bpf_probe_read_str(buf, sizeof(buf), - * ctx->di); - * - * // Consume buf, for example push it to - * // userspace via bpf_perf_event_output(); we - * // can use res (the string length) as event - * // size, after checking its boundaries. - * } - * - * In comparison, using **bpf_probe_read()** helper here instead - * to read the string would require to estimate the length at - * compile time, and would often result in copying more memory - * than necessary. - * - * Another useful use case is when parsing individual process - * arguments or individual environment variables navigating - * *current*\ **->mm->arg_start** and *current*\ - * **->mm->env_start**: using this helper and the return value, - * one can quickly iterate at the right offset of the memory area. - * Return - * On success, the strictly positive length of the string, - * including the trailing NUL character. On error, a negative - * value. - * - * u64 bpf_get_socket_cookie(struct sk_buff *skb) - * Description - * If the **struct sk_buff** pointed by *skb* has a known socket, - * retrieve the cookie (generated by the kernel) of this socket. - * If no cookie has been set yet, generate a new cookie. Once - * generated, the socket cookie remains stable for the life of the - * socket. This helper can be useful for monitoring per socket - * networking traffic statistics as it provides a unique socket - * identifier per namespace. - * Return - * A 8-byte long non-decreasing number on success, or 0 if the - * socket field is missing inside *skb*. - * - * u64 bpf_get_socket_cookie(struct bpf_sock_addr *ctx) - * Description - * Equivalent to bpf_get_socket_cookie() helper that accepts - * *skb*, but gets socket from **struct bpf_sock_addr** context. - * Return - * A 8-byte long non-decreasing number. - * - * u64 bpf_get_socket_cookie(struct bpf_sock_ops *ctx) - * Description - * Equivalent to bpf_get_socket_cookie() helper that accepts - * *skb*, but gets socket from **struct bpf_sock_ops** context. - * Return - * A 8-byte long non-decreasing number. - * - * u32 bpf_get_socket_uid(struct sk_buff *skb) - * Return - * The owner UID of the socket associated to *skb*. If the socket - * is **NULL**, or if it is not a full socket (i.e. if it is a - * time-wait or a request socket instead), **overflowuid** value - * is returned (note that **overflowuid** might also be the actual - * UID value for the socket). - * - * u32 bpf_set_hash(struct sk_buff *skb, u32 hash) - * Description - * Set the full hash for *skb* (set the field *skb*\ **->hash**) - * to value *hash*. - * Return - * 0 - * - * int bpf_setsockopt(struct bpf_sock_ops *bpf_socket, int level, int optname, char *optval, int optlen) - * Description - * Emulate a call to **setsockopt()** on the socket associated to - * *bpf_socket*, which must be a full socket. The *level* at - * which the option resides and the name *optname* of the option - * must be specified, see **setsockopt(2)** for more information. - * The option value of length *optlen* is pointed by *optval*. - * - * This helper actually implements a subset of **setsockopt()**. - * It supports the following *level*\ s: - * - * * **SOL_SOCKET**, which supports the following *optname*\ s: - * **SO_RCVBUF**, **SO_SNDBUF**, **SO_MAX_PACING_RATE**, - * **SO_PRIORITY**, **SO_RCVLOWAT**, **SO_MARK**. - * * **IPPROTO_TCP**, which supports the following *optname*\ s: - * **TCP_CONGESTION**, **TCP_BPF_IW**, - * **TCP_BPF_SNDCWND_CLAMP**. - * * **IPPROTO_IP**, which supports *optname* **IP_TOS**. - * * **IPPROTO_IPV6**, which supports *optname* **IPV6_TCLASS**. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_adjust_room(struct sk_buff *skb, s32 len_diff, u32 mode, u64 flags) - * Description - * Grow or shrink the room for data in the packet associated to - * *skb* by *len_diff*, and according to the selected *mode*. - * - * There are two supported modes at this time: - * - * * **BPF_ADJ_ROOM_MAC**: Adjust room at the mac layer - * (room space is added or removed below the layer 2 header). - * - * * **BPF_ADJ_ROOM_NET**: Adjust room at the network layer - * (room space is added or removed below the layer 3 header). - * - * The following flags are supported at this time: - * - * * **BPF_F_ADJ_ROOM_FIXED_GSO**: Do not adjust gso_size. - * Adjusting mss in this way is not allowed for datagrams. - * - * * **BPF_F_ADJ_ROOM_ENCAP_L3_IPV4 **: - * * **BPF_F_ADJ_ROOM_ENCAP_L3_IPV6 **: - * Any new space is reserved to hold a tunnel header. - * Configure skb offsets and other fields accordingly. - * - * * **BPF_F_ADJ_ROOM_ENCAP_L4_GRE **: - * * **BPF_F_ADJ_ROOM_ENCAP_L4_UDP **: - * Use with ENCAP_L3 flags to further specify the tunnel type. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_redirect_map(struct bpf_map *map, u32 key, u64 flags) - * Description - * Redirect the packet to the endpoint referenced by *map* at - * index *key*. Depending on its type, this *map* can contain - * references to net devices (for forwarding packets through other - * ports), or to CPUs (for redirecting XDP frames to another CPU; - * but this is only implemented for native XDP (with driver - * support) as of this writing). - * - * All values for *flags* are reserved for future usage, and must - * be left at zero. - * - * When used to redirect packets to net devices, this helper - * provides a high performance increase over **bpf_redirect**\ (). - * This is due to various implementation details of the underlying - * mechanisms, one of which is the fact that **bpf_redirect_map**\ - * () tries to send packet as a "bulk" to the device. - * Return - * **XDP_REDIRECT** on success, or **XDP_ABORTED** on error. - * - * int bpf_sk_redirect_map(struct bpf_map *map, u32 key, u64 flags) - * Description - * Redirect the packet to the socket referenced by *map* (of type - * **BPF_MAP_TYPE_SOCKMAP**) at index *key*. Both ingress and - * egress interfaces can be used for redirection. The - * **BPF_F_INGRESS** value in *flags* is used to make the - * distinction (ingress path is selected if the flag is present, - * egress path otherwise). This is the only flag supported for now. - * Return - * **SK_PASS** on success, or **SK_DROP** on error. - * - * int bpf_sock_map_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags) - * Description - * Add an entry to, or update a *map* referencing sockets. The - * *skops* is used as a new value for the entry associated to - * *key*. *flags* is one of: - * - * **BPF_NOEXIST** - * The entry for *key* must not exist in the map. - * **BPF_EXIST** - * The entry for *key* must already exist in the map. - * **BPF_ANY** - * No condition on the existence of the entry for *key*. - * - * If the *map* has eBPF programs (parser and verdict), those will - * be inherited by the socket being added. If the socket is - * already attached to eBPF programs, this results in an error. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_xdp_adjust_meta(struct xdp_buff *xdp_md, int delta) - * Description - * Adjust the address pointed by *xdp_md*\ **->data_meta** by - * *delta* (which can be positive or negative). Note that this - * operation modifies the address stored in *xdp_md*\ **->data**, - * so the latter must be loaded only after the helper has been - * called. - * - * The use of *xdp_md*\ **->data_meta** is optional and programs - * are not required to use it. The rationale is that when the - * packet is processed with XDP (e.g. as DoS filter), it is - * possible to push further meta data along with it before passing - * to the stack, and to give the guarantee that an ingress eBPF - * program attached as a TC classifier on the same device can pick - * this up for further post-processing. Since TC works with socket - * buffers, it remains possible to set from XDP the **mark** or - * **priority** pointers, or other pointers for the socket buffer. - * Having this scratch space generic and programmable allows for - * more flexibility as the user is free to store whatever meta - * data they need. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_perf_event_read_value(struct bpf_map *map, u64 flags, struct bpf_perf_event_value *buf, u32 buf_size) - * Description - * Read the value of a perf event counter, and store it into *buf* - * of size *buf_size*. This helper relies on a *map* of type - * **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. The nature of the perf event - * counter is selected when *map* is updated with perf event file - * descriptors. The *map* is an array whose size is the number of - * available CPUs, and each cell contains a value relative to one - * CPU. The value to retrieve is indicated by *flags*, that - * contains the index of the CPU to look up, masked with - * **BPF_F_INDEX_MASK**. Alternatively, *flags* can be set to - * **BPF_F_CURRENT_CPU** to indicate that the value for the - * current CPU should be retrieved. - * - * This helper behaves in a way close to - * **bpf_perf_event_read**\ () helper, save that instead of - * just returning the value observed, it fills the *buf* - * structure. This allows for additional data to be retrieved: in - * particular, the enabled and running times (in *buf*\ - * **->enabled** and *buf*\ **->running**, respectively) are - * copied. In general, **bpf_perf_event_read_value**\ () is - * recommended over **bpf_perf_event_read**\ (), which has some - * ABI issues and provides fewer functionalities. - * - * These values are interesting, because hardware PMU (Performance - * Monitoring Unit) counters are limited resources. When there are - * more PMU based perf events opened than available counters, - * kernel will multiplex these events so each event gets certain - * percentage (but not all) of the PMU time. In case that - * multiplexing happens, the number of samples or counter value - * will not reflect the case compared to when no multiplexing - * occurs. This makes comparison between different runs difficult. - * Typically, the counter value should be normalized before - * comparing to other experiments. The usual normalization is done - * as follows. - * - * :: - * - * normalized_counter = counter * t_enabled / t_running - * - * Where t_enabled is the time enabled for event and t_running is - * the time running for event since last normalization. The - * enabled and running times are accumulated since the perf event - * open. To achieve scaling factor between two invocations of an - * eBPF program, users can can use CPU id as the key (which is - * typical for perf array usage model) to remember the previous - * value and do the calculation inside the eBPF program. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_perf_prog_read_value(struct bpf_perf_event_data *ctx, struct bpf_perf_event_value *buf, u32 buf_size) - * Description - * For en eBPF program attached to a perf event, retrieve the - * value of the event counter associated to *ctx* and store it in - * the structure pointed by *buf* and of size *buf_size*. Enabled - * and running times are also stored in the structure (see - * description of helper **bpf_perf_event_read_value**\ () for - * more details). - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_getsockopt(struct bpf_sock_ops *bpf_socket, int level, int optname, char *optval, int optlen) - * Description - * Emulate a call to **getsockopt()** on the socket associated to - * *bpf_socket*, which must be a full socket. The *level* at - * which the option resides and the name *optname* of the option - * must be specified, see **getsockopt(2)** for more information. - * The retrieved value is stored in the structure pointed by - * *opval* and of length *optlen*. - * - * This helper actually implements a subset of **getsockopt()**. - * It supports the following *level*\ s: - * - * * **IPPROTO_TCP**, which supports *optname* - * **TCP_CONGESTION**. - * * **IPPROTO_IP**, which supports *optname* **IP_TOS**. - * * **IPPROTO_IPV6**, which supports *optname* **IPV6_TCLASS**. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_override_return(struct pt_reg *regs, u64 rc) - * Description - * Used for error injection, this helper uses kprobes to override - * the return value of the probed function, and to set it to *rc*. - * The first argument is the context *regs* on which the kprobe - * works. - * - * This helper works by setting setting the PC (program counter) - * to an override function which is run in place of the original - * probed function. This means the probed function is not run at - * all. The replacement function just returns with the required - * value. - * - * This helper has security implications, and thus is subject to - * restrictions. It is only available if the kernel was compiled - * with the **CONFIG_BPF_KPROBE_OVERRIDE** configuration - * option, and in this case it only works on functions tagged with - * **ALLOW_ERROR_INJECTION** in the kernel code. - * - * Also, the helper is only available for the architectures having - * the CONFIG_FUNCTION_ERROR_INJECTION option. As of this writing, - * x86 architecture is the only one to support this feature. - * Return - * 0 - * - * int bpf_sock_ops_cb_flags_set(struct bpf_sock_ops *bpf_sock, int argval) - * Description - * Attempt to set the value of the **bpf_sock_ops_cb_flags** field - * for the full TCP socket associated to *bpf_sock_ops* to - * *argval*. - * - * The primary use of this field is to determine if there should - * be calls to eBPF programs of type - * **BPF_PROG_TYPE_SOCK_OPS** at various points in the TCP - * code. A program of the same type can change its value, per - * connection and as necessary, when the connection is - * established. This field is directly accessible for reading, but - * this helper must be used for updates in order to return an - * error if an eBPF program tries to set a callback that is not - * supported in the current kernel. - * - * The supported callback values that *argval* can combine are: - * - * * **BPF_SOCK_OPS_RTO_CB_FLAG** (retransmission time out) - * * **BPF_SOCK_OPS_RETRANS_CB_FLAG** (retransmission) - * * **BPF_SOCK_OPS_STATE_CB_FLAG** (TCP state change) - * - * Here are some examples of where one could call such eBPF - * program: - * - * * When RTO fires. - * * When a packet is retransmitted. - * * When the connection terminates. - * * When a packet is sent. - * * When a packet is received. - * Return - * Code **-EINVAL** if the socket is not a full TCP socket; - * otherwise, a positive number containing the bits that could not - * be set is returned (which comes down to 0 if all bits were set - * as required). - * - * int bpf_msg_redirect_map(struct sk_msg_buff *msg, struct bpf_map *map, u32 key, u64 flags) - * Description - * This helper is used in programs implementing policies at the - * socket level. If the message *msg* is allowed to pass (i.e. if - * the verdict eBPF program returns **SK_PASS**), redirect it to - * the socket referenced by *map* (of type - * **BPF_MAP_TYPE_SOCKMAP**) at index *key*. Both ingress and - * egress interfaces can be used for redirection. The - * **BPF_F_INGRESS** value in *flags* is used to make the - * distinction (ingress path is selected if the flag is present, - * egress path otherwise). This is the only flag supported for now. - * Return - * **SK_PASS** on success, or **SK_DROP** on error. - * - * int bpf_msg_apply_bytes(struct sk_msg_buff *msg, u32 bytes) - * Description - * For socket policies, apply the verdict of the eBPF program to - * the next *bytes* (number of bytes) of message *msg*. - * - * For example, this helper can be used in the following cases: - * - * * A single **sendmsg**\ () or **sendfile**\ () system call - * contains multiple logical messages that the eBPF program is - * supposed to read and for which it should apply a verdict. - * * An eBPF program only cares to read the first *bytes* of a - * *msg*. If the message has a large payload, then setting up - * and calling the eBPF program repeatedly for all bytes, even - * though the verdict is already known, would create unnecessary - * overhead. - * - * When called from within an eBPF program, the helper sets a - * counter internal to the BPF infrastructure, that is used to - * apply the last verdict to the next *bytes*. If *bytes* is - * smaller than the current data being processed from a - * **sendmsg**\ () or **sendfile**\ () system call, the first - * *bytes* will be sent and the eBPF program will be re-run with - * the pointer for start of data pointing to byte number *bytes* - * **+ 1**. If *bytes* is larger than the current data being - * processed, then the eBPF verdict will be applied to multiple - * **sendmsg**\ () or **sendfile**\ () calls until *bytes* are - * consumed. - * - * Note that if a socket closes with the internal counter holding - * a non-zero value, this is not a problem because data is not - * being buffered for *bytes* and is sent as it is received. - * Return - * 0 - * - * int bpf_msg_cork_bytes(struct sk_msg_buff *msg, u32 bytes) - * Description - * For socket policies, prevent the execution of the verdict eBPF - * program for message *msg* until *bytes* (byte number) have been - * accumulated. - * - * This can be used when one needs a specific number of bytes - * before a verdict can be assigned, even if the data spans - * multiple **sendmsg**\ () or **sendfile**\ () calls. The extreme - * case would be a user calling **sendmsg**\ () repeatedly with - * 1-byte long message segments. Obviously, this is bad for - * performance, but it is still valid. If the eBPF program needs - * *bytes* bytes to validate a header, this helper can be used to - * prevent the eBPF program to be called again until *bytes* have - * been accumulated. - * Return - * 0 - * - * int bpf_msg_pull_data(struct sk_msg_buff *msg, u32 start, u32 end, u64 flags) - * Description - * For socket policies, pull in non-linear data from user space - * for *msg* and set pointers *msg*\ **->data** and *msg*\ - * **->data_end** to *start* and *end* bytes offsets into *msg*, - * respectively. - * - * If a program of type **BPF_PROG_TYPE_SK_MSG** is run on a - * *msg* it can only parse data that the (**data**, **data_end**) - * pointers have already consumed. For **sendmsg**\ () hooks this - * is likely the first scatterlist element. But for calls relying - * on the **sendpage** handler (e.g. **sendfile**\ ()) this will - * be the range (**0**, **0**) because the data is shared with - * user space and by default the objective is to avoid allowing - * user space to modify data while (or after) eBPF verdict is - * being decided. This helper can be used to pull in data and to - * set the start and end pointer to given values. Data will be - * copied if necessary (i.e. if data was not linear and if start - * and end pointers do not point to the same chunk). - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * - * All values for *flags* are reserved for future usage, and must - * be left at zero. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_bind(struct bpf_sock_addr *ctx, struct sockaddr *addr, int addr_len) - * Description - * Bind the socket associated to *ctx* to the address pointed by - * *addr*, of length *addr_len*. This allows for making outgoing - * connection from the desired IP address, which can be useful for - * example when all processes inside a cgroup should use one - * single IP address on a host that has multiple IP configured. - * - * This helper works for IPv4 and IPv6, TCP and UDP sockets. The - * domain (*addr*\ **->sa_family**) must be **AF_INET** (or - * **AF_INET6**). Looking for a free port to bind to can be - * expensive, therefore binding to port is not permitted by the - * helper: *addr*\ **->sin_port** (or **sin6_port**, respectively) - * must be set to zero. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_xdp_adjust_tail(struct xdp_buff *xdp_md, int delta) - * Description - * Adjust (move) *xdp_md*\ **->data_end** by *delta* bytes. It is - * only possible to shrink the packet as of this writing, - * therefore *delta* must be a negative integer. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_skb_get_xfrm_state(struct sk_buff *skb, u32 index, struct bpf_xfrm_state *xfrm_state, u32 size, u64 flags) - * Description - * Retrieve the XFRM state (IP transform framework, see also - * **ip-xfrm(8)**) at *index* in XFRM "security path" for *skb*. - * - * The retrieved value is stored in the **struct bpf_xfrm_state** - * pointed by *xfrm_state* and of length *size*. - * - * All values for *flags* are reserved for future usage, and must - * be left at zero. - * - * This helper is available only if the kernel was compiled with - * **CONFIG_XFRM** configuration option. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_get_stack(struct pt_regs *regs, void *buf, u32 size, u64 flags) - * Description - * Return a user or a kernel stack in bpf program provided buffer. - * To achieve this, the helper needs *ctx*, which is a pointer - * to the context on which the tracing program is executed. - * To store the stacktrace, the bpf program provides *buf* with - * a nonnegative *size*. - * - * The last argument, *flags*, holds the number of stack frames to - * skip (from 0 to 255), masked with - * **BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set - * the following flags: - * - * **BPF_F_USER_STACK** - * Collect a user space stack instead of a kernel stack. - * **BPF_F_USER_BUILD_ID** - * Collect buildid+offset instead of ips for user stack, - * only valid if **BPF_F_USER_STACK** is also specified. - * - * **bpf_get_stack**\ () can collect up to - * **PERF_MAX_STACK_DEPTH** both kernel and user frames, subject - * to sufficient large buffer size. Note that - * this limit can be controlled with the **sysctl** program, and - * that it should be manually increased in order to profile long - * user stacks (such as stacks for Java programs). To do so, use: - * - * :: - * - * # sysctl kernel.perf_event_max_stack=<new value> - * Return - * A non-negative value equal to or less than *size* on success, - * or a negative error in case of failure. - * - * int bpf_skb_load_bytes_relative(const struct sk_buff *skb, u32 offset, void *to, u32 len, u32 start_header) - * Description - * This helper is similar to **bpf_skb_load_bytes**\ () in that - * it provides an easy way to load *len* bytes from *offset* - * from the packet associated to *skb*, into the buffer pointed - * by *to*. The difference to **bpf_skb_load_bytes**\ () is that - * a fifth argument *start_header* exists in order to select a - * base offset to start from. *start_header* can be one of: - * - * **BPF_HDR_START_MAC** - * Base offset to load data from is *skb*'s mac header. - * **BPF_HDR_START_NET** - * Base offset to load data from is *skb*'s network header. - * - * In general, "direct packet access" is the preferred method to - * access packet data, however, this helper is in particular useful - * in socket filters where *skb*\ **->data** does not always point - * to the start of the mac header and where "direct packet access" - * is not available. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_fib_lookup(void *ctx, struct bpf_fib_lookup *params, int plen, u32 flags) - * Description - * Do FIB lookup in kernel tables using parameters in *params*. - * If lookup is successful and result shows packet is to be - * forwarded, the neighbor tables are searched for the nexthop. - * If successful (ie., FIB lookup shows forwarding and nexthop - * is resolved), the nexthop address is returned in ipv4_dst - * or ipv6_dst based on family, smac is set to mac address of - * egress device, dmac is set to nexthop mac address, rt_metric - * is set to metric from route (IPv4/IPv6 only), and ifindex - * is set to the device index of the nexthop from the FIB lookup. - * - * *plen* argument is the size of the passed in struct. - * *flags* argument can be a combination of one or more of the - * following values: - * - * **BPF_FIB_LOOKUP_DIRECT** - * Do a direct table lookup vs full lookup using FIB - * rules. - * **BPF_FIB_LOOKUP_OUTPUT** - * Perform lookup from an egress perspective (default is - * ingress). - * - * *ctx* is either **struct xdp_md** for XDP programs or - * **struct sk_buff** tc cls_act programs. - * Return - * * < 0 if any input argument is invalid - * * 0 on success (packet is forwarded, nexthop neighbor exists) - * * > 0 one of **BPF_FIB_LKUP_RET_** codes explaining why the - * packet is not forwarded or needs assist from full stack - * - * int bpf_sock_hash_update(struct bpf_sock_ops_kern *skops, struct bpf_map *map, void *key, u64 flags) - * Description - * Add an entry to, or update a sockhash *map* referencing sockets. - * The *skops* is used as a new value for the entry associated to - * *key*. *flags* is one of: - * - * **BPF_NOEXIST** - * The entry for *key* must not exist in the map. - * **BPF_EXIST** - * The entry for *key* must already exist in the map. - * **BPF_ANY** - * No condition on the existence of the entry for *key*. - * - * If the *map* has eBPF programs (parser and verdict), those will - * be inherited by the socket being added. If the socket is - * already attached to eBPF programs, this results in an error. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_msg_redirect_hash(struct sk_msg_buff *msg, struct bpf_map *map, void *key, u64 flags) - * Description - * This helper is used in programs implementing policies at the - * socket level. If the message *msg* is allowed to pass (i.e. if - * the verdict eBPF program returns **SK_PASS**), redirect it to - * the socket referenced by *map* (of type - * **BPF_MAP_TYPE_SOCKHASH**) using hash *key*. Both ingress and - * egress interfaces can be used for redirection. The - * **BPF_F_INGRESS** value in *flags* is used to make the - * distinction (ingress path is selected if the flag is present, - * egress path otherwise). This is the only flag supported for now. - * Return - * **SK_PASS** on success, or **SK_DROP** on error. - * - * int bpf_sk_redirect_hash(struct sk_buff *skb, struct bpf_map *map, void *key, u64 flags) - * Description - * This helper is used in programs implementing policies at the - * skb socket level. If the sk_buff *skb* is allowed to pass (i.e. - * if the verdeict eBPF program returns **SK_PASS**), redirect it - * to the socket referenced by *map* (of type - * **BPF_MAP_TYPE_SOCKHASH**) using hash *key*. Both ingress and - * egress interfaces can be used for redirection. The - * **BPF_F_INGRESS** value in *flags* is used to make the - * distinction (ingress path is selected if the flag is present, - * egress otherwise). This is the only flag supported for now. - * Return - * **SK_PASS** on success, or **SK_DROP** on error. - * - * int bpf_lwt_push_encap(struct sk_buff *skb, u32 type, void *hdr, u32 len) - * Description - * Encapsulate the packet associated to *skb* within a Layer 3 - * protocol header. This header is provided in the buffer at - * address *hdr*, with *len* its size in bytes. *type* indicates - * the protocol of the header and can be one of: - * - * **BPF_LWT_ENCAP_SEG6** - * IPv6 encapsulation with Segment Routing Header - * (**struct ipv6_sr_hdr**). *hdr* only contains the SRH, - * the IPv6 header is computed by the kernel. - * **BPF_LWT_ENCAP_SEG6_INLINE** - * Only works if *skb* contains an IPv6 packet. Insert a - * Segment Routing Header (**struct ipv6_sr_hdr**) inside - * the IPv6 header. - * **BPF_LWT_ENCAP_IP** - * IP encapsulation (GRE/GUE/IPIP/etc). The outer header - * must be IPv4 or IPv6, followed by zero or more - * additional headers, up to LWT_BPF_MAX_HEADROOM total - * bytes in all prepended headers. Please note that - * if skb_is_gso(skb) is true, no more than two headers - * can be prepended, and the inner header, if present, - * should be either GRE or UDP/GUE. - * - * BPF_LWT_ENCAP_SEG6*** types can be called by bpf programs of - * type BPF_PROG_TYPE_LWT_IN; BPF_LWT_ENCAP_IP type can be called - * by bpf programs of types BPF_PROG_TYPE_LWT_IN and - * BPF_PROG_TYPE_LWT_XMIT. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_lwt_seg6_store_bytes(struct sk_buff *skb, u32 offset, const void *from, u32 len) - * Description - * Store *len* bytes from address *from* into the packet - * associated to *skb*, at *offset*. Only the flags, tag and TLVs - * inside the outermost IPv6 Segment Routing Header can be - * modified through this helper. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_lwt_seg6_adjust_srh(struct sk_buff *skb, u32 offset, s32 delta) - * Description - * Adjust the size allocated to TLVs in the outermost IPv6 - * Segment Routing Header contained in the packet associated to - * *skb*, at position *offset* by *delta* bytes. Only offsets - * after the segments are accepted. *delta* can be as well - * positive (growing) as negative (shrinking). - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_lwt_seg6_action(struct sk_buff *skb, u32 action, void *param, u32 param_len) - * Description - * Apply an IPv6 Segment Routing action of type *action* to the - * packet associated to *skb*. Each action takes a parameter - * contained at address *param*, and of length *param_len* bytes. - * *action* can be one of: - * - * **SEG6_LOCAL_ACTION_END_X** - * End.X action: Endpoint with Layer-3 cross-connect. - * Type of *param*: **struct in6_addr**. - * **SEG6_LOCAL_ACTION_END_T** - * End.T action: Endpoint with specific IPv6 table lookup. - * Type of *param*: **int**. - * **SEG6_LOCAL_ACTION_END_B6** - * End.B6 action: Endpoint bound to an SRv6 policy. - * Type of param: **struct ipv6_sr_hdr**. - * **SEG6_LOCAL_ACTION_END_B6_ENCAP** - * End.B6.Encap action: Endpoint bound to an SRv6 - * encapsulation policy. - * Type of param: **struct ipv6_sr_hdr**. - * - * A call to this helper is susceptible to change the underlaying - * packet buffer. Therefore, at load time, all checks on pointers - * previously done by the verifier are invalidated and must be - * performed again, if the helper is used in combination with - * direct packet access. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_rc_repeat(void *ctx) - * Description - * This helper is used in programs implementing IR decoding, to - * report a successfully decoded repeat key message. This delays - * the generation of a key up event for previously generated - * key down event. - * - * Some IR protocols like NEC have a special IR message for - * repeating last button, for when a button is held down. - * - * The *ctx* should point to the lirc sample as passed into - * the program. - * - * This helper is only available is the kernel was compiled with - * the **CONFIG_BPF_LIRC_MODE2** configuration option set to - * "**y**". - * Return - * 0 - * - * int bpf_rc_keydown(void *ctx, u32 protocol, u64 scancode, u32 toggle) - * Description - * This helper is used in programs implementing IR decoding, to - * report a successfully decoded key press with *scancode*, - * *toggle* value in the given *protocol*. The scancode will be - * translated to a keycode using the rc keymap, and reported as - * an input key down event. After a period a key up event is - * generated. This period can be extended by calling either - * **bpf_rc_keydown**\ () again with the same values, or calling - * **bpf_rc_repeat**\ (). - * - * Some protocols include a toggle bit, in case the button was - * released and pressed again between consecutive scancodes. - * - * The *ctx* should point to the lirc sample as passed into - * the program. - * - * The *protocol* is the decoded protocol number (see - * **enum rc_proto** for some predefined values). - * - * This helper is only available is the kernel was compiled with - * the **CONFIG_BPF_LIRC_MODE2** configuration option set to - * "**y**". - * Return - * 0 - * - * u64 bpf_skb_cgroup_id(struct sk_buff *skb) - * Description - * Return the cgroup v2 id of the socket associated with the *skb*. - * This is roughly similar to the **bpf_get_cgroup_classid**\ () - * helper for cgroup v1 by providing a tag resp. identifier that - * can be matched on or used for map lookups e.g. to implement - * policy. The cgroup v2 id of a given path in the hierarchy is - * exposed in user space through the f_handle API in order to get - * to the same 64-bit id. - * - * This helper can be used on TC egress path, but not on ingress, - * and is available only if the kernel was compiled with the - * **CONFIG_SOCK_CGROUP_DATA** configuration option. - * Return - * The id is returned or 0 in case the id could not be retrieved. - * - * u64 bpf_get_current_cgroup_id(void) - * Return - * A 64-bit integer containing the current cgroup id based - * on the cgroup within which the current task is running. - * - * void *bpf_get_local_storage(void *map, u64 flags) - * Description - * Get the pointer to the local storage area. - * The type and the size of the local storage is defined - * by the *map* argument. - * The *flags* meaning is specific for each map type, - * and has to be 0 for cgroup local storage. - * - * Depending on the BPF program type, a local storage area - * can be shared between multiple instances of the BPF program, - * running simultaneously. - * - * A user should care about the synchronization by himself. - * For example, by using the **BPF_STX_XADD** instruction to alter - * the shared data. - * Return - * A pointer to the local storage area. - * - * int bpf_sk_select_reuseport(struct sk_reuseport_md *reuse, struct bpf_map *map, void *key, u64 flags) - * Description - * Select a **SO_REUSEPORT** socket from a - * **BPF_MAP_TYPE_REUSEPORT_ARRAY** *map*. - * It checks the selected socket is matching the incoming - * request in the socket buffer. - * Return - * 0 on success, or a negative error in case of failure. - * - * u64 bpf_skb_ancestor_cgroup_id(struct sk_buff *skb, int ancestor_level) - * Description - * Return id of cgroup v2 that is ancestor of cgroup associated - * with the *skb* at the *ancestor_level*. The root cgroup is at - * *ancestor_level* zero and each step down the hierarchy - * increments the level. If *ancestor_level* == level of cgroup - * associated with *skb*, then return value will be same as that - * of **bpf_skb_cgroup_id**\ (). - * - * The helper is useful to implement policies based on cgroups - * that are upper in hierarchy than immediate cgroup associated - * with *skb*. - * - * The format of returned id and helper limitations are same as in - * **bpf_skb_cgroup_id**\ (). - * Return - * The id is returned or 0 in case the id could not be retrieved. - * - * struct bpf_sock *bpf_sk_lookup_tcp(void *ctx, struct bpf_sock_tuple *tuple, u32 tuple_size, u64 netns, u64 flags) - * Description - * Look for TCP socket matching *tuple*, optionally in a child - * network namespace *netns*. The return value must be checked, - * and if non-**NULL**, released via **bpf_sk_release**\ (). - * - * The *ctx* should point to the context of the program, such as - * the skb or socket (depending on the hook in use). This is used - * to determine the base network namespace for the lookup. - * - * *tuple_size* must be one of: - * - * **sizeof**\ (*tuple*\ **->ipv4**) - * Look for an IPv4 socket. - * **sizeof**\ (*tuple*\ **->ipv6**) - * Look for an IPv6 socket. - * - * If the *netns* is a negative signed 32-bit integer, then the - * socket lookup table in the netns associated with the *ctx* will - * will be used. For the TC hooks, this is the netns of the device - * in the skb. For socket hooks, this is the netns of the socket. - * If *netns* is any other signed 32-bit value greater than or - * equal to zero then it specifies the ID of the netns relative to - * the netns associated with the *ctx*. *netns* values beyond the - * range of 32-bit integers are reserved for future use. - * - * All values for *flags* are reserved for future usage, and must - * be left at zero. - * - * This helper is available only if the kernel was compiled with - * **CONFIG_NET** configuration option. - * Return - * Pointer to **struct bpf_sock**, or **NULL** in case of failure. - * For sockets with reuseport option, the **struct bpf_sock** - * result is from **reuse->socks**\ [] using the hash of the tuple. - * - * struct bpf_sock *bpf_sk_lookup_udp(void *ctx, struct bpf_sock_tuple *tuple, u32 tuple_size, u64 netns, u64 flags) - * Description - * Look for UDP socket matching *tuple*, optionally in a child - * network namespace *netns*. The return value must be checked, - * and if non-**NULL**, released via **bpf_sk_release**\ (). - * - * The *ctx* should point to the context of the program, such as - * the skb or socket (depending on the hook in use). This is used - * to determine the base network namespace for the lookup. - * - * *tuple_size* must be one of: - * - * **sizeof**\ (*tuple*\ **->ipv4**) - * Look for an IPv4 socket. - * **sizeof**\ (*tuple*\ **->ipv6**) - * Look for an IPv6 socket. - * - * If the *netns* is a negative signed 32-bit integer, then the - * socket lookup table in the netns associated with the *ctx* will - * will be used. For the TC hooks, this is the netns of the device - * in the skb. For socket hooks, this is the netns of the socket. - * If *netns* is any other signed 32-bit value greater than or - * equal to zero then it specifies the ID of the netns relative to - * the netns associated with the *ctx*. *netns* values beyond the - * range of 32-bit integers are reserved for future use. - * - * All values for *flags* are reserved for future usage, and must - * be left at zero. - * - * This helper is available only if the kernel was compiled with - * **CONFIG_NET** configuration option. - * Return - * Pointer to **struct bpf_sock**, or **NULL** in case of failure. - * For sockets with reuseport option, the **struct bpf_sock** - * result is from **reuse->socks**\ [] using the hash of the tuple. - * - * int bpf_sk_release(struct bpf_sock *sock) - * Description - * Release the reference held by *sock*. *sock* must be a - * non-**NULL** pointer that was returned from - * **bpf_sk_lookup_xxx**\ (). - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_map_push_elem(struct bpf_map *map, const void *value, u64 flags) - * Description - * Push an element *value* in *map*. *flags* is one of: - * - * **BPF_EXIST** - * If the queue/stack is full, the oldest element is - * removed to make room for this. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_map_pop_elem(struct bpf_map *map, void *value) - * Description - * Pop an element from *map*. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_map_peek_elem(struct bpf_map *map, void *value) - * Description - * Get an element from *map* without removing it. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_msg_push_data(struct sk_buff *skb, u32 start, u32 len, u64 flags) - * Description - * For socket policies, insert *len* bytes into *msg* at offset - * *start*. - * - * If a program of type **BPF_PROG_TYPE_SK_MSG** is run on a - * *msg* it may want to insert metadata or options into the *msg*. - * This can later be read and used by any of the lower layer BPF - * hooks. - * - * This helper may fail if under memory pressure (a malloc - * fails) in these cases BPF programs will get an appropriate - * error and BPF programs will need to handle them. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_msg_pop_data(struct sk_msg_buff *msg, u32 start, u32 pop, u64 flags) - * Description - * Will remove *pop* bytes from a *msg* starting at byte *start*. - * This may result in **ENOMEM** errors under certain situations if - * an allocation and copy are required due to a full ring buffer. - * However, the helper will try to avoid doing the allocation - * if possible. Other errors can occur if input parameters are - * invalid either due to *start* byte not being valid part of *msg* - * payload and/or *pop* value being to large. - * Return - * 0 on success, or a negative error in case of failure. - * - * int bpf_rc_pointer_rel(void *ctx, s32 rel_x, s32 rel_y) - * Description - * This helper is used in programs implementing IR decoding, to - * report a successfully decoded pointer movement. - * - * The *ctx* should point to the lirc sample as passed into - * the program. - * - * This helper is only available is the kernel was compiled with - * the **CONFIG_BPF_LIRC_MODE2** configuration option set to - * "**y**". - * Return - * 0 - * - * int bpf_spin_lock(struct bpf_spin_lock *lock) - * Description - * Acquire a spinlock represented by the pointer *lock*, which is - * stored as part of a value of a map. Taking the lock allows to - * safely update the rest of the fields in that value. The - * spinlock can (and must) later be released with a call to - * **bpf_spin_unlock**\ (\ *lock*\ ). - * - * Spinlocks in BPF programs come with a number of restrictions - * and constraints: - * - * * **bpf_spin_lock** objects are only allowed inside maps of - * types **BPF_MAP_TYPE_HASH** and **BPF_MAP_TYPE_ARRAY** (this - * list could be extended in the future). - * * BTF description of the map is mandatory. - * * The BPF program can take ONE lock at a time, since taking two - * or more could cause dead locks. - * * Only one **struct bpf_spin_lock** is allowed per map element. - * * When the lock is taken, calls (either BPF to BPF or helpers) - * are not allowed. - * * The **BPF_LD_ABS** and **BPF_LD_IND** instructions are not - * allowed inside a spinlock-ed region. - * * The BPF program MUST call **bpf_spin_unlock**\ () to release - * the lock, on all execution paths, before it returns. - * * The BPF program can access **struct bpf_spin_lock** only via - * the **bpf_spin_lock**\ () and **bpf_spin_unlock**\ () - * helpers. Loading or storing data into the **struct - * bpf_spin_lock** *lock*\ **;** field of a map is not allowed. - * * To use the **bpf_spin_lock**\ () helper, the BTF description - * of the map value must be a struct and have **struct - * bpf_spin_lock** *anyname*\ **;** field at the top level. - * Nested lock inside another struct is not allowed. - * * The **struct bpf_spin_lock** *lock* field in a map value must - * be aligned on a multiple of 4 bytes in that value. - * * Syscall with command **BPF_MAP_LOOKUP_ELEM** does not copy - * the **bpf_spin_lock** field to user space. - * * Syscall with command **BPF_MAP_UPDATE_ELEM**, or update from - * a BPF program, do not update the **bpf_spin_lock** field. - * * **bpf_spin_lock** cannot be on the stack or inside a - * networking packet (it can only be inside of a map values). - * * **bpf_spin_lock** is available to root only. - * * Tracing programs and socket filter programs cannot use - * **bpf_spin_lock**\ () due to insufficient preemption checks - * (but this may change in the future). - * * **bpf_spin_lock** is not allowed in inner maps of map-in-map. - * Return - * 0 - * - * int bpf_spin_unlock(struct bpf_spin_lock *lock) - * Description - * Release the *lock* previously locked by a call to - * **bpf_spin_lock**\ (\ *lock*\ ). - * Return - * 0 - * - * struct bpf_sock *bpf_sk_fullsock(struct bpf_sock *sk) - * Description - * This helper gets a **struct bpf_sock** pointer such - * that all the fields in this **bpf_sock** can be accessed. - * Return - * A **struct bpf_sock** pointer on success, or **NULL** in - * case of failure. - * - * struct bpf_tcp_sock *bpf_tcp_sock(struct bpf_sock *sk) - * Description - * This helper gets a **struct bpf_tcp_sock** pointer from a - * **struct bpf_sock** pointer. - * Return - * A **struct bpf_tcp_sock** pointer on success, or **NULL** in - * case of failure. - * - * int bpf_skb_ecn_set_ce(struct sk_buf *skb) - * Description - * Set ECN (Explicit Congestion Notification) field of IP header - * to **CE** (Congestion Encountered) if current value is **ECT** - * (ECN Capable Transport). Otherwise, do nothing. Works with IPv6 - * and IPv4. - * Return - * 1 if the **CE** flag is set (either by the current helper call - * or because it was already present), 0 if it is not set. - * - * struct bpf_sock *bpf_get_listener_sock(struct bpf_sock *sk) - * Description - * Return a **struct bpf_sock** pointer in **TCP_LISTEN** state. - * **bpf_sk_release**\ () is unnecessary and not allowed. - * Return - * A **struct bpf_sock** pointer on success, or **NULL** in - * case of failure. - * - * struct bpf_sock *bpf_skc_lookup_tcp(void *ctx, struct bpf_sock_tuple *tuple, u32 tuple_size, u64 netns, u64 flags) - * Description - * Look for TCP socket matching *tuple*, optionally in a child - * network namespace *netns*. The return value must be checked, - * and if non-**NULL**, released via **bpf_sk_release**\ (). - * - * This function is identical to bpf_sk_lookup_tcp, except that it - * also returns timewait or request sockets. Use bpf_sk_fullsock - * or bpf_tcp_socket to access the full structure. - * - * This helper is available only if the kernel was compiled with - * **CONFIG_NET** configuration option. - * Return - * Pointer to **struct bpf_sock**, or **NULL** in case of failure. - * For sockets with reuseport option, the **struct bpf_sock** - * result is from **reuse->socks**\ [] using the hash of the tuple. - * - * int bpf_tcp_check_syncookie(struct bpf_sock *sk, void *iph, u32 iph_len, struct tcphdr *th, u32 th_len) - * Description - * Check whether iph and th contain a valid SYN cookie ACK for - * the listening socket in sk. - * - * iph points to the start of the IPv4 or IPv6 header, while - * iph_len contains sizeof(struct iphdr) or sizeof(struct ip6hdr). - * - * th points to the start of the TCP header, while th_len contains - * sizeof(struct tcphdr). - * - * Return - * 0 if iph and th are a valid SYN cookie ACK, or a negative error - * otherwise. - */ -#define __BPF_FUNC_MAPPER(FN) \ - FN(unspec), \ - FN(map_lookup_elem), \ - FN(map_update_elem), \ - FN(map_delete_elem), \ - FN(probe_read), \ - FN(ktime_get_ns), \ - FN(trace_printk), \ - FN(get_prandom_u32), \ - FN(get_smp_processor_id), \ - FN(skb_store_bytes), \ - FN(l3_csum_replace), \ - FN(l4_csum_replace), \ - FN(tail_call), \ - FN(clone_redirect), \ - FN(get_current_pid_tgid), \ - FN(get_current_uid_gid), \ - FN(get_current_comm), \ - FN(get_cgroup_classid), \ - FN(skb_vlan_push), \ - FN(skb_vlan_pop), \ - FN(skb_get_tunnel_key), \ - FN(skb_set_tunnel_key), \ - FN(perf_event_read), \ - FN(redirect), \ - FN(get_route_realm), \ - FN(perf_event_output), \ - FN(skb_load_bytes), \ - FN(get_stackid), \ - FN(csum_diff), \ - FN(skb_get_tunnel_opt), \ - FN(skb_set_tunnel_opt), \ - FN(skb_change_proto), \ - FN(skb_change_type), \ - FN(skb_under_cgroup), \ - FN(get_hash_recalc), \ - FN(get_current_task), \ - FN(probe_write_user), \ - FN(current_task_under_cgroup), \ - FN(skb_change_tail), \ - FN(skb_pull_data), \ - FN(csum_update), \ - FN(set_hash_invalid), \ - FN(get_numa_node_id), \ - FN(skb_change_head), \ - FN(xdp_adjust_head), \ - FN(probe_read_str), \ - FN(get_socket_cookie), \ - FN(get_socket_uid), \ - FN(set_hash), \ - FN(setsockopt), \ - FN(skb_adjust_room), \ - FN(redirect_map), \ - FN(sk_redirect_map), \ - FN(sock_map_update), \ - FN(xdp_adjust_meta), \ - FN(perf_event_read_value), \ - FN(perf_prog_read_value), \ - FN(getsockopt), \ - FN(override_return), \ - FN(sock_ops_cb_flags_set), \ - FN(msg_redirect_map), \ - FN(msg_apply_bytes), \ - FN(msg_cork_bytes), \ - FN(msg_pull_data), \ - FN(bind), \ - FN(xdp_adjust_tail), \ - FN(skb_get_xfrm_state), \ - FN(get_stack), \ - FN(skb_load_bytes_relative), \ - FN(fib_lookup), \ - FN(sock_hash_update), \ - FN(msg_redirect_hash), \ - FN(sk_redirect_hash), \ - FN(lwt_push_encap), \ - FN(lwt_seg6_store_bytes), \ - FN(lwt_seg6_adjust_srh), \ - FN(lwt_seg6_action), \ - FN(rc_repeat), \ - FN(rc_keydown), \ - FN(skb_cgroup_id), \ - FN(get_current_cgroup_id), \ - FN(get_local_storage), \ - FN(sk_select_reuseport), \ - FN(skb_ancestor_cgroup_id), \ - FN(sk_lookup_tcp), \ - FN(sk_lookup_udp), \ - FN(sk_release), \ - FN(map_push_elem), \ - FN(map_pop_elem), \ - FN(map_peek_elem), \ - FN(msg_push_data), \ - FN(msg_pop_data), \ - FN(rc_pointer_rel), \ - FN(spin_lock), \ - FN(spin_unlock), \ - FN(sk_fullsock), \ - FN(tcp_sock), \ - FN(skb_ecn_set_ce), \ - FN(get_listener_sock), \ - FN(skc_lookup_tcp), \ - FN(tcp_check_syncookie), - -/* integer value in 'imm' field of BPF_CALL instruction selects which helper - * function eBPF program intends to call - */ -#define __BPF_ENUM_FN(x) BPF_FUNC_ ## x -enum bpf_func_id { - __BPF_FUNC_MAPPER(__BPF_ENUM_FN) - __BPF_FUNC_MAX_ID, -}; -#undef __BPF_ENUM_FN - -/* All flags used by eBPF helper functions, placed here. */ - -/* BPF_FUNC_skb_store_bytes flags. */ -#define BPF_F_RECOMPUTE_CSUM (1ULL << 0) -#define BPF_F_INVALIDATE_HASH (1ULL << 1) - -/* BPF_FUNC_l3_csum_replace and BPF_FUNC_l4_csum_replace flags. - * First 4 bits are for passing the header field size. - */ -#define BPF_F_HDR_FIELD_MASK 0xfULL - -/* BPF_FUNC_l4_csum_replace flags. */ -#define BPF_F_PSEUDO_HDR (1ULL << 4) -#define BPF_F_MARK_MANGLED_0 (1ULL << 5) -#define BPF_F_MARK_ENFORCE (1ULL << 6) - -/* BPF_FUNC_clone_redirect and BPF_FUNC_redirect flags. */ -#define BPF_F_INGRESS (1ULL << 0) - -/* BPF_FUNC_skb_set_tunnel_key and BPF_FUNC_skb_get_tunnel_key flags. */ -#define BPF_F_TUNINFO_IPV6 (1ULL << 0) - -/* flags for both BPF_FUNC_get_stackid and BPF_FUNC_get_stack. */ -#define BPF_F_SKIP_FIELD_MASK 0xffULL -#define BPF_F_USER_STACK (1ULL << 8) -/* flags used by BPF_FUNC_get_stackid only. */ -#define BPF_F_FAST_STACK_CMP (1ULL << 9) -#define BPF_F_REUSE_STACKID (1ULL << 10) -/* flags used by BPF_FUNC_get_stack only. */ -#define BPF_F_USER_BUILD_ID (1ULL << 11) - -/* BPF_FUNC_skb_set_tunnel_key flags. */ -#define BPF_F_ZERO_CSUM_TX (1ULL << 1) -#define BPF_F_DONT_FRAGMENT (1ULL << 2) -#define BPF_F_SEQ_NUMBER (1ULL << 3) - -/* BPF_FUNC_perf_event_output, BPF_FUNC_perf_event_read and - * BPF_FUNC_perf_event_read_value flags. - */ -#define BPF_F_INDEX_MASK 0xffffffffULL -#define BPF_F_CURRENT_CPU BPF_F_INDEX_MASK -/* BPF_FUNC_perf_event_output for sk_buff input context. */ -#define BPF_F_CTXLEN_MASK (0xfffffULL << 32) - -/* Current network namespace */ -#define BPF_F_CURRENT_NETNS (-1L) - -/* BPF_FUNC_skb_adjust_room flags. */ -#define BPF_F_ADJ_ROOM_FIXED_GSO (1ULL << 0) - -#define BPF_F_ADJ_ROOM_ENCAP_L3_IPV4 (1ULL << 1) -#define BPF_F_ADJ_ROOM_ENCAP_L3_IPV6 (1ULL << 2) -#define BPF_F_ADJ_ROOM_ENCAP_L4_GRE (1ULL << 3) -#define BPF_F_ADJ_ROOM_ENCAP_L4_UDP (1ULL << 4) - -/* Mode for BPF_FUNC_skb_adjust_room helper. */ -enum bpf_adj_room_mode { - BPF_ADJ_ROOM_NET, - BPF_ADJ_ROOM_MAC, -}; - -/* Mode for BPF_FUNC_skb_load_bytes_relative helper. */ -enum bpf_hdr_start_off { - BPF_HDR_START_MAC, - BPF_HDR_START_NET, -}; - -/* Encapsulation type for BPF_FUNC_lwt_push_encap helper. */ -enum bpf_lwt_encap_mode { - BPF_LWT_ENCAP_SEG6, - BPF_LWT_ENCAP_SEG6_INLINE, - BPF_LWT_ENCAP_IP, -}; - -#define __bpf_md_ptr(type, name) \ -union { \ - type name; \ - __u64 :64; \ -} __attribute__((aligned(8))) - -/* user accessible mirror of in-kernel sk_buff. - * new fields can only be added to the end of this structure - */ -struct __sk_buff { - __u32 len; - __u32 pkt_type; - __u32 mark; - __u32 queue_mapping; - __u32 protocol; - __u32 vlan_present; - __u32 vlan_tci; - __u32 vlan_proto; - __u32 priority; - __u32 ingress_ifindex; - __u32 ifindex; - __u32 tc_index; - __u32 cb[5]; - __u32 hash; - __u32 tc_classid; - __u32 data; - __u32 data_end; - __u32 napi_id; - - /* Accessed by BPF_PROG_TYPE_sk_skb types from here to ... */ - __u32 family; - __u32 remote_ip4; /* Stored in network byte order */ - __u32 local_ip4; /* Stored in network byte order */ - __u32 remote_ip6[4]; /* Stored in network byte order */ - __u32 local_ip6[4]; /* Stored in network byte order */ - __u32 remote_port; /* Stored in network byte order */ - __u32 local_port; /* stored in host byte order */ - /* ... here. */ - - __u32 data_meta; - __bpf_md_ptr(struct bpf_flow_keys *, flow_keys); - __u64 tstamp; - __u32 wire_len; - __u32 gso_segs; - __bpf_md_ptr(struct bpf_sock *, sk); -}; - -struct bpf_tunnel_key { - __u32 tunnel_id; - union { - __u32 remote_ipv4; - __u32 remote_ipv6[4]; - }; - __u8 tunnel_tos; - __u8 tunnel_ttl; - __u16 tunnel_ext; /* Padding, future use. */ - __u32 tunnel_label; -}; - -/* user accessible mirror of in-kernel xfrm_state. - * new fields can only be added to the end of this structure - */ -struct bpf_xfrm_state { - __u32 reqid; - __u32 spi; /* Stored in network byte order */ - __u16 family; - __u16 ext; /* Padding, future use. */ - union { - __u32 remote_ipv4; /* Stored in network byte order */ - __u32 remote_ipv6[4]; /* Stored in network byte order */ - }; -}; - -/* Generic BPF return codes which all BPF program types may support. - * The values are binary compatible with their TC_ACT_* counter-part to - * provide backwards compatibility with existing SCHED_CLS and SCHED_ACT - * programs. - * - * XDP is handled seprately, see XDP_*. - */ -enum bpf_ret_code { - BPF_OK = 0, - /* 1 reserved */ - BPF_DROP = 2, - /* 3-6 reserved */ - BPF_REDIRECT = 7, - /* >127 are reserved for prog type specific return codes. - * - * BPF_LWT_REROUTE: used by BPF_PROG_TYPE_LWT_IN and - * BPF_PROG_TYPE_LWT_XMIT to indicate that skb had been - * changed and should be routed based on its new L3 header. - * (This is an L3 redirect, as opposed to L2 redirect - * represented by BPF_REDIRECT above). - */ - BPF_LWT_REROUTE = 128, -}; - -struct bpf_sock { - __u32 bound_dev_if; - __u32 family; - __u32 type; - __u32 protocol; - __u32 mark; - __u32 priority; - /* IP address also allows 1 and 2 bytes access */ - __u32 src_ip4; - __u32 src_ip6[4]; - __u32 src_port; /* host byte order */ - __u32 dst_port; /* network byte order */ - __u32 dst_ip4; - __u32 dst_ip6[4]; - __u32 state; -}; - -struct bpf_tcp_sock { - __u32 snd_cwnd; /* Sending congestion window */ - __u32 srtt_us; /* smoothed round trip time << 3 in usecs */ - __u32 rtt_min; - __u32 snd_ssthresh; /* Slow start size threshold */ - __u32 rcv_nxt; /* What we want to receive next */ - __u32 snd_nxt; /* Next sequence we send */ - __u32 snd_una; /* First byte we want an ack for */ - __u32 mss_cache; /* Cached effective mss, not including SACKS */ - __u32 ecn_flags; /* ECN status bits. */ - __u32 rate_delivered; /* saved rate sample: packets delivered */ - __u32 rate_interval_us; /* saved rate sample: time elapsed */ - __u32 packets_out; /* Packets which are "in flight" */ - __u32 retrans_out; /* Retransmitted packets out */ - __u32 total_retrans; /* Total retransmits for entire connection */ - __u32 segs_in; /* RFC4898 tcpEStatsPerfSegsIn - * total number of segments in. - */ - __u32 data_segs_in; /* RFC4898 tcpEStatsPerfDataSegsIn - * total number of data segments in. - */ - __u32 segs_out; /* RFC4898 tcpEStatsPerfSegsOut - * The total number of segments sent. - */ - __u32 data_segs_out; /* RFC4898 tcpEStatsPerfDataSegsOut - * total number of data segments sent. - */ - __u32 lost_out; /* Lost packets */ - __u32 sacked_out; /* SACK'd packets */ - __u64 bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived - * sum(delta(rcv_nxt)), or how many bytes - * were acked. - */ - __u64 bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked - * sum(delta(snd_una)), or how many bytes - * were acked. - */ -}; - -struct bpf_sock_tuple { - union { - struct { - __be32 saddr; - __be32 daddr; - __be16 sport; - __be16 dport; - } ipv4; - struct { - __be32 saddr[4]; - __be32 daddr[4]; - __be16 sport; - __be16 dport; - } ipv6; - }; -}; - -#define XDP_PACKET_HEADROOM 256 - -/* User return codes for XDP prog type. - * A valid XDP program must return one of these defined values. All other - * return codes are reserved for future use. Unknown return codes will - * result in packet drops and a warning via bpf_warn_invalid_xdp_action(). - */ -enum xdp_action { - XDP_ABORTED = 0, - XDP_DROP, - XDP_PASS, - XDP_TX, - XDP_REDIRECT, -}; - -/* user accessible metadata for XDP packet hook - * new fields must be added to the end of this structure - */ -struct xdp_md { - __u32 data; - __u32 data_end; - __u32 data_meta; - /* Below access go through struct xdp_rxq_info */ - __u32 ingress_ifindex; /* rxq->dev->ifindex */ - __u32 rx_queue_index; /* rxq->queue_index */ -}; - -enum sk_action { - SK_DROP = 0, - SK_PASS, -}; - -/* user accessible metadata for SK_MSG packet hook, new fields must - * be added to the end of this structure - */ -struct sk_msg_md { - __bpf_md_ptr(void *, data); - __bpf_md_ptr(void *, data_end); - - __u32 family; - __u32 remote_ip4; /* Stored in network byte order */ - __u32 local_ip4; /* Stored in network byte order */ - __u32 remote_ip6[4]; /* Stored in network byte order */ - __u32 local_ip6[4]; /* Stored in network byte order */ - __u32 remote_port; /* Stored in network byte order */ - __u32 local_port; /* stored in host byte order */ - __u32 size; /* Total size of sk_msg */ -}; - -struct sk_reuseport_md { - /* - * Start of directly accessible data. It begins from - * the tcp/udp header. - */ - __bpf_md_ptr(void *, data); - /* End of directly accessible data */ - __bpf_md_ptr(void *, data_end); - /* - * Total length of packet (starting from the tcp/udp header). - * Note that the directly accessible bytes (data_end - data) - * could be less than this "len". Those bytes could be - * indirectly read by a helper "bpf_skb_load_bytes()". - */ - __u32 len; - /* - * Eth protocol in the mac header (network byte order). e.g. - * ETH_P_IP(0x0800) and ETH_P_IPV6(0x86DD) - */ - __u32 eth_protocol; - __u32 ip_protocol; /* IP protocol. e.g. IPPROTO_TCP, IPPROTO_UDP */ - __u32 bind_inany; /* Is sock bound to an INANY address? */ - __u32 hash; /* A hash of the packet 4 tuples */ -}; - -#define BPF_TAG_SIZE 8 - -struct bpf_prog_info { - __u32 type; - __u32 id; - __u8 tag[BPF_TAG_SIZE]; - __u32 jited_prog_len; - __u32 xlated_prog_len; - __aligned_u64 jited_prog_insns; - __aligned_u64 xlated_prog_insns; - __u64 load_time; /* ns since boottime */ - __u32 created_by_uid; - __u32 nr_map_ids; - __aligned_u64 map_ids; - char name[BPF_OBJ_NAME_LEN]; - __u32 ifindex; - __u32 gpl_compatible:1; - __u64 netns_dev; - __u64 netns_ino; - __u32 nr_jited_ksyms; - __u32 nr_jited_func_lens; - __aligned_u64 jited_ksyms; - __aligned_u64 jited_func_lens; - __u32 btf_id; - __u32 func_info_rec_size; - __aligned_u64 func_info; - __u32 nr_func_info; - __u32 nr_line_info; - __aligned_u64 line_info; - __aligned_u64 jited_line_info; - __u32 nr_jited_line_info; - __u32 line_info_rec_size; - __u32 jited_line_info_rec_size; - __u32 nr_prog_tags; - __aligned_u64 prog_tags; - __u64 run_time_ns; - __u64 run_cnt; -} __attribute__((aligned(8))); - -struct bpf_map_info { - __u32 type; - __u32 id; - __u32 key_size; - __u32 value_size; - __u32 max_entries; - __u32 map_flags; - char name[BPF_OBJ_NAME_LEN]; - __u32 ifindex; - __u32 :32; - __u64 netns_dev; - __u64 netns_ino; - __u32 btf_id; - __u32 btf_key_type_id; - __u32 btf_value_type_id; -} __attribute__((aligned(8))); - -struct bpf_btf_info { - __aligned_u64 btf; - __u32 btf_size; - __u32 id; -} __attribute__((aligned(8))); - -/* User bpf_sock_addr struct to access socket fields and sockaddr struct passed - * by user and intended to be used by socket (e.g. to bind to, depends on - * attach attach type). - */ -struct bpf_sock_addr { - __u32 user_family; /* Allows 4-byte read, but no write. */ - __u32 user_ip4; /* Allows 1,2,4-byte read and 4-byte write. - * Stored in network byte order. - */ - __u32 user_ip6[4]; /* Allows 1,2,4-byte read an 4-byte write. - * Stored in network byte order. - */ - __u32 user_port; /* Allows 4-byte read and write. - * Stored in network byte order - */ - __u32 family; /* Allows 4-byte read, but no write */ - __u32 type; /* Allows 4-byte read, but no write */ - __u32 protocol; /* Allows 4-byte read, but no write */ - __u32 msg_src_ip4; /* Allows 1,2,4-byte read an 4-byte write. - * Stored in network byte order. - */ - __u32 msg_src_ip6[4]; /* Allows 1,2,4-byte read an 4-byte write. - * Stored in network byte order. - */ -}; - -/* User bpf_sock_ops struct to access socket values and specify request ops - * and their replies. - * Some of this fields are in network (bigendian) byte order and may need - * to be converted before use (bpf_ntohl() defined in samples/bpf/bpf_endian.h). - * New fields can only be added at the end of this structure - */ -struct bpf_sock_ops { - __u32 op; - union { - __u32 args[4]; /* Optionally passed to bpf program */ - __u32 reply; /* Returned by bpf program */ - __u32 replylong[4]; /* Optionally returned by bpf prog */ - }; - __u32 family; - __u32 remote_ip4; /* Stored in network byte order */ - __u32 local_ip4; /* Stored in network byte order */ - __u32 remote_ip6[4]; /* Stored in network byte order */ - __u32 local_ip6[4]; /* Stored in network byte order */ - __u32 remote_port; /* Stored in network byte order */ - __u32 local_port; /* stored in host byte order */ - __u32 is_fullsock; /* Some TCP fields are only valid if - * there is a full socket. If not, the - * fields read as zero. - */ - __u32 snd_cwnd; - __u32 srtt_us; /* Averaged RTT << 3 in usecs */ - __u32 bpf_sock_ops_cb_flags; /* flags defined in uapi/linux/tcp.h */ - __u32 state; - __u32 rtt_min; - __u32 snd_ssthresh; - __u32 rcv_nxt; - __u32 snd_nxt; - __u32 snd_una; - __u32 mss_cache; - __u32 ecn_flags; - __u32 rate_delivered; - __u32 rate_interval_us; - __u32 packets_out; - __u32 retrans_out; - __u32 total_retrans; - __u32 segs_in; - __u32 data_segs_in; - __u32 segs_out; - __u32 data_segs_out; - __u32 lost_out; - __u32 sacked_out; - __u32 sk_txhash; - __u64 bytes_received; - __u64 bytes_acked; -}; - -/* Definitions for bpf_sock_ops_cb_flags */ -#define BPF_SOCK_OPS_RTO_CB_FLAG (1<<0) -#define BPF_SOCK_OPS_RETRANS_CB_FLAG (1<<1) -#define BPF_SOCK_OPS_STATE_CB_FLAG (1<<2) -#define BPF_SOCK_OPS_ALL_CB_FLAGS 0x7 /* Mask of all currently - * supported cb flags - */ - -/* List of known BPF sock_ops operators. - * New entries can only be added at the end - */ -enum { - BPF_SOCK_OPS_VOID, - BPF_SOCK_OPS_TIMEOUT_INIT, /* Should return SYN-RTO value to use or - * -1 if default value should be used - */ - BPF_SOCK_OPS_RWND_INIT, /* Should return initial advertized - * window (in packets) or -1 if default - * value should be used - */ - BPF_SOCK_OPS_TCP_CONNECT_CB, /* Calls BPF program right before an - * active connection is initialized - */ - BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB, /* Calls BPF program when an - * active connection is - * established - */ - BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB, /* Calls BPF program when a - * passive connection is - * established - */ - BPF_SOCK_OPS_NEEDS_ECN, /* If connection's congestion control - * needs ECN - */ - BPF_SOCK_OPS_BASE_RTT, /* Get base RTT. The correct value is - * based on the path and may be - * dependent on the congestion control - * algorithm. In general it indicates - * a congestion threshold. RTTs above - * this indicate congestion - */ - BPF_SOCK_OPS_RTO_CB, /* Called when an RTO has triggered. - * Arg1: value of icsk_retransmits - * Arg2: value of icsk_rto - * Arg3: whether RTO has expired - */ - BPF_SOCK_OPS_RETRANS_CB, /* Called when skb is retransmitted. - * Arg1: sequence number of 1st byte - * Arg2: # segments - * Arg3: return value of - * tcp_transmit_skb (0 => success) - */ - BPF_SOCK_OPS_STATE_CB, /* Called when TCP changes state. - * Arg1: old_state - * Arg2: new_state - */ - BPF_SOCK_OPS_TCP_LISTEN_CB, /* Called on listen(2), right after - * socket transition to LISTEN state. - */ -}; - -/* List of TCP states. There is a build check in net/ipv4/tcp.c to detect - * changes between the TCP and BPF versions. Ideally this should never happen. - * If it does, we need to add code to convert them before calling - * the BPF sock_ops function. - */ -enum { - BPF_TCP_ESTABLISHED = 1, - BPF_TCP_SYN_SENT, - BPF_TCP_SYN_RECV, - BPF_TCP_FIN_WAIT1, - BPF_TCP_FIN_WAIT2, - BPF_TCP_TIME_WAIT, - BPF_TCP_CLOSE, - BPF_TCP_CLOSE_WAIT, - BPF_TCP_LAST_ACK, - BPF_TCP_LISTEN, - BPF_TCP_CLOSING, /* Now a valid state */ - BPF_TCP_NEW_SYN_RECV, - - BPF_TCP_MAX_STATES /* Leave at the end! */ -}; - -#define TCP_BPF_IW 1001 /* Set TCP initial congestion window */ -#define TCP_BPF_SNDCWND_CLAMP 1002 /* Set sndcwnd_clamp */ - -struct bpf_perf_event_value { - __u64 counter; - __u64 enabled; - __u64 running; -}; - -#define BPF_DEVCG_ACC_MKNOD (1ULL << 0) -#define BPF_DEVCG_ACC_READ (1ULL << 1) -#define BPF_DEVCG_ACC_WRITE (1ULL << 2) - -#define BPF_DEVCG_DEV_BLOCK (1ULL << 0) -#define BPF_DEVCG_DEV_CHAR (1ULL << 1) - -struct bpf_cgroup_dev_ctx { - /* access_type encoded as (BPF_DEVCG_ACC_* << 16) | BPF_DEVCG_DEV_* */ - __u32 access_type; - __u32 major; - __u32 minor; -}; - -struct bpf_raw_tracepoint_args { - __u64 args[0]; -}; - -/* DIRECT: Skip the FIB rules and go to FIB table associated with device - * OUTPUT: Do lookup from egress perspective; default is ingress - */ -#define BPF_FIB_LOOKUP_DIRECT BIT(0) -#define BPF_FIB_LOOKUP_OUTPUT BIT(1) - -enum { - BPF_FIB_LKUP_RET_SUCCESS, /* lookup successful */ - BPF_FIB_LKUP_RET_BLACKHOLE, /* dest is blackholed; can be dropped */ - BPF_FIB_LKUP_RET_UNREACHABLE, /* dest is unreachable; can be dropped */ - BPF_FIB_LKUP_RET_PROHIBIT, /* dest not allowed; can be dropped */ - BPF_FIB_LKUP_RET_NOT_FWDED, /* packet is not forwarded */ - BPF_FIB_LKUP_RET_FWD_DISABLED, /* fwding is not enabled on ingress */ - BPF_FIB_LKUP_RET_UNSUPP_LWT, /* fwd requires encapsulation */ - BPF_FIB_LKUP_RET_NO_NEIGH, /* no neighbor entry for nh */ - BPF_FIB_LKUP_RET_FRAG_NEEDED, /* fragmentation required to fwd */ -}; - -struct bpf_fib_lookup { - /* input: network family for lookup (AF_INET, AF_INET6) - * output: network family of egress nexthop - */ - __u8 family; - - /* set if lookup is to consider L4 data - e.g., FIB rules */ - __u8 l4_protocol; - __be16 sport; - __be16 dport; - - /* total length of packet from network header - used for MTU check */ - __u16 tot_len; - - /* input: L3 device index for lookup - * output: device index from FIB lookup - */ - __u32 ifindex; - - union { - /* inputs to lookup */ - __u8 tos; /* AF_INET */ - __be32 flowinfo; /* AF_INET6, flow_label + priority */ - - /* output: metric of fib result (IPv4/IPv6 only) */ - __u32 rt_metric; - }; - - union { - __be32 ipv4_src; - __u32 ipv6_src[4]; /* in6_addr; network order */ - }; - - /* input to bpf_fib_lookup, ipv{4,6}_dst is destination address in - * network header. output: bpf_fib_lookup sets to gateway address - * if FIB lookup returns gateway route - */ - union { - __be32 ipv4_dst; - __u32 ipv6_dst[4]; /* in6_addr; network order */ - }; - - /* output */ - __be16 h_vlan_proto; - __be16 h_vlan_TCI; - __u8 smac[6]; /* ETH_ALEN */ - __u8 dmac[6]; /* ETH_ALEN */ -}; - -enum bpf_task_fd_type { - BPF_FD_TYPE_RAW_TRACEPOINT, /* tp name */ - BPF_FD_TYPE_TRACEPOINT, /* tp name */ - BPF_FD_TYPE_KPROBE, /* (symbol + offset) or addr */ - BPF_FD_TYPE_KRETPROBE, /* (symbol + offset) or addr */ - BPF_FD_TYPE_UPROBE, /* filename + offset */ - BPF_FD_TYPE_URETPROBE, /* filename + offset */ -}; - -struct bpf_flow_keys { - __u16 nhoff; - __u16 thoff; - __u16 addr_proto; /* ETH_P_* of valid addrs */ - __u8 is_frag; - __u8 is_first_frag; - __u8 is_encap; - __u8 ip_proto; - __be16 n_proto; - __be16 sport; - __be16 dport; - union { - struct { - __be32 ipv4_src; - __be32 ipv4_dst; - }; - struct { - __u32 ipv6_src[4]; /* in6_addr; network order */ - __u32 ipv6_dst[4]; /* in6_addr; network order */ - }; - }; -}; - -struct bpf_func_info { - __u32 insn_off; - __u32 type_id; -}; - -#define BPF_LINE_INFO_LINE_NUM(line_col) ((line_col) >> 10) -#define BPF_LINE_INFO_LINE_COL(line_col) ((line_col) & 0x3ff) - -struct bpf_line_info { - __u32 insn_off; - __u32 file_name_off; - __u32 line_off; - __u32 line_col; -}; - -struct bpf_spin_lock { - __u32 val; -}; -#endif /* _UAPI__LINUX_BPF_H__ */ diff --git a/src/xdp/include/linux/err.h b/src/xdp/include/linux/err.h deleted file mode 100644 index a3db4cf5e..000000000 --- a/src/xdp/include/linux/err.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ - -#ifndef __LINUX_ERR_H -#define __LINUX_ERR_H - -#include <linux/types.h> -#include <asm/errno.h> - -#define MAX_ERRNO 4095 - -#define IS_ERR_VALUE(x) ((x) >= (unsigned long)-MAX_ERRNO) - -static inline void * ERR_PTR(long error_) -{ - return (void *) error_; -} - -static inline long PTR_ERR(const void *ptr) -{ - return (long) ptr; -} - -static inline bool IS_ERR(const void *ptr) -{ - return IS_ERR_VALUE((unsigned long)ptr); -} - -static inline bool IS_ERR_OR_NULL(const void *ptr) -{ - return (!ptr) || IS_ERR_VALUE((unsigned long)ptr); -} - -#endif diff --git a/src/xdp/include/linux/if_link.h b/src/xdp/include/linux/if_link.h deleted file mode 100644 index 5b225ff63..000000000 --- a/src/xdp/include/linux/if_link.h +++ /dev/null @@ -1,1025 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#ifndef _UAPI_LINUX_IF_LINK_H -#define _UAPI_LINUX_IF_LINK_H - -#include <linux/types.h> -#include <linux/netlink.h> - -/* This struct should be in sync with struct rtnl_link_stats64 */ -struct rtnl_link_stats { - __u32 rx_packets; /* total packets received */ - __u32 tx_packets; /* total packets transmitted */ - __u32 rx_bytes; /* total bytes received */ - __u32 tx_bytes; /* total bytes transmitted */ - __u32 rx_errors; /* bad packets received */ - __u32 tx_errors; /* packet transmit problems */ - __u32 rx_dropped; /* no space in linux buffers */ - __u32 tx_dropped; /* no space available in linux */ - __u32 multicast; /* multicast packets received */ - __u32 collisions; - - /* detailed rx_errors: */ - __u32 rx_length_errors; - __u32 rx_over_errors; /* receiver ring buff overflow */ - __u32 rx_crc_errors; /* recved pkt with crc error */ - __u32 rx_frame_errors; /* recv'd frame alignment error */ - __u32 rx_fifo_errors; /* recv'r fifo overrun */ - __u32 rx_missed_errors; /* receiver missed packet */ - - /* detailed tx_errors */ - __u32 tx_aborted_errors; - __u32 tx_carrier_errors; - __u32 tx_fifo_errors; - __u32 tx_heartbeat_errors; - __u32 tx_window_errors; - - /* for cslip etc */ - __u32 rx_compressed; - __u32 tx_compressed; - - __u32 rx_nohandler; /* dropped, no handler found */ -}; - -/* The main device statistics structure */ -struct rtnl_link_stats64 { - __u64 rx_packets; /* total packets received */ - __u64 tx_packets; /* total packets transmitted */ - __u64 rx_bytes; /* total bytes received */ - __u64 tx_bytes; /* total bytes transmitted */ - __u64 rx_errors; /* bad packets received */ - __u64 tx_errors; /* packet transmit problems */ - __u64 rx_dropped; /* no space in linux buffers */ - __u64 tx_dropped; /* no space available in linux */ - __u64 multicast; /* multicast packets received */ - __u64 collisions; - - /* detailed rx_errors: */ - __u64 rx_length_errors; - __u64 rx_over_errors; /* receiver ring buff overflow */ - __u64 rx_crc_errors; /* recved pkt with crc error */ - __u64 rx_frame_errors; /* recv'd frame alignment error */ - __u64 rx_fifo_errors; /* recv'r fifo overrun */ - __u64 rx_missed_errors; /* receiver missed packet */ - - /* detailed tx_errors */ - __u64 tx_aborted_errors; - __u64 tx_carrier_errors; - __u64 tx_fifo_errors; - __u64 tx_heartbeat_errors; - __u64 tx_window_errors; - - /* for cslip etc */ - __u64 rx_compressed; - __u64 tx_compressed; - - __u64 rx_nohandler; /* dropped, no handler found */ -}; - -/* The struct should be in sync with struct ifmap */ -struct rtnl_link_ifmap { - __u64 mem_start; - __u64 mem_end; - __u64 base_addr; - __u16 irq; - __u8 dma; - __u8 port; -}; - -/* - * IFLA_AF_SPEC - * Contains nested attributes for address family specific attributes. - * Each address family may create a attribute with the address family - * number as type and create its own attribute structure in it. - * - * Example: - * [IFLA_AF_SPEC] = { - * [AF_INET] = { - * [IFLA_INET_CONF] = ..., - * }, - * [AF_INET6] = { - * [IFLA_INET6_FLAGS] = ..., - * [IFLA_INET6_CONF] = ..., - * } - * } - */ - -enum { - IFLA_UNSPEC, - IFLA_ADDRESS, - IFLA_BROADCAST, - IFLA_IFNAME, - IFLA_MTU, - IFLA_LINK, - IFLA_QDISC, - IFLA_STATS, - IFLA_COST, -#define IFLA_COST IFLA_COST - IFLA_PRIORITY, -#define IFLA_PRIORITY IFLA_PRIORITY - IFLA_MASTER, -#define IFLA_MASTER IFLA_MASTER - IFLA_WIRELESS, /* Wireless Extension event - see wireless.h */ -#define IFLA_WIRELESS IFLA_WIRELESS - IFLA_PROTINFO, /* Protocol specific information for a link */ -#define IFLA_PROTINFO IFLA_PROTINFO - IFLA_TXQLEN, -#define IFLA_TXQLEN IFLA_TXQLEN - IFLA_MAP, -#define IFLA_MAP IFLA_MAP - IFLA_WEIGHT, -#define IFLA_WEIGHT IFLA_WEIGHT - IFLA_OPERSTATE, - IFLA_LINKMODE, - IFLA_LINKINFO, -#define IFLA_LINKINFO IFLA_LINKINFO - IFLA_NET_NS_PID, - IFLA_IFALIAS, - IFLA_NUM_VF, /* Number of VFs if device is SR-IOV PF */ - IFLA_VFINFO_LIST, - IFLA_STATS64, - IFLA_VF_PORTS, - IFLA_PORT_SELF, - IFLA_AF_SPEC, - IFLA_GROUP, /* Group the device belongs to */ - IFLA_NET_NS_FD, - IFLA_EXT_MASK, /* Extended info mask, VFs, etc */ - IFLA_PROMISCUITY, /* Promiscuity count: > 0 means acts PROMISC */ -#define IFLA_PROMISCUITY IFLA_PROMISCUITY - IFLA_NUM_TX_QUEUES, - IFLA_NUM_RX_QUEUES, - IFLA_CARRIER, - IFLA_PHYS_PORT_ID, - IFLA_CARRIER_CHANGES, - IFLA_PHYS_SWITCH_ID, - IFLA_LINK_NETNSID, - IFLA_PHYS_PORT_NAME, - IFLA_PROTO_DOWN, - IFLA_GSO_MAX_SEGS, - IFLA_GSO_MAX_SIZE, - IFLA_PAD, - IFLA_XDP, - IFLA_EVENT, - IFLA_NEW_NETNSID, - IFLA_IF_NETNSID, - IFLA_TARGET_NETNSID = IFLA_IF_NETNSID, /* new alias */ - IFLA_CARRIER_UP_COUNT, - IFLA_CARRIER_DOWN_COUNT, - IFLA_NEW_IFINDEX, - IFLA_MIN_MTU, - IFLA_MAX_MTU, - __IFLA_MAX -}; - - -#define IFLA_MAX (__IFLA_MAX - 1) - -/* backwards compatibility for userspace */ -#ifndef __KERNEL__ -#define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) -#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) -#endif - -enum { - IFLA_INET_UNSPEC, - IFLA_INET_CONF, - __IFLA_INET_MAX, -}; - -#define IFLA_INET_MAX (__IFLA_INET_MAX - 1) - -/* ifi_flags. - - IFF_* flags. - - The only change is: - IFF_LOOPBACK, IFF_BROADCAST and IFF_POINTOPOINT are - more not changeable by user. They describe link media - characteristics and set by device driver. - - Comments: - - Combination IFF_BROADCAST|IFF_POINTOPOINT is invalid - - If neither of these three flags are set; - the interface is NBMA. - - - IFF_MULTICAST does not mean anything special: - multicasts can be used on all not-NBMA links. - IFF_MULTICAST means that this media uses special encapsulation - for multicast frames. Apparently, all IFF_POINTOPOINT and - IFF_BROADCAST devices are able to use multicasts too. - */ - -/* IFLA_LINK. - For usual devices it is equal ifi_index. - If it is a "virtual interface" (f.e. tunnel), ifi_link - can point to real physical interface (f.e. for bandwidth calculations), - or maybe 0, what means, that real media is unknown (usual - for IPIP tunnels, when route to endpoint is allowed to change) - */ - -/* Subtype attributes for IFLA_PROTINFO */ -enum { - IFLA_INET6_UNSPEC, - IFLA_INET6_FLAGS, /* link flags */ - IFLA_INET6_CONF, /* sysctl parameters */ - IFLA_INET6_STATS, /* statistics */ - IFLA_INET6_MCAST, /* MC things. What of them? */ - IFLA_INET6_CACHEINFO, /* time values and max reasm size */ - IFLA_INET6_ICMP6STATS, /* statistics (icmpv6) */ - IFLA_INET6_TOKEN, /* device token */ - IFLA_INET6_ADDR_GEN_MODE, /* implicit address generator mode */ - __IFLA_INET6_MAX -}; - -#define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) - -enum in6_addr_gen_mode { - IN6_ADDR_GEN_MODE_EUI64, - IN6_ADDR_GEN_MODE_NONE, - IN6_ADDR_GEN_MODE_STABLE_PRIVACY, - IN6_ADDR_GEN_MODE_RANDOM, -}; - -/* Bridge section */ - -enum { - IFLA_BR_UNSPEC, - IFLA_BR_FORWARD_DELAY, - IFLA_BR_HELLO_TIME, - IFLA_BR_MAX_AGE, - IFLA_BR_AGEING_TIME, - IFLA_BR_STP_STATE, - IFLA_BR_PRIORITY, - IFLA_BR_VLAN_FILTERING, - IFLA_BR_VLAN_PROTOCOL, - IFLA_BR_GROUP_FWD_MASK, - IFLA_BR_ROOT_ID, - IFLA_BR_BRIDGE_ID, - IFLA_BR_ROOT_PORT, - IFLA_BR_ROOT_PATH_COST, - IFLA_BR_TOPOLOGY_CHANGE, - IFLA_BR_TOPOLOGY_CHANGE_DETECTED, - IFLA_BR_HELLO_TIMER, - IFLA_BR_TCN_TIMER, - IFLA_BR_TOPOLOGY_CHANGE_TIMER, - IFLA_BR_GC_TIMER, - IFLA_BR_GROUP_ADDR, - IFLA_BR_FDB_FLUSH, - IFLA_BR_MCAST_ROUTER, - IFLA_BR_MCAST_SNOOPING, - IFLA_BR_MCAST_QUERY_USE_IFADDR, - IFLA_BR_MCAST_QUERIER, - IFLA_BR_MCAST_HASH_ELASTICITY, - IFLA_BR_MCAST_HASH_MAX, - IFLA_BR_MCAST_LAST_MEMBER_CNT, - IFLA_BR_MCAST_STARTUP_QUERY_CNT, - IFLA_BR_MCAST_LAST_MEMBER_INTVL, - IFLA_BR_MCAST_MEMBERSHIP_INTVL, - IFLA_BR_MCAST_QUERIER_INTVL, - IFLA_BR_MCAST_QUERY_INTVL, - IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, - IFLA_BR_MCAST_STARTUP_QUERY_INTVL, - IFLA_BR_NF_CALL_IPTABLES, - IFLA_BR_NF_CALL_IP6TABLES, - IFLA_BR_NF_CALL_ARPTABLES, - IFLA_BR_VLAN_DEFAULT_PVID, - IFLA_BR_PAD, - IFLA_BR_VLAN_STATS_ENABLED, - IFLA_BR_MCAST_STATS_ENABLED, - IFLA_BR_MCAST_IGMP_VERSION, - IFLA_BR_MCAST_MLD_VERSION, - IFLA_BR_VLAN_STATS_PER_PORT, - IFLA_BR_MULTI_BOOLOPT, - __IFLA_BR_MAX, -}; - -#define IFLA_BR_MAX (__IFLA_BR_MAX - 1) - -struct ifla_bridge_id { - __u8 prio[2]; - __u8 addr[6]; /* ETH_ALEN */ -}; - -enum { - BRIDGE_MODE_UNSPEC, - BRIDGE_MODE_HAIRPIN, -}; - -enum { - IFLA_BRPORT_UNSPEC, - IFLA_BRPORT_STATE, /* Spanning tree state */ - IFLA_BRPORT_PRIORITY, /* " priority */ - IFLA_BRPORT_COST, /* " cost */ - IFLA_BRPORT_MODE, /* mode (hairpin) */ - IFLA_BRPORT_GUARD, /* bpdu guard */ - IFLA_BRPORT_PROTECT, /* root port protection */ - IFLA_BRPORT_FAST_LEAVE, /* multicast fast leave */ - IFLA_BRPORT_LEARNING, /* mac learning */ - IFLA_BRPORT_UNICAST_FLOOD, /* flood unicast traffic */ - IFLA_BRPORT_PROXYARP, /* proxy ARP */ - IFLA_BRPORT_LEARNING_SYNC, /* mac learning sync from device */ - IFLA_BRPORT_PROXYARP_WIFI, /* proxy ARP for Wi-Fi */ - IFLA_BRPORT_ROOT_ID, /* designated root */ - IFLA_BRPORT_BRIDGE_ID, /* designated bridge */ - IFLA_BRPORT_DESIGNATED_PORT, - IFLA_BRPORT_DESIGNATED_COST, - IFLA_BRPORT_ID, - IFLA_BRPORT_NO, - IFLA_BRPORT_TOPOLOGY_CHANGE_ACK, - IFLA_BRPORT_CONFIG_PENDING, - IFLA_BRPORT_MESSAGE_AGE_TIMER, - IFLA_BRPORT_FORWARD_DELAY_TIMER, - IFLA_BRPORT_HOLD_TIMER, - IFLA_BRPORT_FLUSH, - IFLA_BRPORT_MULTICAST_ROUTER, - IFLA_BRPORT_PAD, - IFLA_BRPORT_MCAST_FLOOD, - IFLA_BRPORT_MCAST_TO_UCAST, - IFLA_BRPORT_VLAN_TUNNEL, - IFLA_BRPORT_BCAST_FLOOD, - IFLA_BRPORT_GROUP_FWD_MASK, - IFLA_BRPORT_NEIGH_SUPPRESS, - IFLA_BRPORT_ISOLATED, - IFLA_BRPORT_BACKUP_PORT, - __IFLA_BRPORT_MAX -}; -#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) - -struct ifla_cacheinfo { - __u32 max_reasm_len; - __u32 tstamp; /* ipv6InterfaceTable updated timestamp */ - __u32 reachable_time; - __u32 retrans_time; -}; - -enum { - IFLA_INFO_UNSPEC, - IFLA_INFO_KIND, - IFLA_INFO_DATA, - IFLA_INFO_XSTATS, - IFLA_INFO_SLAVE_KIND, - IFLA_INFO_SLAVE_DATA, - __IFLA_INFO_MAX, -}; - -#define IFLA_INFO_MAX (__IFLA_INFO_MAX - 1) - -/* VLAN section */ - -enum { - IFLA_VLAN_UNSPEC, - IFLA_VLAN_ID, - IFLA_VLAN_FLAGS, - IFLA_VLAN_EGRESS_QOS, - IFLA_VLAN_INGRESS_QOS, - IFLA_VLAN_PROTOCOL, - __IFLA_VLAN_MAX, -}; - -#define IFLA_VLAN_MAX (__IFLA_VLAN_MAX - 1) - -struct ifla_vlan_flags { - __u32 flags; - __u32 mask; -}; - -enum { - IFLA_VLAN_QOS_UNSPEC, - IFLA_VLAN_QOS_MAPPING, - __IFLA_VLAN_QOS_MAX -}; - -#define IFLA_VLAN_QOS_MAX (__IFLA_VLAN_QOS_MAX - 1) - -struct ifla_vlan_qos_mapping { - __u32 from; - __u32 to; -}; - -/* MACVLAN section */ -enum { - IFLA_MACVLAN_UNSPEC, - IFLA_MACVLAN_MODE, - IFLA_MACVLAN_FLAGS, - IFLA_MACVLAN_MACADDR_MODE, - IFLA_MACVLAN_MACADDR, - IFLA_MACVLAN_MACADDR_DATA, - IFLA_MACVLAN_MACADDR_COUNT, - __IFLA_MACVLAN_MAX, -}; - -#define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1) - -enum macvlan_mode { - MACVLAN_MODE_PRIVATE = 1, /* don't talk to other macvlans */ - MACVLAN_MODE_VEPA = 2, /* talk to other ports through ext bridge */ - MACVLAN_MODE_BRIDGE = 4, /* talk to bridge ports directly */ - MACVLAN_MODE_PASSTHRU = 8,/* take over the underlying device */ - MACVLAN_MODE_SOURCE = 16,/* use source MAC address list to assign */ -}; - -enum macvlan_macaddr_mode { - MACVLAN_MACADDR_ADD, - MACVLAN_MACADDR_DEL, - MACVLAN_MACADDR_FLUSH, - MACVLAN_MACADDR_SET, -}; - -#define MACVLAN_FLAG_NOPROMISC 1 - -/* VRF section */ -enum { - IFLA_VRF_UNSPEC, - IFLA_VRF_TABLE, - __IFLA_VRF_MAX -}; - -#define IFLA_VRF_MAX (__IFLA_VRF_MAX - 1) - -enum { - IFLA_VRF_PORT_UNSPEC, - IFLA_VRF_PORT_TABLE, - __IFLA_VRF_PORT_MAX -}; - -#define IFLA_VRF_PORT_MAX (__IFLA_VRF_PORT_MAX - 1) - -/* MACSEC section */ -enum { - IFLA_MACSEC_UNSPEC, - IFLA_MACSEC_SCI, - IFLA_MACSEC_PORT, - IFLA_MACSEC_ICV_LEN, - IFLA_MACSEC_CIPHER_SUITE, - IFLA_MACSEC_WINDOW, - IFLA_MACSEC_ENCODING_SA, - IFLA_MACSEC_ENCRYPT, - IFLA_MACSEC_PROTECT, - IFLA_MACSEC_INC_SCI, - IFLA_MACSEC_ES, - IFLA_MACSEC_SCB, - IFLA_MACSEC_REPLAY_PROTECT, - IFLA_MACSEC_VALIDATION, - IFLA_MACSEC_PAD, - __IFLA_MACSEC_MAX, -}; - -#define IFLA_MACSEC_MAX (__IFLA_MACSEC_MAX - 1) - -/* XFRM section */ -enum { - IFLA_XFRM_UNSPEC, - IFLA_XFRM_LINK, - IFLA_XFRM_IF_ID, - __IFLA_XFRM_MAX -}; - -#define IFLA_XFRM_MAX (__IFLA_XFRM_MAX - 1) - -enum macsec_validation_type { - MACSEC_VALIDATE_DISABLED = 0, - MACSEC_VALIDATE_CHECK = 1, - MACSEC_VALIDATE_STRICT = 2, - __MACSEC_VALIDATE_END, - MACSEC_VALIDATE_MAX = __MACSEC_VALIDATE_END - 1, -}; - -/* IPVLAN section */ -enum { - IFLA_IPVLAN_UNSPEC, - IFLA_IPVLAN_MODE, - IFLA_IPVLAN_FLAGS, - __IFLA_IPVLAN_MAX -}; - -#define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1) - -enum ipvlan_mode { - IPVLAN_MODE_L2 = 0, - IPVLAN_MODE_L3, - IPVLAN_MODE_L3S, - IPVLAN_MODE_MAX -}; - -#define IPVLAN_F_PRIVATE 0x01 -#define IPVLAN_F_VEPA 0x02 - -/* VXLAN section */ -enum { - IFLA_VXLAN_UNSPEC, - IFLA_VXLAN_ID, - IFLA_VXLAN_GROUP, /* group or remote address */ - IFLA_VXLAN_LINK, - IFLA_VXLAN_LOCAL, - IFLA_VXLAN_TTL, - IFLA_VXLAN_TOS, - IFLA_VXLAN_LEARNING, - IFLA_VXLAN_AGEING, - IFLA_VXLAN_LIMIT, - IFLA_VXLAN_PORT_RANGE, /* source port */ - IFLA_VXLAN_PROXY, - IFLA_VXLAN_RSC, - IFLA_VXLAN_L2MISS, - IFLA_VXLAN_L3MISS, - IFLA_VXLAN_PORT, /* destination port */ - IFLA_VXLAN_GROUP6, - IFLA_VXLAN_LOCAL6, - IFLA_VXLAN_UDP_CSUM, - IFLA_VXLAN_UDP_ZERO_CSUM6_TX, - IFLA_VXLAN_UDP_ZERO_CSUM6_RX, - IFLA_VXLAN_REMCSUM_TX, - IFLA_VXLAN_REMCSUM_RX, - IFLA_VXLAN_GBP, - IFLA_VXLAN_REMCSUM_NOPARTIAL, - IFLA_VXLAN_COLLECT_METADATA, - IFLA_VXLAN_LABEL, - IFLA_VXLAN_GPE, - IFLA_VXLAN_TTL_INHERIT, - IFLA_VXLAN_DF, - __IFLA_VXLAN_MAX -}; -#define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) - -struct ifla_vxlan_port_range { - __be16 low; - __be16 high; -}; - -enum ifla_vxlan_df { - VXLAN_DF_UNSET = 0, - VXLAN_DF_SET, - VXLAN_DF_INHERIT, - __VXLAN_DF_END, - VXLAN_DF_MAX = __VXLAN_DF_END - 1, -}; - -/* GENEVE section */ -enum { - IFLA_GENEVE_UNSPEC, - IFLA_GENEVE_ID, - IFLA_GENEVE_REMOTE, - IFLA_GENEVE_TTL, - IFLA_GENEVE_TOS, - IFLA_GENEVE_PORT, /* destination port */ - IFLA_GENEVE_COLLECT_METADATA, - IFLA_GENEVE_REMOTE6, - IFLA_GENEVE_UDP_CSUM, - IFLA_GENEVE_UDP_ZERO_CSUM6_TX, - IFLA_GENEVE_UDP_ZERO_CSUM6_RX, - IFLA_GENEVE_LABEL, - IFLA_GENEVE_TTL_INHERIT, - IFLA_GENEVE_DF, - __IFLA_GENEVE_MAX -}; -#define IFLA_GENEVE_MAX (__IFLA_GENEVE_MAX - 1) - -enum ifla_geneve_df { - GENEVE_DF_UNSET = 0, - GENEVE_DF_SET, - GENEVE_DF_INHERIT, - __GENEVE_DF_END, - GENEVE_DF_MAX = __GENEVE_DF_END - 1, -}; - -/* PPP section */ -enum { - IFLA_PPP_UNSPEC, - IFLA_PPP_DEV_FD, - __IFLA_PPP_MAX -}; -#define IFLA_PPP_MAX (__IFLA_PPP_MAX - 1) - -/* GTP section */ - -enum ifla_gtp_role { - GTP_ROLE_GGSN = 0, - GTP_ROLE_SGSN, -}; - -enum { - IFLA_GTP_UNSPEC, - IFLA_GTP_FD0, - IFLA_GTP_FD1, - IFLA_GTP_PDP_HASHSIZE, - IFLA_GTP_ROLE, - __IFLA_GTP_MAX, -}; -#define IFLA_GTP_MAX (__IFLA_GTP_MAX - 1) - -/* Bonding section */ - -enum { - IFLA_BOND_UNSPEC, - IFLA_BOND_MODE, - IFLA_BOND_ACTIVE_SLAVE, - IFLA_BOND_MIIMON, - IFLA_BOND_UPDELAY, - IFLA_BOND_DOWNDELAY, - IFLA_BOND_USE_CARRIER, - IFLA_BOND_ARP_INTERVAL, - IFLA_BOND_ARP_IP_TARGET, - IFLA_BOND_ARP_VALIDATE, - IFLA_BOND_ARP_ALL_TARGETS, - IFLA_BOND_PRIMARY, - IFLA_BOND_PRIMARY_RESELECT, - IFLA_BOND_FAIL_OVER_MAC, - IFLA_BOND_XMIT_HASH_POLICY, - IFLA_BOND_RESEND_IGMP, - IFLA_BOND_NUM_PEER_NOTIF, - IFLA_BOND_ALL_SLAVES_ACTIVE, - IFLA_BOND_MIN_LINKS, - IFLA_BOND_LP_INTERVAL, - IFLA_BOND_PACKETS_PER_SLAVE, - IFLA_BOND_AD_LACP_RATE, - IFLA_BOND_AD_SELECT, - IFLA_BOND_AD_INFO, - IFLA_BOND_AD_ACTOR_SYS_PRIO, - IFLA_BOND_AD_USER_PORT_KEY, - IFLA_BOND_AD_ACTOR_SYSTEM, - IFLA_BOND_TLB_DYNAMIC_LB, - __IFLA_BOND_MAX, -}; - -#define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1) - -enum { - IFLA_BOND_AD_INFO_UNSPEC, - IFLA_BOND_AD_INFO_AGGREGATOR, - IFLA_BOND_AD_INFO_NUM_PORTS, - IFLA_BOND_AD_INFO_ACTOR_KEY, - IFLA_BOND_AD_INFO_PARTNER_KEY, - IFLA_BOND_AD_INFO_PARTNER_MAC, - __IFLA_BOND_AD_INFO_MAX, -}; - -#define IFLA_BOND_AD_INFO_MAX (__IFLA_BOND_AD_INFO_MAX - 1) - -enum { - IFLA_BOND_SLAVE_UNSPEC, - IFLA_BOND_SLAVE_STATE, - IFLA_BOND_SLAVE_MII_STATUS, - IFLA_BOND_SLAVE_LINK_FAILURE_COUNT, - IFLA_BOND_SLAVE_PERM_HWADDR, - IFLA_BOND_SLAVE_QUEUE_ID, - IFLA_BOND_SLAVE_AD_AGGREGATOR_ID, - IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE, - IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE, - __IFLA_BOND_SLAVE_MAX, -}; - -#define IFLA_BOND_SLAVE_MAX (__IFLA_BOND_SLAVE_MAX - 1) - -/* SR-IOV virtual function management section */ - -enum { - IFLA_VF_INFO_UNSPEC, - IFLA_VF_INFO, - __IFLA_VF_INFO_MAX, -}; - -#define IFLA_VF_INFO_MAX (__IFLA_VF_INFO_MAX - 1) - -enum { - IFLA_VF_UNSPEC, - IFLA_VF_MAC, /* Hardware queue specific attributes */ - IFLA_VF_VLAN, /* VLAN ID and QoS */ - IFLA_VF_TX_RATE, /* Max TX Bandwidth Allocation */ - IFLA_VF_SPOOFCHK, /* Spoof Checking on/off switch */ - IFLA_VF_LINK_STATE, /* link state enable/disable/auto switch */ - IFLA_VF_RATE, /* Min and Max TX Bandwidth Allocation */ - IFLA_VF_RSS_QUERY_EN, /* RSS Redirection Table and Hash Key query - * on/off switch - */ - IFLA_VF_STATS, /* network device statistics */ - IFLA_VF_TRUST, /* Trust VF */ - IFLA_VF_IB_NODE_GUID, /* VF Infiniband node GUID */ - IFLA_VF_IB_PORT_GUID, /* VF Infiniband port GUID */ - IFLA_VF_VLAN_LIST, /* nested list of vlans, option for QinQ */ - __IFLA_VF_MAX, -}; - -#define IFLA_VF_MAX (__IFLA_VF_MAX - 1) - -struct ifla_vf_mac { - __u32 vf; - __u8 mac[32]; /* MAX_ADDR_LEN */ -}; - -struct ifla_vf_vlan { - __u32 vf; - __u32 vlan; /* 0 - 4095, 0 disables VLAN filter */ - __u32 qos; -}; - -enum { - IFLA_VF_VLAN_INFO_UNSPEC, - IFLA_VF_VLAN_INFO, /* VLAN ID, QoS and VLAN protocol */ - __IFLA_VF_VLAN_INFO_MAX, -}; - -#define IFLA_VF_VLAN_INFO_MAX (__IFLA_VF_VLAN_INFO_MAX - 1) -#define MAX_VLAN_LIST_LEN 1 - -struct ifla_vf_vlan_info { - __u32 vf; - __u32 vlan; /* 0 - 4095, 0 disables VLAN filter */ - __u32 qos; - __be16 vlan_proto; /* VLAN protocol either 802.1Q or 802.1ad */ -}; - -struct ifla_vf_tx_rate { - __u32 vf; - __u32 rate; /* Max TX bandwidth in Mbps, 0 disables throttling */ -}; - -struct ifla_vf_rate { - __u32 vf; - __u32 min_tx_rate; /* Min Bandwidth in Mbps */ - __u32 max_tx_rate; /* Max Bandwidth in Mbps */ -}; - -struct ifla_vf_spoofchk { - __u32 vf; - __u32 setting; -}; - -struct ifla_vf_guid { - __u32 vf; - __u64 guid; -}; - -enum { - IFLA_VF_LINK_STATE_AUTO, /* link state of the uplink */ - IFLA_VF_LINK_STATE_ENABLE, /* link always up */ - IFLA_VF_LINK_STATE_DISABLE, /* link always down */ - __IFLA_VF_LINK_STATE_MAX, -}; - -struct ifla_vf_link_state { - __u32 vf; - __u32 link_state; -}; - -struct ifla_vf_rss_query_en { - __u32 vf; - __u32 setting; -}; - -enum { - IFLA_VF_STATS_RX_PACKETS, - IFLA_VF_STATS_TX_PACKETS, - IFLA_VF_STATS_RX_BYTES, - IFLA_VF_STATS_TX_BYTES, - IFLA_VF_STATS_BROADCAST, - IFLA_VF_STATS_MULTICAST, - IFLA_VF_STATS_PAD, - IFLA_VF_STATS_RX_DROPPED, - IFLA_VF_STATS_TX_DROPPED, - __IFLA_VF_STATS_MAX, -}; - -#define IFLA_VF_STATS_MAX (__IFLA_VF_STATS_MAX - 1) - -struct ifla_vf_trust { - __u32 vf; - __u32 setting; -}; - -/* VF ports management section - * - * Nested layout of set/get msg is: - * - * [IFLA_NUM_VF] - * [IFLA_VF_PORTS] - * [IFLA_VF_PORT] - * [IFLA_PORT_*], ... - * [IFLA_VF_PORT] - * [IFLA_PORT_*], ... - * ... - * [IFLA_PORT_SELF] - * [IFLA_PORT_*], ... - */ - -enum { - IFLA_VF_PORT_UNSPEC, - IFLA_VF_PORT, /* nest */ - __IFLA_VF_PORT_MAX, -}; - -#define IFLA_VF_PORT_MAX (__IFLA_VF_PORT_MAX - 1) - -enum { - IFLA_PORT_UNSPEC, - IFLA_PORT_VF, /* __u32 */ - IFLA_PORT_PROFILE, /* string */ - IFLA_PORT_VSI_TYPE, /* 802.1Qbg (pre-)standard VDP */ - IFLA_PORT_INSTANCE_UUID, /* binary UUID */ - IFLA_PORT_HOST_UUID, /* binary UUID */ - IFLA_PORT_REQUEST, /* __u8 */ - IFLA_PORT_RESPONSE, /* __u16, output only */ - __IFLA_PORT_MAX, -}; - -#define IFLA_PORT_MAX (__IFLA_PORT_MAX - 1) - -#define PORT_PROFILE_MAX 40 -#define PORT_UUID_MAX 16 -#define PORT_SELF_VF -1 - -enum { - PORT_REQUEST_PREASSOCIATE = 0, - PORT_REQUEST_PREASSOCIATE_RR, - PORT_REQUEST_ASSOCIATE, - PORT_REQUEST_DISASSOCIATE, -}; - -enum { - PORT_VDP_RESPONSE_SUCCESS = 0, - PORT_VDP_RESPONSE_INVALID_FORMAT, - PORT_VDP_RESPONSE_INSUFFICIENT_RESOURCES, - PORT_VDP_RESPONSE_UNUSED_VTID, - PORT_VDP_RESPONSE_VTID_VIOLATION, - PORT_VDP_RESPONSE_VTID_VERSION_VIOALTION, - PORT_VDP_RESPONSE_OUT_OF_SYNC, - /* 0x08-0xFF reserved for future VDP use */ - PORT_PROFILE_RESPONSE_SUCCESS = 0x100, - PORT_PROFILE_RESPONSE_INPROGRESS, - PORT_PROFILE_RESPONSE_INVALID, - PORT_PROFILE_RESPONSE_BADSTATE, - PORT_PROFILE_RESPONSE_INSUFFICIENT_RESOURCES, - PORT_PROFILE_RESPONSE_ERROR, -}; - -struct ifla_port_vsi { - __u8 vsi_mgr_id; - __u8 vsi_type_id[3]; - __u8 vsi_type_version; - __u8 pad[3]; -}; - - -/* IPoIB section */ - -enum { - IFLA_IPOIB_UNSPEC, - IFLA_IPOIB_PKEY, - IFLA_IPOIB_MODE, - IFLA_IPOIB_UMCAST, - __IFLA_IPOIB_MAX -}; - -enum { - IPOIB_MODE_DATAGRAM = 0, /* using unreliable datagram QPs */ - IPOIB_MODE_CONNECTED = 1, /* using connected QPs */ -}; - -#define IFLA_IPOIB_MAX (__IFLA_IPOIB_MAX - 1) - - -/* HSR section */ - -enum { - IFLA_HSR_UNSPEC, - IFLA_HSR_SLAVE1, - IFLA_HSR_SLAVE2, - IFLA_HSR_MULTICAST_SPEC, /* Last byte of supervision addr */ - IFLA_HSR_SUPERVISION_ADDR, /* Supervision frame multicast addr */ - IFLA_HSR_SEQ_NR, - IFLA_HSR_VERSION, /* HSR version */ - __IFLA_HSR_MAX, -}; - -#define IFLA_HSR_MAX (__IFLA_HSR_MAX - 1) - -/* STATS section */ - -struct if_stats_msg { - __u8 family; - __u8 pad1; - __u16 pad2; - __u32 ifindex; - __u32 filter_mask; -}; - -/* A stats attribute can be netdev specific or a global stat. - * For netdev stats, lets use the prefix IFLA_STATS_LINK_* - */ -enum { - IFLA_STATS_UNSPEC, /* also used as 64bit pad attribute */ - IFLA_STATS_LINK_64, - IFLA_STATS_LINK_XSTATS, - IFLA_STATS_LINK_XSTATS_SLAVE, - IFLA_STATS_LINK_OFFLOAD_XSTATS, - IFLA_STATS_AF_SPEC, - __IFLA_STATS_MAX, -}; - -#define IFLA_STATS_MAX (__IFLA_STATS_MAX - 1) - -#define IFLA_STATS_FILTER_BIT(ATTR) (1 << (ATTR - 1)) - -/* These are embedded into IFLA_STATS_LINK_XSTATS: - * [IFLA_STATS_LINK_XSTATS] - * -> [LINK_XSTATS_TYPE_xxx] - * -> [rtnl link type specific attributes] - */ -enum { - LINK_XSTATS_TYPE_UNSPEC, - LINK_XSTATS_TYPE_BRIDGE, - LINK_XSTATS_TYPE_BOND, - __LINK_XSTATS_TYPE_MAX -}; -#define LINK_XSTATS_TYPE_MAX (__LINK_XSTATS_TYPE_MAX - 1) - -/* These are stats embedded into IFLA_STATS_LINK_OFFLOAD_XSTATS */ -enum { - IFLA_OFFLOAD_XSTATS_UNSPEC, - IFLA_OFFLOAD_XSTATS_CPU_HIT, /* struct rtnl_link_stats64 */ - __IFLA_OFFLOAD_XSTATS_MAX -}; -#define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1) - -/* XDP section */ - -#define XDP_FLAGS_UPDATE_IF_NOEXIST (1U << 0) -#define XDP_FLAGS_SKB_MODE (1U << 1) -#define XDP_FLAGS_DRV_MODE (1U << 2) -#define XDP_FLAGS_HW_MODE (1U << 3) -#define XDP_FLAGS_MODES (XDP_FLAGS_SKB_MODE | \ - XDP_FLAGS_DRV_MODE | \ - XDP_FLAGS_HW_MODE) -#define XDP_FLAGS_MASK (XDP_FLAGS_UPDATE_IF_NOEXIST | \ - XDP_FLAGS_MODES) - -/* These are stored into IFLA_XDP_ATTACHED on dump. */ -enum { - XDP_ATTACHED_NONE = 0, - XDP_ATTACHED_DRV, - XDP_ATTACHED_SKB, - XDP_ATTACHED_HW, - XDP_ATTACHED_MULTI, -}; - -enum { - IFLA_XDP_UNSPEC, - IFLA_XDP_FD, - IFLA_XDP_ATTACHED, - IFLA_XDP_FLAGS, - IFLA_XDP_PROG_ID, - IFLA_XDP_DRV_PROG_ID, - IFLA_XDP_SKB_PROG_ID, - IFLA_XDP_HW_PROG_ID, - __IFLA_XDP_MAX, -}; - -#define IFLA_XDP_MAX (__IFLA_XDP_MAX - 1) - -enum { - IFLA_EVENT_NONE, - IFLA_EVENT_REBOOT, /* internal reset / reboot */ - IFLA_EVENT_FEATURES, /* change in offload features */ - IFLA_EVENT_BONDING_FAILOVER, /* change in active slave */ - IFLA_EVENT_NOTIFY_PEERS, /* re-sent grat. arp/ndisc */ - IFLA_EVENT_IGMP_RESEND, /* re-sent IGMP JOIN */ - IFLA_EVENT_BONDING_OPTIONS, /* change in bonding options */ -}; - -/* tun section */ - -enum { - IFLA_TUN_UNSPEC, - IFLA_TUN_OWNER, - IFLA_TUN_GROUP, - IFLA_TUN_TYPE, - IFLA_TUN_PI, - IFLA_TUN_VNET_HDR, - IFLA_TUN_PERSIST, - IFLA_TUN_MULTI_QUEUE, - IFLA_TUN_NUM_QUEUES, - IFLA_TUN_NUM_DISABLED_QUEUES, - __IFLA_TUN_MAX, -}; - -#define IFLA_TUN_MAX (__IFLA_TUN_MAX - 1) - -/* rmnet section */ - -#define RMNET_FLAGS_INGRESS_DEAGGREGATION (1U << 0) -#define RMNET_FLAGS_INGRESS_MAP_COMMANDS (1U << 1) -#define RMNET_FLAGS_INGRESS_MAP_CKSUMV4 (1U << 2) -#define RMNET_FLAGS_EGRESS_MAP_CKSUMV4 (1U << 3) - -enum { - IFLA_RMNET_UNSPEC, - IFLA_RMNET_MUX_ID, - IFLA_RMNET_FLAGS, - __IFLA_RMNET_MAX, -}; - -#define IFLA_RMNET_MAX (__IFLA_RMNET_MAX - 1) - -struct ifla_rmnet_flags { - __u32 flags; - __u32 mask; -}; - -#endif /* _UAPI_LINUX_IF_LINK_H */ diff --git a/src/xdp/include/linux/if_xdp.h b/src/xdp/include/linux/if_xdp.h deleted file mode 100644 index be328c593..000000000 --- a/src/xdp/include/linux/if_xdp.h +++ /dev/null @@ -1,108 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * if_xdp: XDP socket user-space interface - * Copyright(c) 2018 Intel Corporation. - * - * Author(s): Björn Töpel <bjorn.topel@intel.com> - * Magnus Karlsson <magnus.karlsson@intel.com> - */ - -#ifndef _LINUX_IF_XDP_H -#define _LINUX_IF_XDP_H - -#include <linux/types.h> - -/* Options for the sxdp_flags field */ -#define XDP_SHARED_UMEM (1 << 0) -#define XDP_COPY (1 << 1) /* Force copy-mode */ -#define XDP_ZEROCOPY (1 << 2) /* Force zero-copy mode */ -/* If this option is set, the driver might go sleep and in that case - * the XDP_RING_NEED_WAKEUP flag in the fill and/or Tx rings will be - * set. If it is set, the application need to explicitly wake up the - * driver with a poll() (Rx and Tx) or sendto() (Tx only). If you are - * running the driver and the application on the same core, you should - * use this option so that the kernel will yield to the user space - * application. - */ -#define XDP_USE_NEED_WAKEUP (1 << 3) - -/* Flags for xsk_umem_config flags */ -#define XDP_UMEM_UNALIGNED_CHUNK_FLAG (1 << 0) - -struct sockaddr_xdp { - __u16 sxdp_family; - __u16 sxdp_flags; - __u32 sxdp_ifindex; - __u32 sxdp_queue_id; - __u32 sxdp_shared_umem_fd; -}; - -/* XDP_RING flags */ -#define XDP_RING_NEED_WAKEUP (1 << 0) - -struct xdp_ring_offset { - __u64 producer; - __u64 consumer; - __u64 desc; - __u64 flags; -}; - -struct xdp_mmap_offsets { - struct xdp_ring_offset rx; - struct xdp_ring_offset tx; - struct xdp_ring_offset fr; /* Fill */ - struct xdp_ring_offset cr; /* Completion */ -}; - -/* XDP socket options */ -#define XDP_MMAP_OFFSETS 1 -#define XDP_RX_RING 2 -#define XDP_TX_RING 3 -#define XDP_UMEM_REG 4 -#define XDP_UMEM_FILL_RING 5 -#define XDP_UMEM_COMPLETION_RING 6 -#define XDP_STATISTICS 7 -#define XDP_OPTIONS 8 - -struct xdp_umem_reg { - __u64 addr; /* Start of packet data area */ - __u64 len; /* Length of packet data area */ - __u32 chunk_size; - __u32 headroom; - __u32 flags; -}; - -struct xdp_statistics { - __u64 rx_dropped; /* Dropped for reasons other than invalid desc */ - __u64 rx_invalid_descs; /* Dropped due to invalid descriptor */ - __u64 tx_invalid_descs; /* Dropped due to invalid descriptor */ -}; - -struct xdp_options { - __u32 flags; -}; - -/* Flags for the flags field of struct xdp_options */ -#define XDP_OPTIONS_ZEROCOPY (1 << 0) - -/* Pgoff for mmaping the rings */ -#define XDP_PGOFF_RX_RING 0 -#define XDP_PGOFF_TX_RING 0x80000000 -#define XDP_UMEM_PGOFF_FILL_RING 0x100000000ULL -#define XDP_UMEM_PGOFF_COMPLETION_RING 0x180000000ULL - -/* Masks for unaligned chunks mode */ -#define XSK_UNALIGNED_BUF_OFFSET_SHIFT 48 -#define XSK_UNALIGNED_BUF_ADDR_MASK \ - ((1ULL << XSK_UNALIGNED_BUF_OFFSET_SHIFT) - 1) - -/* Rx/Tx descriptor */ -struct xdp_desc { - __u64 addr; - __u32 len; - __u32 options; -}; - -/* UMEM descriptor is __u64 */ - -#endif /* _LINUX_IF_XDP_H */ diff --git a/src/xdp/include/perf-sys.h b/src/xdp/include/perf-sys.h deleted file mode 100644 index 2fd16e482..000000000 --- a/src/xdp/include/perf-sys.h +++ /dev/null @@ -1,77 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* Copied from $(LINUX)/tools/perf/perf-sys.h (kernel 4.18) */ -#ifndef _PERF_SYS_H -#define _PERF_SYS_H - -#include <unistd.h> -#include <sys/types.h> -#include <sys/syscall.h> -#include <linux/types.h> -#include <linux/perf_event.h> -/* - * remove the following headers to allow for userspace program compilation - * #include <linux/compiler.h> - * #include <asm/barrier.h> - */ -#ifdef __powerpc__ -#define CPUINFO_PROC {"cpu"} -#endif - -#ifdef __s390__ -#define CPUINFO_PROC {"vendor_id"} -#endif - -#ifdef __sh__ -#define CPUINFO_PROC {"cpu type"} -#endif - -#ifdef __hppa__ -#define CPUINFO_PROC {"cpu"} -#endif - -#ifdef __sparc__ -#define CPUINFO_PROC {"cpu"} -#endif - -#ifdef __alpha__ -#define CPUINFO_PROC {"cpu model"} -#endif - -#ifdef __arm__ -#define CPUINFO_PROC {"model name", "Processor"} -#endif - -#ifdef __mips__ -#define CPUINFO_PROC {"cpu model"} -#endif - -#ifdef __arc__ -#define CPUINFO_PROC {"Processor"} -#endif - -#ifdef __xtensa__ -#define CPUINFO_PROC {"core ID"} -#endif - -#ifndef CPUINFO_PROC -#define CPUINFO_PROC { "model name", } -#endif - -static inline int -sys_perf_event_open(struct perf_event_attr *attr, - pid_t pid, int cpu, int group_fd, - unsigned long flags) -{ - int fd; - - fd = syscall(__NR_perf_event_open, attr, pid, cpu, - group_fd, flags); - -#ifdef HAVE_ATTR_TEST - if (unlikely(test_attr__enabled)) - test_attr__open(attr, pid, cpu, fd, group_fd, flags); -#endif - return fd; -} - -#endif /* _PERF_SYS_H */ diff --git a/src/xdp/utils/Makefile b/src/xdp/utils/Makefile deleted file mode 100644 index 42d6e63e3..000000000 --- a/src/xdp/utils/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) - -USER_TARGETS := xdp_stats xdp_loader - -LIBBPF_DIR = ../libbpf/src/ -COMMON_DIR = ../common/ - -# Extend with another COMMON_OBJS -COMMON_OBJS += $(COMMON_DIR)/common_libbpf.o - -include $(COMMON_DIR)/common.mk diff --git a/src/xdp/utils/xdp_loader.c b/src/xdp/utils/xdp_loader.c deleted file mode 100644 index d3b05d0c0..000000000 --- a/src/xdp/utils/xdp_loader.c +++ /dev/null @@ -1,165 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -static const char *__doc__ = "XDP loader\n" - " - Allows selecting BPF section --progsec name to XDP-attach to --dev\n"; - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <getopt.h> - -#include <locale.h> -#include <unistd.h> -#include <time.h> - -#include <bpf/bpf.h> -#include <bpf/libbpf.h> - -#include <net/if.h> -#include <linux/if_link.h> /* depend on kernel-headers installed */ - -#include "../common/common_params.h" -#include "../common/common_user_bpf_xdp.h" -#include "../common/common_libbpf.h" - -static const char *default_filename = "xdp_prog_kern.o"; - -static const struct option_wrapper long_options[] = { - - {{"help", no_argument, NULL, 'h' }, - "Show help", false}, - - {{"dev", required_argument, NULL, 'd' }, - "Operate on device <ifname>", "<ifname>", true}, - - {{"skb-mode", no_argument, NULL, 'S' }, - "Install XDP program in SKB (AKA generic) mode"}, - - {{"native-mode", no_argument, NULL, 'N' }, - "Install XDP program in native mode"}, - - {{"auto-mode", no_argument, NULL, 'A' }, - "Auto-detect SKB or native mode"}, - - {{"force", no_argument, NULL, 'F' }, - "Force install, replacing existing program on interface"}, - - {{"unload", no_argument, NULL, 'U' }, - "Unload XDP program instead of loading"}, - - {{"reuse-maps", no_argument, NULL, 'M' }, - "Reuse pinned maps"}, - - {{"quiet", no_argument, NULL, 'q' }, - "Quiet mode (no output)"}, - - {{"filename", required_argument, NULL, 1 }, - "Load program from <file>", "<file>"}, - - {{"progsec", required_argument, NULL, 2 }, - "Load program in <section> of the ELF file", "<section>"}, - - {{0, 0, NULL, 0 }, NULL, false} -}; - -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif - -const char *pin_basedir = "/sys/fs/bpf"; -const char *map_name = "xdp_stats_map"; - -/* Pinning maps under /sys/fs/bpf in subdir */ -int pin_maps_in_bpf_object(struct bpf_object *bpf_obj, struct config *cfg) -{ - char map_filename[PATH_MAX]; - int err, len; - - len = snprintf(map_filename, PATH_MAX, "%s/%s/%s", - pin_basedir, cfg->ifname, map_name); - if (len < 0) { - fprintf(stderr, "ERR: creating map_name\n"); - return EXIT_FAIL_OPTION; - } - - /* Existing/previous XDP prog might not have cleaned up */ - if (access(map_filename, F_OK ) != -1 ) { - if (verbose) - printf(" - Unpinning (remove) prev maps in %s/\n", - cfg->pin_dir); - - /* Basically calls unlink(3) on map_filename */ - err = bpf_object__unpin_maps(bpf_obj, cfg->pin_dir); - if (err) { - fprintf(stderr, "ERR: UNpinning maps in %s\n", cfg->pin_dir); - return EXIT_FAIL_BPF; - } - } - if (verbose) - printf(" - Pinning maps in %s/\n", cfg->pin_dir); - - /* This will pin all maps in our bpf_object */ - err = bpf_object__pin_maps(bpf_obj, cfg->pin_dir); - if (err) - return EXIT_FAIL_BPF; - - return 0; -} - -int main(int argc, char **argv) -{ - struct bpf_object *bpf_obj; - int err, len; - - struct config cfg = { - .xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE, - .ifindex = -1, - .do_unload = false, - }; - /* Set default BPF-ELF object file and BPF program name */ - strncpy(cfg.filename, default_filename, sizeof(cfg.filename)); - /* Cmdline options can change progsec */ - parse_cmdline_args(argc, argv, long_options, &cfg, __doc__); - - /* Required option */ - if (cfg.ifindex == -1) { - fprintf(stderr, "ERR: required option --dev missing\n\n"); - usage(argv[0], __doc__, long_options, (argc == 1)); - return EXIT_FAIL_OPTION; - } - if (cfg.do_unload) { - if (!cfg.reuse_maps) { - /* TODO: Miss unpin of maps on unload */ - } - return xdp_link_detach(cfg.ifindex, cfg.xdp_flags, 0); - } - - len = snprintf(cfg.pin_dir, PATH_MAX, "%s/%s", pin_basedir, cfg.ifname); - if (len < 0) { - fprintf(stderr, "ERR: creating pin dirname\n"); - return EXIT_FAIL_OPTION; - } - - - bpf_obj = load_bpf_and_xdp_attach(&cfg); - if (!bpf_obj) - return EXIT_FAIL_BPF; - - if (verbose) { - printf("Success: Loaded BPF-object(%s) and used section(%s)\n", - cfg.filename, cfg.progsec); - printf(" - XDP prog attached on device:%s(ifindex:%d)\n", - cfg.ifname, cfg.ifindex); - } - - /* Use the --dev name as subdir for exporting/pinning maps */ - if (!cfg.reuse_maps) { - err = pin_maps_in_bpf_object(bpf_obj, &cfg); - if (err) { - fprintf(stderr, "ERR: pinning maps\n"); - return err; - } - } - - return EXIT_OK; -} diff --git a/src/xdp/utils/xdp_stats.c b/src/xdp/utils/xdp_stats.c deleted file mode 100644 index f9fa7438f..000000000 --- a/src/xdp/utils/xdp_stats.c +++ /dev/null @@ -1,298 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -static const char *__doc__ = "XDP stats program\n" - " - Finding xdp_stats_map via --dev name info\n"; - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <getopt.h> - -#include <locale.h> -#include <unistd.h> -#include <time.h> - -#include <bpf/bpf.h> -/* Lesson#1: this prog does not need to #include <bpf/libbpf.h> as it only uses - * the simple bpf-syscall wrappers, defined in libbpf #include<bpf/bpf.h> - */ - -#include <net/if.h> -#include <linux/if_link.h> /* depend on kernel-headers installed */ - -#include "../common/common_params.h" -#include "../common/common_user_bpf_xdp.h" -#include "../common/xdp_stats_kern_user.h" - -#include "../include/bpf_util.h" /* bpf_num_possible_cpus */ - -static const struct option_wrapper long_options[] = { - {{"help", no_argument, NULL, 'h' }, - "Show help", false}, - - {{"dev", required_argument, NULL, 'd' }, - "Operate on device <ifname>", "<ifname>", true}, - - {{"quiet", no_argument, NULL, 'q' }, - "Quiet mode (no output)"}, - - {{0, 0, NULL, 0 }} -}; - -#define NANOSEC_PER_SEC 1000000000 /* 10^9 */ -static __u64 gettime(void) -{ - struct timespec t; - int res; - - res = clock_gettime(CLOCK_MONOTONIC, &t); - if (res < 0) { - fprintf(stderr, "Error with gettimeofday! (%i)\n", res); - exit(EXIT_FAIL); - } - return (__u64) t.tv_sec * NANOSEC_PER_SEC + t.tv_nsec; -} - -struct record { - __u64 timestamp; - struct datarec total; /* defined in common_kern_user.h */ -}; - -struct stats_record { - struct record stats[XDP_ACTION_MAX]; -}; - -static double calc_period(struct record *r, struct record *p) -{ - double period_ = 0; - __u64 period = 0; - - period = r->timestamp - p->timestamp; - if (period > 0) - period_ = ((double) period / NANOSEC_PER_SEC); - - return period_; -} - -static void stats_print_header() -{ - /* Print stats "header" */ - printf("%-12s\n", "XDP-action"); -} - -static void stats_print(struct stats_record *stats_rec, - struct stats_record *stats_prev) -{ - struct record *rec, *prev; - __u64 packets, bytes; - double period; - double pps; /* packets per sec */ - double bps; /* bits per sec */ - int i; - - stats_print_header(); /* Print stats "header" */ - - /* Print for each XDP actions stats */ - for (i = 0; i < XDP_ACTION_MAX; i++) - { - char *fmt = "%-12s %'11lld pkts (%'10.0f pps)" - " %'11lld Kbytes (%'6.0f Mbits/s)" - " period:%f\n"; - const char *action = action2str(i); - - rec = &stats_rec->stats[i]; - prev = &stats_prev->stats[i]; - - period = calc_period(rec, prev); - if (period == 0) - return; - - packets = rec->total.rx_packets - prev->total.rx_packets; - pps = packets / period; - - bytes = rec->total.rx_bytes - prev->total.rx_bytes; - bps = (bytes * 8)/ period / 1000000; - - printf(fmt, action, rec->total.rx_packets, pps, - rec->total.rx_bytes / 1000 , bps, - period); - } - printf("\n"); -} - - -/* BPF_MAP_TYPE_ARRAY */ -void map_get_value_array(int fd, __u32 key, struct datarec *value) -{ - if ((bpf_map_lookup_elem(fd, &key, value)) != 0) { - fprintf(stderr, - "ERR: bpf_map_lookup_elem failed key:0x%X\n", key); - } -} - -/* BPF_MAP_TYPE_PERCPU_ARRAY */ -void map_get_value_percpu_array(int fd, __u32 key, struct datarec *value) -{ - /* For percpu maps, userspace gets a value per possible CPU */ - unsigned int nr_cpus = bpf_num_possible_cpus(); - struct datarec values[nr_cpus]; - __u64 sum_bytes = 0; - __u64 sum_pkts = 0; - int i; - - if ((bpf_map_lookup_elem(fd, &key, values)) != 0) { - fprintf(stderr, - "ERR: bpf_map_lookup_elem failed key:0x%X\n", key); - return; - } - - /* Sum values from each CPU */ - for (i = 0; i < nr_cpus; i++) { - sum_pkts += values[i].rx_packets; - sum_bytes += values[i].rx_bytes; - } - value->rx_packets = sum_pkts; - value->rx_bytes = sum_bytes; -} - -static bool map_collect(int fd, __u32 map_type, __u32 key, struct record *rec) -{ - struct datarec value; - - /* Get time as close as possible to reading map contents */ - rec->timestamp = gettime(); - - switch (map_type) { - case BPF_MAP_TYPE_ARRAY: - map_get_value_array(fd, key, &value); - break; - case BPF_MAP_TYPE_PERCPU_ARRAY: - map_get_value_percpu_array(fd, key, &value); - break; - default: - fprintf(stderr, "ERR: Unknown map_type(%u) cannot handle\n", - map_type); - return false; - break; - } - - rec->total.rx_packets = value.rx_packets; - rec->total.rx_bytes = value.rx_bytes; - return true; -} - -static void stats_collect(int map_fd, __u32 map_type, - struct stats_record *stats_rec) -{ - /* Collect all XDP actions stats */ - __u32 key; - - for (key = 0; key < XDP_ACTION_MAX; key++) { - map_collect(map_fd, map_type, key, &stats_rec->stats[key]); - } -} - -static int stats_poll(const char *pin_dir, int map_fd, __u32 id, - __u32 map_type, int interval) -{ - struct bpf_map_info info = {}; - struct stats_record prev, record = { 0 }; - int counter = 0; - - /* Trick to pretty printf with thousands separators use %' */ - setlocale(LC_NUMERIC, "en_US"); - - /* Get initial reading quickly */ - stats_collect(map_fd, map_type, &record); - usleep(1000000/4); - - while (1) { - prev = record; /* struct copy */ - - map_fd = open_bpf_map_file(pin_dir, "xdp_stats_map", &info); - if (map_fd < 0) { - return EXIT_FAIL_BPF; - } else if (id != info.id) { - printf("BPF map xdp_stats_map changed its ID, restarting\n"); - return 0; - } - - stats_collect(map_fd, map_type, &record); - stats_print(&record, &prev); - sleep(interval); - counter++; - if (counter > 1) { - return 0; - } - } - - return 0; -} - -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif - -const char *pin_basedir = "/sys/fs/bpf"; - -int main(int argc, char **argv) -{ - const struct bpf_map_info map_expect = { - .key_size = sizeof(__u32), - .value_size = sizeof(struct datarec), - .max_entries = XDP_ACTION_MAX, - }; - struct bpf_map_info info = { 0 }; - char pin_dir[PATH_MAX]; - int stats_map_fd; - int interval = 2; - int len, err; - - struct config cfg = { - .ifindex = -1, - .do_unload = false, - }; - - /* Cmdline options can change progsec */ - parse_cmdline_args(argc, argv, long_options, &cfg, __doc__); - - /* Required option */ - if (cfg.ifindex == -1) { - fprintf(stderr, "ERR: required option --dev missing\n\n"); - usage(argv[0], __doc__, long_options, (argc == 1)); - return EXIT_FAIL_OPTION; - } - - /* Use the --dev name as subdir for finding pinned maps */ - len = snprintf(pin_dir, PATH_MAX, "%s/%s", pin_basedir, cfg.ifname); - if (len < 0) { - fprintf(stderr, "ERR: creating pin dirname\n"); - return EXIT_FAIL_OPTION; - } - - stats_map_fd = open_bpf_map_file(pin_dir, "xdp_stats_map", &info); - if (stats_map_fd < 0) { - return EXIT_FAIL_BPF; - } - - /* check map info, e.g. datarec is expected size */ - err = check_map_fd_info(&info, &map_expect); - if (err) { - fprintf(stderr, "ERR: map via FD not compatible\n"); - return err; - } - if (verbose) { - printf("\nCollecting stats from BPF map\n"); - printf(" - BPF map (bpf_map_type:%d) id:%d name:%s" - " key_size:%d value_size:%d max_entries:%d\n", - info.type, info.id, info.name, - info.key_size, info.value_size, info.max_entries - ); - } - - err = stats_poll(pin_dir, stats_map_fd, info.id, info.type, interval); - if (err < 0) - return err; - - return EXIT_OK; -} diff --git a/src/xdp/xdp_prog_kern.c b/src/xdp/xdp_prog_kern.c deleted file mode 100644 index 59308325d..000000000 --- a/src/xdp/xdp_prog_kern.c +++ /dev/null @@ -1,347 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#include <linux/bpf.h> -#include <linux/in.h> -#include <bpf/bpf_helpers.h> -#include <bpf/bpf_endian.h> - -// The parsing helper functions from the packet01 lesson have moved here -#include "common/parsing_helpers.h" -#include "common/rewrite_helpers.h" - -/* Defines xdp_stats_map */ -#include "common/xdp_stats_kern_user.h" -#include "common/xdp_stats_kern.h" - -#ifndef memcpy -#define memcpy(dest, src, n) __builtin_memcpy((dest), (src), (n)) -#endif - -struct { - __uint(type, BPF_MAP_TYPE_DEVMAP); - __type(key, int); - __type(value, int); - __uint(max_entries, 256); -} tx_port SEC(".maps"); - -struct { - __uint(type, BPF_MAP_TYPE_HASH); - __type(key, ETH_ALEN); - __type(value, ETH_ALEN); - __uint(max_entries, 1); -} redirect_params SEC(".maps"); - -static __always_inline __u16 csum_fold_helper(__u32 csum) -{ - return ~((csum & 0xffff) + (csum >> 16)); -} - -/* - * The icmp_checksum_diff function takes pointers to old and new structures and - * the old checksum and returns the new checksum. It uses the bpf_csum_diff - * helper to compute the checksum difference. Note that the sizes passed to the - * bpf_csum_diff helper should be multiples of 4, as it operates on 32-bit - * words. - */ -static __always_inline __u16 icmp_checksum_diff( - __u16 seed, - struct icmphdr_common *icmphdr_new, - struct icmphdr_common *icmphdr_old) -{ - __u32 csum, size = sizeof(struct icmphdr_common); - - csum = bpf_csum_diff((__be32 *)icmphdr_old, size, (__be32 *)icmphdr_new, size, seed); - return csum_fold_helper(csum); -} - -/* Solution to packet03/assignment-1 */ -SEC("xdp_icmp_echo") -int xdp_icmp_echo_func(struct xdp_md *ctx) -{ - void *data_end = (void *)(long)ctx->data_end; - void *data = (void *)(long)ctx->data; - struct hdr_cursor nh; - struct ethhdr *eth; - int eth_type; - int ip_type; - int icmp_type; - struct iphdr *iphdr; - struct ipv6hdr *ipv6hdr; - __u16 echo_reply, old_csum; - struct icmphdr_common *icmphdr; - struct icmphdr_common icmphdr_old; - __u32 action = XDP_PASS; - - /* These keep track of the next header type and iterator pointer */ - nh.pos = data; - - /* Parse Ethernet and IP/IPv6 headers */ - eth_type = parse_ethhdr(&nh, data_end, ð); - if (eth_type == bpf_htons(ETH_P_IP)) { - ip_type = parse_iphdr(&nh, data_end, &iphdr); - if (ip_type != IPPROTO_ICMP) - goto out; - } else if (eth_type == bpf_htons(ETH_P_IPV6)) { - ip_type = parse_ip6hdr(&nh, data_end, &ipv6hdr); - if (ip_type != IPPROTO_ICMPV6) - goto out; - } else { - goto out; - } - - /* - * We are using a special parser here which returns a stucture - * containing the "protocol-independent" part of an ICMP or ICMPv6 - * header. For purposes of this Assignment we are not interested in - * the rest of the structure. - */ - icmp_type = parse_icmphdr_common(&nh, data_end, &icmphdr); - if (eth_type == bpf_htons(ETH_P_IP) && icmp_type == ICMP_ECHO) { - /* Swap IP source and destination */ - swap_src_dst_ipv4(iphdr); - echo_reply = ICMP_ECHOREPLY; - } else if (eth_type == bpf_htons(ETH_P_IPV6) - && icmp_type == ICMPV6_ECHO_REQUEST) { - /* Swap IPv6 source and destination */ - swap_src_dst_ipv6(ipv6hdr); - echo_reply = ICMPV6_ECHO_REPLY; - } else { - goto out; - } - - /* Swap Ethernet source and destination */ - swap_src_dst_mac(eth); - - - /* Patch the packet and update the checksum.*/ - old_csum = icmphdr->cksum; - icmphdr->cksum = 0; - icmphdr_old = *icmphdr; - icmphdr->type = echo_reply; - icmphdr->cksum = icmp_checksum_diff(~old_csum, icmphdr, &icmphdr_old); - - /* Another, less generic, but a bit more efficient way to update the - * checksum is listed below. As only one 16-bit word changed, the sum - * can be patched using this formula: sum' = ~(~sum + ~m0 + m1), where - * sum' is a new sum, sum is an old sum, m0 and m1 are the old and new - * 16-bit words, correspondingly. In the formula above the + operation - * is defined as the following function: - * - * static __always_inline __u16 csum16_add(__u16 csum, __u16 addend) - * { - * csum += addend; - * return csum + (csum < addend); - * } - * - * So an alternative code to update the checksum might look like this: - * - * __u16 m0 = * (__u16 *) icmphdr; - * icmphdr->type = echo_reply; - * __u16 m1 = * (__u16 *) icmphdr; - * icmphdr->checksum = ~(csum16_add(csum16_add(~icmphdr->checksum, ~m0), m1)); - */ - - action = XDP_TX; - -out: - return xdp_stats_record_action(ctx, action); -} - -/* Solution to packet03/assignment-2 */ -SEC("xdp_redirect") -int xdp_redirect_func(struct xdp_md *ctx) -{ - void *data_end = (void *)(long)ctx->data_end; - void *data = (void *)(long)ctx->data; - struct hdr_cursor nh; - struct ethhdr *eth; - int eth_type; - int action = XDP_PASS; - unsigned char dst[ETH_ALEN] = { /* TODO: put your values here */ }; - unsigned ifindex = 0/* TODO: put your values here */; - - /* These keep track of the next header type and iterator pointer */ - nh.pos = data; - - /* Parse Ethernet and IP/IPv6 headers */ - eth_type = parse_ethhdr(&nh, data_end, ð); - if (eth_type == -1) - goto out; - - /* Set a proper destination address */ - memcpy(eth->h_dest, dst, ETH_ALEN); - action = bpf_redirect(ifindex, 0); - -out: - return xdp_stats_record_action(ctx, action); -} - -/* Solution to packet03/assignment-3 */ -SEC("xdp_redirect_map") -int xdp_redirect_map_func(struct xdp_md *ctx) -{ - void *data_end = (void *)(long)ctx->data_end; - void *data = (void *)(long)ctx->data; - struct hdr_cursor nh; - struct ethhdr *eth; - int eth_type; - int action = XDP_PASS; - unsigned char *dst; - - /* These keep track of the next header type and iterator pointer */ - nh.pos = data; - - /* Parse Ethernet and IP/IPv6 headers */ - eth_type = parse_ethhdr(&nh, data_end, ð); - if (eth_type == -1) - goto out; - - /* Do we know where to redirect this packet? */ - dst = bpf_map_lookup_elem(&redirect_params, eth->h_source); - if (!dst) - goto out; - - /* Set a proper destination address */ - memcpy(eth->h_dest, dst, ETH_ALEN); - action = bpf_redirect_map(&tx_port, 0, 0); - -out: - return xdp_stats_record_action(ctx, action); -} - -#ifndef AF_INET -#define AF_INET 2 -#endif -#ifndef AF_INET6 -#define AF_INET6 10 -#endif -#define IPV6_FLOWINFO_MASK bpf_htonl(0x0FFFFFFF) - -/* from include/net/ip.h */ -static __always_inline int ip_decrease_ttl(struct iphdr *iph) -{ - __u32 check = iph->check; - check += bpf_htons(0x0100); - iph->check = (__u16)(check + (check >= 0xFFFF)); - return --iph->ttl; -} - -/* Solution to packet03/assignment-4 */ -/* xdp_router is the name of the xdp program */ -SEC("xdp_router") -int xdp_router_func(struct xdp_md *ctx) -{ - /* this is the packet context*/ - void *data_end = (void *)(long)ctx->data_end; - void *data = (void *)(long)ctx->data; - struct bpf_fib_lookup fib_params = {}; - struct ethhdr *eth = data; - struct ipv6hdr *ip6h; - struct iphdr *iph; - __u16 h_proto; - __u64 nh_off; - int rc; - /* default action is to pass */ - int action = XDP_PASS; - - nh_off = sizeof(*eth); - if (data + nh_off > data_end) { - action = XDP_DROP; - goto out; - } - - /* determine if this is IP4 or IPv6 by looking at the Ethernet protocol field */ - h_proto = eth->h_proto; - if (h_proto == bpf_htons(ETH_P_IP)) { - /* IPv4 part of the code */ - iph = data + nh_off; - - if (iph + 1 > data_end) { - action = XDP_DROP; - goto out; - } - /* as a real router, we need to check the TTL to prevent never ending loops*/ - if (iph->ttl <= 1) - goto out; - - /* populate the fib_params fields to prepare for the lookup */ - fib_params.family = AF_INET; - fib_params.tos = iph->tos; - fib_params.l4_protocol = iph->protocol; - fib_params.sport = 0; - fib_params.dport = 0; - fib_params.tot_len = bpf_ntohs(iph->tot_len); - fib_params.ipv4_src = iph->saddr; - fib_params.ipv4_dst = iph->daddr; - } else if (h_proto == bpf_htons(ETH_P_IPV6)) { - /* IPv6 part of the code */ - struct in6_addr *src = (struct in6_addr *) fib_params.ipv6_src; - struct in6_addr *dst = (struct in6_addr *) fib_params.ipv6_dst; - - ip6h = data + nh_off; - if (ip6h + 1 > data_end) { - action = XDP_DROP; - goto out; - } - /* as a real router, we need to check the TTL to prevent never ending loops*/ - if (ip6h->hop_limit <= 1) - goto out; - - /* populate the fib_params fields to prepare for the lookup */ - fib_params.family = AF_INET6; - fib_params.flowinfo = *(__be32 *) ip6h & IPV6_FLOWINFO_MASK; - fib_params.l4_protocol = ip6h->nexthdr; - fib_params.sport = 0; - fib_params.dport = 0; - fib_params.tot_len = bpf_ntohs(ip6h->payload_len); - *src = ip6h->saddr; - *dst = ip6h->daddr; - } else { - goto out; - } - - fib_params.ifindex = ctx->ingress_ifindex; - - /* this is where the FIB lookup happens. If the lookup is successful */ - /* it will populate the fib_params.ifindex with the egress interface index */ - - rc = bpf_fib_lookup(ctx, &fib_params, sizeof(fib_params), 0); - switch (rc) { - case BPF_FIB_LKUP_RET_SUCCESS: /* lookup successful */ - /* we are a router, so we need to decrease the ttl */ - if (h_proto == bpf_htons(ETH_P_IP)) - ip_decrease_ttl(iph); - else if (h_proto == bpf_htons(ETH_P_IPV6)) - ip6h->hop_limit--; - /* set the correct new source and destionation mac addresses */ - /* can be found in fib_params.dmac and fib_params.smac */ - memcpy(eth->h_dest, fib_params.dmac, ETH_ALEN); - memcpy(eth->h_source, fib_params.smac, ETH_ALEN); - /* and done, now we set the action to bpf_redirect_map with fib_params.ifindex which is the egress port as paramater */ - action = bpf_redirect_map(&tx_port, fib_params.ifindex, 0); - break; - case BPF_FIB_LKUP_RET_BLACKHOLE: /* dest is blackholed; can be dropped */ - case BPF_FIB_LKUP_RET_UNREACHABLE: /* dest is unreachable; can be dropped */ - case BPF_FIB_LKUP_RET_PROHIBIT: /* dest not allowed; can be dropped */ - action = XDP_DROP; - break; - case BPF_FIB_LKUP_RET_NOT_FWDED: /* packet is not forwarded */ - case BPF_FIB_LKUP_RET_FWD_DISABLED: /* fwding is not enabled on ingress */ - case BPF_FIB_LKUP_RET_UNSUPP_LWT: /* fwd requires encapsulation */ - case BPF_FIB_LKUP_RET_NO_NEIGH: /* no neighbor entry for nh */ - case BPF_FIB_LKUP_RET_FRAG_NEEDED: /* fragmentation required to fwd */ - /* PASS */ - break; - } - -out: - /* and done, update stats and return action */ - return xdp_stats_record_action(ctx, action); -} - -SEC("xdp_pass") -int xdp_pass_func(struct xdp_md *ctx) -{ - return xdp_stats_record_action(ctx, XDP_PASS); -} - -char _license[] SEC("license") = "GPL"; diff --git a/src/xdp/xdp_prog_user.c b/src/xdp/xdp_prog_user.c deleted file mode 100644 index c47d2977d..000000000 --- a/src/xdp/xdp_prog_user.c +++ /dev/null @@ -1,182 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -static const char *__doc__ = "XDP redirect helper\n" - " - Allows to populate/query tx_port and redirect_params maps\n"; - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <getopt.h> -#include <stdbool.h> - -#include <locale.h> -#include <unistd.h> -#include <time.h> - -#include <bpf/bpf.h> -#include <bpf/libbpf.h> - -#include <net/if.h> -#include <linux/if_ether.h> -#include <linux/if_link.h> /* depend on kernel-headers installed */ - -#include "common/common_params.h" -#include "common/common_user_bpf_xdp.h" -#include "common/common_libbpf.h" - -#include "common/xdp_stats_kern_user.h" - -static const struct option_wrapper long_options[] = { - - {{"help", no_argument, NULL, 'h' }, - "Show help", false}, - - {{"dev", required_argument, NULL, 'd' }, - "Operate on device <ifname>", "<ifname>", true}, - - {{"redirect-dev", required_argument, NULL, 'r' }, - "Redirect to device <ifname>", "<ifname>", true}, - - {{"src-mac", required_argument, NULL, 'L' }, - "Source MAC address of <dev>", "<mac>", true }, - - {{"dest-mac", required_argument, NULL, 'R' }, - "Destination MAC address of <redirect-dev>", "<mac>", true }, - - {{"quiet", no_argument, NULL, 'q' }, - "Quiet mode (no output)"}, - - {{0, 0, NULL, 0 }, NULL, false} -}; - -static int parse_u8(char *str, unsigned char *x) -{ - unsigned long z; - - z = strtoul(str, 0, 16); - if (z > 0xff) - return -1; - - if (x) - *x = z; - - return 0; -} - -static int parse_mac(char *str, unsigned char mac[ETH_ALEN]) -{ - if (parse_u8(str, &mac[0]) < 0) - return -1; - if (parse_u8(str + 3, &mac[1]) < 0) - return -1; - if (parse_u8(str + 6, &mac[2]) < 0) - return -1; - if (parse_u8(str + 9, &mac[3]) < 0) - return -1; - if (parse_u8(str + 12, &mac[4]) < 0) - return -1; - if (parse_u8(str + 15, &mac[5]) < 0) - return -1; - - return 0; -} - -static int write_iface_params(int map_fd, unsigned char *src, unsigned char *dest) -{ - if (bpf_map_update_elem(map_fd, src, dest, 0) < 0) { - fprintf(stderr, - "WARN: Failed to update bpf map file: err(%d):%s\n", - errno, strerror(errno)); - return -1; - } - - printf("forward: %02x:%02x:%02x:%02x:%02x:%02x -> %02x:%02x:%02x:%02x:%02x:%02x\n", - src[0], src[1], src[2], src[3], src[4], src[5], - dest[0], dest[1], dest[2], dest[3], dest[4], dest[5] - ); - - return 0; -} - -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif - -const char *pin_basedir = "/sys/fs/bpf"; - -int main(int argc, char **argv) -{ - int i; - int len; - int map_fd; - bool redirect_map; - char pin_dir[PATH_MAX]; - unsigned char src[ETH_ALEN]; - unsigned char dest[ETH_ALEN]; - - struct config cfg = { - .ifindex = -1, - .redirect_ifindex = -1, - }; - - /* Cmdline options can change progsec */ - parse_cmdline_args(argc, argv, long_options, &cfg, __doc__); - - redirect_map = (cfg.ifindex > 0) && (cfg.redirect_ifindex > 0); - - if (cfg.redirect_ifindex > 0 && cfg.ifindex == -1) { - fprintf(stderr, "ERR: required option --dev missing\n\n"); - usage(argv[0], __doc__, long_options, (argc == 1)); - return EXIT_FAIL_OPTION; - } - - len = snprintf(pin_dir, PATH_MAX, "%s/%s", pin_basedir, cfg.ifname); - if (len < 0) { - fprintf(stderr, "ERR: creating pin dirname\n"); - return EXIT_FAIL_OPTION; - } - - if (parse_mac(cfg.src_mac, src) < 0) { - fprintf(stderr, "ERR: can't parse mac address %s\n", cfg.src_mac); - return EXIT_FAIL_OPTION; - } - - if (parse_mac(cfg.dest_mac, dest) < 0) { - fprintf(stderr, "ERR: can't parse mac address %s\n", cfg.dest_mac); - return EXIT_FAIL_OPTION; - } - - /* Open the tx_port map corresponding to the cfg.ifname interface */ - map_fd = open_bpf_map_file(pin_dir, "tx_port", NULL); - if (map_fd < 0) { - return EXIT_FAIL_BPF; - } - - printf("map dir: %s\n", pin_dir); - - if (redirect_map) { - /* setup a virtual port for the static redirect */ - i = 0; - bpf_map_update_elem(map_fd, &i, &cfg.redirect_ifindex, 0); - printf("redirect from ifnum=%d to ifnum=%d\n", cfg.ifindex, cfg.redirect_ifindex); - - /* Open the redirect_params map */ - map_fd = open_bpf_map_file(pin_dir, "redirect_params", NULL); - if (map_fd < 0) { - return EXIT_FAIL_BPF; - } - - /* Setup the mapping containing MAC addresses */ - if (write_iface_params(map_fd, src, dest) < 0) { - fprintf(stderr, "can't write iface params\n"); - return 1; - } - } else { - /* setup 1-1 mapping for the dynamic router */ - for (i = 1; i < 256; ++i) - bpf_map_update_elem(map_fd, &i, &i, 0); - } - - return EXIT_OK; -} |