From 852253ce2c5cae371663d0f15b9f416b0848e9b9 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 21 Feb 2019 22:46:51 +0100 Subject: [IPsec] T1260: VICI-based implementation of "show vpn ipsec sa" --- debian/control | 1 + src/op_mode/show_ipsec_sa.py | 168 +++++++++++++++++++++++-------------------- 2 files changed, 90 insertions(+), 79 deletions(-) diff --git a/debian/control b/debian/control index 859c36f14..45e037c07 100644 --- a/debian/control +++ b/debian/control @@ -26,6 +26,7 @@ Depends: python3, python3-six, python3-isc-dhcp-leases, python3-hurry.filesize, + python3-vici (>= 5.7.2), ipaddrcheck, tcpdump, tshark, diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py index d1385f959..0828743e8 100755 --- a/src/op_mode/show_ipsec_sa.py +++ b/src/op_mode/show_ipsec_sa.py @@ -1,90 +1,100 @@ #!/usr/bin/env python3 +# +# Copyright (C) 2019 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 . import re import sys -import subprocess +import vici import tabulate import hurry.filesize -def parse_conn_spec(s): - try: - # Example: ESTABLISHED 14 seconds ago, 10.0.0.2[foo]...10.0.0.1[10.0.0.1] - return re.search(r'.*ESTABLISHED\s+(.*)ago,\s(.*)\[(.*)\]\.\.\.(.*)\[(.*)\].*', s).groups() - except AttributeError: - # No active SAs found, so we have nothing to display - print("No established security associations found.") - print("Use \"show vpn ipsec sa verbose\" to view inactive and connecting tunnels.") - sys.exit(0) - -def parse_sa_counters(s): - bytes_in, bytes_out = None, None - try: - # Example with traffic: AES_CBC_256/HMAC_SHA2_256_128/ECP_521, 2382660 bytes_i (1789 pkts, 2s ago), 2382660 bytes_o ... - bytes_in, bytes_out = re.search(r'\s+(\d+)\s+bytes_i\s\(.*pkts,.*\),\s+(\d+)\s+bytes_o', s).groups() - except AttributeError: - try: - # Example without traffic: 3DES_CBC/HMAC_MD5_96/MODP_1024, 0 bytes_i, 0 bytes_o, rekeying in 45 minutes - bytes_in, bytes_out = re.search(r'\s+(\d+)\s+bytes_i,\s+(\d+)\s+bytes_o,\s+rekeying', s).groups() - except AttributeError: - pass - - if (bytes_in is not None) and (bytes_out is not None): - # Convert bytes to human-readable units - bytes_in = hurry.filesize.size(int(bytes_in)) - bytes_out = hurry.filesize.size(int(bytes_out)) - - result = "{0}/{1}".format(bytes_in, bytes_out) - else: - result = "N/A" - - return result - -def parse_ike_proposal(s): - result = re.search(r'IKE proposal:\s+(.*)\s', s) - if result: - return result.groups(0)[0] - else: - return "N/A" - - -# Get a list of all configured connections -with open('/etc/ipsec.conf', 'r') as f: - config = f.read() - connections = set(re.findall(r'conn\s([^\s]+)\s*\n', config)) - connections = list(filter(lambda s: s != '%default', connections)) +import vyos.util + try: - # DMVPN connections have to be handled separately - with open('/etc/swanctl/swanctl.conf', 'r') as f: - dmvpn_config = f.read() - dmvpn_connections = re.findall(r'\s+(dmvpn-.*)\s+{\n', dmvpn_config) - connections += dmvpn_connections -except: - pass - -status_data = [] - -for conn in connections: - status = subprocess.check_output("ipsec statusall {0}".format(conn), shell=True).decode() - if not re.search(r'ESTABLISHED', status): - status_line = [conn, "down", None, None, None, None, None] - else: - try: - time, _, _, ip, id = parse_conn_spec(status) - if ip == id: - id = None - counters = parse_sa_counters(status) - enc = parse_ike_proposal(status) - status_line = [conn, "up", time, counters, ip, id, enc] - except Exception as e: - print(status) - raise e - status_line = [conn, None, None, None, None, None] - - status_line = list(map(lambda x: "N/A" if x is None else x, status_line)) - status_data.append(status_line) - -headers = ["Connection", "State", "Up", "Bytes In/Out", "Remote address", "Remote ID", "Proposal"] -output = tabulate.tabulate(status_data, headers) + session = vici.Session() + sas = session.list_sas() +except PermissionError: + print("You do not have a permission to connect to the IPsec daemon") + sys.exit(1) +except ConnectionRefusedError: + print("IPsec is not runing") + sys.exit(1) +except Exception as e: + print("An error occured: {0}".format(e)) + sys.exit(1) + +sa_data = [] + +for sa in sas: + # list_sas() returns a list of single-item dicts + for peer in sa: + parent_sa = sa[peer] + + if parent_sa["state"] == b"ESTABLISHED": + state = "up" + else: + state = "down" + + if state == "up": + uptime = vyos.util.seconds_to_human(parent_sa["established"].decode()) + else: + uptime = "N/A" + + 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 + child_sas = parent_sa["child-sas"] + installed_sas = {k: v for k, v in child_sas.items() if v["state"] == b"INSTALLED"} + + if not installed_sas: + data = [peer, state, "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"] + sa_data.append(data) + else: + for csa in installed_sas: + isa = installed_sas[csa] + + 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) + + enc = isa["encr-alg"].decode() + key_size = isa["encr-keysize"].decode() + hash = isa["integ-alg"].decode() + if "dh-group" in isa: + dh_group = isa["dh-group"].decode() + else: + dh_group = "" + proposal = "{0}_{1}/{2}".format(enc, key_size, hash) + if dh_group: + proposal = "{0}/{1}".format(proposal, dh_group) + + data = [peer, state, uptime, bytes_str, pkts_str, remote_host, remote_id, proposal] + sa_data.append(data) + +headers = ["Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", "Remote address", "Remote ID", "Proposal"] +output = tabulate.tabulate(sa_data, headers) print(output) -- cgit v1.2.3