diff options
author | Christian Breunig <christian@breunig.cc> | 2024-01-01 00:59:46 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-01 00:59:46 +0100 |
commit | 22d5b2bab254668dd0fe8c543fb1bd0edcbead18 (patch) | |
tree | e98bf08f93c029ec4431a3b6ca078e7562e0cc58 /src/conf_mode/policy_local-route.py | |
parent | 2286b8600da6c631b17e1d5b9b341843e50f9abf (diff) | |
parent | 4ef110fd2c501b718344c72d495ad7e16d2bd465 (diff) | |
download | vyos-1x-22d5b2bab254668dd0fe8c543fb1bd0edcbead18.tar.gz vyos-1x-22d5b2bab254668dd0fe8c543fb1bd0edcbead18.zip |
Merge pull request #2729 from c-po/rename-T5474
T5474: establish common file name pattern for XML conf mode commands
Diffstat (limited to 'src/conf_mode/policy_local-route.py')
-rwxr-xr-x | src/conf_mode/policy_local-route.py | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/src/conf_mode/policy_local-route.py b/src/conf_mode/policy_local-route.py new file mode 100755 index 000000000..91e4fce2c --- /dev/null +++ b/src/conf_mode/policy_local-route.py @@ -0,0 +1,315 @@ +#!/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 os + +from itertools import product +from sys import exit + +from netifaces import interfaces +from vyos.config import Config +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.utils.process import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +def get_config(config=None): + + if config: + conf = config + else: + conf = Config() + base = ['policy'] + + pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + for route in ['local_route', 'local_route6']: + dict_id = 'rule_remove' if route == 'local_route' else 'rule6_remove' + route_key = 'local-route' if route == 'local_route' else 'local-route6' + base_rule = base + [route_key, 'rule'] + + # delete policy local-route + dict = {} + tmp = node_changed(conf, base_rule, key_mangling=('-', '_')) + if tmp: + for rule in (tmp or []): + src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address']) + src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address']) + dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port']) + table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) + proto = leaf_node_changed(conf, base_rule + [rule, 'protocol']) + rule_def = {} + if src: + rule_def = dict_merge({'source': {'address': src}}, rule_def) + if src_port: + rule_def = dict_merge({'source': {'port': src_port}}, rule_def) + if fwmk: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if iif: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) + if dst: + rule_def = dict_merge({'destination': {'address': dst}}, rule_def) + if dst_port: + rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def) + if table: + rule_def = dict_merge({'table' : table}, rule_def) + if proto: + rule_def = dict_merge({'protocol' : proto}, rule_def) + dict = dict_merge({dict_id : {rule : rule_def}}, dict) + pbr.update(dict) + + if not route in pbr: + continue + + # delete policy local-route rule x source x.x.x.x + # delete policy local-route rule x fwmark x + # delete policy local-route rule x destination x.x.x.x + if 'rule' in pbr[route]: + for rule, rule_config in pbr[route]['rule'].items(): + src = leaf_node_changed(conf, base_rule + [rule, 'source', 'address']) + src_port = leaf_node_changed(conf, base_rule + [rule, 'source', 'port']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination', 'address']) + dst_port = leaf_node_changed(conf, base_rule + [rule, 'destination', 'port']) + table = leaf_node_changed(conf, base_rule + [rule, 'set', 'table']) + proto = leaf_node_changed(conf, base_rule + [rule, 'protocol']) + # keep track of changes in configuration + # otherwise we might remove an existing node although nothing else has changed + changed = False + + rule_def = {} + # src is None if there are no changes to src + if src is None: + # if src hasn't changed, include it in the removal selector + # if a new selector is added, we have to remove all previous rules without this selector + # to make sure we remove all previous rules with this source(s), it will be included + if 'source' in rule_config: + if 'address' in rule_config['source']: + rule_def = dict_merge({'source': {'address': rule_config['source']['address']}}, rule_def) + else: + # if src is not None, it's previous content will be returned + # this can be an empty array if it's just being set, or the previous value + # either way, something has to be changed and we only want to remove previous values + changed = True + # set the old value for removal if it's not empty + if len(src) > 0: + rule_def = dict_merge({'source': {'address': src}}, rule_def) + + # source port + if src_port is None: + if 'source' in rule_config: + if 'port' in rule_config['source']: + tmp = rule_config['source']['port'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'source': {'port': tmp}}, rule_def) + else: + changed = True + if len(src_port) > 0: + rule_def = dict_merge({'source': {'port': src_port}}, rule_def) + + # fwmark + if fwmk is None: + if 'fwmark' in rule_config: + tmp = rule_config['fwmark'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'fwmark': tmp}, rule_def) + else: + changed = True + if len(fwmk) > 0: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + + # inbound-interface + if iif is None: + if 'inbound_interface' in rule_config: + rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def) + else: + changed = True + if len(iif) > 0: + rule_def = dict_merge({'inbound_interface' : iif}, rule_def) + + # destination address + if dst is None: + if 'destination' in rule_config: + if 'address' in rule_config['destination']: + rule_def = dict_merge({'destination': {'address': rule_config['destination']['address']}}, rule_def) + else: + changed = True + if len(dst) > 0: + rule_def = dict_merge({'destination': {'address': dst}}, rule_def) + + # destination port + if dst_port is None: + if 'destination' in rule_config: + if 'port' in rule_config['destination']: + tmp = rule_config['destination']['port'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'destination': {'port': tmp}}, rule_def) + else: + changed = True + if len(dst_port) > 0: + rule_def = dict_merge({'destination': {'port': dst_port}}, rule_def) + + # table + if table is None: + if 'set' in rule_config and 'table' in rule_config['set']: + rule_def = dict_merge({'table': [rule_config['set']['table']]}, rule_def) + else: + changed = True + if len(table) > 0: + rule_def = dict_merge({'table' : table}, rule_def) + + # protocol + if proto is None: + if 'protocol' in rule_config: + tmp = rule_config['protocol'] + if isinstance(tmp, str): + tmp = [tmp] + rule_def = dict_merge({'protocol': tmp}, rule_def) + else: + changed = True + if len(proto) > 0: + rule_def = dict_merge({'protocol' : proto}, rule_def) + + if changed: + dict = dict_merge({dict_id : {rule : rule_def}}, dict) + pbr.update(dict) + + return pbr + +def verify(pbr): + # bail out early - looks like removal from running config + if not pbr: + return None + + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + pbr_route = pbr[route] + if 'rule' in pbr_route: + for rule in pbr_route['rule']: + if ( + 'source' not in pbr_route['rule'][rule] and + 'destination' not in pbr_route['rule'][rule] and + 'fwmark' not in pbr_route['rule'][rule] and + 'inbound_interface' not in pbr_route['rule'][rule] and + 'protocol' not in pbr_route['rule'][rule] + ): + raise ConfigError('Source or destination address or fwmark or inbound-interface or protocol is required!') + + if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: + raise ConfigError('Table set is required!') + + if 'inbound_interface' in pbr_route['rule'][rule]: + interface = pbr_route['rule'][rule]['inbound_interface'] + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist') + + return None + +def generate(pbr): + if not pbr: + return None + + return None + +def apply(pbr): + if not pbr: + return None + + # Delete old rule if needed + for rule_rm in ['rule_remove', 'rule6_remove']: + if rule_rm in pbr: + v6 = " -6" if rule_rm == 'rule6_remove' else "" + + for rule, rule_config in pbr[rule_rm].items(): + source = rule_config.get('source', {}).get('address', ['']) + source_port = rule_config.get('source', {}).get('port', ['']) + destination = rule_config.get('destination', {}).get('address', ['']) + destination_port = rule_config.get('destination', {}).get('port', ['']) + fwmark = rule_config.get('fwmark', ['']) + inbound_interface = rule_config.get('inbound_interface', ['']) + protocol = rule_config.get('protocol', ['']) + table = rule_config.get('table', ['']) + + for src, dst, src_port, dst_port, fwmk, iif, proto, table in product( + source, destination, source_port, destination_port, + fwmark, inbound_interface, protocol, table): + f_src = '' if src == '' else f' from {src} ' + f_src_port = '' if src_port == '' else f' sport {src_port} ' + f_dst = '' if dst == '' else f' to {dst} ' + f_dst_port = '' if dst_port == '' else f' dport {dst_port} ' + f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' + f_iif = '' if iif == '' else f' iif {iif} ' + f_proto = '' if proto == '' else f' ipproto {proto} ' + f_table = '' if table == '' else f' lookup {table} ' + + call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif}{f_table}') + + # Generate new config + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + v6 = " -6" if route == 'local_route6' else "" + pbr_route = pbr[route] + + if 'rule' in pbr_route: + for rule, rule_config in pbr_route['rule'].items(): + table = rule_config['set'].get('table', '') + source = rule_config.get('source', {}).get('address', ['all']) + source_port = rule_config.get('source', {}).get('port', '') + destination = rule_config.get('destination', {}).get('address', ['all']) + destination_port = rule_config.get('destination', {}).get('port', '') + fwmark = rule_config.get('fwmark', '') + inbound_interface = rule_config.get('inbound_interface', '') + protocol = rule_config.get('protocol', '') + + for src in source: + f_src = f' from {src} ' if src else '' + for dst in destination: + f_dst = f' to {dst} ' if dst else '' + f_src_port = f' sport {source_port} ' if source_port else '' + f_dst_port = f' dport {destination_port} ' if destination_port else '' + f_fwmk = f' fwmark {fwmark} ' if fwmark else '' + f_iif = f' iif {inbound_interface} ' if inbound_interface else '' + f_proto = f' ipproto {protocol} ' if protocol else '' + + call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_src_port}{f_dst_port}{f_fwmk}{f_iif} lookup {table}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) |