diff options
author | Christian Poessinger <christian@poessinger.com> | 2020-04-17 08:11:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-17 08:11:31 +0200 |
commit | 75c7e2a5cea6713623208a4023455de64d13d7e1 (patch) | |
tree | 8be3e77575d316d428dfd1d570a7502257dea4b5 /python | |
parent | 3964ee10a3a85e3655135d7e0235b4d1b2f08214 (diff) | |
parent | 7d04bfbcc74e062b80b337753e7018a6af81e70c (diff) | |
download | vyos-1x-75c7e2a5cea6713623208a4023455de64d13d7e1.tar.gz vyos-1x-75c7e2a5cea6713623208a4023455de64d13d7e1.zip |
Merge pull request #341 from thomas-mangin/T2223
op_mode: T2223: convert vyatta-show-interfaces.pl to show_interfaces.py
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/ifconfig/__init__.py | 4 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 80 | ||||
-rw-r--r-- | python/vyos/ifconfig/operational.py | 179 | ||||
-rw-r--r-- | python/vyos/ifconfig/section.py | 18 | ||||
-rw-r--r-- | python/vyos/ifconfig/vrrp.py | 145 | ||||
-rw-r--r-- | python/vyos/ifconfig/wireguard.py | 239 | ||||
-rw-r--r-- | python/vyos/keepalived.py | 140 |
7 files changed, 490 insertions, 315 deletions
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index cd1696ca1..4d98901b7 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -15,7 +15,11 @@ from vyos.ifconfig.section import Section +from vyos.ifconfig.control import Control from vyos.ifconfig.interface import Interface +from vyos.ifconfig.operational import Operational +from vyos.ifconfig.dhcp import DHCP +from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.bond import BondIf from vyos.ifconfig.bridge import BridgeIf diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 43f823eca..32ce1a80c 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -14,23 +14,19 @@ # License along with this library. If not, see <http://www.gnu.org/licenses/>. import os -import re import json -import glob -import time -from time import sleep -from os.path import isfile from copy import deepcopy -from datetime import timedelta -from hurry.filesize import size, alternative -from ipaddress import IPv4Network, IPv6Address, IPv6Network -from netifaces import ifaddresses, AF_INET, AF_INET6 -from tabulate import tabulate +from ipaddress import IPv4Network +from ipaddress import IPv6Address +from ipaddress import IPv6Network +from netifaces import ifaddresses +# this is not the same as socket.AF_INET/INET6 +from netifaces import AF_INET +from netifaces import AF_INET6 -from vyos.util import mac2eui64 from vyos import ConfigError -from vyos.ifconfig.dhcp import DHCP +from vyos.util import mac2eui64 from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 from vyos.validate import is_intf_addr_assigned @@ -42,9 +38,17 @@ from vyos.validate import assert_positive from vyos.validate import assert_range from vyos.ifconfig.control import Control +from vyos.ifconfig.dhcp import DHCP +from vyos.ifconfig.vrrp import VRRP +from vyos.ifconfig.operational import Operational class Interface(Control): + # This is the class which will be used to create + # self.operational, it allows subclasses, such as + # WireGuard to modify their display behaviour + OperationalClass = Operational + options = [] required = [] default = { @@ -154,6 +158,10 @@ class Interface(Control): }, } + @classmethod + def exists(cls, ifname): + return os.path.exists(f'/sys/class/net/{ifname}') + def __init__(self, ifname, **kargs): """ This is the base interface class which supports basic IP/MAC address @@ -184,14 +192,15 @@ class Interface(Control): # we must have updated config before initialising the Interface super().__init__(**kargs) + self.ifname = ifname self.dhcp = DHCP(ifname) - if not os.path.exists('/sys/class/net/{}'.format(self.config['ifname'])): + if not self.exists(ifname): # Any instance of Interface, such as Interface('eth0') # can be used safely to access the generic function in this class # as 'type' is unset, the class can not be created if not self.config['type']: - raise Exception('interface "{}" not found'.format(self.config['ifname'])) + raise Exception(f'interface "{ifname}" not found') # Should an Instance of a child class (EthernetIf, DummyIf, ..) # be required, then create should be set to False to not accidentally create it. @@ -211,6 +220,9 @@ class Interface(Control): # list of assigned IP addresses self._addr = [] + self.operational = self.OperationalClass(ifname) + self.vrrp = VRRP(ifname) + def _create(self): cmd = 'ip link add dev {ifname} type {type}'.format(**self.config) self._cmd(cmd) @@ -556,19 +568,6 @@ class Interface(Control): """ return self.set_interface('admin_state', state) - def get_oper_state(self): - """ - Get interface operational state - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').get_oper_sate() - 'up' - """ - # https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-net - # "unknown", "notpresent", "down", "lowerlayerdown", "testing", "dormant", "up" - return self.get_interface('oper_state') - def set_proxy_arp(self, enable): """ Set per interface proxy ARP configuration @@ -713,30 +712,3 @@ class Interface(Control): if is_intf_addr_assigned(self.config['ifname'], addr): cmd = 'ip addr del "{}" dev "{}"'.format(addr, self.config['ifname']) return self._cmd(cmd) - - def op_show_interface_stats(self): - stats = self.get_interface_stats() - rx = [['bytes','packets','errors','dropped','overrun','mcast'],[stats['rx_bytes'],stats['rx_packets'],stats['rx_errors'],stats['rx_dropped'],stats['rx_over_errors'],stats['multicast']]] - tx = [['bytes','packets','errors','dropped','carrier','collisions'],[stats['tx_bytes'],stats['tx_packets'],stats['tx_errors'],stats['tx_dropped'],stats['tx_carrier_errors'],stats['collisions']]] - output = "RX: \n" - output += tabulate(rx,headers="firstrow",numalign="right",tablefmt="plain") - output += "\n\nTX: \n" - output += tabulate(tx,headers="firstrow",numalign="right",tablefmt="plain") - print(' '.join(('\n'+output.lstrip()).splitlines(True))) - - def get_interface_stats(self): - interface_stats = dict() - devices = [f for f in glob.glob("/sys/class/net/**/statistics")] - for dev_path in devices: - metrics = [f for f in glob.glob(dev_path +"/**")] - dev = re.findall(r"/sys/class/net/(.*)/statistics",dev_path)[0] - dev_dict = dict() - for metric_path in metrics: - metric = metric_path.replace(dev_path+"/","") - if isfile(metric_path): - data = open(metric_path, 'r').read()[:-1] - dev_dict[metric] = int(data) - interface_stats[dev] = dev_dict - - return interface_stats[self.config['ifname']] - diff --git a/python/vyos/ifconfig/operational.py b/python/vyos/ifconfig/operational.py new file mode 100644 index 000000000..d585c1873 --- /dev/null +++ b/python/vyos/ifconfig/operational.py @@ -0,0 +1,179 @@ +# Copyright 2019 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 os +from time import time +from datetime import datetime +from functools import reduce + +from tabulate import tabulate + +from vyos.ifconfig import Control + + +class Operational(Control): + """ + A class able to load Interface statistics + """ + + cache_magic = 'XYZZYX' + + _stat_names = { + 'rx': ['bytes', 'packets', 'errors', 'dropped', 'overrun', 'mcast'], + 'tx': ['bytes', 'packets', 'errors', 'dropped', 'carrier', 'collisions'], + } + + _stats_dir = { + 'rx': ['rx_bytes', 'rx_packets', 'rx_errors', 'rx_dropped', 'rx_over_errors', 'multicast'], + 'tx': ['tx_bytes', 'tx_packets', 'tx_errors', 'tx_dropped', 'tx_carrier_errors', 'collisions'], + } + + # a list made of the content of _stats_dir['rx'] + _stats_dir['tx'] + _stats_all = reduce(lambda x, y: x+y, _stats_dir.values()) + + # this is not an interface but will be able to be controlled like one + _sysfs_get = { + 'oper_state':{ + 'location': '/sys/class/net/{ifname}/operstate', + }, + } + + + @classmethod + def cachefile (cls, ifname): + # the file where we are saving the counters + return f'/var/run/vyatta/{ifname}.stats' + + + def __init__(self, ifname): + """ + Operational provide access to the counters of an interface + It behave like an interface when it comes to access sysfs + + interface is an instance of the interface for which we want + to look at (a subclass of Interface, such as EthernetIf) + """ + + # add a self.config to minic Interface behaviour and make + # coding similar. Perhaps part of class Interface could be + # moved into a shared base class. + self.config = { + 'ifname': ifname, + 'create': False, + 'debug': False, + } + super().__init__(**self.config) + self.ifname = ifname + + # adds all the counters of an interface + for stat in self._stats_all: + self._sysfs_get[stat] = { + 'location': '/sys/class/net/{ifname}/statistics/'+stat, + } + + def get_state(self): + """ + Get interface operational state + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').operational.get_sate() + 'up' + """ + # https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-net + # "unknown", "notpresent", "down", "lowerlayerdown", "testing", "dormant", "up" + return self.get_interface('oper_state') + + @classmethod + def strtime (cls, epoc): + """ + represent an epoc/unix date in the format used by operation commands + """ + return datetime.fromtimestamp(epoc).strftime("%a %b %d %R:%S %Z %Y") + + def save_counters(self, stats): + """ + record the provided stats to a file keeping vyatta compatibility + """ + + with open(self.cachefile(self.ifname), 'w') as f: + f.write(self.cache_magic) + f.write('\n') + f.write(str(int(time()))) + f.write('\n') + for k,v in stats.items(): + if v: + f.write(f'{k},{v}\n') + + def load_counters(self): + """ + load the stats from a file keeping vyatta compatibility + return a dict() with the value for each interface counter for the cache + """ + ifname = self.config['ifname'] + + stats = {} + no_stats = {} + for name in self._stats_all: + stats[name] = 0 + no_stats[name] = 0 + + try: + with open(self.cachefile(self.ifname),'r') as f: + magic = f.readline().strip() + if magic != self.cache_magic: + print(f'bad magic {ifname}') + return no_stats + stats['timestamp'] = f.readline().strip() + for line in f: + k, v = line.split(',') + stats[k] = int(v) + return stats + except IOError: + return no_stats + + def clear_counters(self, counters=None): + clear = self._stats_all if counters is None else [] + stats = self.load_counters() + for counter, value in stats.items(): + stats[counter] = 0 if counter in clear else value + self.save_counters(stats) + + def reset_counters(self): + os.remove(self.cachefile(self.ifname)) + + def get_stats(self): + """ return a dict() with the value for each interface counter """ + stats = {} + for counter in self._stats_all: + stats[counter] = int(self.get_interface(counter)) + return stats + + def formated_stats(self, indent=4): + tabs = [] + stats = self.get_stats() + for rtx in self._stats_dir: + tabs.append([f'{rtx.upper()}:', ] + [_ for _ in self._stat_names[rtx]]) + tabs.append(['', ] + [stats[_] for _ in self._stats_dir[rtx]]) + + s = tabulate( + tabs, + stralign="right", + numalign="right", + tablefmt="plain" + ) + + p = ' '*indent + return f'{p}' + s.replace('\n', f'\n{p}') diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index ab340d247..092236fef 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -69,7 +69,14 @@ class Section: if name in cls._prefixes: return cls._prefixes[name].definition['section'] return '' - + + @classmethod + def sections(cls): + """ + return all the sections we found under 'set interfaces' + """ + return list(set([cls._prefixes[_].definition['section'] for _ in cls._prefixes])) + @classmethod def klass(cls, name, vlan=True): name = cls._basename(name, vlan) @@ -80,7 +87,8 @@ class Section: @classmethod def _intf_under_section (cls,section=''): """ - return a generator with the name of the interface which are under a section + return a generator with the name of the configured interface + which are under a section """ interfaces = netifaces.interfaces() @@ -97,7 +105,7 @@ class Section: @classmethod def interfaces(cls, section=''): """ - return a list of the name of the interface which are under a section + return a list of the name of the configured interface which are under a section if no section is provided, then it returns all configured interfaces """ return list(cls._intf_under_section(section)) @@ -105,7 +113,7 @@ class Section: @classmethod def _intf_with_feature(cls, feature=''): """ - return a generator with the name of the interface which have + return a generator with the name of the configured interface which have a particular feature set in their definition such as: bondable, broadcast, bridgeable, ... """ @@ -116,7 +124,7 @@ class Section: @classmethod def feature(cls, feature=''): """ - return list with the name of the interface which have + return list with the name of the configured interface which have a particular feature set in their definition such as: bondable, broadcast, bridgeable, ... """ diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py new file mode 100644 index 000000000..29b10dd9e --- /dev/null +++ b/python/vyos/ifconfig/vrrp.py @@ -0,0 +1,145 @@ +# Copyright 2019 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 os +import json +import signal +from time import time +from time import sleep + +from tabulate import tabulate + +from vyos import airbag +from vyos import util + + +class VRRPError(Exception): + pass + +class VRRP(object): + _vrrp_prefix = '00:00:5E:00:01:' + location = { + 'pid': '/run/keepalived.pid', + 'fifo': '/run/keepalived_notify_fifo', + 'state': '/tmp/keepalived.data', + 'stats': '/tmp/keepalived.stats', + 'json': '/tmp/keepalived.json', + 'daemon': '/etc/default/keepalived', + 'config': '/etc/keepalived/keepalived.conf', + 'vyos': '/run/keepalived_config.dict', + } + + _signal = { + 'state': signal.SIGUSR1, + 'stats': signal.SIGUSR2, + 'json': signal.SIGRTMIN + 2, + } + + _name = { + 'state': 'information', + 'stats': 'statistics', + 'json': 'data', + } + + state = { + 0: 'INIT', + 1: 'BACKUP', + 2: 'MASTER', + 3: 'FAULT', + # UNKNOWN + } + + def __init__(self,ifname): + self.ifname = ifname + + def enabled(self): + return self.ifname in self.active_interfaces() + + @classmethod + def active_interfaces(cls): + if not os.path.exists(cls.location['pid']): + return [] + data = cls.collect('json') + return [group['data']['ifp_ifname'] for group in json.loads(data)] + + @classmethod + def decode_state(cls, code): + return cls.state.get(code,'UNKNOWN') + + # used in conf mode + @classmethod + def is_running(cls): + if not os.path.exists(cls.location['pid']): + return False + return util.process_running(cls.location['pid']) + + @classmethod + def collect(cls, what): + fname = cls.location[what] + try: + # send signal to generate the configuration file + pid = util.read_file(cls.location['pid']) + os.kill(int(pid), cls._signal[what]) + + # shoud look for file size change ? + sleep(0.2) + return util.read_file(fname) + except Exception: + name = cls._name[what] + raise VRRPError(f'VRRP {name} is not available') + finally: + if os.path.exists(fname): + os.remove(fname) + + @classmethod + def disabled(cls): + if not os.path.exists(cls.location['vyos']): + return [] + + disabled = [] + config = json.loads(util.readfile(cls.location['vyos'])) + + # add disabled groups to the list + for group in config['vrrp_groups']: + if group['disable']: + disabled.append( + [group['name'], group['interface'], group['vrid'], 'DISABLED', '']) + + # return list with disabled instances + return disabled + + @classmethod + def format (cls, data): + headers = ["Name", "Interface", "VRID", "State", "Last Transition"] + groups = [] + + data = json.loads(data) + for group in data: + data = group['data'] + + name = data['iname'] + intf = data['ifp_ifname'] + vrid = data['vrid'] + state = cls.decode_state(data["state"]) + + since = int(time() - float(data['last_transition'])) + last = util.seconds_to_human(since) + + groups.append([name, intf, vrid, state, last]) + + # add to the active list disabled instances + groups.extend(cls.disabled()) + return(tabulate(groups, headers)) + diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index e2b8a5924..ff945c9d0 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -18,13 +18,134 @@ import os import time from datetime import timedelta +from hurry.filesize import size +from hurry.filesize import alternative + from vyos.config import Config -from vyos.ifconfig.interface import Interface -from hurry.filesize import size,alternative +from vyos.ifconfig import Interface +from vyos.ifconfig import Operational + + +class WireGuardOperational(Operational): + def _dump(self): + """Dump wireguard data in a python friendly way.""" + last_device = None + output = {} + + # Dump wireguard connection data + _f = self._cmd('wg show all dump') + for line in _f.split('\n'): + if not line: + # Skip empty lines and last line + continue + items = line.split('\t') + + if last_device != items[0]: + # We are currently entering a new node + device, private_key, public_key, listen_port, fw_mark = items + last_device = device + + output[device] = { + 'private_key': None if private_key == '(none)' else private_key, + 'public_key': None if public_key == '(none)' else public_key, + 'listen_port': int(listen_port), + 'fw_mark': None if fw_mark == 'off' else int(fw_mark), + 'peers': {}, + } + else: + # We are entering a peer + device, public_key, preshared_key, endpoint, allowed_ips, latest_handshake, transfer_rx, transfer_tx, persistent_keepalive = items + if allowed_ips == '(none)': + allowed_ips = [] + else: + allowed_ips = allowed_ips.split('\t') + output[device]['peers'][public_key] = { + 'preshared_key': None if preshared_key == '(none)' else preshared_key, + 'endpoint': None if endpoint == '(none)' else endpoint, + 'allowed_ips': allowed_ips, + 'latest_handshake': None if latest_handshake == '0' else int(latest_handshake), + 'transfer_rx': int(transfer_rx), + 'transfer_tx': int(transfer_tx), + 'persistent_keepalive': None if persistent_keepalive == 'off' else int(persistent_keepalive), + } + return output + + def show_interface(self): + wgdump = self._dump().get(self.config['ifname'], None) + + c = Config() + + c.set_level(["interfaces", "wireguard", self.config['ifname']]) + description = c.return_effective_value(["description"]) + ips = c.return_effective_values(["address"]) + + answer = "interface: {}\n".format(self.config['ifname']) + if (description): + answer += " description: {}\n".format(description) + if (ips): + answer += " address: {}\n".format(", ".join(ips)) + + answer += " public key: {}\n".format(wgdump['public_key']) + answer += " private key: (hidden)\n" + answer += " listening port: {}\n".format(wgdump['listen_port']) + answer += "\n" + + for peer in c.list_effective_nodes(["peer"]): + if wgdump['peers']: + pubkey = c.return_effective_value(["peer", peer, "pubkey"]) + if pubkey in wgdump['peers']: + wgpeer = wgdump['peers'][pubkey] + + answer += " peer: {}\n".format(peer) + answer += " public key: {}\n".format(pubkey) + + """ figure out if the tunnel is recently active or not """ + status = "inactive" + if (wgpeer['latest_handshake'] is None): + """ no handshake ever """ + status = "inactive" + else: + if int(wgpeer['latest_handshake']) > 0: + delta = timedelta(seconds=int( + time.time() - wgpeer['latest_handshake'])) + answer += " latest handshake: {}\n".format(delta) + if (time.time() - int(wgpeer['latest_handshake']) < (60*5)): + """ Five minutes and the tunnel is still active """ + status = "active" + else: + """ it's been longer than 5 minutes """ + status = "inactive" + elif int(wgpeer['latest_handshake']) == 0: + """ no handshake ever """ + status = "inactive" + answer += " status: {}\n".format(status) + + if wgpeer['endpoint'] is not None: + answer += " endpoint: {}\n".format(wgpeer['endpoint']) + + if wgpeer['allowed_ips'] is not None: + answer += " allowed ips: {}\n".format( + ",".join(wgpeer['allowed_ips']).replace(",", ", ")) + + if wgpeer['transfer_rx'] > 0 or wgpeer['transfer_tx'] > 0: + rx_size = size( + wgpeer['transfer_rx'], system=alternative) + tx_size = size( + wgpeer['transfer_tx'], system=alternative) + answer += " transfer: {} received, {} sent\n".format( + rx_size, tx_size) + + if wgpeer['persistent_keepalive'] is not None: + answer += " persistent keepalive: every {} seconds\n".format( + wgpeer['persistent_keepalive']) + answer += '\n' + return answer + super().formated_stats() @Interface.register class WireGuardIf(Interface): + OperationalClass = WireGuardOperational + default = { 'type': 'wireguard', 'port': 0, @@ -106,117 +227,3 @@ class WireGuardIf(Interface): cmd = "wg set {0} peer {1} remove".format( self.config['ifname'], str(peerkey)) return self._cmd(cmd) - - def op_show_interface(self): - wgdump = self._dump().get( - self.config['ifname'], None) - - c = Config() - c.set_level(["interfaces", "wireguard", self.config['ifname']]) - description = c.return_effective_value(["description"]) - ips = c.return_effective_values(["address"]) - - print ("interface: {}".format(self.config['ifname'])) - if (description): - print (" description: {}".format(description)) - - if (ips): - print (" address: {}".format(", ".join(ips))) - print (" public key: {}".format(wgdump['public_key'])) - print (" private key: (hidden)") - print (" listening port: {}".format(wgdump['listen_port'])) - print () - - for peer in c.list_effective_nodes(["peer"]): - if wgdump['peers']: - pubkey = c.return_effective_value(["peer", peer, "pubkey"]) - if pubkey in wgdump['peers']: - wgpeer = wgdump['peers'][pubkey] - - print (" peer: {}".format(peer)) - print (" public key: {}".format(pubkey)) - - """ figure out if the tunnel is recently active or not """ - status = "inactive" - if (wgpeer['latest_handshake'] is None): - """ no handshake ever """ - status = "inactive" - else: - if int(wgpeer['latest_handshake']) > 0: - delta = timedelta(seconds=int( - time.time() - wgpeer['latest_handshake'])) - print (" latest handshake: {}".format(delta)) - if (time.time() - int(wgpeer['latest_handshake']) < (60*5)): - """ Five minutes and the tunnel is still active """ - status = "active" - else: - """ it's been longer than 5 minutes """ - status = "inactive" - elif int(wgpeer['latest_handshake']) == 0: - """ no handshake ever """ - status = "inactive" - print (" status: {}".format(status)) - - if wgpeer['endpoint'] is not None: - print (" endpoint: {}".format(wgpeer['endpoint'])) - - if wgpeer['allowed_ips'] is not None: - print (" allowed ips: {}".format( - ",".join(wgpeer['allowed_ips']).replace(",", ", "))) - - if wgpeer['transfer_rx'] > 0 or wgpeer['transfer_tx'] > 0: - rx_size = size( - wgpeer['transfer_rx'], system=alternative) - tx_size = size( - wgpeer['transfer_tx'], system=alternative) - print (" transfer: {} received, {} sent".format( - rx_size, tx_size)) - - if wgpeer['persistent_keepalive'] is not None: - print (" persistent keepalive: every {} seconds".format( - wgpeer['persistent_keepalive'])) - print() - super().op_show_interface_stats() - - def _dump(self): - """Dump wireguard data in a python friendly way.""" - last_device = None - output = {} - - # Dump wireguard connection data - _f = self._cmd('wg show all dump') - for line in _f.split('\n'): - if not line: - # Skip empty lines and last line - continue - items = line.split('\t') - - if last_device != items[0]: - # We are currently entering a new node - device, private_key, public_key, listen_port, fw_mark = items - last_device = device - - output[device] = { - 'private_key': None if private_key == '(none)' else private_key, - 'public_key': None if public_key == '(none)' else public_key, - 'listen_port': int(listen_port), - 'fw_mark': None if fw_mark == 'off' else int(fw_mark), - 'peers': {}, - } - else: - # We are entering a peer - device, public_key, preshared_key, endpoint, allowed_ips, latest_handshake, transfer_rx, transfer_tx, persistent_keepalive = items - if allowed_ips == '(none)': - allowed_ips = [] - else: - allowed_ips = allowed_ips.split('\t') - output[device]['peers'][public_key] = { - 'preshared_key': None if preshared_key == '(none)' else preshared_key, - 'endpoint': None if endpoint == '(none)' else endpoint, - 'allowed_ips': allowed_ips, - 'latest_handshake': None if latest_handshake == '0' else int(latest_handshake), - 'transfer_rx': int(transfer_rx), - 'transfer_tx': int(transfer_tx), - 'persistent_keepalive': None if persistent_keepalive == 'off' else int(persistent_keepalive), - } - return output diff --git a/python/vyos/keepalived.py b/python/vyos/keepalived.py deleted file mode 100644 index 3984ca792..000000000 --- a/python/vyos/keepalived.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright 2018 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 os -import signal -import json - -import vyos.util - - -pid_file = '/var/run/keepalived.pid' -state_file = '/tmp/keepalived.data' -stats_file = '/tmp/keepalived.stats' -json_file = '/tmp/keepalived.json' - -def vrrp_running(): - if not os.path.exists(vyos.keepalived.pid_file) \ - or not vyos.util.process_running(vyos.keepalived.pid_file): - return False - else: - return True - -def keepalived_running(): - return vyos.util.process_running(pid_file) - -## Clear VRRP data after showing -def remove_vrrp_data(data_file): - if data_file == "json" and os.path.exists(json_file): - os.remove(json_file) - elif data_file == "stats" and os.path.exists(stats_file): - os.remove(stats_file) - elif data_file == "state" and os.path.exists(state_file): - os.remove(state_file) - -def force_state_data_dump(): - pid = vyos.util.read_file(pid_file) - os.kill(int(pid), signal.SIGUSR1) - -def force_stats_dump(): - pid = vyos.util.read_file(pid_file) - os.kill(int(pid), signal.SIGUSR2) - -def force_json_dump(): - pid = vyos.util.read_file(pid_file) - os.kill(int(pid), signal.SIGRTMIN+2) - -def get_json_data(): - with open(json_file, 'r') as f: - j = json.load(f) - return j - -def get_statistics(): - return vyos.util.read_file(stats_file) - -def get_state_data(): - return vyos.util.read_file(state_file) - -def decode_state(code): - state = None - if code == 0: - state = "INIT" - elif code == 1: - state = "BACKUP" - elif code == 2: - state = "MASTER" - elif code == 3: - state = "FAULT" - else: - state = "UNKNOWN" - - return state - -## These functions are for the old, and hopefully obsolete plaintext -## (non machine-readable) data format introduced by Vyatta back in the days -## They are kept here just in case, if JSON output option turns out or becomes -## insufficient. - -def read_state_data(): - with open(state_file, 'r') as f: - lines = f.readlines() - return lines - -def parse_keepalived_data(data_lines): - vrrp_groups = {} - - # Scratch variable - group_name = None - - # Sadly there is no explicit end marker in that format, so we have - # only two states, one before the first VRRP instance is encountered - # and one after an instance/"group" was encountered - # We'll set group_name once the first group is encountered, - # and assume we are inside a group if it's set afterwards - # - # It may not be necessary since the keywords found inside groups and before - # the VRRP Topology section seem to have no intersection, - # but better safe than sorry. - - for line in data_lines: - if re.match(r'^\s*VRRP Instance', line, re.IGNORECASE): - # Example: "VRRP Instance = Foo" - name = re.match(r'^\s*VRRP Instance\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip() - group_name = name - vrrp_groups[name] = {} - elif re.match(r'^\s*State', line, re.IGNORECASE) and group_name: - # Example: " State = MASTER" - group_state = re.match(r'^\s*State\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip() - vrrp_groups[group_name]["state"] = group_state - elif re.match(r'^\s*Last transition', line, re.IGNORECASE) and group_name: - # Example: " Last transition = 1532043820 (Thu Jul 19 23:43:40 2018)" - trans_time = re.match(r'^\s*Last transition\s+=\s+(\d+)\s', line, re.IGNORECASE).groups()[0] - vrrp_groups[group_name]["last_transition"] = trans_time - elif re.match(r'^\s*Interface', line, re.IGNORECASE) and group_name: - # Example: " Interface = eth0.30" - interface = re.match(r'\s*Interface\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip() - vrrp_groups[group_name]["interface"] = interface - elif re.match(r'^\s*Virtual Router ID', line, re.IGNORECASE) and group_name: - # Example: " Virtual Router ID = 14" - vrid = re.match(r'^\s*Virtual Router ID\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip() - vrrp_groups[group_name]["vrid"] = vrid - elif re.match(r'^\s*------< Interfaces', line, re.IGNORECASE): - # Interfaces section appears to always be present, - # and there's nothing of interest for us below that section, - # so we use it as an end of input marker - break - - return vrrp_groups |