summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2020-04-17 08:11:31 +0200
committerGitHub <noreply@github.com>2020-04-17 08:11:31 +0200
commit75c7e2a5cea6713623208a4023455de64d13d7e1 (patch)
tree8be3e77575d316d428dfd1d570a7502257dea4b5
parent3964ee10a3a85e3655135d7e0235b4d1b2f08214 (diff)
parent7d04bfbcc74e062b80b337753e7018a6af81e70c (diff)
downloadvyos-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
-rw-r--r--op-mode-definitions/openvpn.xml6
-rw-r--r--op-mode-definitions/show-interfaces-pppoe.xml2
-rw-r--r--op-mode-definitions/show-interfaces-wirelessmodem.xml2
-rw-r--r--op-mode-definitions/wireless.xml16
-rw-r--r--python/vyos/ifconfig/__init__.py4
-rw-r--r--python/vyos/ifconfig/interface.py80
-rw-r--r--python/vyos/ifconfig/operational.py179
-rw-r--r--python/vyos/ifconfig/section.py18
-rw-r--r--python/vyos/ifconfig/vrrp.py145
-rw-r--r--python/vyos/ifconfig/wireguard.py239
-rw-r--r--python/vyos/keepalived.py140
-rwxr-xr-xsrc/conf_mode/vrrp.py21
-rwxr-xr-xsrc/op_mode/show_interfaces.py267
-rwxr-xr-xsrc/op_mode/vrrp.py87
-rwxr-xr-xsrc/op_mode/wireguard.py2
15 files changed, 787 insertions, 421 deletions
diff --git a/op-mode-definitions/openvpn.xml b/op-mode-definitions/openvpn.xml
index d7c4fc101..53c0157c6 100644
--- a/op-mode-definitions/openvpn.xml
+++ b/op-mode-definitions/openvpn.xml
@@ -87,7 +87,7 @@
<properties>
<help>Show detailed OpenVPN interface information</help>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf-type=openvpn --action=show</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=openvpn --action=show</command>
</leafNode>
</children>
</node>
@@ -98,13 +98,13 @@
<script>sudo ${vyos_completion_dir}/list_interfaces.py --type openvpn</script>
</completionHelp>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf=$4</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf=$4</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of specified OpenVPN interface information</help>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-interfaces-pppoe.xml b/op-mode-definitions/show-interfaces-pppoe.xml
index 591ec8f5b..e68d05da9 100644
--- a/op-mode-definitions/show-interfaces-pppoe.xml
+++ b/op-mode-definitions/show-interfaces-pppoe.xml
@@ -11,7 +11,7 @@
<script>${vyos_completion_dir}/list_pppoe_peers.sh</script>
</completionHelp>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl pppoe --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py pppoe --intf="$4"</command>
<children>
<node name="log">
<properties>
diff --git a/op-mode-definitions/show-interfaces-wirelessmodem.xml b/op-mode-definitions/show-interfaces-wirelessmodem.xml
index 681f54f3d..c17efbc4e 100644
--- a/op-mode-definitions/show-interfaces-wirelessmodem.xml
+++ b/op-mode-definitions/show-interfaces-wirelessmodem.xml
@@ -11,7 +11,7 @@
<script>${vyos_completion_dir}/list_wlm_peers.sh</script>
</completionHelp>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
<children>
<node name="log">
<properties>
diff --git a/op-mode-definitions/wireless.xml b/op-mode-definitions/wireless.xml
index c3c6dee59..a3a9d1f55 100644
--- a/op-mode-definitions/wireless.xml
+++ b/op-mode-definitions/wireless.xml
@@ -13,7 +13,7 @@
<properties>
<help>Clear all wireless interface counters</help>
</properties>
- <command>sudo ${vyatta_bindir}/sudo-users/vyatta-show-interfaces.pl --action=clear --intf-type="$3"</command>
+ <command>sudo ${vyos_op_scripts_dir}/show_interfaces.py --action=clear --intf-type="$3"</command>
</leafNode>
</children>
</node>
@@ -29,7 +29,7 @@
<properties>
<help>Clear all wireless interface counters</help>
</properties>
- <command>sudo ${vyatta_bindir}/sudo-users/vyatta-show-interfaces.pl --action=clear --intf="$4"</command>
+ <command>sudo ${vyos_op_scripts_dir}/show_interfaces.py --action=clear --intf="$4"</command>
</leafNode>
</children>
</tagNode>
@@ -45,13 +45,13 @@
<properties>
<help>Show wireless interface information</help>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf-type=wireless --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireless --action=show-brief</command>
<children>
<leafNode name="detail">
<properties>
<help>Show detailed wireless interface information</help>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf-type=wireless --action=show</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireless --action=show</command>
</leafNode>
<leafNode name="info">
<properties>
@@ -68,13 +68,13 @@
<script>${vyos_completion_dir}/list_interfaces.py --type wireless</script>
</completionHelp>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf="$4"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of the specified wireless interface information</help>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf="$4" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief</command>
</leafNode>
<node name="scan">
<properties>
@@ -100,13 +100,13 @@
<properties>
<help>Show specified virtual network interface (vif) information</help>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf="$4.$6"</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6"</command>
<children>
<leafNode name="brief">
<properties>
<help>Show summary of specified virtual network interface (vif) information</help>
</properties>
- <command>${vyatta_bindir}/vyatta-show-interfaces.pl --intf="$4.$6" --action=show-brief</command>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4.$6" --action=show-brief</command>
</leafNode>
</children>
</tagNode>
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
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py
index b9b0405e2..f358891a5 100755
--- a/src/conf_mode/vrrp.py
+++ b/src/conf_mode/vrrp.py
@@ -22,16 +22,13 @@ from json import dumps
from pathlib import Path
import vyos.config
-import vyos.keepalived
from vyos import ConfigError
from vyos.util import call
from vyos.template import render
+from vyos.ifconfig.vrrp import VRRP
-daemon_file = "/etc/default/keepalived"
-config_file = "/etc/keepalived/keepalived.conf"
-config_dict_path = "/run/keepalived_config.dict"
def get_config():
vrrp_groups = []
@@ -127,7 +124,7 @@ def get_config():
sync_groups.append(sync_group)
# create a file with dict with proposed configuration
- with open("{}.temp".format(config_dict_path), 'w') as dict_file:
+ with open("{}.temp".format(VRRP.location['vyos']), 'w') as dict_file:
dict_file.write(dumps({'vrrp_groups': vrrp_groups, 'sync_groups': sync_groups}))
return (vrrp_groups, sync_groups)
@@ -212,9 +209,9 @@ def generate(data):
# Filter out disabled groups
vrrp_groups = list(filter(lambda x: x["disable"] is not True, vrrp_groups))
- render(config_file, 'vrrp/keepalived.conf.tmpl',
- {"groups": vrrp_groups, "sync_groups": sync_groups})
- render(daemon_file, 'vrrp/daemon.tmpl', {})
+ render(VRRP.location['config'], 'vrrp/keepalived.conf.tmpl',
+ {"groups": vrrp_groups, "sync_groups": sync_groups})
+ render(VRRP.location['daemon'], 'vrrp/daemon.tmpl', {})
return None
@@ -223,12 +220,12 @@ def apply(data):
if vrrp_groups:
# safely rename a temporary file with configuration dict
try:
- dict_file = Path("{}.temp".format(config_dict_path))
- dict_file.rename(Path(config_dict_path))
+ dict_file = Path("{}.temp".format(VRRP.location['vyos']))
+ dict_file.rename(Path(VRRP.location['vyos']))
except Exception as err:
print("Unable to rename the file with keepalived config for FIFO pipe: {}".format(err))
- if not vyos.keepalived.vrrp_running():
+ if not VRRP.is_running():
print("Starting the VRRP process")
ret = call("sudo systemctl restart keepalived.service")
else:
@@ -241,7 +238,7 @@ def apply(data):
# VRRP is removed in the commit
print("Stopping the VRRP process")
call("sudo systemctl stop keepalived.service")
- os.unlink(config_file)
+ os.unlink(VRRP.location['daemon'])
return None
diff --git a/src/op_mode/show_interfaces.py b/src/op_mode/show_interfaces.py
new file mode 100755
index 000000000..8b6690b7d
--- /dev/null
+++ b/src/op_mode/show_interfaces.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python3
+
+# Copyright 2017, 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 re
+import sys
+import datetime
+import argparse
+import netifaces
+
+from vyos.ifconfig import Section
+from vyos.ifconfig import Interface
+from vyos.ifconfig import VRRP
+from vyos.util import cmd
+
+
+# interfaces = Sections.reserved()
+interfaces = ['eno', 'ens', 'enp', 'enx', 'eth', 'vmnet', 'lo', 'tun', 'wan', 'pppoe', 'pppoa', 'adsl']
+glob_ifnames = '/sys/class/net/({})*'.format('|'.join(interfaces))
+
+
+actions = {}
+def register (name):
+ """
+ decorator to register a function into actions with a name
+ it allows to use actions[name] to call the registered function
+ """
+ def _register(function):
+ actions[name] = function
+ return function
+ return _register
+
+
+def filtered_interfaces(ifnames, iftypes, vif, vrrp):
+ """
+ get all the interfaces from the OS and returns them
+ ifnames can be used to filter which interfaces should be considered
+
+ ifnames: a list of interfaces names to consider, empty do not filter
+ return an instance of the interface class
+ """
+ allnames = Section.interfaces()
+ allnames.sort()
+
+ vrrp_interfaces = VRRP.active_interfaces() if vrrp else []
+
+ for ifname in allnames:
+ if ifnames and ifname not in ifnames:
+ continue
+
+ # return the class which can handle this interface name
+ klass = Section.klass(ifname)
+ # connect to the interface
+ interface = klass(ifname, create=False, debug=False)
+
+ if iftypes and interface.definition['section'] not in iftypes:
+ continue
+
+ if vif and not '.' in ifname:
+ continue
+
+ if vrrp and ifname not in vrrp_interfaces:
+ continue
+
+ yield interface
+
+
+def split_text(text, used=0):
+ """
+ take a string and attempt to split it to fit with the width of the screen
+
+ text: the string to split
+ used: number of characted already used in the screen
+ """
+ returned = cmd('stty size')
+ if len(returned) == 2:
+ rows, columns = [int(_) for _ in returned]
+ else:
+ rows, columns = (40, 80)
+
+ desc_len = columns - used
+
+ line = ''
+ for word in text.split():
+ if len(line) + len(word) >= desc_len:
+ yield f'{line} {word}'[1:]
+ line = ''
+ line = f'{line} {word}'
+ yield line[1:]
+
+
+def get_vrrp_intf():
+ return [intf for intf in Section.interfaces() if intf.is_vrrp()]
+
+
+def get_counter_val(clear, now):
+ """
+ attempt to correct a counter if it wrapped, copied from perl
+
+ clear: previous counter
+ now: the current counter
+ """
+ # This function has to deal with both 32 and 64 bit counters
+ if clear == 0:
+ return now
+
+ # device is using 64 bit values assume they never wrap
+ value = now - clear
+ if (now >> 32) != 0:
+ return value
+
+ # The counter has rolled. If the counter has rolled
+ # multiple times since the clear value, then this math
+ # is meaningless.
+ if (value < 0):
+ value = (4294967296 - clear) + now
+
+ return value
+
+
+@register('help')
+def usage(*args):
+ print(f"Usage: {sys.argv[0]} [intf=NAME|intf-type=TYPE|vif|vrrp] action=ACTION")
+ print(f" NAME = " + ' | '.join(Section.interfaces()))
+ print(f" TYPE = " + ' | '.join(Section.sections()))
+ print(f" ACTION = " + ' | '.join(actions))
+ sys.exit(1)
+
+
+@register('allowed')
+def run_allowed(**kwarg):
+ sys.stdout.write(' '.join(Section.interfaces()))
+
+
+@register('show')
+def run_show_intf(ifnames, iftypes, vif, vrrp):
+ for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
+ cache = interface.operational.load_counters()
+
+ out = cmd(f'ip addr show {interface.ifname}')
+ out = re.sub(f'^\d+:\s+','',out)
+ if re.search("link/tunnel6", out):
+ tunnel = cmd(f'ip -6 tun show {interface.ifname}')
+ # tun0: ip/ipv6 remote ::2 local ::1 encaplimit 4 hoplimit 64 tclass inherit flowlabel inherit (flowinfo 0x00000000)
+ tunnel = re.sub('.*encap', 'encap', tunnel)
+ out = re.sub('(\n\s+)(link/tunnel6)', f'\g<1>{tunnel}\g<1>\g<2>', out)
+
+ print(out)
+
+ timestamp = int(cache.get('timestamp', 0))
+ if timestamp:
+ when = interface.operational.strtime(timestamp)
+ print(f' Last clear: {when}')
+
+ description = interface.get_alias()
+ if description:
+ print(f' Description: {description}')
+
+ print()
+ print(interface.operational.formated_stats())
+
+
+@register('show-brief')
+def run_show_intf_brief(ifnames, iftypes, vif, vrrp):
+ format1 = '%-16s %-33s %-4s %s'
+ format2 = '%-16s %s'
+
+ print('Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down')
+ print(format1 % ("Interface", "IP Address", "S/L", "Description"))
+ print(format1 % ("---------", "----------", "---", "-----------"))
+
+ for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
+ oper_state = interface.operational.get_state()
+ admin_state = interface.get_admin_state()
+
+ intf = [interface.ifname,]
+ oper = ['u', ] if oper_state in ('up', 'unknown') else ['A', ]
+ admin = ['u', ] if oper_state in ('up', 'unknown') else ['D', ]
+ addrs = [_ for _ in interface.get_addr() if not _.startswith('fe80::')] or ['-', ]
+ # do not ask me why 56, it was the number in the perl code ...
+ descs = list(split_text(interface.get_alias(),56))
+
+ while intf or oper or admin or addrs or descs:
+ i = intf.pop(0) if intf else ''
+ a = addrs.pop(0) if addrs else ''
+ d = descs.pop(0) if descs else ''
+ s = [oper.pop(0)] if oper else []
+ l = [admin.pop(0)] if admin else []
+ if len(a) < 33:
+ print(format1 % (i, a, '/'.join(s+l), d))
+ else:
+ print(format2 % (i, a))
+ print(format1 % ('', '', '/'.join(s+l), d))
+
+
+@register('show-count')
+def run_show_counters(ifnames, iftypes, vif, vrrp):
+ formating = '%-12s %10s %10s %10s %10s'
+ print(formating % ('Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes'))
+
+ for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
+ oper = interface.operational.get_state()
+
+ if oper not in ('up','unknown'):
+ continue
+
+ stats = interface.operational.get_stats()
+ cache = interface.operational.load_counters()
+ print(formating % (
+ interface.ifname,
+ get_counter_val(cache['rx_packets'], stats['rx_packets']),
+ get_counter_val(cache['rx_bytes'], stats['rx_bytes']),
+ get_counter_val(cache['tx_packets'], stats['tx_packets']),
+ get_counter_val(cache['tx_bytes'], stats['tx_bytes']),
+ ))
+
+
+@register('clear')
+def run_clear_intf(intf, iftypes, vif, vrrp):
+ for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
+ print(f'Clearing {interface.ifname}')
+ interface = Interface(ifname, create=False, debug=False)
+ interface.operational.clear_counters()
+
+
+@register('reset')
+def run_reset_intf(intf, iftypes, vif, vrrp):
+ for interface in filtered_interfaces(ifnames, iftypes, vif, vrrp):
+ interface = Interface(ifname, create=False, debug=False)
+ interface.operational.reset_counters()
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(add_help=False, description='Show interface information')
+ parser.add_argument('--intf', action="store", type=str, default='', help='only show the specified interface(s)')
+ parser.add_argument('--intf-type', action="store", type=str, default='', help='only show the specified interface type')
+ parser.add_argument('--action', action="store", type=str, default='show', help='action to perform')
+ parser.add_argument('--vif', action='store_true', default=False, help="only show vif interfaces")
+ parser.add_argument('--vrrp', action='store_true', default=False, help="only show vrrp interfaces")
+ parser.add_argument('--help', action='store_true', default=False, help="show help")
+
+ args = parser.parse_args()
+
+ def missing(*args):
+ print('Invalid action [{args.action}]')
+ usage()
+
+ actions.get(args.action, missing)(
+ [_ for _ in args.intf.split(' ') if _],
+ [_ for _ in args.intf_type.split(' ') if _],
+ args.vif,
+ args.vrrp
+ )
diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py
index 8a993f92c..923cfa4d4 100755
--- a/src/op_mode/vrrp.py
+++ b/src/op_mode/vrrp.py
@@ -24,81 +24,8 @@ import tabulate
import vyos.keepalived
import vyos.util
-config_dict_path = '/run/keepalived_config.dict'
-
-
-# get disabled instances from a config
-def vrrp_get_disabled():
- # read the dictionary file with configuration
- with open(config_dict_path, 'r') as dict_file:
- vrrp_config_dict = json.load(dict_file)
- vrrp_disabled = []
- # add disabled groups to the list
- for vrrp_group in vrrp_config_dict['vrrp_groups']:
- if vrrp_group['disable']:
- vrrp_disabled.append([vrrp_group['name'], vrrp_group['interface'], vrrp_group['vrid'], 'DISABLED', ''])
- # return list with disabled instances
- return vrrp_disabled
-
-
-def print_summary():
- try:
- vyos.keepalived.force_json_dump()
- # Wait for keepalived to produce the data
- # Replace with inotify or similar if it proves problematic
- time.sleep(0.2)
- json_data = vyos.keepalived.get_json_data()
- vyos.keepalived.remove_vrrp_data("json")
- except:
- print("VRRP information is not available")
- sys.exit(1)
-
- groups = []
- for group in json_data:
- data = group["data"]
-
- name = data["iname"]
-
- ltrans_timestamp = float(data["last_transition"])
- ltrans_time = vyos.util.seconds_to_human(int(time.time() - ltrans_timestamp))
-
- interface = data["ifp_ifname"]
- vrid = data["vrid"]
-
- state = vyos.keepalived.decode_state(data["state"])
-
- row = [name, interface, vrid, state, ltrans_time]
- groups.append(row)
-
- # add to the active list disabled instances
- groups.extend(vrrp_get_disabled())
- headers = ["Name", "Interface", "VRID", "State", "Last Transition"]
- output = tabulate.tabulate(groups, headers)
- print(output)
-
-
-def print_statistics():
- try:
- vyos.keepalived.force_stats_dump()
- time.sleep(0.2)
- output = vyos.keepalived.get_statistics()
- print(output)
- vyos.keepalived.remove_vrrp_data("stats")
- except:
- print("VRRP statistics are not available")
- sys.exit(1)
-
-
-def print_state_data():
- try:
- vyos.keepalived.force_state_data_dump()
- time.sleep(0.2)
- output = vyos.keepalived.get_state_data()
- print(output)
- vyos.keepalived.remove_vrrp_data("state")
- except:
- print("VRRP information is not available")
- sys.exit(1)
+from vyos.ifconfig.vrrp import VRRP
+from vyos.ifconfig.vrrp import VRRPError
parser = argparse.ArgumentParser()
@@ -110,16 +37,16 @@ group.add_argument("-d", "--data", action="store_true", help="Print detailed VRR
args = parser.parse_args()
# Exit early if VRRP is dead or not configured
-if not vyos.keepalived.vrrp_running():
- print("VRRP is not running")
+if not VRRP.is_running():
+ print('VRRP is not running')
sys.exit(0)
if args.summary:
- print_summary()
+ print(VRRP.format(VRRP.collect('json')))
elif args.statistics:
- print_statistics()
+ print(VRRP.collect('stats'))
elif args.data:
- print_state_data()
+ print(VRRP.collect('state'))
else:
parser.print_help()
sys.exit(1)
diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py
index 1b90f4fa7..297ba599d 100755
--- a/src/op_mode/wireguard.py
+++ b/src/op_mode/wireguard.py
@@ -148,7 +148,7 @@ if __name__ == '__main__':
list_key_dirs()
if args.showinterface:
intf = WireGuardIf(args.showinterface, create=False, debug=False)
- intf.op_show_interface()
+ print(intf.operational.show_interface())
if args.delkdir:
if args.location:
del_key_dir(args.location)