diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/conntrack_sync.py | 6 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-ethernet.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-openvpn.py | 13 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-tunnel.py | 45 | ||||
-rwxr-xr-x | src/conf_mode/policy-route.py | 4 | ||||
-rwxr-xr-x | src/conf_mode/vrf.py | 108 | ||||
-rwxr-xr-x | src/etc/telegraf/custom_scripts/show_firewall_input_filter.py | 73 | ||||
-rwxr-xr-x | src/helpers/system-versions-foot.py | 2 | ||||
-rwxr-xr-x | src/op_mode/generate_ovpn_client_file.py | 145 | ||||
-rwxr-xr-x | src/op_mode/show_ipsec_sa.py | 186 |
10 files changed, 409 insertions, 175 deletions
diff --git a/src/conf_mode/conntrack_sync.py b/src/conf_mode/conntrack_sync.py index 8f9837c2b..34d1f7398 100755 --- a/src/conf_mode/conntrack_sync.py +++ b/src/conf_mode/conntrack_sync.py @@ -93,9 +93,9 @@ def verify(conntrack): raise ConfigError('Can not configure expect-sync "all" with other protocols!') if 'listen_address' in conntrack: - address = conntrack['listen_address'] - if not is_addr_assigned(address): - raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') + for address in conntrack['listen_address']: + if not is_addr_assigned(address): + raise ConfigError(f'Specified listen-address {address} not assigned to any interface!') vrrp_group = dict_search('failover_mechanism.vrrp.sync_group', conntrack) if vrrp_group == None: diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index e7250fb49..ab8d58f81 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -165,7 +165,7 @@ 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'][cert_name] + pki_ca_cert = ethernet['pki']['ca'][ca_cert_name] write_file(ca_cert_file_path, wrap_certificate(pki_ca_cert['certificate'])) diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 0f6114b4a..329399274 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2021 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -226,11 +226,12 @@ def verify(openvpn): if 'local_address' not in openvpn and 'is_bridge_member' not in openvpn: raise ConfigError('Must specify "local-address" or add interface to bridge') - if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: - raise ConfigError('Only one IPv4 local-address can be specified') + if 'local_address' in openvpn: + if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1: + raise ConfigError('Only one IPv4 local-address can be specified') - if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1: - raise ConfigError('Only one IPv6 local-address can be specified') + if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1: + raise ConfigError('Only one IPv6 local-address can be specified') if openvpn['device_type'] == 'tun': if 'remote_address' not in openvpn: @@ -269,7 +270,7 @@ def verify(openvpn): if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn): raise ConfigError('"remote-address" and "remote-host" can not be the same') - if openvpn['device_type'] == 'tap': + if openvpn['device_type'] == 'tap' and 'local_address' in openvpn: # we can only have one local_address, this is ensured above v4addr = None for laddr in openvpn['local_address']: diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 30f57ec0c..4c1204b4e 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2021 VyOS maintainers and contributors +# Copyright (C) 2018-2022 yOS 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,9 +21,7 @@ from netifaces import interfaces from ipaddress import IPv4Address from vyos.config import Config -from vyos.configdict import dict_merge from vyos.configdict import get_interface_dict -from vyos.configdict import node_changed from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete @@ -34,8 +32,6 @@ from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.ifconfig import TunnelIf -from vyos.template import is_ipv4 -from vyos.template import is_ipv6 from vyos.util import get_interface_config from vyos.util import dict_search from vyos import ConfigError @@ -103,19 +99,22 @@ def verify(tunnel): raise ConfigError('Tunnel parameters ip key must be set!') if tunnel['encapsulation'] in ['gre', 'gretap']: - if dict_search('parameters.ip.key', tunnel) != None: - # Check pairs tunnel source-address/encapsulation/key with exists tunnels. - # Prevent the same key for 2 tunnels with same source-address/encap. T2920 - for tunnel_if in Section.interfaces('tunnel'): - # It makes no sense to run the test for re-used GRE keys on our - # own interface we are currently working on - if tunnel['ifname'] == tunnel_if: - continue - tunnel_cfg = get_interface_config(tunnel_if) - # no match on encapsulation - bail out - if dict_search('linkinfo.info_kind', tunnel_cfg) != tunnel['encapsulation']: - continue - new_source_address = dict_search('source_address', tunnel) + # Check pairs tunnel source-address/encapsulation/key with exists tunnels. + # Prevent the same key for 2 tunnels with same source-address/encap. T2920 + for tunnel_if in Section.interfaces('tunnel'): + # It makes no sense to run the test against our own interface we + # are currently configuring + if tunnel['ifname'] == tunnel_if: + continue + + tunnel_cfg = get_interface_config(tunnel_if) + # no match on encapsulation - bail out + if dict_search('linkinfo.info_kind', tunnel_cfg) != tunnel['encapsulation']: + continue + + new_source_address = dict_search('source_address', tunnel) + new_source_interface = dict_search('source_interface', tunnel) + if dict_search('parameters.ip.key', tunnel) != None: # Convert tunnel key to ip key, format "ip -j link show" # 1 => 0.0.0.1, 999 => 0.0.3.231 orig_new_key = dict_search('parameters.ip.key', tunnel) @@ -125,6 +124,16 @@ def verify(tunnel): dict_search('linkinfo.info_data.ikey', tunnel_cfg) == new_key: raise ConfigError(f'Key "{orig_new_key}" for source-address "{new_source_address}" ' \ f'is already used for tunnel "{tunnel_if}"!') + else: + # If no IP GRE key is used we can not have more then one GRE tunnel + # bound to any one interface/IP address. This will result in a OS + # PermissionError: add tunnel "gre0" failed: File exists + if (dict_search('address', tunnel_cfg) == new_source_address or + (dict_search('address', tunnel_cfg) == '0.0.0.0' and + dict_search('link', tunnel_cfg) == new_source_interface)): + raise ConfigError(f'Missing required "ip key" parameter when \ + running more then one GRE based tunnel on the \ + same source-interface/source-address') # Keys are not allowed with ipip and sit tunnels if tunnel['encapsulation'] in ['ipip', 'sit']: diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 82f668acf..3d1d7d8c5 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -123,6 +123,10 @@ def verify_rule(policy, name, rule_conf, ipv6): for group in valid_groups: if group in side_conf['group']: group_name = side_conf['group'][group] + + if group_name.startswith('!'): + group_name = group_name[1:] + fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group error_group = fw_group.replace("_", "-") group_obj = dict_search_args(policy['firewall_group'], fw_group, group_name) diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 38c0c4463..cfe0f4d8e 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2022 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 @@ -29,6 +29,7 @@ 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 from vyos import ConfigError from vyos import frr from vyos import airbag @@ -37,10 +38,16 @@ airbag.enable() config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf' nft_vrf_config = '/tmp/nftables-vrf-zones' -def list_rules(): - command = 'ip -j -4 rule show' - answer = loads(cmd(command)) - return [_ for _ in answer if _] +def has_rule(af : str, priority : int, table : str): + """ Check if a given ip rule exists """ + if af not in ['-4', '-6']: + raise ValueError() + command = f'ip -j {af} rule show' + for tmp in loads(cmd(command)): + if {'priority', 'table'} <= set(tmp): + if tmp['priority'] == priority and tmp['table'] == table: + return True + return False def vrf_interfaces(c, match): matched = [] @@ -69,7 +76,6 @@ def vrf_routing(c, match): c.set_level(old_level) return matched - def get_config(config=None): if config: conf = config @@ -148,13 +154,11 @@ def apply(vrf): bind_all = '0' if 'bind-to-all' in vrf: bind_all = '1' - call(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}') - call(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}') + sysctl('net.ipv4.tcp_l3mdev_accept', bind_all) + sysctl('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 -4 route del vrf {tmp} unreachable default metric 4278198272') - call(f'ip -6 route del vrf {tmp} unreachable default metric 4278198272') call(f'ip link delete dev {tmp}') # Remove nftables conntrack zone map item nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{tmp}" }}' @@ -165,31 +169,59 @@ def apply(vrf): # check if table already exists _, err = popen('nft list table inet vrf_zones') # If not, create a table - if err: - if os.path.exists(nft_vrf_config): - cmd(f'nft -f {nft_vrf_config}') - os.unlink(nft_vrf_config) + if err and os.path.exists(nft_vrf_config): + cmd(f'nft -f {nft_vrf_config}') + os.unlink(nft_vrf_config) + + # Linux routing uses rules to find tables - routing targets are then + # looked up in those tables. If the lookup got a matching route, the + # process ends. + # + # TL;DR; first table with a matching entry wins! + # + # You can see your routing table lookup rules using "ip rule", sadly the + # local lookup is hit before any VRF lookup. Pinging an addresses from the + # VRF will usually find a hit in the local table, and never reach the VRF + # routing table - this is usually not what you want. Thus we will + # re-arrange the tables and move the local lookup further down once VRFs + # are enabled. + # + # Thanks to https://stbuehler.de/blog/article/2020/02/29/using_vrf__virtual_routing_and_forwarding__on_linux.html + + for afi in ['-4', '-6']: + # move lookup local to pref 32765 (from 0) + if not has_rule(afi, 32765, 'local'): + call(f'ip {afi} rule add pref 32765 table local') + if has_rule(afi, 0, 'local'): + call(f'ip {afi} rule del pref 0') + # make sure that in VRFs after failed lookup in the VRF specific table + # nothing else is reached + if not has_rule(afi, 1000, 'l3mdev'): + # this should be added by the kernel when a VRF is created + # add it here for completeness + call(f'ip {afi} rule add pref 1000 l3mdev protocol kernel') + + # add another rule with an unreachable target which only triggers in VRF context + # if a route could not be reached + if not has_rule(afi, 2000, 'l3mdev'): + call(f'ip {afi} rule add pref 2000 l3mdev unreachable') for name, config in vrf['name'].items(): table = config['table'] - if not os.path.isdir(f'/sys/class/net/{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}') - # The kernel Documentation/networking/vrf.txt also recommends - # adding unreachable routes to the VRF routing tables so that routes - # afterwards are taken. - call(f'ip -4 route add vrf {name} unreachable default metric 4278198272') - call(f'ip -6 route add vrf {name} unreachable default metric 4278198272') - # We also should add proper loopback IP addresses to the newly - # created VRFs for services bound to the loopback address (SNMP, NTP) - call(f'ip -4 addr add 127.0.0.1/8 dev {name}') - call(f'ip -6 addr add ::1/128 dev {name}') # set VRF description for e.g. SNMP monitoring vrf_if = Interface(name) + # We also should add proper loopback IP addresses to the newly + # created VRFs for services bound to the loopback address (SNMP, NTP) + vrf_if.add_addr('127.0.0.1/8') + vrf_if.add_addr('::1/128') + # add VRF description if available vrf_if.set_alias(config.get('description', '')) + # Enable/Disable of an interface must always be done at the end of the # derived class to make use of the ref-counting set_admin_state() # function. We will only enable the interface if 'up' was called as @@ -203,37 +235,9 @@ def apply(vrf): nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}' cmd(f'nft {nft_add_element}') - # Linux routing uses rules to find tables - routing targets are then - # looked up in those tables. If the lookup got a matching route, the - # process ends. - # - # TL;DR; first table with a matching entry wins! - # - # You can see your routing table lookup rules using "ip rule", sadly the - # local lookup is hit before any VRF lookup. Pinging an addresses from the - # VRF will usually find a hit in the local table, and never reach the VRF - # routing table - this is usually not what you want. Thus we will - # re-arrange the tables and move the local lookup furhter down once VRFs - # are enabled. - - # get current preference on local table - local_pref = [r.get('priority') for r in list_rules() if r.get('table') == 'local'][0] - - # change preference when VRFs are enabled and local lookup table is default - if not local_pref and 'name' in vrf: - for af in ['-4', '-6']: - call(f'ip {af} rule add pref 32765 table local') - call(f'ip {af} rule del pref 0') # return to default lookup preference when no VRF is configured if 'name' not in vrf: - for af in ['-4', '-6']: - call(f'ip {af} rule add pref 0 table local') - call(f'ip {af} rule del pref 32765') - - # clean out l3mdev-table rule if present - if 1000 in [r.get('priority') for r in list_rules() if r.get('priority') == 1000]: - call(f'ip {af} rule del pref 1000') # Remove VRF zones table from nftables tmp = run('nft list table inet vrf_zones') if tmp == 0: diff --git a/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py new file mode 100755 index 000000000..bf4bfd05d --- /dev/null +++ b/src/etc/telegraf/custom_scripts/show_firewall_input_filter.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +import json +import re +import time + +from vyos.util import cmd + + +def get_nft_filter_chains(): + """ + Get list of nft chains for table filter + """ + nft = cmd('/usr/sbin/nft --json list table ip filter') + nft = json.loads(nft) + chain_list = [] + + for output in nft['nftables']: + if 'chain' in output: + chain = output['chain']['name'] + chain_list.append(chain) + + return chain_list + + +def get_nftables_details(name): + """ + Get dict, counters packets and bytes for chain + """ + command = f'/usr/sbin/nft list chain ip filter {name}' + try: + results = cmd(command) + except: + return {} + + # Trick to remove 'NAME_' from chain name in the comment + # It was added to any chain T4218 + # counter packets 0 bytes 0 return comment "FOO default-action accept" + comment_name = name.replace("NAME_", "") + out = {} + for line in results.split('\n'): + comment_search = re.search(rf'{comment_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 get_nft_telegraf(name): + """ + Get data for telegraf in influxDB format + """ + for rule, rule_config in get_nftables_details(name).items(): + print(f'nftables,table=filter,chain={name},' + f'ruleid={rule} ' + f'pkts={rule_config["packets"]}i,' + f'bytes={rule_config["bytes"]}i ' + f'{str(int(time.time()))}000000000') + + +chains = get_nft_filter_chains() + +for chain in chains: + get_nft_telegraf(chain) diff --git a/src/helpers/system-versions-foot.py b/src/helpers/system-versions-foot.py index c33e41d79..2aa687221 100755 --- a/src/helpers/system-versions-foot.py +++ b/src/helpers/system-versions-foot.py @@ -21,7 +21,7 @@ import vyos.systemversions as systemversions import vyos.defaults import vyos.version -sys_versions = systemversions.get_system_versions() +sys_versions = systemversions.get_system_component_version() component_string = formatversions.format_versions_string(sys_versions) diff --git a/src/op_mode/generate_ovpn_client_file.py b/src/op_mode/generate_ovpn_client_file.py new file mode 100755 index 000000000..29db41e37 --- /dev/null +++ b/src/op_mode/generate_ovpn_client_file.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 os + +from jinja2 import Template + +from vyos.configquery import ConfigTreeQuery +from vyos.ifconfig import Section +from vyos.util import cmd + + +client_config = """ + +client +nobind +remote {{ remote_host }} {{ port }} +remote-cert-tls server +proto {{ 'tcp-client' if protocol == 'tcp-active' else 'udp' }} +dev {{ device }} +dev-type {{ device }} +persist-key +persist-tun +verb 3 + +# Encryption options +{% if encryption is defined and encryption is not none %} +{% if encryption.cipher is defined and encryption.cipher is not none %} +cipher {{ encryption.cipher }} +{% if encryption.cipher == 'bf128' %} +keysize 128 +{% elif encryption.cipher == 'bf256' %} +keysize 256 +{% endif %} +{% endif %} +{% if encryption.ncp_ciphers is defined and encryption.ncp_ciphers is not none %} +data-ciphers {{ encryption.ncp_ciphers }} +{% endif %} +{% endif %} + +{% if hash is defined and hash is not none %} +auth {{ hash }} +{% endif %} +keysize 256 +comp-lzo {{ '' if use_lzo_compression is defined else 'no' }} + +<ca> +-----BEGIN CERTIFICATE----- +{{ ca }} +-----END CERTIFICATE----- + +</ca> + +<cert> +-----BEGIN CERTIFICATE----- +{{ cert }} +-----END CERTIFICATE----- + +</cert> + +<key> +-----BEGIN PRIVATE KEY----- +{{ key }} +-----END PRIVATE KEY----- + +</key> + +""" + +config = ConfigTreeQuery() +base = ['interfaces', 'openvpn'] + +if not config.exists(base): + print('OpenVPN not configured') + exit(0) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--interface", type=str, help='OpenVPN interface the client is connecting to', required=True) + parser.add_argument("-a", "--ca", type=str, help='OpenVPN CA cerificate', required=True) + parser.add_argument("-c", "--cert", type=str, help='OpenVPN client cerificate', required=True) + parser.add_argument("-k", "--key", type=str, help='OpenVPN client cerificate key', action="store") + args = parser.parse_args() + + interface = args.interface + ca = args.ca + cert = args.cert + key = args.key + if not key: + key = args.cert + + if interface not in Section.interfaces('openvpn'): + exit(f'OpenVPN interface "{interface}" does not exist!') + + if not config.exists(['pki', 'ca', ca, 'certificate']): + exit(f'OpenVPN CA certificate "{ca}" does not exist!') + + if not config.exists(['pki', 'certificate', cert, 'certificate']): + exit(f'OpenVPN certificate "{cert}" does not exist!') + + if not config.exists(['pki', 'certificate', cert, 'private', 'key']): + exit(f'OpenVPN certificate key "{key}" does not exist!') + + ca = config.value(['pki', 'ca', ca, 'certificate']) + cert = config.value(['pki', 'certificate', cert, 'certificate']) + key = config.value(['pki', 'certificate', key, 'private', 'key']) + remote_host = config.value(base + [interface, 'local-host']) + + ovpn_conf = config.get_config_dict(base + [interface], key_mangling=('-', '_'), get_first_key=True) + + port = '1194' if 'local_port' not in ovpn_conf else ovpn_conf['local_port'] + proto = 'udp' if 'protocol' not in ovpn_conf else ovpn_conf['protocol'] + device = 'tun' if 'device_type' not in ovpn_conf else ovpn_conf['device_type'] + + config = { + 'interface' : interface, + 'ca' : ca, + 'cert' : cert, + 'key' : key, + 'device' : device, + 'port' : port, + 'proto' : proto, + 'remote_host' : remote_host, + 'address' : [], + } + +# Clear out terminal first +print('\x1b[2J\x1b[H') +client = Template(client_config, trim_blocks=True).render(config) +print(client) diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py index e72f0f965..5b8f00dba 100755 --- a/src/op_mode/show_ipsec_sa.py +++ b/src/op_mode/show_ipsec_sa.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2022 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 @@ -14,119 +14,117 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re -import sys +from re import split as re_split +from sys import exit -import vici -import tabulate -import hurry.filesize +from hurry import filesize +from tabulate import tabulate +from vici import Session as vici_session + +from vyos.util import seconds_to_human -import vyos.util def convert(text): return int(text) if text.isdigit() else text.lower() + def alphanum_key(key): - return [convert(c) for c in re.split('([0-9]+)', str(key))] + return [convert(c) for c in re_split('([0-9]+)', str(key))] -def format_output(conns, sas): + +def format_output(sas): sa_data = [] - for peer, parent_conn in conns.items(): - if peer not in sas: - continue - - parent_sa = sas[peer] - child_sas = parent_sa['child-sas'] - installed_sas = {v['name'].decode(): v for k, v in child_sas.items() if v["state"] == b"INSTALLED"} - - # parent_sa["state"] = IKE state, child_sas["state"] = ESP state - state = 'down' - uptime = 'N/A' - - if parent_sa["state"] == b"ESTABLISHED" and installed_sas: - state = "up" - - remote_host = parent_sa["remote-host"].decode() - remote_id = parent_sa["remote-id"].decode() - - if remote_host == remote_id: - remote_id = "N/A" - - # The counters can only be obtained from the child SAs - for child_conn in parent_conn['children']: - if child_conn not in installed_sas: - data = [child_conn, "down", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"] - sa_data.append(data) - continue - - isa = installed_sas[child_conn] - csa_name = isa['name'] - csa_name = csa_name.decode() - - bytes_in = hurry.filesize.size(int(isa["bytes-in"].decode())) - bytes_out = hurry.filesize.size(int(isa["bytes-out"].decode())) - bytes_str = "{0}/{1}".format(bytes_in, bytes_out) - - pkts_in = hurry.filesize.size(int(isa["packets-in"].decode()), system=hurry.filesize.si) - pkts_out = hurry.filesize.size(int(isa["packets-out"].decode()), system=hurry.filesize.si) - pkts_str = "{0}/{1}".format(pkts_in, pkts_out) - # Remove B from <1K values - pkts_str = re.sub(r'B', r'', pkts_str) - - uptime = vyos.util.seconds_to_human(isa['install-time'].decode()) - - enc = isa["encr-alg"].decode() - if "encr-keysize" in isa: - key_size = isa["encr-keysize"].decode() - else: - key_size = "" - if "integ-alg" in isa: - hash = isa["integ-alg"].decode() - else: - hash = "" - if "dh-group" in isa: - dh_group = isa["dh-group"].decode() - else: - dh_group = "" - - proposal = enc - if key_size: - proposal = "{0}_{1}".format(proposal, key_size) - if hash: - proposal = "{0}/{1}".format(proposal, hash) - if dh_group: - proposal = "{0}/{1}".format(proposal, dh_group) - - data = [csa_name, state, uptime, bytes_str, pkts_str, remote_host, remote_id, proposal] - sa_data.append(data) + for sa in sas: + for parent_sa in sa.values(): + # create an item for each child-sa + for child_sa in parent_sa.get('child-sas', {}).values(): + # prepare a list for output data + sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = sa_out_packets = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A' + + # collect raw data + sa_name = child_sa.get('name') + sa_state = child_sa.get('state') + sa_uptime = child_sa.get('install-time') + sa_bytes_in = child_sa.get('bytes-in') + sa_bytes_out = child_sa.get('bytes-out') + sa_packets_in = child_sa.get('packets-in') + sa_packets_out = child_sa.get('packets-out') + sa_remote_addr = parent_sa.get('remote-host') + sa_remote_id = parent_sa.get('remote-id') + sa_proposal_encr_alg = child_sa.get('encr-alg') + sa_proposal_integ_alg = child_sa.get('integ-alg') + sa_proposal_encr_keysize = child_sa.get('encr-keysize') + sa_proposal_dh_group = child_sa.get('dh-group') + + # format data to display + if sa_name: + sa_out_name = sa_name.decode() + if sa_state: + if sa_state == b'INSTALLED': + sa_out_state = 'up' + else: + sa_out_state = 'down' + if sa_uptime: + sa_out_uptime = seconds_to_human(sa_uptime.decode()) + if sa_bytes_in and sa_bytes_out: + bytes_in = filesize.size(int(sa_bytes_in.decode())) + bytes_out = filesize.size(int(sa_bytes_out.decode())) + sa_out_bytes = f'{bytes_in}/{bytes_out}' + if sa_packets_in and sa_packets_out: + packets_in = filesize.size(int(sa_packets_in.decode()), + system=filesize.si) + packets_out = filesize.size(int(sa_packets_out.decode()), + system=filesize.si) + sa_out_packets = f'{packets_in}/{packets_out}' + if sa_remote_addr: + sa_out_remote_addr = sa_remote_addr.decode() + if sa_remote_id: + sa_out_remote_id = sa_remote_id.decode() + # format proposal + if sa_proposal_encr_alg: + sa_out_proposal = sa_proposal_encr_alg.decode() + if sa_proposal_encr_keysize: + sa_proposal_encr_keysize_str = sa_proposal_encr_keysize.decode() + sa_out_proposal = f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}' + if sa_proposal_integ_alg: + sa_proposal_integ_alg_str = sa_proposal_integ_alg.decode() + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}' + if sa_proposal_dh_group: + sa_proposal_dh_group_str = sa_proposal_dh_group.decode() + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}' + + # add a new item to output data + sa_data.append([ + sa_out_name, sa_out_state, sa_out_uptime, sa_out_bytes, + sa_out_packets, sa_out_remote_addr, sa_out_remote_id, + sa_out_proposal + ]) + + # return output data return sa_data + if __name__ == '__main__': try: - session = vici.Session() - conns = {} - sas = {} + session = vici_session() + sas = list(session.list_sas()) - for conn in session.list_conns(): - for key in conn: - conns[key] = conn[key] - - for sa in session.list_sas(): - for key in sa: - sas[key] = sa[key] - - headers = ["Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", "Remote address", "Remote ID", "Proposal"] - sa_data = format_output(conns, sas) + sa_data = format_output(sas) sa_data = sorted(sa_data, key=alphanum_key) - output = tabulate.tabulate(sa_data, headers) + + headers = [ + "Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", + "Remote address", "Remote ID", "Proposal" + ] + output = tabulate(sa_data, headers) print(output) except PermissionError: print("You do not have a permission to connect to the IPsec daemon") - sys.exit(1) + exit(1) except ConnectionRefusedError: print("IPsec is not runing") - sys.exit(1) + exit(1) except Exception as e: print("An error occured: {0}".format(e)) - sys.exit(1) + exit(1) |