#!/usr/bin/env python3 # # 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/>. import argparse import re import tabulate from vyos.config import Config from vyos.util import cmd from vyos.util import dict_search_args def get_policy_interfaces(conf, policy, name=None, ipv6=False): interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) routes = ['route', 'route6'] def parse_if(ifname, if_conf): if 'policy' in if_conf: for route in routes: if route in if_conf['policy']: route_name = if_conf['policy'][route] name_str = f'({ifname},{route})' if not name: policy[route][route_name]['interface'].append(name_str) elif not ipv6 and name == route_name: policy['interface'].append(name_str) for iftype in ['vif', 'vif_s', 'vif_c']: if iftype in if_conf: for vifname, vif_conf in if_conf[iftype].items(): parse_if(f'{ifname}.{vifname}', vif_conf) for iftype, iftype_conf in interfaces.items(): for ifname, if_conf in iftype_conf.items(): parse_if(ifname, if_conf) def get_config_policy(conf, name=None, ipv6=False, interfaces=True): config_path = ['policy'] if name: config_path += ['route6' if ipv6 else 'route', name] policy = conf.get_config_dict(config_path, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) if policy and interfaces: if name: policy['interface'] = [] else: if 'route' in policy: for route_name, route_conf in policy['route'].items(): route_conf['interface'] = [] if 'route6' in policy: for route_name, route_conf in policy['route6'].items(): route_conf['interface'] = [] get_policy_interfaces(conf, policy, name, ipv6) return policy def get_nftables_details(name, ipv6=False): suffix = '6' if ipv6 else '' command = f'sudo nft list chain ip{suffix} mangle VYOS_PBR{suffix}_{name}' try: results = cmd(command) except: return {} out = {} for line in results.split('\n'): comment_search = re.search(rf'{name}[\- ](\d+|default-action)', line) if not comment_search: continue rule = {} rule_id = comment_search[1] counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line) if counter_search: rule['packets'] = counter_search[1] rule['bytes'] = counter_search[2] rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip() out[rule_id] = rule return out 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']: print('Active on: {0}\n'.format(" ".join(route_conf['interface']))) details = get_nftables_details(name, ipv6) rows = [] if 'rule' in route_conf: for rule_id, rule_conf in route_conf['rule'].items(): if single_rule_id and rule_id != single_rule_id: continue if 'disable' in rule_conf: continue action = rule_conf['action'] if 'action' in rule_conf else 'set' protocol = rule_conf['protocol'] if 'protocol' in rule_conf else 'all' row = [rule_id, action, protocol] if rule_id in details: rule_details = details[rule_id] row.append(rule_details.get('packets', 0)) row.append(rule_details.get('bytes', 0)) row.append(rule_details['conditions']) rows.append(row) if 'default_action' in route_conf and not single_rule_id: row = ['default', route_conf['default_action'], 'all'] if 'default-action' in details: rule_details = details['default-action'] row.append(rule_details.get('packets', 0)) row.append(rule_details.get('bytes', 0)) rows.append(row) if rows: header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions'] print(tabulate.tabulate(rows, header) + '\n') def show_policy(ipv6=False): print('Ruleset Information') conf = Config() policy = get_config_policy(conf) if not policy: return if not ipv6 and 'route' in policy: for route, route_conf in policy['route'].items(): output_policy_route(route, route_conf, ipv6=False) if ipv6 and 'route6' in policy: for route, route_conf in policy['route6'].items(): output_policy_route(route, route_conf, ipv6=True) def show_policy_name(name, ipv6=False): print('Ruleset Information') conf = Config() policy = get_config_policy(conf, name, ipv6) if policy: output_policy_route(name, policy, ipv6) def show_policy_rule(name, rule_id, ipv6=False): print('Rule Information') conf = Config() policy = get_config_policy(conf, name, ipv6) if policy: output_policy_route(name, policy, ipv6, rule_id) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--action', help='Action', required=False) parser.add_argument('--name', help='Policy name', required=False, action='store', nargs='?', default='') parser.add_argument('--rule', help='Policy Rule ID', required=False) parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true') args = parser.parse_args() if args.action == 'show': if not args.rule: show_policy_name(args.name, args.ipv6) else: show_policy_rule(args.name, args.rule, args.ipv6) elif args.action == 'show_all': show_policy(args.ipv6)