summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--interface-definitions/include/interface-description.xml.i4
-rw-r--r--interface-definitions/include/interface-vrf.xml.i12
-rw-r--r--interface-definitions/include/radius-server.xml.i56
-rw-r--r--interface-definitions/include/vif.xml.i1
-rw-r--r--interface-definitions/interfaces-bonding.xml.in1
-rw-r--r--interface-definitions/interfaces-bridge.xml.in1
-rw-r--r--interface-definitions/interfaces-dummy.xml.in1
-rw-r--r--interface-definitions/interfaces-ethernet.xml.in1
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in1
-rw-r--r--interface-definitions/interfaces-pseudo-ethernet.xml.in1
-rw-r--r--interface-definitions/interfaces-wireless.xml.in38
-rw-r--r--interface-definitions/system-login.xml.in49
-rw-r--r--interface-definitions/vrf.xml.in47
-rw-r--r--op-mode-definitions/show-vrf.xml22
-rw-r--r--python/setup.py2
-rw-r--r--python/vyos/configdict.py7
-rw-r--r--python/vyos/ifconfig.py2158
-rw-r--r--python/vyos/ifconfig/__init__.py29
-rw-r--r--python/vyos/ifconfig/bond.py270
-rw-r--r--python/vyos/ifconfig/bridge.py180
-rw-r--r--python/vyos/ifconfig/control.py143
-rw-r--r--python/vyos/ifconfig/dummy.py32
-rw-r--r--python/vyos/ifconfig/ethernet.py234
-rw-r--r--python/vyos/ifconfig/geneve.py60
-rw-r--r--python/vyos/ifconfig/interface.py836
-rw-r--r--python/vyos/ifconfig/l2tpv3.py108
-rw-r--r--python/vyos/ifconfig/loopback.py50
-rw-r--r--python/vyos/ifconfig/macvlan.py61
-rw-r--r--python/vyos/ifconfig/stp.py69
-rw-r--r--python/vyos/ifconfig/vlan.py134
-rw-r--r--python/vyos/ifconfig/vxlan.py91
-rw-r--r--python/vyos/ifconfig/wireguard.py169
-rw-r--r--python/vyos/ifconfig_vlan.py52
-rw-r--r--python/vyos/validate.py2
-rwxr-xr-xscripts/build-command-op-templates2
-rwxr-xr-xsrc/conf_mode/interfaces-bonding.py41
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py17
-rwxr-xr-xsrc/conf_mode/interfaces-dummy.py18
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py39
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py149
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py54
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py7
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py46
-rwxr-xr-xsrc/conf_mode/system-login-banner.py19
-rwxr-xr-xsrc/conf_mode/system-login.py8
-rwxr-xr-xsrc/conf_mode/vrf.py281
-rwxr-xr-xsrc/etc/ppp/ip-pre-up51
-rwxr-xr-xsrc/migration-scripts/quagga/3-to-425
-rwxr-xr-xsrc/migration-scripts/quagga/4-to-563
-rwxr-xr-xsrc/op_mode/reset_openvpn.py1
-rwxr-xr-xsrc/op_mode/show_vrf.py67
52 files changed, 3469 insertions, 2342 deletions
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index d8388eecc..5bb7ea507 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -1,5 +1,6 @@
etc/dhcp
etc/init.d
+etc/ppp
etc/rsyslog.d
etc/systemd
etc/vyos
diff --git a/interface-definitions/include/interface-description.xml.i b/interface-definitions/include/interface-description.xml.i
index 7a7a37871..961533e26 100644
--- a/interface-definitions/include/interface-description.xml.i
+++ b/interface-definitions/include/interface-description.xml.i
@@ -1,9 +1,9 @@
<leafNode name="description">
<properties>
- <help>Interface description</help>
+ <help>Interface specific description</help>
<constraint>
<regex>.{1,256}$</regex>
</constraint>
- <constraintErrorMessage>Interface description too long (limit 256 characters)</constraintErrorMessage>
+ <constraintErrorMessage>Description too long (limit 256 characters)</constraintErrorMessage>
</properties>
</leafNode>
diff --git a/interface-definitions/include/interface-vrf.xml.i b/interface-definitions/include/interface-vrf.xml.i
new file mode 100644
index 000000000..355e7f0f3
--- /dev/null
+++ b/interface-definitions/include/interface-vrf.xml.i
@@ -0,0 +1,12 @@
+<leafNode name="vrf">
+ <properties>
+ <help>VRF instance name</help>
+ <valueHelp>
+ <format>text</format>
+ <description>VRF instance name</description>
+ </valueHelp>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+</leafNode>
diff --git a/interface-definitions/include/radius-server.xml.i b/interface-definitions/include/radius-server.xml.i
new file mode 100644
index 000000000..d1068b0e4
--- /dev/null
+++ b/interface-definitions/include/radius-server.xml.i
@@ -0,0 +1,56 @@
+<node name="radius">
+ <properties>
+ <help>RADIUS based user authentication</help>
+ </properties>
+ <children>
+ <leafNode name="source-address">
+ <properties>
+ <help>RADIUS client source address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>TFTP IPv4 listen address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <tagNode name="server">
+ <properties>
+ <help>RADIUS server configuration</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>RADIUS server IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="disable">
+ <properties>
+ <help>Temporary disable this server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="key">
+ <properties>
+ <help>Shared secret key</help>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Authentication port</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port (default: 1812)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+</node>
diff --git a/interface-definitions/include/vif.xml.i b/interface-definitions/include/vif.xml.i
index 85e901852..88693e0d3 100644
--- a/interface-definitions/include/vif.xml.i
+++ b/interface-definitions/include/vif.xml.i
@@ -16,6 +16,7 @@
#include <include/dhcp-dhcpv6-options.xml.i>
#include <include/interface-disable-link-detect.xml.i>
#include <include/interface-disable.xml.i>
+ #include <include/interface-vrf.xml.i>
<leafNode name="egress-qos">
<properties>
<help>VLAN egress QoS</help>
diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in
index 586a8437d..80943a1fd 100644
--- a/interface-definitions/interfaces-bonding.xml.in
+++ b/interface-definitions/interfaces-bonding.xml.in
@@ -53,6 +53,7 @@
#include <include/dhcp-dhcpv6-options.xml.i>
#include <include/interface-disable-link-detect.xml.i>
#include <include/interface-disable.xml.i>
+ #include <include/interface-vrf.xml.i>
<leafNode name="hash-policy">
<properties>
<help>Bonding transmit hash policy</help>
diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in
index e8285b16c..d36a1abbc 100644
--- a/interface-definitions/interfaces-bridge.xml.in
+++ b/interface-definitions/interfaces-bridge.xml.in
@@ -37,6 +37,7 @@
#include <include/dhcp-dhcpv6-options.xml.i>
#include <include/interface-disable-link-detect.xml.i>
#include <include/interface-disable.xml.i>
+ #include <include/interface-vrf.xml.i>
<leafNode name="forwarding-delay">
<properties>
<help>Forwarding delay</help>
diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in
index 39809a610..5229e602a 100644
--- a/interface-definitions/interfaces-dummy.xml.in
+++ b/interface-definitions/interfaces-dummy.xml.in
@@ -19,6 +19,7 @@
#include <include/address-ipv4-ipv6.xml.i>
#include <include/interface-description.xml.i>
#include <include/interface-disable.xml.i>
+ #include <include/interface-vrf.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in
index 8f5d7355b..5728d2f37 100644
--- a/interface-definitions/interfaces-ethernet.xml.in
+++ b/interface-definitions/interfaces-ethernet.xml.in
@@ -31,6 +31,7 @@
</leafNode>
#include <include/interface-disable-link-detect.xml.i>
#include <include/interface-disable.xml.i>
+ #include <include/interface-vrf.xml.i>
<leafNode name="duplex">
<properties>
<help>Duplex mode</help>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index b6b54c915..bbaff5f04 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -75,6 +75,7 @@
</leafNode>
#include <include/interface-description.xml.i>
#include <include/interface-disable.xml.i>
+ #include <include/interface-vrf.xml.i>
<leafNode name="idle-timeout">
<properties>
<help>Delay before disconnecting idle session (in seconds)</help>
diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in
index c2dea438a..e6e8fd20c 100644
--- a/interface-definitions/interfaces-pseudo-ethernet.xml.in
+++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in
@@ -21,6 +21,7 @@
#include <include/dhcp-dhcpv6-options.xml.i>
#include <include/interface-disable-link-detect.xml.i>
#include <include/interface-disable.xml.i>
+ #include <include/interface-vrf.xml.i>
<node name="ip">
<children>
#include <include/interface-arp-cache-timeout.xml.i>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index d6b257978..8632bb881 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -454,6 +454,7 @@
</leafNode>
#include <include/interface-disable-link-detect.xml.i>
#include <include/interface-disable.xml.i>
+ #include <include/interface-vrf.xml.i>
<leafNode name="expunge-failing-stations">
<properties>
<help>Disassociate stations based on excessive transmission failures</help>
@@ -666,28 +667,10 @@
<constraintErrorMessage>Invalid WPA pass phrase, must be 8 to 63 printable characters!</constraintErrorMessage>
</properties>
</leafNode>
+ #include <include/radius-server.xml.i>
<node name="radius">
- <properties>
- <help>RADIUS specific configuration</help>
- </properties>
<children>
- <leafNode name="source-address">
- <properties>
- <help>RADIUS client forced local IP address</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address of RADIUS server</description>
- </valueHelp>
- </properties>
- </leafNode>
<tagNode name="server">
- <properties>
- <help>IP address of RADIUS server</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address of RADIUS server</description>
- </valueHelp>
- </properties>
<children>
<leafNode name="accounting">
<properties>
@@ -695,23 +678,6 @@
<valueless/>
</properties>
</leafNode>
- <leafNode name="port">
- <properties>
- <help>RADIUS server port (default: 1812)</help>
- <valueHelp>
- <format>1-65535</format>
- <description>RADIUS server port</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-65535"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="key">
- <properties>
- <help>RADIUS shared secret key</help>
- </properties>
- </leafNode>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in
index 3ed85b8d3..2499a192c 100644
--- a/interface-definitions/system-login.xml.in
+++ b/interface-definitions/system-login.xml.in
@@ -110,58 +110,11 @@
</leafNode>
</children>
</tagNode>
+ #include <include/radius-server.xml.i>
<node name="radius">
- <properties>
- <help>RADIUS based user authentication</help>
- </properties>
<children>
- <leafNode name="source-address">
- <properties>
- <help>RADIUS client source address</help>
- <valueHelp>
- <format>ipv4</format>
- <description>TFTP IPv4 listen address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
- </leafNode>
<tagNode name="server">
- <properties>
- <help>RADIUS server configuration</help>
- <valueHelp>
- <format>ipv4</format>
- <description>RADIUS server IPv4 address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
<children>
- <leafNode name="disable">
- <properties>
- <help>Temporary disable this server</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="key">
- <properties>
- <help>Shared secret key</help>
- </properties>
- </leafNode>
- <leafNode name="port">
- <properties>
- <help>Authentication port</help>
- <valueHelp>
- <format>1-65535</format>
- <description>Numeric IP port (default: 1812)</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-65535"/>
- </constraint>
- </properties>
- </leafNode>
<leafNode name="timeout">
<properties>
<help>Session timeout</help>
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
new file mode 100644
index 000000000..f1895598e
--- /dev/null
+++ b/interface-definitions/vrf.xml.in
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="vrf" owner="${vyos_conf_scripts_dir}/vrf.py">
+ <properties>
+ <help>Virtual Routing and Forwarding</help>
+ <!-- must be before any interface creation -->
+ <priority>210</priority>
+ </properties>
+ <children>
+ <leafNode name="bind-to-all">
+ <properties>
+ <help>Enable binding services to all VRFs</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <tagNode name="name">
+ <properties>
+ <help>VRF instance name</help>
+ <constraint>
+ <regex>[^/\s]{1,16}$</regex>
+ </constraint>
+ <constraintErrorMessage>VRF instance name must be 16 characters or less</constraintErrorMessage>
+ <valueHelp>
+ <format>name</format>
+ <description>Instance name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="table">
+ <properties>
+ <help>Routing table associated with this instance</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ <constraintErrorMessage>Invalid kernel table number</constraintErrorMessage>
+ <valueHelp>
+ <format>1-2147483647</format>
+ <description>Routing table ID</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ #include <include/interface-description.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition> \ No newline at end of file
diff --git a/op-mode-definitions/show-vrf.xml b/op-mode-definitions/show-vrf.xml
new file mode 100644
index 000000000..360153d8e
--- /dev/null
+++ b/op-mode-definitions/show-vrf.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="vrf">
+ <properties>
+ <help>Show VRF information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_vrf.py -e</command>
+ </node>
+ <tagNode name="vrf">
+ <properties>
+ <help>Show information on specific VRF instance</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_vrf.py -e "$3"</command>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/setup.py b/python/setup.py
index 304ea5cb7..ac7d0b573 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -10,7 +10,7 @@ setup(
license = "LGPLv2+",
keywords = "vyos",
url = "http://www.vyos.io",
- packages=['vyos'],
+ packages=["vyos","vyos.ifconfig"],
long_description="VyOS configuration libraries",
classifiers=[
"Development Status :: 4 - Beta",
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 80e199907..a1499479a 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -126,7 +126,8 @@ def vlan_to_dict(conf):
'ingress_qos': '',
'ingress_qos_changed': False,
'mac': '',
- 'mtu': 1500
+ 'mtu': 1500,
+ 'vrf': ''
}
# retrieve configured interface addresses
if conf.exists('address'):
@@ -194,6 +195,10 @@ def vlan_to_dict(conf):
if conf.exists('mtu'):
vlan['mtu'] = int(conf.return_value('mtu'))
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ vlan['vrf'] = conf.return_value('vrf')
+
# VLAN egress QoS
if conf.exists('egress-qos'):
vlan['egress_qos'] = conf.return_value('egress-qos')
diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py
deleted file mode 100644
index beeafa420..000000000
--- a/python/vyos/ifconfig.py
+++ /dev/null
@@ -1,2158 +0,0 @@
-# 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 re
-import jinja2
-import json
-import glob
-import time
-from copy import deepcopy
-
-import vyos.interfaces
-
-from vyos.validate import *
-from vyos.config import Config
-from vyos import ConfigError
-
-from ipaddress import IPv4Network, IPv6Address
-from netifaces import ifaddresses, AF_INET, AF_INET6
-from subprocess import Popen, PIPE, STDOUT
-from time import sleep
-from os.path import isfile
-from tabulate import tabulate
-from hurry.filesize import size,alternative
-from datetime import timedelta
-
-dhclient_base = r'/var/lib/dhcp/dhclient_'
-dhcp_cfg = """
-# generated by ifconfig.py
-option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
-timeout 60;
-retry 300;
-
-interface "{{ intf }}" {
- send host-name "{{ hostname }}";
- {% if client_id -%}
- send dhcp-client-identifier "{{ client_id }}";
- {% endif -%}
- {% if vendor_class_id -%}
- send vendor-class-identifier "{{ vendor_class_id }}";
- {% endif -%}
- request subnet-mask, broadcast-address, routers, domain-name-servers,
- rfc3442-classless-static-routes, domain-name, interface-mtu;
- require subnet-mask;
-}
-
-"""
-
-dhcpv6_cfg = """
-# generated by ifconfig.py
-interface "{{ intf }}" {
- request routers, domain-name-servers, domain-name;
-}
-
-"""
-
-class Control:
- _command_get = {}
- _command_set = {}
-
- def _debug_msg(self, msg):
- if os.path.isfile('/tmp/vyos.ifconfig.debug'):
- print('DEBUG/{:<6} {}'.format(self.config['ifname'], msg))
-
- def _cmd(self, command):
- p = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
- tmp = p.communicate()[0].strip()
- self._debug_msg("cmd '{}'".format(command))
- if tmp.decode():
- self._debug_msg("returned:\n{}".format(tmp.decode()))
-
- # do we need some error checking code here?
- return tmp.decode()
-
- def _get_command(self, config, name):
- """
- Using the defined names, set data write to sysfs.
- """
- cmd = self._command_get[name]['shellcmd'].format(**config)
- return self._cmd(cmd)
-
- def _set_command(self, config, name, value):
- """
- Using the defined names, set data write to sysfs.
- """
- if not value:
- return None
-
- # the code can pass int as int
- value = str(value)
-
- validate = self._command_set[name].get('validate', None)
- if validate:
- validate(value)
-
- config = {**config, **{'value': value}}
-
- convert = self._command_set[name].get('convert', None)
- if convert:
- value = convert(value)
-
- cmd = self._command_set[name]['shellcmd'].format(**config)
- return self._cmd(cmd)
-
- _sysfs_get = {}
- _sysfs_set = {}
-
- def _read_sysfs(self, filename):
- """
- Provide a single primitive w/ error checking for reading from sysfs.
- """
- value = None
- with open(filename, 'r') as f:
- value = f.read().rstrip('\n')
-
- self._debug_msg("read '{}' < '{}'".format(value, filename))
- return value
-
- def _write_sysfs(self, filename, value):
- """
- Provide a single primitive w/ error checking for writing to sysfs.
- """
- self._debug_msg("write '{}' > '{}'".format(value, filename))
- if os.path.isfile(filename):
- with open(filename, 'w') as f:
- f.write(str(value))
- return True
- return False
-
- def _get_sysfs(self, config, name):
- """
- Using the defined names, get data write from sysfs.
- """
- filename = self._sysfs_get[name]['location'].format(config)
- if not filename:
- return None
- return self._read_sysfs(filename)
-
- def _set_sysfs(self, config, name, value):
- """
- Using the defined names, set data write to sysfs.
- """
- if not value:
- return None
-
- # the code can pass int as int
- value = str(value)
-
- validate = self._sysfs_set[name].get('validate', None)
- if validate:
- validate(value)
-
- config = {**config, **{'value': value}}
-
- convert = self._sysfs_set[name].get('convert', None)
- if convert:
- value = convert(value)
-
- commited = self._write_sysfs(self._sysfs_set[name]['location'].format(**config), value)
- if not commited:
- errmsg = self._sysfs_set.get('errormsg','')
- if errmsg:
- raise TypeError(errmsg.format(**config))
- return commited
-
- def get_interface(self, name):
- if name in self._sysfs_get:
- return self._get_sysfs(self.config, name)
- if name in self._command_get:
- return self._get_command(self.config, name)
- raise KeyError(f'{name} is not a attribute of the interface we can get')
-
- def set_interface(self, name, value):
- if name in self._sysfs_set:
- return self._set_sysfs(self.config, name, value)
- if name in self._command_set:
- return self._set_command(self.config, name, value)
- raise KeyError(f'{name} is not a attribute of the interface we can set')
-
-
-class Interface(Control):
- options = []
- required = []
- default = {
- 'type': '',
- }
-
- _command_set = {
- 'mac': {
- 'validate': assert_mac,
- 'shellcmd': 'ip link set dev {ifname} address {value}',
- },
- }
-
- _sysfs_get = {
- 'mtu': {
- 'location': '/sys/class/net/{ifname}/mtu',
- },
- }
-
- _sysfs_set = {
- 'alias': {
- 'convert': lambda name: name if name else '\0',
- 'location': '/sys/class/net/{ifname}/ifalias',
- },
- 'mtu': {
- 'validate': assert_mtu,
- 'location': '/sys/class/net/{ifname}/mtu',
- },
- 'arp_cache_tmo': {
- 'convert': lambda tmo: (int(tmo) * 1000),
- 'location': '/proc/sys/net/ipv4/neigh/{ifname}/base_reachable_time_ms',
- },
- 'arp_filter': {
- 'validate': assert_boolean,
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_filter',
- },
- 'arp_accept': {
- 'validate': lambda arp: assert_range(arp,0,2),
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_accept',
- },
- 'arp_announce': {
- 'validate': assert_boolean,
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_announce',
- },
- 'arp_ignore': {
- 'validate': assert_boolean,
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_ignore',
- },
- 'proxy_arp': {
- 'validate': assert_boolean,
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp',
- },
- 'proxy_arp_pvlan': {
- 'validate': assert_boolean,
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp_pvlan',
- },
- # link_detect vs link_filter name weirdness
- 'link_detect': {
- 'validate': lambda link: assert_range(link,0,3),
- 'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter',
- },
- }
-
- def __init__(self, ifname, **kargs):
- """
- This is the base interface class which supports basic IP/MAC address
- operations as well as DHCP(v6). Other interface which represent e.g.
- and ethernet bridge are implemented as derived classes adding all
- additional functionality.
-
- For creation you will need to provide the interface type, otherwise
- the existing interface is used
-
- DEBUG:
- This class has embedded debugging (print) which can be enabled by
- creating the following file:
- vyos@vyos# touch /tmp/vyos.ifconfig.debug
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> i = Interface('eth0')
- """
-
- self.config = deepcopy(self.default)
- self.config['ifname'] = ifname
-
- for k in kargs:
- if k not in self.options:
- raise ConfigError('invalid option {} for {}'.format(k,self.__class__))
- self.config[k] = kargs[k]
-
- for k in self.required:
- if k not in kargs:
- raise ConfigError('missing required option {} for {}'.format(k,self.__class__))
-
- if not os.path.exists('/sys/class/net/{}'.format(self.config['ifname'])):
- if not self.config['type']:
- raise Exception('interface "{}" not found'.format(self.config['ifname']))
- self._create()
-
- # per interface DHCP config files
- self._dhcp_cfg_file = dhclient_base + self.config['ifname'] + '.conf'
- self._dhcp_pid_file = dhclient_base + self.config['ifname'] + '.pid'
- self._dhcp_lease_file = dhclient_base + self.config['ifname'] + '.leases'
-
- # per interface DHCPv6 config files
- self._dhcpv6_cfg_file = dhclient_base + self.config['ifname'] + '.v6conf'
- self._dhcpv6_pid_file = dhclient_base + self.config['ifname'] + '.v6pid'
- self._dhcpv6_lease_file = dhclient_base + self.config['ifname'] + '.v6leases'
-
- # DHCP options
- self._dhcp_options = {
- 'intf' : self.config['ifname'],
- 'hostname' : '',
- 'client_id' : '',
- 'vendor_class_id' : ''
- }
-
- # DHCPv6 options
- self._dhcpv6_options = {
- 'intf' : self.config['ifname'],
- 'dhcpv6_prm_only' : False,
- 'dhcpv6_temporary' : False
- }
-
- # list of assigned IP addresses
- self._addr = []
-
- def _create(self):
- cmd = 'ip link add dev {ifname} type {type}'.format(**self.config)
- self._cmd(cmd)
-
- def remove(self):
- """
- Remove interface from operating system. Removing the interface
- deconfigures all assigned IP addresses and clear possible DHCP(v6)
- client processes.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> i = Interface('eth0')
- >>> i.remove()
- """
- # stop DHCP(v6) if running
- self._del_dhcp()
- self._del_dhcpv6()
-
- # remove all assigned IP addresses from interface - this is a bit redundant
- # as the kernel will remove all addresses on interface deletion, but we
- # can not delete ALL interfaces, see below
- for addr in self.get_addr():
- self.del_addr(addr)
-
- # ---------------------------------------------------------------------
- # A code refactoring is required as this type check is present as
- # Interface implement behaviour for one of it's sub-class.
-
- # It is required as the current pattern for vlan is:
- # Interface('name').remove() to delete an interface
- # The code should be modified to have a class method called connect and
- # have Interface.connect('name').remove()
-
- # each subclass should register within Interface the pattern for that
- # interface ie: (ethX, etc.) and use this to create an instance of
- # the right class (EthernetIf, ...)
-
- # Ethernet interfaces can not be removed
- if self.__class__ == EthernetIf:
- return
-
- # ---------------------------------------------------------------------
-
- self._delete()
-
- def _delete(self):
- # NOTE (Improvement):
- # after interface removal no other commands should be allowed
- # to be called and instead should raise an Exception:
- cmd = 'ip link del dev {}'.format(self.config['ifname'])
- return self._cmd(cmd)
-
- def get_mtu(self):
- """
- Get/set interface mtu in bytes.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').get_mtu()
- '1500'
- """
- return self.get_interface('mtu')
-
- def set_mtu(self, mtu):
- """
- Get/set interface mtu in bytes.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_mtu(1400)
- >>> Interface('eth0').get_mtu()
- '1400'
- """
- return self.set_interface('mtu', mtu)
-
- def set_mac(self, mac):
- """
- Set interface MAC (Media Access Contrl) address to given value.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_mac('00:50:ab:cd:ef:01')
- """
- self.set_interface('mac', mac)
-
- def set_arp_cache_tmo(self, tmo):
- """
- Set ARP cache timeout value in seconds. Internal Kernel representation
- is in milliseconds.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_arp_cache_tmo(40)
- """
- return self.set_interface('arp_cache_tmo', tmo)
-
- def set_arp_filter(self, arp_filter):
- """
- Filter ARP requests
-
- 1 - Allows you to have multiple network interfaces on the same
- subnet, and have the ARPs for each interface be answered
- based on whether or not the kernel would route a packet from
- the ARP'd IP out that interface (therefore you must use source
- based routing for this to work). In other words it allows control
- of which cards (usually 1) will respond to an arp request.
-
- 0 - (default) The kernel can respond to arp requests with addresses
- from other interfaces. This may seem wrong but it usually makes
- sense, because it increases the chance of successful communication.
- IP addresses are owned by the complete host on Linux, not by
- particular interfaces. Only for more complex setups like load-
- balancing, does this behaviour cause problems.
- """
- return self.set_interface('arp_filter', arp_filter)
-
- def set_arp_accept(self, arp_accept):
- """
- Define behavior for gratuitous ARP frames who's IP is not
- already present in the ARP table:
- 0 - don't create new entries in the ARP table
- 1 - create new entries in the ARP table
-
- Both replies and requests type gratuitous arp will trigger the
- ARP table to be updated, if this setting is on.
-
- If the ARP table already contains the IP address of the
- gratuitous arp frame, the arp table will be updated regardless
- if this setting is on or off.
- """
- return self.set_interface('arp_accept', arp_accept)
-
- def set_arp_announce(self, arp_announce):
- """
- Define different restriction levels for announcing the local
- source IP address from IP packets in ARP requests sent on
- interface:
- 0 - (default) Use any local address, configured on any interface
- 1 - Try to avoid local addresses that are not in the target's
- subnet for this interface. This mode is useful when target
- hosts reachable via this interface require the source IP
- address in ARP requests to be part of their logical network
- configured on the receiving interface. When we generate the
- request we will check all our subnets that include the
- target IP and will preserve the source address if it is from
- such subnet.
-
- Increasing the restriction level gives more chance for
- receiving answer from the resolved target while decreasing
- the level announces more valid sender's information.
- """
- return self.set_interface('arp_announce', arp_announce)
-
- def set_arp_ignore(self, arp_ignore):
- """
- Define different modes for sending replies in response to received ARP
- requests that resolve local target IP addresses:
-
- 0 - (default): reply for any local target IP address, configured
- on any interface
- 1 - reply only if the target IP address is local address
- configured on the incoming interface
- """
- return self.set_interface('arp_ignore', arp_ignore)
-
- def set_link_detect(self, link_filter):
- """
- Configure kernel response in packets received on interfaces that are 'down'
-
- 0 - Allow packets to be received for the address on this interface
- even if interface is disabled or no carrier.
-
- 1 - Ignore packets received if interface associated with the incoming
- address is down.
-
- 2 - Ignore packets received if interface associated with the incoming
- address is down or has no carrier.
-
- Default value is 0. Note that some distributions enable it in startup
- scripts.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_link_detect(1)
- """
- return self.set_interface('link_detect', link_filter)
-
- def set_alias(self, ifalias=''):
- """
- Set interface alias name used by e.g. SNMP
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_alias('VyOS upstream interface')
-
- to clear alias e.g. delete it use:
-
- >>> Interface('eth0').set_ifalias('')
- """
- self.set_interface('alias', ifalias)
-
- def get_state(self):
- """
- Enable (up) / Disable (down) an interface
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').get_state()
- 'up'
- """
- cmd = 'ip -json link show dev {}'.format(self.config['ifname'])
- tmp = self._cmd(cmd)
- out = json.loads(tmp)
- return out[0]['operstate'].lower()
-
- def set_state(self, state):
- """
- Enable (up) / Disable (down) an interface
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_state('down')
- >>> Interface('eth0').get_state()
- 'down'
- """
- if state not in ['up', 'down']:
- raise ValueError('state must be "up" or "down"')
-
- # Assemble command executed on system. Unfortunately there is no way
- # to up/down an interface via sysfs
- cmd = 'ip link set dev {} {}'.format(self.config['ifname'], state)
- return self._cmd(cmd)
-
- def set_proxy_arp(self, enable):
- """
- Set per interface proxy ARP configuration
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_proxy_arp(1)
- """
- self.set_interface('proxy_arp', enable)
-
- def set_proxy_arp_pvlan(self, enable):
- """
- Private VLAN proxy arp.
- Basically allow proxy arp replies back to the same interface
- (from which the ARP request/solicitation was received).
-
- This is done to support (ethernet) switch features, like RFC
- 3069, where the individual ports are NOT allowed to
- communicate with each other, but they are allowed to talk to
- the upstream router. As described in RFC 3069, it is possible
- to allow these hosts to communicate through the upstream
- router by proxy_arp'ing. Don't need to be used together with
- proxy_arp.
-
- This technology is known by different names:
- In RFC 3069 it is called VLAN Aggregation.
- Cisco and Allied Telesyn call it Private VLAN.
- Hewlett-Packard call it Source-Port filtering or port-isolation.
- Ericsson call it MAC-Forced Forwarding (RFC Draft).
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_proxy_arp_pvlan(1)
- """
- self.set_interface('proxy_arp_pvlan', enable)
-
- def get_addr(self):
- """
- Retrieve assigned IPv4 and IPv6 addresses from given interface.
- This is done using the netifaces and ipaddress python modules.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').get_addrs()
- ['172.16.33.30/24', 'fe80::20c:29ff:fe11:a174/64']
- """
-
- ipv4 = []
- ipv6 = []
-
- if AF_INET in ifaddresses(self.config['ifname']).keys():
- for v4_addr in ifaddresses(self.config['ifname'])[AF_INET]:
- # we need to manually assemble a list of IPv4 address/prefix
- prefix = '/' + \
- str(IPv4Network('0.0.0.0/' + v4_addr['netmask']).prefixlen)
- ipv4.append(v4_addr['addr'] + prefix)
-
- if AF_INET6 in ifaddresses(self.config['ifname']).keys():
- for v6_addr in ifaddresses(self.config['ifname'])[AF_INET6]:
- # Note that currently expanded netmasks are not supported. That means
- # 2001:db00::0/24 is a valid argument while 2001:db00::0/ffff:ff00:: not.
- # see https://docs.python.org/3/library/ipaddress.html
- bits = bin(
- int(v6_addr['netmask'].replace(':', ''), 16)).count('1')
- prefix = '/' + str(bits)
-
- # we alsoneed to remove the interface suffix on link local
- # addresses
- v6_addr['addr'] = v6_addr['addr'].split('%')[0]
- ipv6.append(v6_addr['addr'] + prefix)
-
- return ipv4 + ipv6
-
- def add_addr(self, addr):
- """
- Add IP(v6) address to interface. Address is only added if it is not
- already assigned to that interface.
-
- addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6!
- IPv4: add IPv4 address to interface
- IPv6: add IPv6 address to interface
- dhcp: start dhclient (IPv4) on interface
- dhcpv6: start dhclient (IPv6) on interface
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.add_addr('192.0.2.1/24')
- >>> j.add_addr('2001:db8::ffff/64')
- >>> j.get_addr()
- ['192.0.2.1/24', '2001:db8::ffff/64']
- """
-
- # cache new IP address which is assigned to interface
- self._addr.append(addr)
-
- # we can not have both DHCP and static IPv4 addresses assigned to an interface
- if 'dhcp' in self._addr:
- for addr in self._addr:
- # do not change below 'if' ordering esle you will get an exception as:
- # ValueError: 'dhcp' does not appear to be an IPv4 or IPv6 address
- if addr != 'dhcp' and is_ipv4(addr):
- raise ConfigError("Can't configure both static IPv4 and DHCP address on the same interface")
-
- if addr == 'dhcp':
- self._set_dhcp()
- elif addr == 'dhcpv6':
- self._set_dhcpv6()
- else:
- if not is_intf_addr_assigned(self.config['ifname'], addr):
- cmd = 'ip addr add "{}" dev "{}"'.format(addr, self.config['ifname'])
- return self._cmd(cmd)
-
- def del_addr(self, addr):
- """
- Delete IP(v6) address to interface. Address is only added if it is
- assigned to that interface.
-
- addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6!
- IPv4: delete IPv4 address from interface
- IPv6: delete IPv6 address from interface
- dhcp: stop dhclient (IPv4) on interface
- dhcpv6: stop dhclient (IPv6) on interface
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.add_addr('2001:db8::ffff/64')
- >>> j.add_addr('192.0.2.1/24')
- >>> j.get_addr()
- ['192.0.2.1/24', '2001:db8::ffff/64']
- >>> j.del_addr('192.0.2.1/24')
- >>> j.get_addr()
- ['2001:db8::ffff/64']
- """
- if addr == 'dhcp':
- self._del_dhcp()
- elif addr == 'dhcpv6':
- self._del_dhcpv6()
- else:
- if is_intf_addr_assigned(self.config['ifname'], addr):
- cmd = 'ip addr del "{}" dev "{}"'.format(addr, self.config['ifname'])
- return self._cmd(cmd)
-
-
- def get_dhcp_options(self):
- """
- Return dictionary with supported DHCP options.
-
- Dictionary should be altered and send back via set_dhcp_options()
- so those options are applied when DHCP is run.
- """
- return self._dhcp_options
-
- def set_dhcp_options(self, options):
- """
- Store new DHCP options used by next run of DHCP client.
- """
- self._dhcp_options = options
-
- def get_dhcpv6_options(self):
- """
- Return dictionary with supported DHCPv6 options.
-
- Dictionary should be altered and send back via set_dhcp_options()
- so those options are applied when DHCP is run.
- """
- return self._dhcpv6_options
-
- def set_dhcpv6_options(self, options):
- """
- Store new DHCP options used by next run of DHCP client.
- """
- self._dhcpv6_options = options
-
- # replace dhcpv4/v6 with systemd.networkd?
- def _set_dhcp(self):
- """
- Configure interface as DHCP client. The dhclient binary is automatically
- started in background!
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.set_dhcp()
- """
-
- dhcp = self.get_dhcp_options()
- if not dhcp['hostname']:
- # read configured system hostname.
- # maybe change to vyos hostd client ???
- with open('/etc/hostname', 'r') as f:
- dhcp['hostname'] = f.read().rstrip('\n')
-
- # render DHCP configuration
- tmpl = jinja2.Template(dhcp_cfg)
- dhcp_text = tmpl.render(dhcp)
- with open(self._dhcp_cfg_file, 'w') as f:
- f.write(dhcp_text)
-
- cmd = 'start-stop-daemon --start --quiet --pidfile ' + \
- self._dhcp_pid_file
- cmd += ' --exec /sbin/dhclient --'
- # now pass arguments to dhclient binary
- cmd += ' -4 -nw -cf {} -pf {} -lf {} {}'.format(
- self._dhcp_cfg_file, self._dhcp_pid_file, self._dhcp_lease_file, self.config['ifname'])
- return self._cmd(cmd)
-
-
- def _del_dhcp(self):
- """
- De-configure interface as DHCP clinet. All auto generated files like
- pid, config and lease will be removed.
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.del_dhcp()
- """
- pid = 0
- if os.path.isfile(self._dhcp_pid_file):
- with open(self._dhcp_pid_file, 'r') as f:
- pid = int(f.read())
- else:
- self._debug_msg('No DHCP client PID found')
- return None
-
- # stop dhclient, we need to call dhclient and tell it should release the
- # aquired IP address. tcpdump tells me:
- # 172.16.35.103.68 > 172.16.35.254.67: [bad udp cksum 0xa0cb -> 0xb943!] BOOTP/DHCP, Request from 00:50:56:9d:11:df, length 300, xid 0x620e6946, Flags [none] (0x0000)
- # Client-IP 172.16.35.103
- # Client-Ethernet-Address 00:50:56:9d:11:df
- # Vendor-rfc1048 Extensions
- # Magic Cookie 0x63825363
- # DHCP-Message Option 53, length 1: Release
- # Server-ID Option 54, length 4: 172.16.35.254
- # Hostname Option 12, length 10: "vyos"
- #
- cmd = '/sbin/dhclient -cf {} -pf {} -lf {} -r {}'.format(
- self._dhcp_cfg_file, self._dhcp_pid_file, self._dhcp_lease_file, self.config['ifname'])
- self._cmd(cmd)
-
- # cleanup old config file
- if os.path.isfile(self._dhcp_cfg_file):
- os.remove(self._dhcp_cfg_file)
-
- # cleanup old pid file
- if os.path.isfile(self._dhcp_pid_file):
- os.remove(self._dhcp_pid_file)
-
- # cleanup old lease file
- if os.path.isfile(self._dhcp_lease_file):
- os.remove(self._dhcp_lease_file)
-
-
- def _set_dhcpv6(self):
- """
- Configure interface as DHCPv6 client. The dhclient binary is automatically
- started in background!
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.set_dhcpv6()
- """
- dhcpv6 = self.get_dhcpv6_options()
-
- # better save then sorry .. should be checked in interface script
- # but if you missed it we are safe!
- if dhcpv6['dhcpv6_prm_only'] and dhcpv6['dhcpv6_temporary']:
- raise Exception('DHCPv6 temporary and parameters-only options are mutually exclusive!')
-
- # render DHCP configuration
- tmpl = jinja2.Template(dhcpv6_cfg)
- dhcpv6_text = tmpl.render(dhcpv6)
- with open(self._dhcpv6_cfg_file, 'w') as f:
- f.write(dhcpv6_text)
-
- # https://bugs.launchpad.net/ubuntu/+source/ifupdown/+bug/1447715
- #
- # wee need to wait for IPv6 DAD to finish once and interface is added
- # this suxx :-(
- sleep(5)
-
- # no longer accept router announcements on this interface
- self._write_sysfs('/proc/sys/net/ipv6/conf/{}/accept_ra'
- .format(self.config['ifname']), 0)
-
- # assemble command-line to start DHCPv6 client (dhclient)
- cmd = 'start-stop-daemon --start --quiet --pidfile ' + \
- self._dhcpv6_pid_file
- cmd += ' --exec /sbin/dhclient --'
- # now pass arguments to dhclient binary
- cmd += ' -6 -nw -cf {} -pf {} -lf {}'.format(
- self._dhcpv6_cfg_file, self._dhcpv6_pid_file, self._dhcpv6_lease_file)
-
- # add optional arguments
- if dhcpv6['dhcpv6_prm_only']:
- cmd += ' -S'
- if dhcpv6['dhcpv6_temporary']:
- cmd += ' -T'
-
- cmd += ' {}'.format(self.config['ifname'])
- return self._cmd(cmd)
-
-
- def _del_dhcpv6(self):
- """
- De-configure interface as DHCPv6 clinet. All auto generated files like
- pid, config and lease will be removed.
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.del_dhcpv6()
- """
- pid = 0
- if os.path.isfile(self._dhcpv6_pid_file):
- with open(self._dhcpv6_pid_file, 'r') as f:
- pid = int(f.read())
- else:
- self._debug_msg('No DHCPv6 client PID found')
- return None
-
- # stop dhclient
- cmd = 'start-stop-daemon --stop --quiet --pidfile {}'.format(self._dhcpv6_pid_file)
- self._cmd(cmd)
-
- # accept router announcements on this interface
- self._write_sysfs('/proc/sys/net/ipv6/conf/{}/accept_ra'
- .format(self.config['ifname']), 1)
-
- # cleanup old config file
- if os.path.isfile(self._dhcpv6_cfg_file):
- os.remove(self._dhcpv6_cfg_file)
-
- # cleanup old pid file
- if os.path.isfile(self._dhcpv6_pid_file):
- os.remove(self._dhcpv6_pid_file)
-
- # cleanup old lease file
- if os.path.isfile(self._dhcpv6_lease_file):
- os.remove(self._dhcpv6_lease_file)
-
- 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']]
-
-class LoopbackIf(Interface):
- """
- The loopback device is a special, virtual network interface that your router
- uses to communicate with itself.
- """
-
- default = {
- 'type': 'loopback',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def remove(self):
- """
- Loopback interface can not be deleted from operating system. We can
- only remove all assigned IP addresses.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> i = LoopbackIf('lo').remove()
- """
- # remove all assigned IP addresses from interface
- for addr in self.get_addr():
- if addr in ["127.0.0.1/8", "::1/128"]:
- # Do not allow deletion of the default loopback addresses as
- # this will cause weird system behavior like snmp/ssh no longer
- # operating as expected, see https://phabricator.vyos.net/T2034.
- continue
-
- self.del_addr(addr)
-
-class DummyIf(Interface):
- """
- A dummy interface is entirely virtual like, for example, the loopback
- interface. The purpose of a dummy interface is to provide a device to route
- packets through without actually transmitting them.
- """
-
- default = {
- 'type': 'dummy',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
-
-class STPIf(Interface):
- """
- A spanning-tree capable interface. This applies only to bridge port member
- interfaces!
- """
- _sysfs_set = {**Interface._sysfs_set, **{
- 'path_cost': {
- # XXX: we should set a maximum
- 'validate': assert_positive,
- 'location': '/sys/class/net/{ifname}/brport/path_cost',
- 'errormsg': '{ifname} is not a bridge port member'
- },
- 'path_priority': {
- # XXX: we should set a maximum
- 'validate': assert_positive,
- 'location': '/sys/class/net/{ifname}/brport/priority',
- 'errormsg': '{ifname} is not a bridge port member'
- },
- }}
-
- default = {
- 'type': 'stp',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def set_path_cost(self, cost):
- """
- Set interface path cost, only relevant for STP enabled interfaces
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_path_cost(4)
- """
- self.set_interface('path_cost', cost)
-
- def set_path_priority(self, priority):
- """
- Set interface path priority, only relevant for STP enabled interfaces
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> Interface('eth0').set_path_priority(4)
- """
- self.set_interface('path_priority', priority)
-
-class BridgeIf(Interface):
- """
- A bridge is a way to connect two Ethernet segments together in a protocol
- independent way. Packets are forwarded based on Ethernet address, rather
- than IP address (like a router). Since forwarding is done at Layer 2, all
- protocols can go transparently through a bridge.
-
- The Linux bridge code implements a subset of the ANSI/IEEE 802.1d standard.
- """
-
- _sysfs_set = {**Interface._sysfs_set, **{
- 'ageing_time': {
- 'validate': assert_positive,
- 'convert': lambda time: int(time) * 100,
- 'location': '/sys/class/net/{ifname}/bridge/ageing_time',
- },
- 'forward_delay': {
- 'validate': assert_positive,
- 'convert': lambda time: int(time) * 100,
- 'location': '/sys/class/net/{ifname}/bridge/forward_delay',
- },
- 'hello_time': {
- 'validate': assert_positive,
- 'convert': lambda time: int(time) * 100,
- 'location': '/sys/class/net/{ifname}/bridge/hello_time',
- },
- 'max_age': {
- 'validate': assert_positive,
- 'convert': lambda time: int(time) * 100,
- 'location': '/sys/class/net/{ifname}/bridge/max_age',
- },
- 'priority': {
- 'validate': assert_positive,
- 'convert': lambda time: int(time) * 100,
- 'location': '/sys/class/net/{ifname}/bridge/priority',
- },
- 'stp': {
- 'validate': assert_boolean,
- 'location': '/sys/class/net/{ifconfig}/bridge/stp_state',
- },
- 'multicast_querier': {
- 'validate': assert_boolean,
- 'location': '/sys/class/net/{ifname}/bridge/multicast_querier',
- },
- }}
-
- _command_set = {**Interface._command_set, **{
- 'add_port': {
- 'validate': assert_boolean,
- 'shellcmd': 'ip link set dev {value} master {ifname}',
- },
- 'del_port': {
- 'validate': assert_boolean,
- 'shellcmd': 'ip link set dev {value} nomaster',
- },
- }}
-
- default = {
- 'type': 'bridge',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def set_ageing_time(self, time):
- """
- Set bridge interface MAC address aging time in seconds. Internal kernel
- representation is in centiseconds. Kernel default is 300 seconds.
-
- Example:
- >>> from vyos.ifconfig import BridgeIf
- >>> BridgeIf('br0').ageing_time(2)
- """
- self.set_interface('ageing_time', time)
-
- def set_forward_delay(self, time):
- """
- Set bridge forwarding delay in seconds. Internal Kernel representation
- is in centiseconds.
-
- Example:
- >>> from vyos.ifconfig import BridgeIf
- >>> BridgeIf('br0').forward_delay(15)
- """
- self.set_interface('forward_delay', time)
-
-
- def set_hello_time(self, time):
- """
- Set bridge hello time in seconds. Internal Kernel representation
- is in centiseconds.
-
- Example:
- >>> from vyos.ifconfig import BridgeIf
- >>> BridgeIf('br0').set_hello_time(2)
- """
- self.set_interface('hello_time', time)
-
- def set_max_age(self, time):
- """
- Set bridge max message age in seconds. Internal Kernel representation
- is in centiseconds.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> BridgeIf('br0').set_max_age(30)
- """
- self.set_interface('max_age', time)
-
- def set_priority(self, priority):
- """
- Set bridge max aging time in seconds.
-
- Example:
- >>> from vyos.ifconfig import BridgeIf
- >>> BridgeIf('br0').set_priority(8192)
- """
- self.set_interface('priority', time)
-
- def set_stp(self, state):
- """
- Set bridge STP (Spanning Tree) state. 0 -> STP disabled, 1 -> STP enabled
-
- Example:
- >>> from vyos.ifconfig import BridgeIf
- >>> BridgeIf('br0').set_stp(1)
- """
- self.set_interface('stp', state)
-
-
- def set_multicast_querier(self, enable):
- """
- Sets whether the bridge actively runs a multicast querier or not. When a
- bridge receives a 'multicast host membership' query from another network
- host, that host is tracked based on the time that the query was received
- plus the multicast query interval time.
-
- Use enable=1 to enable or enable=0 to disable
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> BridgeIf('br0').set_multicast_querier(1)
- """
- self.set_interface('multicast_querier', enable)
-
-
- def add_port(self, interface):
- """
- Add physical interface to bridge (member port)
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> BridgeIf('br0').add_port('eth0')
- >>> BridgeIf('br0').add_port('eth1')
- """
- return self.set_interface('add_port', interface)
-
- def del_port(self, interface):
- """
- Remove member port from bridge instance.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> BridgeIf('br0').del_port('eth1')
- """
- return self.set_interface('del_port', interface)
-
-class VLANIf(Interface):
- """
- This class handels the creation and removal of a VLAN interface. It serves
- as base class for BondIf and EthernetIf.
- """
-
- default = {
- 'type': 'vlan',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def remove(self):
- """
- Remove interface from operating system. Removing the interface
- deconfigures all assigned IP addresses and clear possible DHCP(v6)
- client processes.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> i = Interface('eth0')
- >>> i.remove()
- """
- # Do we have sub interfaces (VLANs)? We apply a regex matching
- # subinterfaces (indicated by a .) of a parent interface.
- #
- # As interfaces need to be deleted "in order" starting from Q-in-Q
- # we delete them first.
- vlan_ifs = [f for f in os.listdir(r'/sys/class/net') \
- if re.match(self.config['ifname'] + r'(?:\.\d+)(?:\.\d+)', f)]
-
- for vlan in vlan_ifs:
- Interface(vlan).remove()
-
- # After deleting all Q-in-Q interfaces delete other VLAN interfaces
- # which probably acted as parent to Q-in-Q or have been regular 802.1q
- # interface.
- vlan_ifs = [f for f in os.listdir(r'/sys/class/net') \
- if re.match(self.config['ifname'] + r'(?:\.\d+)', f)]
-
- for vlan in vlan_ifs:
- Interface(vlan).remove()
-
- # All subinterfaces are now removed, continue on the physical interface
- super().remove()
-
-
- def add_vlan(self, vlan_id, ethertype='', ingress_qos='', egress_qos=''):
- """
- A virtual LAN (VLAN) is any broadcast domain that is partitioned and
- isolated in a computer network at the data link layer (OSI layer 2).
- Use this function to create a new VLAN interface on a given physical
- interface.
-
- This function creates both 802.1q and 802.1ad (Q-in-Q) interfaces. Proto
- parameter is used to indicate VLAN type.
-
- A new object of type VLANIf is returned once the interface has been
- created.
-
- @param ethertype: If specified, create 802.1ad or 802.1q Q-in-Q VLAN
- interface
- @param ingress_qos: Defines a mapping of VLAN header prio field to the
- Linux internal packet priority on incoming frames.
- @param ingress_qos: Defines a mapping of Linux internal packet priority
- to VLAN header prio field but for outgoing frames.
-
- Example:
- >>> from vyos.ifconfig import VLANIf
- >>> i = VLANIf('eth0')
- >>> i.add_vlan(10)
- """
- vlan_ifname = self.config['ifname'] + '.' + str(vlan_id)
- if not os.path.exists('/sys/class/net/{}'.format(vlan_ifname)):
- self._vlan_id = int(vlan_id)
-
- if ethertype:
- self._ethertype = ethertype
- ethertype = 'proto {}'.format(ethertype)
-
- # Optional ingress QOS mapping
- opt_i = ''
- if ingress_qos:
- opt_i = 'ingress-qos-map ' + ingress_qos
- # Optional egress QOS mapping
- opt_e = ''
- if egress_qos:
- opt_e = 'egress-qos-map ' + egress_qos
-
- # create interface in the system
- cmd = 'ip link add link {intf} name {intf}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \
- .format(intf=self.config['ifname'], vlan=self._vlan_id, proto=ethertype, opt_e=opt_e, opt_i=opt_i)
- self._cmd(cmd)
-
- # return new object mapping to the newly created interface
- # we can now work on this object for e.g. IP address setting
- # or interface description and so on
- return VLANIf(vlan_ifname)
-
-
- def del_vlan(self, vlan_id):
- """
- Remove VLAN interface from operating system. Removing the interface
- deconfigures all assigned IP addresses and clear possible DHCP(v6)
- client processes.
-
- Example:
- >>> from vyos.ifconfig import VLANIf
- >>> i = VLANIf('eth0.10')
- >>> i.del_vlan()
- """
- vlan_ifname = self.config['ifname'] + '.' + str(vlan_id)
- VLANIf(vlan_ifname).remove()
-
-
-class EthernetIf(VLANIf):
- """
- Abstraction of a Linux Ethernet Interface
- """
-
- _command_set = {**Interface._command_set, **{
- 'gro': {
- 'validate': lambda v: assert_list(v,['on','off']),
- 'shellcmd': '/sbin/ethtool -K {ifname} gro {value}',
- },
- 'gso': {
- 'validate': lambda v: assert_list(v,['on','off']),
- 'shellcmd': '/sbin/ethtool -K {ifname} gso {value}',
- },
- 'sg': {
- 'validate': lambda v: assert_list(v,['on','off']),
- 'shellcmd': '/sbin/ethtool -K {ifname} sg {value}',
- },
- 'tso': {
- 'validate': lambda v: assert_list(v,['on','off']),
- 'shellcmd': '/sbin/ethtool -K {ifname} tso {value}',
- },
- 'ufo': {
- 'validate': lambda v: assert_list(v,['on','off']),
- 'shellcmd': '/sbin/ethtool -K {ifname} ufo {value}',
- },
- }}
-
- default = {
- 'type': 'ethernet',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def _delete (self):
- # Ethernet interfaces can not be removed
- pass
-
- def get_driver_name(self):
- """
- Return the driver name used by NIC. Some NICs don't support all
- features e.g. changing link-speed, duplex
-
- Example:
- >>> from vyos.ifconfig import EthernetIf
- >>> i = EthernetIf('eth0')
- >>> i.get_driver_name()
- 'vmxnet3'
- """
- sysfs_file = '/sys/class/net/{}/device/driver/module'.format(self.config['ifname'])
- if os.path.exists(sysfs_file):
- link = os.readlink(sysfs_file)
- return os.path.basename(link)
- else:
- return None
-
- def set_flow_control(self, enable):
- """
- Changes the pause parameters of the specified Ethernet device.
-
- @param enable: true -> enable pause frames, false -> disable pause frames
-
- Example:
- >>> from vyos.ifconfig import EthernetIf
- >>> i = EthernetIf('eth0')
- >>> i.set_flow_control(True)
- """
- if enable not in ['on', 'off']:
- raise ValueError("Value out of range")
-
- if self.get_driver_name() in ['vmxnet3', 'virtio_net']:
- self._debug_msg('{} driver does not support changing flow control settings!'
- .format(self.get_driver_name()))
- return
-
- # Get current flow control settings:
- cmd = '/sbin/ethtool --show-pause {0}'.format(self.config['ifname'])
- tmp = self._cmd(cmd)
-
- # The above command returns - with tabs:
- #
- # Pause parameters for eth0:
- # Autonegotiate: on
- # RX: off
- # TX: off
- if re.search("Autonegotiate:\ton", tmp):
- if enable == "on":
- # flowcontrol is already enabled - no need to re-enable it again
- # this will prevent the interface from flapping as applying the
- # flow-control settings will take the interface down and bring
- # it back up every time.
- return
-
- # Assemble command executed on system. Unfortunately there is no way
- # to change this setting via sysfs
- cmd = '/sbin/ethtool --pause {0} autoneg {1} tx {1} rx {1}'.format(
- self.config['ifname'], enable)
- try:
- # An exception will be thrown if the settings are not changed
- return self._cmd(cmd)
- except CalledProcessError:
- pass
-
-
- def set_speed_duplex(self, speed, duplex):
- """
- Set link speed in Mbit/s and duplex.
-
- @speed can be any link speed in MBit/s, e.g. 10, 100, 1000 auto
- @duplex can be half, full, auto
-
- Example:
- >>> from vyos.ifconfig import EthernetIf
- >>> i = EthernetIf('eth0')
- >>> i.set_speed_duplex('auto', 'auto')
- """
-
- if speed not in ['auto', '10', '100', '1000', '2500', '5000', '10000', '25000', '40000', '50000', '100000', '400000']:
- raise ValueError("Value out of range (speed)")
-
- if duplex not in ['auto', 'full', 'half']:
- raise ValueError("Value out of range (duplex)")
-
- if self.get_driver_name() in ['vmxnet3', 'virtio_net']:
- self._debug_msg('{} driver does not support changing speed/duplex settings!'
- .format(self.get_driver_name()))
- return
-
- # Get current speed and duplex settings:
- cmd = '/sbin/ethtool {0}'.format(self.config['ifname'])
- tmp = self._cmd(cmd)
-
- if re.search("\tAuto-negotiation: on", tmp):
- if speed == 'auto' and duplex == 'auto':
- # bail out early as nothing is to change
- return
- else:
- # read in current speed and duplex settings
- cur_speed = 0
- cur_duplex = ''
- for line in tmp.splitlines():
- if line.lstrip().startswith("Speed:"):
- non_decimal = re.compile(r'[^\d.]+')
- cur_speed = non_decimal.sub('', line)
- continue
-
- if line.lstrip().startswith("Duplex:"):
- cur_duplex = line.split()[-1].lower()
- break
-
- if (cur_speed == speed) and (cur_duplex == duplex):
- # bail out early as nothing is to change
- return
-
- cmd = '/sbin/ethtool -s {}'.format(self.config['ifname'])
- if speed == 'auto' or duplex == 'auto':
- cmd += ' autoneg on'
- else:
- cmd += ' speed {} duplex {} autoneg off'.format(speed, duplex)
-
- return self._cmd(cmd)
-
-
- def set_gro(self, state):
- """
- Example:
- >>> from vyos.ifconfig import EthernetIf
- >>> i = EthernetIf('eth0')
- >>> i.set_gro('on')
- """
- return self.set_interface('gro', state)
-
-
- def set_gso(self, state):
- """
- Example:
- >>> from vyos.ifconfig import EthernetIf
- >>> i = EthernetIf('eth0')
- >>> i.set_gso('on')
- """
- return self.set_interface('gso', state)
-
-
- def set_sg(self, state):
- """
- Example:
- >>> from vyos.ifconfig import EthernetIf
- >>> i = EthernetIf('eth0')
- >>> i.set_sg('on')
- """
- return self.set_interface('sg', state)
-
-
- def set_tso(self, state):
- """
- Example:
- >>> from vyos.ifconfig import EthernetIf
- >>> i = EthernetIf('eth0')
- >>> i.set_tso('on')
- """
- return self.set_interface('tso', state)
-
-
- def set_ufo(self, state):
- """
- Example:
- >>> from vyos.ifconfig import EthernetIf
- >>> i = EthernetIf('eth0')
- >>> i.set_udp_offload('on')
- """
- if state not in ['on', 'off']:
- raise ValueError('state must be "on" or "off"')
-
- cmd = '/sbin/ethtool -K {} ufo {}'.format(self.config['ifname'], state)
- return self._cmd(cmd)
-
-class MACVLANIf(VLANIf):
- """
- Abstraction of a Linux MACvlan interface
- """
-
- options = VLANIf.options + ['link', 'mode']
- default = {
- 'type': 'macvlan',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def _create(self):
- cmd = 'ip link add {intf} link {link} type macvlan mode {mode}'.format(**self.config)
- self._cmd(cmd)
-
- @staticmethod
- def get_config():
- """
- VXLAN interfaces require a configuration when they are added using
- iproute2. This static method will provide the configuration dictionary
- used by this class.
-
- Example:
- >> dict = MACVLANIf().get_config()
- """
- config = {
- 'address': '',
- 'link': 0,
- 'mode': ''
- }
- return config
-
- def set_mode(self, mode):
- """
- """
-
- cmd = 'ip link set dev {} type macvlan mode {}'.format(self.config['ifname'], mode)
- return self._cmd(cmd)
-
-
-class BondIf(VLANIf):
- """
- The Linux bonding driver provides a method for aggregating multiple network
- interfaces into a single logical "bonded" interface. The behavior of the
- bonded interfaces depends upon the mode; generally speaking, modes provide
- either hot standby or load balancing services. Additionally, link integrity
- monitoring may be performed.
- """
-
- _sysfs_set = {**Interface._sysfs_set, **{
- 'bond_hash_policy': {
- 'validate': lambda v: assert_list(v,['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']),
- 'location': '/sys/class/net/{ifname}/bonding/xmit_hash_policy',
- },
- 'bond_miimon': {
- 'validate': assert_positive,
- 'location': '/sys/class/net/{ifname}/bonding/miimon'
- },
- 'bond_arp_interval': {
- 'validate': assert_positive,
- 'location': '/sys/class/net/{ifname}/bonding/arp_interval'
- },
- 'bond_arp_ip_target': {
- # XXX: no validation of the IP
- 'location': '/sys/class/net/{ifname}/bonding/arp_ip_target',
- },
- 'bond_add_port': {
- 'location': '/sys/class/net/{ifname}+{value}/bonding/slaves',
- },
- 'bond_del_port': {
- 'location': '/sys/class/net/{ifname}-{value}/bonding/slaves',
- },
- 'bond_primary': {
- 'convert': lambda name: name if name else '\0',
- 'location': '/sys/class/net/{ifname}/bonding/primary',
- },
- 'bond_mode': {
- 'validate': lambda v: assert_list(v,['balance-rr', 'active-backup', 'balance-xor', 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb']),
- 'location': '/sys/class/net/{ifname}/bonding/mode',
- },
- }}
-
- _sysfs_get = {**Interface._sysfs_get, **{
- 'bond_arp_ip_target': {
- 'location': '/sys/class/net/{ifname}/bonding/arp_ip_target',
- }
- }}
-
- default = {
- 'type': 'bond',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def remove(self):
- """
- Remove interface from operating system. Removing the interface
- deconfigures all assigned IP addresses and clear possible DHCP(v6)
- client processes.
- Example:
- >>> from vyos.ifconfig import Interface
- >>> i = Interface('eth0')
- >>> i.remove()
- """
- # when a bond member gets deleted, all members are placed in A/D state
- # even when they are enabled inside CLI. This will make the config
- # and system look async.
- slave_list = []
- for s in self.get_slaves():
- slave = {
- 'ifname' : s,
- 'state': Interface(s).get_state()
- }
- slave_list.append(slave)
-
- # remove bond master which places members in disabled state
- super().remove()
-
- # replicate previous interface state before bond destruction back to
- # physical interface
- for slave in slave_list:
- i = Interface(slave['ifname'])
- i.set_state(slave['state'])
-
-
- def set_hash_policy(self, mode):
- """
- Selects the transmit hash policy to use for slave selection in
- balance-xor, 802.3ad, and tlb modes. Possible values are: layer2,
- layer2+3, layer3+4, encap2+3, encap3+4.
-
- The default value is layer2
-
- Example:
- >>> from vyos.ifconfig import BondIf
- >>> BondIf('bond0').set_hash_policy('layer2+3')
- """
- self.set_interface('bond_hash_policy', mode)
-
- def set_arp_interval(self, interval):
- """
- Specifies the ARP link monitoring frequency in milliseconds.
-
- The ARP monitor works by periodically checking the slave devices
- to determine whether they have sent or received traffic recently
- (the precise criteria depends upon the bonding mode, and the
- state of the slave). Regular traffic is generated via ARP probes
- issued for the addresses specified by the arp_ip_target option.
-
- If ARP monitoring is used in an etherchannel compatible mode
- (modes 0 and 2), the switch should be configured in a mode that
- evenly distributes packets across all links. If the switch is
- configured to distribute the packets in an XOR fashion, all
- replies from the ARP targets will be received on the same link
- which could cause the other team members to fail.
-
- value of 0 disables ARP monitoring. The default value is 0.
-
- Example:
- >>> from vyos.ifconfig import BondIf
- >>> BondIf('bond0').set_arp_interval('100')
- """
- if int(interval) == 0:
- """
- Specifies the MII link monitoring frequency in milliseconds.
- This determines how often the link state of each slave is
- inspected for link failures. A value of zero disables MII
- link monitoring. A value of 100 is a good starting point.
- """
- return self.set_interface('bond_miimon', interval)
- else:
- return self.set_interface('bond_arp_interval', interval)
-
- def get_arp_ip_target(self):
- """
- Specifies the IP addresses to use as ARP monitoring peers when
- arp_interval is > 0. These are the targets of the ARP request sent to
- determine the health of the link to the targets. Specify these values
- in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by
- a comma. At least one IP address must be given for ARP monitoring to
- function. The maximum number of targets that can be specified is 16.
-
- The default value is no IP addresses.
-
- Example:
- >>> from vyos.ifconfig import BondIf
- >>> BondIf('bond0').get_arp_ip_target()
- '192.0.2.1'
- """
- return self.get_interface('bond_arp_ip_target')
-
- def set_arp_ip_target(self, target):
- """
- Specifies the IP addresses to use as ARP monitoring peers when
- arp_interval is > 0. These are the targets of the ARP request sent to
- determine the health of the link to the targets. Specify these values
- in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by
- a comma. At least one IP address must be given for ARP monitoring to
- function. The maximum number of targets that can be specified is 16.
-
- The default value is no IP addresses.
-
- Example:
- >>> from vyos.ifconfig import BondIf
- >>> BondIf('bond0').set_arp_ip_target('192.0.2.1')
- >>> BondIf('bond0').get_arp_ip_target()
- '192.0.2.1'
- """
- return self.set_interface('bond_arp_ip_target', target)
-
- def add_port(self, interface):
- """
- Enslave physical interface to bond.
-
- Example:
- >>> from vyos.ifconfig import BondIf
- >>> BondIf('bond0').add_port('eth0')
- >>> BondIf('bond0').add_port('eth1')
- """
- # An interface can only be added to a bond if it is in 'down' state. If
- # interface is in 'up' state, the following Kernel error will be thrown:
- # bond0: eth1 is up - this may be due to an out of date ifenslave.
- Interface(interface).set_state('down')
- return self.set_interface('bond_add_port', interface)
-
- def del_port(self, interface):
- """
- Remove physical port from bond
-
- Example:
- >>> from vyos.ifconfig import BondIf
- >>> BondIf('bond0').del_port('eth1')
- """
- return self.set_interface('bond_del_port', interface)
-
- def get_slaves(self):
- """
- Return a list with all configured slave interfaces on this bond.
-
- Example:
- >>> from vyos.ifconfig import BondIf
- >>> BondIf('bond0').get_slaves()
- ['eth1', 'eth2']
- """
- enslaved_ifs = []
- # retrieve real enslaved interfaces from OS kernel
- sysfs_bond = '/sys/class/net/{}'.format(self.config['ifname'])
- if os.path.isdir(sysfs_bond):
- for directory in os.listdir(sysfs_bond):
- if 'lower_' in directory:
- enslaved_ifs.append(directory.replace('lower_',''))
-
- return enslaved_ifs
-
-
- def set_primary(self, interface):
- """
- A string (eth0, eth2, etc) specifying which slave is the primary
- device. The specified device will always be the active slave while it
- is available. Only when the primary is off-line will alternate devices
- be used. This is useful when one slave is preferred over another, e.g.,
- when one slave has higher throughput than another.
-
- The primary option is only valid for active-backup, balance-tlb and
- balance-alb mode.
-
- Example:
- >>> from vyos.ifconfig import BondIf
- >>> BondIf('bond0').set_primary('eth2')
- """
- return self.set_interface('bond_primary', interface)
-
- def set_mode(self, mode):
- """
- Specifies one of the bonding policies. The default is balance-rr
- (round robin).
-
- Possible values are: balance-rr, active-backup, balance-xor,
- broadcast, 802.3ad, balance-tlb, balance-alb
-
- NOTE: the bonding mode can not be changed when the bond itself has
- slaves
-
- Example:
- >>> from vyos.ifconfig import BondIf
- >>> BondIf('bond0').set_mode('802.3ad')
- """
- return self.set_interface('bond_mode', mode)
-
-class WireGuardIf(Interface):
- options = ['port', 'private-key', 'pubkey', 'psk', 'allowed-ips', 'fwmark', 'endpoint', 'keepalive']
-
- default = {
- 'type': 'wireguard',
- 'port': 0,
- 'private-key': None,
- 'pubkey': None,
- 'psk': '/dev/null',
- 'allowed-ips': [],
- 'fwmark': 0x00,
- 'endpoint': None,
- 'keepalive': 0
- }
-
- """
- Wireguard interface class, contains a comnfig dictionary since
- wireguard VPN is being comnfigured via the wg command rather than
- writing the config into a file. Otherwise if a pre-shared key is used
- (symetric enryption key), it would we exposed within multiple files.
- Currently it's only within the config.boot if the config was saved.
-
- Example:
- >>> from vyos.ifconfig import WireGuardIf as wg_if
- >>> wg_intfc = wg_if("wg01")
- >>> print (wg_intfc.wg_config)
- {'private-key': None, 'keepalive': 0, 'endpoint': None, 'port': 0,
- 'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'}
- >>> wg_intfc.wg_config['keepalive'] = 100
- >>> print (wg_intfc.wg_config)
- {'private-key': None, 'keepalive': 100, 'endpoint': None, 'port': 0,
- 'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'}
- """
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def update(self):
- if not self.config['private-key']:
- raise ValueError("private key required")
- else:
- # fmask permission check?
- pass
-
- cmd = "wg set {} ".format(self.config['ifname'])
- cmd += "listen-port {} ".format(self.config['port'])
- cmd += "fwmark {} ".format(str(self.config['fwmark']))
- cmd += "private-key {} ".format(self.config['private-key'])
- cmd += "peer {} ".format(self.config['pubkey'])
- cmd += " preshared-key {} ".format(self.config['psk'])
- cmd += " allowed-ips "
- for aip in self.config['allowed-ips']:
- if aip != self.config['allowed-ips'][-1]:
- cmd += aip + ","
- else:
- cmd += aip
- if self.config['endpoint']:
- cmd += " endpoint {}".format(self.config['endpoint'])
- cmd += " persistent-keepalive {}".format(self.config['keepalive'])
-
- self._cmd(cmd)
-
- # remove psk since it isn't required anymore and is saved in the cli
- # config only !!
- if self.config['psk'] != '/dev/null':
- if os.path.exists(self.config['psk']):
- os.remove(self.config['psk'])
-
-
- def remove_peer(self, peerkey):
- """
- Remove a peer of an interface, peers are identified by their public key.
- Giving it a readable name is a vyos feature, to remove a peer the pubkey
- and the interface is needed, to remove the entry.
- """
- cmd = "wg set {0} peer {1} remove".format(
- self.config['ifname'], str(peerkey))
- return self._cmd(cmd)
-
- def op_show_interface(self):
- wgdump = vyos.interfaces.wireguard_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()
-
-
-class VXLANIf(Interface):
- """
- The VXLAN protocol is a tunnelling protocol designed to solve the
- problem of limited VLAN IDs (4096) in IEEE 802.1q. With VXLAN the
- size of the identifier is expanded to 24 bits (16777216).
-
- VXLAN is described by IETF RFC 7348, and has been implemented by a
- number of vendors. The protocol runs over UDP using a single
- destination port. This document describes the Linux kernel tunnel
- device, there is also a separate implementation of VXLAN for
- Openvswitch.
-
- Unlike most tunnels, a VXLAN is a 1 to N network, not just point to
- point. A VXLAN device can learn the IP address of the other endpoint
- either dynamically in a manner similar to a learning bridge, or make
- use of statically-configured forwarding entries.
-
- For more information please refer to:
- https://www.kernel.org/doc/Documentation/networking/vxlan.txt
- """
-
- default = {
- 'type': 'vxlan',
- 'vni': 0,
- 'dev': '',
- 'group': '',
- 'remote': '',
- 'port': 8472, # The Linux implementation of VXLAN pre-dates
- # the IANA's selection of a standard destination port
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def _create(self):
- # we assume that by default a multicast interface is created
- group = 'group {}'.format(self.config['group'])
-
- # if remote host is specified we ignore the multicast address
- if config['remote']:
- group = 'remote {}'.format(self.config['remote'])
-
- # an underlay device is not always specified
- dev = ''
- if self.config['dev']:
- dev = 'dev {}'.format(self.config['dev'])
-
- cmd = 'ip link add {intf} type vxlan id {vni} {grp_rem} {dev} dstport {port}' \
- .format(intf=self.config['ifname'], vni=self.config['vni'], grp_rem=group, dev=dev, port=self.config['port'])
- self._cmd(cmd)
-
- @staticmethod
- def get_config():
- """
- VXLAN interfaces require a configuration when they are added using
- iproute2. This static method will provide the configuration dictionary
- used by this class.
-
- Example:
- >> dict = VXLANIf().get_config()
- """
- config = {
- 'vni': 0,
- 'dev': '',
- 'group': '',
- 'port': 8472, # The Linux implementation of VXLAN pre-dates
- # the IANA's selection of a standard destination port
- 'remote': ''
- }
- return config
-
-class GeneveIf(Interface):
- """
- Geneve: Generic Network Virtualization Encapsulation
-
- For more information please refer to:
- https://tools.ietf.org/html/draft-gross-geneve-00
- https://www.redhat.com/en/blog/what-geneve
- https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/#geneve
- https://lwn.net/Articles/644938/
- """
-
- default = {
- 'type': 'geneve',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def _create (self):
- cmd = 'ip link add name {} type geneve id {} remote {}' \
- .format(self.config['ifname'], config['vni'], config['remote'])
- self._cmd(cmd)
-
- # interface is always A/D down. It needs to be enabled explicitly
- self.set_state('down')
-
- @staticmethod
- def get_config():
- """
- GENEVE interfaces require a configuration when they are added using
- iproute2. This static method will provide the configuration dictionary
- used by this class.
-
- Example:
- >> dict = GeneveIf().get_config()
- """
- config = {
- 'vni': 0,
- 'remote': ''
- }
- return config
-
-class L2TPv3If(Interface):
- """
- The Linux bonding driver provides a method for aggregating multiple network
- interfaces into a single logical "bonded" interface. The behavior of the
- bonded interfaces depends upon the mode; generally speaking, modes provide
- either hot standby or load balancing services. Additionally, link integrity
- monitoring may be performed.
- """
-
- options = Interface.options + \
- ['tunnel_id','peer_tunnel_id','local_port','remote_port','encapsulation','local_address','remote_address']
- default = {
- 'type': 'l2tp',
- }
-
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
- def _create (self):
- # create tunnel interface
- cmd = 'ip l2tp add tunnel tunnel_id {} '.format(config['tunnel_id'])
- cmd += 'peer_tunnel_id {} '.format(config['peer_tunnel_id'])
- cmd += 'udp_sport {} '.format(config['local_port'])
- cmd += 'udp_dport {} '.format(config['remote_port'])
- cmd += 'encap {} '.format(config['encapsulation'])
- cmd += 'local {} '.format(config['local_address'])
- cmd += 'remote {} '.format(config['remote_address'])
- self._cmd(cmd)
-
- # setup session
- cmd = 'ip l2tp add session name {} '.format(self.config['ifname'])
- cmd += 'tunnel_id {} '.format(config['tunnel_id'])
- cmd += 'session_id {} '.format(config['session_id'])
- cmd += 'peer_session_id {} '.format(config['peer_session_id'])
- self._cmd(cmd)
-
- # interface is always A/D down. It needs to be enabled explicitly
- self.set_state('down')
-
- def remove(self):
- """
- Remove interface from operating system. Removing the interface
- deconfigures all assigned IP addresses.
- Example:
- >>> from vyos.ifconfig import L2TPv3If
- >>> i = L2TPv3If('l2tpeth0')
- >>> i.remove()
- """
-
- if os.path.exists('/sys/class/net/{}'.format(self.config['ifname'])):
- # interface is always A/D down. It needs to be enabled explicitly
- self.set_state('down')
-
- if self._config['tunnel_id'] and self._config['session_id']:
- cmd = 'ip l2tp del session tunnel_id {} '.format(self._config['tunnel_id'])
- cmd += 'session_id {} '.format(self._config['session_id'])
- self._cmd(cmd)
-
- if self._config['tunnel_id']:
- cmd = 'ip l2tp del tunnel tunnel_id {} '.format(self._config['tunnel_id'])
- self._cmd(cmd)
-
- @staticmethod
- def get_config():
- """
- L2TPv3 interfaces require a configuration when they are added using
- iproute2. This static method will provide the configuration dictionary
- used by this class.
-
- Example:
- >> dict = L2TPv3If().get_config()
- """
- config = {
- 'peer_tunnel_id': '',
- 'local_port': 0,
- 'remote_port': 0,
- 'encapsulation': 'udp',
- 'local_address': '',
- 'remote_address': '',
- 'session_id': '',
- 'tunnel_id': '',
- 'peer_session_id': ''
- }
- return config
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
new file mode 100644
index 000000000..c77b2f9db
--- /dev/null
+++ b/python/vyos/ifconfig/__init__.py
@@ -0,0 +1,29 @@
+# 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/>.
+
+
+from vyos.ifconfig.interface import Interface
+
+from vyos.ifconfig.bond import BondIf
+from vyos.ifconfig.bridge import BridgeIf
+from vyos.ifconfig.dummy import DummyIf
+from vyos.ifconfig.ethernet import EthernetIf
+from vyos.ifconfig.geneve import GeneveIf
+from vyos.ifconfig.loopback import LoopbackIf
+from vyos.ifconfig.macvlan import MACVLANIf
+from vyos.ifconfig.stp import STPIf
+from vyos.ifconfig.vlan import VLANIf
+from vyos.ifconfig.vxlan import VXLANIf
+from vyos.ifconfig.wireguard import WireGuardIf
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
new file mode 100644
index 000000000..211790459
--- /dev/null
+++ b/python/vyos/ifconfig/bond.py
@@ -0,0 +1,270 @@
+# 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 vyos.ifconfig.interface import Interface
+from vyos.ifconfig.vlan import VLANIf
+
+from vyos.validate import *
+
+
+class BondIf(VLANIf):
+ """
+ The Linux bonding driver provides a method for aggregating multiple network
+ interfaces into a single logical "bonded" interface. The behavior of the
+ bonded interfaces depends upon the mode; generally speaking, modes provide
+ either hot standby or load balancing services. Additionally, link integrity
+ monitoring may be performed.
+ """
+
+ _sysfs_set = {**VLANIf._sysfs_set, **{
+ 'bond_hash_policy': {
+ 'validate': lambda v: assert_list(v, ['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']),
+ 'location': '/sys/class/net/{ifname}/bonding/xmit_hash_policy',
+ },
+ 'bond_miimon': {
+ 'validate': assert_positive,
+ 'location': '/sys/class/net/{ifname}/bonding/miimon'
+ },
+ 'bond_arp_interval': {
+ 'validate': assert_positive,
+ 'location': '/sys/class/net/{ifname}/bonding/arp_interval'
+ },
+ 'bond_arp_ip_target': {
+ # XXX: no validation of the IP
+ 'location': '/sys/class/net/{ifname}/bonding/arp_ip_target',
+ },
+ 'bond_add_port': {
+ 'location': '/sys/class/net/{ifname}/bonding/slaves',
+ },
+ 'bond_del_port': {
+ 'location': '/sys/class/net/{ifname}/bonding/slaves',
+ },
+ 'bond_primary': {
+ 'convert': lambda name: name if name else '\0',
+ 'location': '/sys/class/net/{ifname}/bonding/primary',
+ },
+ 'bond_mode': {
+ 'validate': lambda v: assert_list(v, ['balance-rr', 'active-backup', 'balance-xor', 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb']),
+ 'location': '/sys/class/net/{ifname}/bonding/mode',
+ },
+ }}
+
+ _sysfs_get = {**VLANIf._sysfs_get, **{
+ 'bond_arp_ip_target': {
+ 'location': '/sys/class/net/{ifname}/bonding/arp_ip_target',
+ }
+ }}
+
+ default = {
+ 'type': 'bond',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def remove(self):
+ """
+ Remove interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses and clear possible DHCP(v6)
+ client processes.
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> i = Interface('eth0')
+ >>> i.remove()
+ """
+ # when a bond member gets deleted, all members are placed in A/D state
+ # even when they are enabled inside CLI. This will make the config
+ # and system look async.
+ slave_list = []
+ for s in self.get_slaves():
+ slave = {
+ 'ifname': s,
+ 'state': Interface(s).get_state()
+ }
+ slave_list.append(slave)
+
+ # remove bond master which places members in disabled state
+ super().remove()
+
+ # replicate previous interface state before bond destruction back to
+ # physical interface
+ for slave in slave_list:
+ i = Interface(slave['ifname'])
+ i.set_state(slave['state'])
+
+ def set_hash_policy(self, mode):
+ """
+ Selects the transmit hash policy to use for slave selection in
+ balance-xor, 802.3ad, and tlb modes. Possible values are: layer2,
+ layer2+3, layer3+4, encap2+3, encap3+4.
+
+ The default value is layer2
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').set_hash_policy('layer2+3')
+ """
+ self.set_interface('bond_hash_policy', mode)
+
+ def set_arp_interval(self, interval):
+ """
+ Specifies the ARP link monitoring frequency in milliseconds.
+
+ The ARP monitor works by periodically checking the slave devices
+ to determine whether they have sent or received traffic recently
+ (the precise criteria depends upon the bonding mode, and the
+ state of the slave). Regular traffic is generated via ARP probes
+ issued for the addresses specified by the arp_ip_target option.
+
+ If ARP monitoring is used in an etherchannel compatible mode
+ (modes 0 and 2), the switch should be configured in a mode that
+ evenly distributes packets across all links. If the switch is
+ configured to distribute the packets in an XOR fashion, all
+ replies from the ARP targets will be received on the same link
+ which could cause the other team members to fail.
+
+ value of 0 disables ARP monitoring. The default value is 0.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').set_arp_interval('100')
+ """
+ if int(interval) == 0:
+ """
+ Specifies the MII link monitoring frequency in milliseconds.
+ This determines how often the link state of each slave is
+ inspected for link failures. A value of zero disables MII
+ link monitoring. A value of 100 is a good starting point.
+ """
+ return self.set_interface('bond_miimon', interval)
+ else:
+ return self.set_interface('bond_arp_interval', interval)
+
+ def get_arp_ip_target(self):
+ """
+ Specifies the IP addresses to use as ARP monitoring peers when
+ arp_interval is > 0. These are the targets of the ARP request sent to
+ determine the health of the link to the targets. Specify these values
+ in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by
+ a comma. At least one IP address must be given for ARP monitoring to
+ function. The maximum number of targets that can be specified is 16.
+
+ The default value is no IP addresses.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').get_arp_ip_target()
+ '192.0.2.1'
+ """
+ return self.get_interface('bond_arp_ip_target')
+
+ def set_arp_ip_target(self, target):
+ """
+ Specifies the IP addresses to use as ARP monitoring peers when
+ arp_interval is > 0. These are the targets of the ARP request sent to
+ determine the health of the link to the targets. Specify these values
+ in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by
+ a comma. At least one IP address must be given for ARP monitoring to
+ function. The maximum number of targets that can be specified is 16.
+
+ The default value is no IP addresses.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').set_arp_ip_target('192.0.2.1')
+ >>> BondIf('bond0').get_arp_ip_target()
+ '192.0.2.1'
+ """
+ return self.set_interface('bond_arp_ip_target', target)
+
+ def add_port(self, interface):
+ """
+ Enslave physical interface to bond.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').add_port('eth0')
+ >>> BondIf('bond0').add_port('eth1')
+ """
+ # An interface can only be added to a bond if it is in 'down' state. If
+ # interface is in 'up' state, the following Kernel error will be thrown:
+ # bond0: eth1 is up - this may be due to an out of date ifenslave.
+ Interface(interface).set_state('down')
+ return self.set_interface('bond_add_port', f'+{interface}')
+
+ def del_port(self, interface):
+ """
+ Remove physical port from bond
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').del_port('eth1')
+ """
+ return self.set_interface('bond_del_port', f'-{interface}')
+
+ def get_slaves(self):
+ """
+ Return a list with all configured slave interfaces on this bond.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').get_slaves()
+ ['eth1', 'eth2']
+ """
+ enslaved_ifs = []
+ # retrieve real enslaved interfaces from OS kernel
+ sysfs_bond = '/sys/class/net/{}'.format(self.config['ifname'])
+ if os.path.isdir(sysfs_bond):
+ for directory in os.listdir(sysfs_bond):
+ if 'lower_' in directory:
+ enslaved_ifs.append(directory.replace('lower_', ''))
+
+ return enslaved_ifs
+
+ def set_primary(self, interface):
+ """
+ A string (eth0, eth2, etc) specifying which slave is the primary
+ device. The specified device will always be the active slave while it
+ is available. Only when the primary is off-line will alternate devices
+ be used. This is useful when one slave is preferred over another, e.g.,
+ when one slave has higher throughput than another.
+
+ The primary option is only valid for active-backup, balance-tlb and
+ balance-alb mode.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').set_primary('eth2')
+ """
+ return self.set_interface('bond_primary', interface)
+
+ def set_mode(self, mode):
+ """
+ Specifies one of the bonding policies. The default is balance-rr
+ (round robin).
+
+ Possible values are: balance-rr, active-backup, balance-xor,
+ broadcast, 802.3ad, balance-tlb, balance-alb
+
+ NOTE: the bonding mode can not be changed when the bond itself has
+ slaves
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').set_mode('802.3ad')
+ """
+ return self.set_interface('bond_mode', mode)
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
new file mode 100644
index 000000000..392718d9f
--- /dev/null
+++ b/python/vyos/ifconfig/bridge.py
@@ -0,0 +1,180 @@
+# 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/>.
+
+
+from vyos.ifconfig.interface import Interface
+
+from vyos.validate import *
+
+class BridgeIf(Interface):
+ """
+ A bridge is a way to connect two Ethernet segments together in a protocol
+ independent way. Packets are forwarded based on Ethernet address, rather
+ than IP address (like a router). Since forwarding is done at Layer 2, all
+ protocols can go transparently through a bridge.
+
+ The Linux bridge code implements a subset of the ANSI/IEEE 802.1d standard.
+ """
+
+ _sysfs_set = {**Interface._sysfs_set, **{
+ 'ageing_time': {
+ 'validate': assert_positive,
+ 'convert': lambda t: int(t) * 100,
+ 'location': '/sys/class/net/{ifname}/bridge/ageing_time',
+ },
+ 'forward_delay': {
+ 'validate': assert_positive,
+ 'convert': lambda t: int(t) * 100,
+ 'location': '/sys/class/net/{ifname}/bridge/forward_delay',
+ },
+ 'hello_time': {
+ 'validate': assert_positive,
+ 'convert': lambda t: int(t) * 100,
+ 'location': '/sys/class/net/{ifname}/bridge/hello_time',
+ },
+ 'max_age': {
+ 'validate': assert_positive,
+ 'convert': lambda t: int(t) * 100,
+ 'location': '/sys/class/net/{ifname}/bridge/max_age',
+ },
+ 'priority': {
+ 'validate': assert_positive,
+ 'location': '/sys/class/net/{ifname}/bridge/priority',
+ },
+ 'stp': {
+ 'validate': assert_boolean,
+ 'location': '/sys/class/net/{ifname}/bridge/stp_state',
+ },
+ 'multicast_querier': {
+ 'validate': assert_boolean,
+ 'location': '/sys/class/net/{ifname}/bridge/multicast_querier',
+ },
+ }}
+
+ _command_set = {**Interface._command_set, **{
+ 'add_port': {
+ 'shellcmd': 'ip link set dev {value} master {ifname}',
+ },
+ 'del_port': {
+ 'shellcmd': 'ip link set dev {value} nomaster',
+ },
+ }}
+
+ default = {
+ 'type': 'bridge',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def set_ageing_time(self, time):
+ """
+ Set bridge interface MAC address aging time in seconds. Internal kernel
+ representation is in centiseconds. Kernel default is 300 seconds.
+
+ Example:
+ >>> from vyos.ifconfig import BridgeIf
+ >>> BridgeIf('br0').ageing_time(2)
+ """
+ self.set_interface('ageing_time', time)
+
+ def set_forward_delay(self, time):
+ """
+ Set bridge forwarding delay in seconds. Internal Kernel representation
+ is in centiseconds.
+
+ Example:
+ >>> from vyos.ifconfig import BridgeIf
+ >>> BridgeIf('br0').forward_delay(15)
+ """
+ self.set_interface('forward_delay', time)
+
+ def set_hello_time(self, time):
+ """
+ Set bridge hello time in seconds. Internal Kernel representation
+ is in centiseconds.
+
+ Example:
+ >>> from vyos.ifconfig import BridgeIf
+ >>> BridgeIf('br0').set_hello_time(2)
+ """
+ self.set_interface('hello_time', time)
+
+ def set_max_age(self, time):
+ """
+ Set bridge max message age in seconds. Internal Kernel representation
+ is in centiseconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').set_max_age(30)
+ """
+ self.set_interface('max_age', time)
+
+ def set_priority(self, priority):
+ """
+ Set bridge max aging time in seconds.
+
+ Example:
+ >>> from vyos.ifconfig import BridgeIf
+ >>> BridgeIf('br0').set_priority(8192)
+ """
+ self.set_interface('priority', priority)
+
+ def set_stp(self, state):
+ """
+ Set bridge STP (Spanning Tree) state. 0 -> STP disabled, 1 -> STP enabled
+
+ Example:
+ >>> from vyos.ifconfig import BridgeIf
+ >>> BridgeIf('br0').set_stp(1)
+ """
+ self.set_interface('stp', state)
+
+ def set_multicast_querier(self, enable):
+ """
+ Sets whether the bridge actively runs a multicast querier or not. When a
+ bridge receives a 'multicast host membership' query from another network
+ host, that host is tracked based on the time that the query was received
+ plus the multicast query interval time.
+
+ Use enable=1 to enable or enable=0 to disable
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').set_multicast_querier(1)
+ """
+ self.set_interface('multicast_querier', enable)
+
+ def add_port(self, interface):
+ """
+ Add physical interface to bridge (member port)
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').add_port('eth0')
+ >>> BridgeIf('br0').add_port('eth1')
+ """
+ return self.set_interface('add_port', interface)
+
+ def del_port(self, interface):
+ """
+ Remove member port from bridge instance.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').del_port('eth1')
+ """
+ return self.set_interface('del_port', interface)
diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py
new file mode 100644
index 000000000..508b4e279
--- /dev/null
+++ b/python/vyos/ifconfig/control.py
@@ -0,0 +1,143 @@
+# 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 subprocess import Popen, PIPE, STDOUT
+
+
+class Control:
+ _command_get = {}
+ _command_set = {}
+
+ def _debug_msg(self, msg):
+ if os.path.isfile('/tmp/vyos.ifconfig.debug'):
+ print('DEBUG/{:<6} {}'.format(self.config['ifname'], msg))
+
+ def _cmd(self, command):
+ p = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
+ tmp = p.communicate()[0].strip()
+ self._debug_msg("cmd '{}'".format(command))
+ if tmp.decode():
+ self._debug_msg("returned:\n{}".format(tmp.decode()))
+
+ # do we need some error checking code here?
+ return tmp.decode()
+
+ def _get_command(self, config, name):
+ """
+ Using the defined names, set data write to sysfs.
+ """
+ cmd = self._command_get[name]['shellcmd'].format(**config)
+ return self._cmd(cmd)
+
+ def _set_command(self, config, name, value):
+ """
+ Using the defined names, set data write to sysfs.
+ """
+ if not value:
+ return None
+
+ # the code can pass int as int
+ value = str(value)
+
+ validate = self._command_set[name].get('validate', None)
+ if validate:
+ validate(value)
+
+ config = {**config, **{'value': value}}
+
+ convert = self._command_set[name].get('convert', None)
+ if convert:
+ value = convert(value)
+
+ cmd = self._command_set[name]['shellcmd'].format(**config)
+ return self._cmd(cmd)
+
+ _sysfs_get = {}
+ _sysfs_set = {}
+
+ def _read_sysfs(self, filename):
+ """
+ Provide a single primitive w/ error checking for reading from sysfs.
+ """
+ value = None
+ with open(filename, 'r') as f:
+ value = f.read().rstrip('\n')
+
+ self._debug_msg("read '{}' < '{}'".format(value, filename))
+ return value
+
+ def _write_sysfs(self, filename, value):
+ """
+ Provide a single primitive w/ error checking for writing to sysfs.
+ """
+ self._debug_msg("write '{}' > '{}'".format(value, filename))
+ if os.path.isfile(filename):
+ with open(filename, 'w') as f:
+ f.write(str(value))
+ return True
+ return False
+
+ def _get_sysfs(self, config, name):
+ """
+ Using the defined names, get data write from sysfs.
+ """
+ filename = self._sysfs_get[name]['location'].format(**config)
+ if not filename:
+ return None
+ return self._read_sysfs(filename)
+
+ def _set_sysfs(self, config, name, value):
+ """
+ Using the defined names, set data write to sysfs.
+ """
+ if not value:
+ return None
+
+ # the code can pass int as int
+ value = str(value)
+
+ validate = self._sysfs_set[name].get('validate', None)
+ if validate:
+ validate(value)
+
+ config = {**config, **{'value': value}}
+
+ convert = self._sysfs_set[name].get('convert', None)
+ if convert:
+ value = convert(value)
+
+ commited = self._write_sysfs(
+ self._sysfs_set[name]['location'].format(**config), value)
+ if not commited:
+ errmsg = self._sysfs_set.get('errormsg', '')
+ if errmsg:
+ raise TypeError(errmsg.format(**config))
+ return commited
+
+ def get_interface(self, name):
+ if name in self._sysfs_get:
+ return self._get_sysfs(self.config, name)
+ if name in self._command_get:
+ return self._get_command(self.config, name)
+ raise KeyError(f'{name} is not a attribute of the interface we can get')
+
+ def set_interface(self, name, value):
+ if name in self._sysfs_set:
+ return self._set_sysfs(self.config, name, value)
+ if name in self._command_set:
+ return self._set_command(self.config, name, value)
+ raise KeyError(f'{name} is not a attribute of the interface we can set')
diff --git a/python/vyos/ifconfig/dummy.py b/python/vyos/ifconfig/dummy.py
new file mode 100644
index 000000000..55935a16e
--- /dev/null
+++ b/python/vyos/ifconfig/dummy.py
@@ -0,0 +1,32 @@
+# 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/>.
+
+
+from vyos.ifconfig.interface import Interface
+
+
+class DummyIf(Interface):
+ """
+ A dummy interface is entirely virtual like, for example, the loopback
+ interface. The purpose of a dummy interface is to provide a device to route
+ packets through without actually transmitting them.
+ """
+
+ default = {
+ 'type': 'dummy',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
new file mode 100644
index 000000000..9863ca826
--- /dev/null
+++ b/python/vyos/ifconfig/ethernet.py
@@ -0,0 +1,234 @@
+# 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 re
+
+from vyos.ifconfig.vlan import VLANIf
+
+from vyos.validate import *
+
+
+class EthernetIf(VLANIf):
+ """
+ Abstraction of a Linux Ethernet Interface
+ """
+
+ _command_set = {**VLANIf._command_set, **{
+ 'gro': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': '/sbin/ethtool -K {ifname} gro {value}',
+ },
+ 'gso': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': '/sbin/ethtool -K {ifname} gso {value}',
+ },
+ 'sg': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': '/sbin/ethtool -K {ifname} sg {value}',
+ },
+ 'tso': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': '/sbin/ethtool -K {ifname} tso {value}',
+ },
+ 'ufo': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': '/sbin/ethtool -K {ifname} ufo {value}',
+ },
+ }}
+
+ default = {
+ 'type': 'ethernet',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def _delete(self):
+ # Ethernet interfaces can not be removed
+ pass
+
+ def get_driver_name(self):
+ """
+ Return the driver name used by NIC. Some NICs don't support all
+ features e.g. changing link-speed, duplex
+
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.get_driver_name()
+ 'vmxnet3'
+ """
+ sysfs_file = '/sys/class/net/{}/device/driver/module'.format(
+ self.config['ifname'])
+ if os.path.exists(sysfs_file):
+ link = os.readlink(sysfs_file)
+ return os.path.basename(link)
+ else:
+ return None
+
+ def set_flow_control(self, enable):
+ """
+ Changes the pause parameters of the specified Ethernet device.
+
+ @param enable: true -> enable pause frames, false -> disable pause frames
+
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_flow_control(True)
+ """
+ if enable not in ['on', 'off']:
+ raise ValueError("Value out of range")
+
+ if self.get_driver_name() in ['vmxnet3', 'virtio_net']:
+ self._debug_msg('{} driver does not support changing flow control settings!'
+ .format(self.get_driver_name()))
+ return
+
+ # Get current flow control settings:
+ cmd = '/sbin/ethtool --show-pause {0}'.format(self.config['ifname'])
+ tmp = self._cmd(cmd)
+
+ # The above command returns - with tabs:
+ #
+ # Pause parameters for eth0:
+ # Autonegotiate: on
+ # RX: off
+ # TX: off
+ if re.search("Autonegotiate:\ton", tmp):
+ if enable == "on":
+ # flowcontrol is already enabled - no need to re-enable it again
+ # this will prevent the interface from flapping as applying the
+ # flow-control settings will take the interface down and bring
+ # it back up every time.
+ return
+
+ # Assemble command executed on system. Unfortunately there is no way
+ # to change this setting via sysfs
+ cmd = '/sbin/ethtool --pause {0} autoneg {1} tx {1} rx {1}'.format(
+ self.config['ifname'], enable)
+ try:
+ # An exception will be thrown if the settings are not changed
+ return self._cmd(cmd)
+ except CalledProcessError:
+ pass
+
+ def set_speed_duplex(self, speed, duplex):
+ """
+ Set link speed in Mbit/s and duplex.
+
+ @speed can be any link speed in MBit/s, e.g. 10, 100, 1000 auto
+ @duplex can be half, full, auto
+
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_speed_duplex('auto', 'auto')
+ """
+
+ if speed not in ['auto', '10', '100', '1000', '2500', '5000', '10000', '25000', '40000', '50000', '100000', '400000']:
+ raise ValueError("Value out of range (speed)")
+
+ if duplex not in ['auto', 'full', 'half']:
+ raise ValueError("Value out of range (duplex)")
+
+ if self.get_driver_name() in ['vmxnet3', 'virtio_net']:
+ self._debug_msg('{} driver does not support changing speed/duplex settings!'
+ .format(self.get_driver_name()))
+ return
+
+ # Get current speed and duplex settings:
+ cmd = '/sbin/ethtool {0}'.format(self.config['ifname'])
+ tmp = self._cmd(cmd)
+
+ if re.search("\tAuto-negotiation: on", tmp):
+ if speed == 'auto' and duplex == 'auto':
+ # bail out early as nothing is to change
+ return
+ else:
+ # read in current speed and duplex settings
+ cur_speed = 0
+ cur_duplex = ''
+ for line in tmp.splitlines():
+ if line.lstrip().startswith("Speed:"):
+ non_decimal = re.compile(r'[^\d.]+')
+ cur_speed = non_decimal.sub('', line)
+ continue
+
+ if line.lstrip().startswith("Duplex:"):
+ cur_duplex = line.split()[-1].lower()
+ break
+
+ if (cur_speed == speed) and (cur_duplex == duplex):
+ # bail out early as nothing is to change
+ return
+
+ cmd = '/sbin/ethtool -s {}'.format(self.config['ifname'])
+ if speed == 'auto' or duplex == 'auto':
+ cmd += ' autoneg on'
+ else:
+ cmd += ' speed {} duplex {} autoneg off'.format(speed, duplex)
+
+ return self._cmd(cmd)
+
+ def set_gro(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_gro('on')
+ """
+ return self.set_interface('gro', state)
+
+ def set_gso(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_gso('on')
+ """
+ return self.set_interface('gso', state)
+
+ def set_sg(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_sg('on')
+ """
+ return self.set_interface('sg', state)
+
+ def set_tso(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_tso('on')
+ """
+ return self.set_interface('tso', state)
+
+ def set_ufo(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_udp_offload('on')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} ufo {}'.format(self.config['ifname'], state)
+ return self._cmd(cmd)
diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py
new file mode 100644
index 000000000..46782a685
--- /dev/null
+++ b/python/vyos/ifconfig/geneve.py
@@ -0,0 +1,60 @@
+# 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/>.
+
+
+from vyos.ifconfig.interface import Interface
+
+
+class GeneveIf(Interface):
+ """
+ Geneve: Generic Network Virtualization Encapsulation
+
+ For more information please refer to:
+ https://tools.ietf.org/html/draft-gross-geneve-00
+ https://www.redhat.com/en/blog/what-geneve
+ https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/#geneve
+ https://lwn.net/Articles/644938/
+ """
+
+ default = {
+ 'type': 'geneve',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def _create(self):
+ cmd = 'ip link add name {} type geneve id {} remote {}' \
+ .format(self.config['ifname'], config['vni'], config['remote'])
+ self._cmd(cmd)
+
+ # interface is always A/D down. It needs to be enabled explicitly
+ self.set_state('down')
+
+ @staticmethod
+ def get_config():
+ """
+ GENEVE interfaces require a configuration when they are added using
+ iproute2. This static method will provide the configuration dictionary
+ used by this class.
+
+ Example:
+ >> dict = GeneveIf().get_config()
+ """
+ config = {
+ 'vni': 0,
+ 'remote': ''
+ }
+ return config
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
new file mode 100644
index 000000000..a750bda3f
--- /dev/null
+++ b/python/vyos/ifconfig/interface.py
@@ -0,0 +1,836 @@
+# 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 re
+import jinja2
+import json
+import glob
+import time
+from copy import deepcopy
+
+import vyos.interfaces
+
+from vyos.validate import * # should not * include
+from vyos.config import Config # not used anymore
+from vyos import ConfigError
+
+from ipaddress import IPv4Network, IPv6Address
+from netifaces import ifaddresses, AF_INET, AF_INET6
+from time import sleep
+from os.path import isfile
+from tabulate import tabulate
+from hurry.filesize import size,alternative
+from datetime import timedelta
+
+from vyos.ifconfig.control import Control
+
+dhclient_base = r'/var/lib/dhcp/dhclient_'
+dhcp_cfg = """
+# generated by ifconfig.py
+option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
+timeout 60;
+retry 300;
+
+interface "{{ intf }}" {
+ send host-name "{{ hostname }}";
+ {% if client_id -%}
+ send dhcp-client-identifier "{{ client_id }}";
+ {% endif -%}
+ {% if vendor_class_id -%}
+ send vendor-class-identifier "{{ vendor_class_id }}";
+ {% endif -%}
+ request subnet-mask, broadcast-address, routers, domain-name-servers,
+ rfc3442-classless-static-routes, domain-name, interface-mtu;
+ require subnet-mask;
+}
+
+"""
+
+dhcpv6_cfg = """
+# generated by ifconfig.py
+interface "{{ intf }}" {
+ request routers, domain-name-servers, domain-name;
+}
+
+"""
+
+
+
+class Interface(Control):
+ options = []
+ required = []
+ default = {
+ 'type': '',
+ }
+
+ _command_set = {
+ 'mac': {
+ 'validate': assert_mac,
+ 'shellcmd': 'ip link set dev {ifname} address {value}',
+ },
+ 'add_vrf': {
+ 'shellcmd': 'ip link set dev {ifname} master {value}',
+ },
+ 'del_vrf': {
+ 'shellcmd': 'ip link set dev {ifname} nomaster {value}',
+ },
+ }
+
+ _sysfs_get = {
+ 'mtu': {
+ 'location': '/sys/class/net/{ifname}/mtu',
+ },
+ }
+
+ _sysfs_set = {
+ 'alias': {
+ 'convert': lambda name: name if name else '\0',
+ 'location': '/sys/class/net/{ifname}/ifalias',
+ },
+ 'mtu': {
+ 'validate': assert_mtu,
+ 'location': '/sys/class/net/{ifname}/mtu',
+ },
+ 'arp_cache_tmo': {
+ 'convert': lambda tmo: (int(tmo) * 1000),
+ 'location': '/proc/sys/net/ipv4/neigh/{ifname}/base_reachable_time_ms',
+ },
+ 'arp_filter': {
+ 'validate': assert_boolean,
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_filter',
+ },
+ 'arp_accept': {
+ 'validate': lambda arp: assert_range(arp,0,2),
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_accept',
+ },
+ 'arp_announce': {
+ 'validate': assert_boolean,
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_announce',
+ },
+ 'arp_ignore': {
+ 'validate': assert_boolean,
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_ignore',
+ },
+ 'proxy_arp': {
+ 'validate': assert_boolean,
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp',
+ },
+ 'proxy_arp_pvlan': {
+ 'validate': assert_boolean,
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/proxy_arp_pvlan',
+ },
+ # link_detect vs link_filter name weirdness
+ 'link_detect': {
+ 'validate': lambda link: assert_range(link,0,3),
+ 'location': '/proc/sys/net/ipv4/conf/{ifname}/link_filter',
+ },
+ }
+
+ def __init__(self, ifname, **kargs):
+ """
+ This is the base interface class which supports basic IP/MAC address
+ operations as well as DHCP(v6). Other interface which represent e.g.
+ and ethernet bridge are implemented as derived classes adding all
+ additional functionality.
+
+ For creation you will need to provide the interface type, otherwise
+ the existing interface is used
+
+ DEBUG:
+ This class has embedded debugging (print) which can be enabled by
+ creating the following file:
+ vyos@vyos# touch /tmp/vyos.ifconfig.debug
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> i = Interface('eth0')
+ """
+
+ self.config = deepcopy(self.default)
+ self.config['ifname'] = ifname
+
+ for k in self.options:
+ if k in kargs:
+ self.config[k] = kargs[k]
+
+ for k in self.required:
+ if k not in kargs:
+ raise ConfigError('missing required option {} for {}'.format(k,self.__class__))
+
+ if not os.path.exists('/sys/class/net/{}'.format(self.config['ifname'])):
+ if not self.config['type']:
+ raise Exception('interface "{}" not found'.format(self.config['ifname']))
+ self._create()
+
+ # per interface DHCP config files
+ self._dhcp_cfg_file = dhclient_base + self.config['ifname'] + '.conf'
+ self._dhcp_pid_file = dhclient_base + self.config['ifname'] + '.pid'
+ self._dhcp_lease_file = dhclient_base + self.config['ifname'] + '.leases'
+
+ # per interface DHCPv6 config files
+ self._dhcpv6_cfg_file = dhclient_base + self.config['ifname'] + '.v6conf'
+ self._dhcpv6_pid_file = dhclient_base + self.config['ifname'] + '.v6pid'
+ self._dhcpv6_lease_file = dhclient_base + self.config['ifname'] + '.v6leases'
+
+ # DHCP options
+ self._dhcp_options = {
+ 'intf' : self.config['ifname'],
+ 'hostname' : '',
+ 'client_id' : '',
+ 'vendor_class_id' : ''
+ }
+
+ # DHCPv6 options
+ self._dhcpv6_options = {
+ 'intf' : self.config['ifname'],
+ 'dhcpv6_prm_only' : False,
+ 'dhcpv6_temporary' : False
+ }
+
+ # list of assigned IP addresses
+ self._addr = []
+
+ def _create(self):
+ cmd = 'ip link add dev {ifname} type {type}'.format(**self.config)
+ self._cmd(cmd)
+
+ def remove(self):
+ """
+ Remove interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses and clear possible DHCP(v6)
+ client processes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> i = Interface('eth0')
+ >>> i.remove()
+ """
+ # stop DHCP(v6) if running
+ self._del_dhcp()
+ self._del_dhcpv6()
+
+ # remove all assigned IP addresses from interface - this is a bit redundant
+ # as the kernel will remove all addresses on interface deletion, but we
+ # can not delete ALL interfaces, see below
+ for addr in self.get_addr():
+ self.del_addr(addr)
+
+ # ---------------------------------------------------------------------
+ # A code refactoring is required as this type check is present as
+ # Interface implement behaviour for one of it's sub-class.
+
+ # It is required as the current pattern for vlan is:
+ # Interface('name').remove() to delete an interface
+ # The code should be modified to have a class method called connect and
+ # have Interface.connect('name').remove()
+
+ # each subclass should register within Interface the pattern for that
+ # interface ie: (ethX, etc.) and use this to create an instance of
+ # the right class (EthernetIf, ...)
+
+ # Ethernet interfaces can not be removed
+
+ # Commented out as nowhere in the code do we call Interface()
+ # This would also cause an import loop
+ # if self.__class__ == EthernetIf:
+ # return
+
+ # ---------------------------------------------------------------------
+
+ self._delete()
+
+ def _delete(self):
+ # NOTE (Improvement):
+ # after interface removal no other commands should be allowed
+ # to be called and instead should raise an Exception:
+ cmd = 'ip link del dev {}'.format(self.config['ifname'])
+ return self._cmd(cmd)
+
+ def get_mtu(self):
+ """
+ Get/set interface mtu in bytes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_mtu()
+ '1500'
+ """
+ return self.get_interface('mtu')
+
+ def set_mtu(self, mtu):
+ """
+ Get/set interface mtu in bytes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_mtu(1400)
+ >>> Interface('eth0').get_mtu()
+ '1400'
+ """
+ return self.set_interface('mtu', mtu)
+
+ def set_mac(self, mac):
+ """
+ Set interface MAC (Media Access Contrl) address to given value.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_mac('00:50:ab:cd:ef:01')
+ """
+ self.set_interface('mac', mac)
+
+ def add_vrf(self, vrf):
+ """
+ Add interface to given VRF instance.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').add_vrf('foo')
+ """
+ self.set_interface('add_vrf', vrf)
+
+ def del_vrf(self, vrf):
+ """
+ Remove interface from given VRF instance.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').del_vrf('foo')
+ """
+ self.set_interface('del_vrf', vrf)
+
+ def set_arp_cache_tmo(self, tmo):
+ """
+ Set ARP cache timeout value in seconds. Internal Kernel representation
+ is in milliseconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_arp_cache_tmo(40)
+ """
+ return self.set_interface('arp_cache_tmo', tmo)
+
+ def set_arp_filter(self, arp_filter):
+ """
+ Filter ARP requests
+
+ 1 - Allows you to have multiple network interfaces on the same
+ subnet, and have the ARPs for each interface be answered
+ based on whether or not the kernel would route a packet from
+ the ARP'd IP out that interface (therefore you must use source
+ based routing for this to work). In other words it allows control
+ of which cards (usually 1) will respond to an arp request.
+
+ 0 - (default) The kernel can respond to arp requests with addresses
+ from other interfaces. This may seem wrong but it usually makes
+ sense, because it increases the chance of successful communication.
+ IP addresses are owned by the complete host on Linux, not by
+ particular interfaces. Only for more complex setups like load-
+ balancing, does this behaviour cause problems.
+ """
+ return self.set_interface('arp_filter', arp_filter)
+
+ def set_arp_accept(self, arp_accept):
+ """
+ Define behavior for gratuitous ARP frames who's IP is not
+ already present in the ARP table:
+ 0 - don't create new entries in the ARP table
+ 1 - create new entries in the ARP table
+
+ Both replies and requests type gratuitous arp will trigger the
+ ARP table to be updated, if this setting is on.
+
+ If the ARP table already contains the IP address of the
+ gratuitous arp frame, the arp table will be updated regardless
+ if this setting is on or off.
+ """
+ return self.set_interface('arp_accept', arp_accept)
+
+ def set_arp_announce(self, arp_announce):
+ """
+ Define different restriction levels for announcing the local
+ source IP address from IP packets in ARP requests sent on
+ interface:
+ 0 - (default) Use any local address, configured on any interface
+ 1 - Try to avoid local addresses that are not in the target's
+ subnet for this interface. This mode is useful when target
+ hosts reachable via this interface require the source IP
+ address in ARP requests to be part of their logical network
+ configured on the receiving interface. When we generate the
+ request we will check all our subnets that include the
+ target IP and will preserve the source address if it is from
+ such subnet.
+
+ Increasing the restriction level gives more chance for
+ receiving answer from the resolved target while decreasing
+ the level announces more valid sender's information.
+ """
+ return self.set_interface('arp_announce', arp_announce)
+
+ def set_arp_ignore(self, arp_ignore):
+ """
+ Define different modes for sending replies in response to received ARP
+ requests that resolve local target IP addresses:
+
+ 0 - (default): reply for any local target IP address, configured
+ on any interface
+ 1 - reply only if the target IP address is local address
+ configured on the incoming interface
+ """
+ return self.set_interface('arp_ignore', arp_ignore)
+
+ def set_link_detect(self, link_filter):
+ """
+ Configure kernel response in packets received on interfaces that are 'down'
+
+ 0 - Allow packets to be received for the address on this interface
+ even if interface is disabled or no carrier.
+
+ 1 - Ignore packets received if interface associated with the incoming
+ address is down.
+
+ 2 - Ignore packets received if interface associated with the incoming
+ address is down or has no carrier.
+
+ Default value is 0. Note that some distributions enable it in startup
+ scripts.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_link_detect(1)
+ """
+ return self.set_interface('link_detect', link_filter)
+
+ def set_alias(self, ifalias=''):
+ """
+ Set interface alias name used by e.g. SNMP
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_alias('VyOS upstream interface')
+
+ to clear alias e.g. delete it use:
+
+ >>> Interface('eth0').set_ifalias('')
+ """
+ self.set_interface('alias', ifalias)
+
+ def get_state(self):
+ """
+ Enable (up) / Disable (down) an interface
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_state()
+ 'up'
+ """
+ cmd = 'ip -json link show dev {}'.format(self.config['ifname'])
+ tmp = self._cmd(cmd)
+ out = json.loads(tmp)
+ return out[0]['operstate'].lower()
+
+ def set_state(self, state):
+ """
+ Enable (up) / Disable (down) an interface
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_state('down')
+ >>> Interface('eth0').get_state()
+ 'down'
+ """
+ if state not in ['up', 'down']:
+ raise ValueError('state must be "up" or "down"')
+
+ # Assemble command executed on system. Unfortunately there is no way
+ # to up/down an interface via sysfs
+ cmd = 'ip link set dev {} {}'.format(self.config['ifname'], state)
+ return self._cmd(cmd)
+
+ def set_proxy_arp(self, enable):
+ """
+ Set per interface proxy ARP configuration
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_proxy_arp(1)
+ """
+ self.set_interface('proxy_arp', enable)
+
+ def set_proxy_arp_pvlan(self, enable):
+ """
+ Private VLAN proxy arp.
+ Basically allow proxy arp replies back to the same interface
+ (from which the ARP request/solicitation was received).
+
+ This is done to support (ethernet) switch features, like RFC
+ 3069, where the individual ports are NOT allowed to
+ communicate with each other, but they are allowed to talk to
+ the upstream router. As described in RFC 3069, it is possible
+ to allow these hosts to communicate through the upstream
+ router by proxy_arp'ing. Don't need to be used together with
+ proxy_arp.
+
+ This technology is known by different names:
+ In RFC 3069 it is called VLAN Aggregation.
+ Cisco and Allied Telesyn call it Private VLAN.
+ Hewlett-Packard call it Source-Port filtering or port-isolation.
+ Ericsson call it MAC-Forced Forwarding (RFC Draft).
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_proxy_arp_pvlan(1)
+ """
+ self.set_interface('proxy_arp_pvlan', enable)
+
+ def get_addr(self):
+ """
+ Retrieve assigned IPv4 and IPv6 addresses from given interface.
+ This is done using the netifaces and ipaddress python modules.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_addrs()
+ ['172.16.33.30/24', 'fe80::20c:29ff:fe11:a174/64']
+ """
+
+ ipv4 = []
+ ipv6 = []
+
+ if AF_INET in ifaddresses(self.config['ifname']).keys():
+ for v4_addr in ifaddresses(self.config['ifname'])[AF_INET]:
+ # we need to manually assemble a list of IPv4 address/prefix
+ prefix = '/' + \
+ str(IPv4Network('0.0.0.0/' + v4_addr['netmask']).prefixlen)
+ ipv4.append(v4_addr['addr'] + prefix)
+
+ if AF_INET6 in ifaddresses(self.config['ifname']).keys():
+ for v6_addr in ifaddresses(self.config['ifname'])[AF_INET6]:
+ # Note that currently expanded netmasks are not supported. That means
+ # 2001:db00::0/24 is a valid argument while 2001:db00::0/ffff:ff00:: not.
+ # see https://docs.python.org/3/library/ipaddress.html
+ bits = bin(
+ int(v6_addr['netmask'].replace(':', ''), 16)).count('1')
+ prefix = '/' + str(bits)
+
+ # we alsoneed to remove the interface suffix on link local
+ # addresses
+ v6_addr['addr'] = v6_addr['addr'].split('%')[0]
+ ipv6.append(v6_addr['addr'] + prefix)
+
+ return ipv4 + ipv6
+
+ def add_addr(self, addr):
+ """
+ Add IP(v6) address to interface. Address is only added if it is not
+ already assigned to that interface.
+
+ addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6!
+ IPv4: add IPv4 address to interface
+ IPv6: add IPv6 address to interface
+ dhcp: start dhclient (IPv4) on interface
+ dhcpv6: start dhclient (IPv6) on interface
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.add_addr('192.0.2.1/24')
+ >>> j.add_addr('2001:db8::ffff/64')
+ >>> j.get_addr()
+ ['192.0.2.1/24', '2001:db8::ffff/64']
+ """
+
+ # cache new IP address which is assigned to interface
+ self._addr.append(addr)
+
+ # we can not have both DHCP and static IPv4 addresses assigned to an interface
+ if 'dhcp' in self._addr:
+ for addr in self._addr:
+ # do not change below 'if' ordering esle you will get an exception as:
+ # ValueError: 'dhcp' does not appear to be an IPv4 or IPv6 address
+ if addr != 'dhcp' and is_ipv4(addr):
+ raise ConfigError("Can't configure both static IPv4 and DHCP address on the same interface")
+
+ if addr == 'dhcp':
+ self._set_dhcp()
+ elif addr == 'dhcpv6':
+ self._set_dhcpv6()
+ else:
+ if not is_intf_addr_assigned(self.config['ifname'], addr):
+ cmd = 'ip addr add "{}" dev "{}"'.format(addr, self.config['ifname'])
+ return self._cmd(cmd)
+
+ def del_addr(self, addr):
+ """
+ Delete IP(v6) address to interface. Address is only added if it is
+ assigned to that interface.
+
+ addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6!
+ IPv4: delete IPv4 address from interface
+ IPv6: delete IPv6 address from interface
+ dhcp: stop dhclient (IPv4) on interface
+ dhcpv6: stop dhclient (IPv6) on interface
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.add_addr('2001:db8::ffff/64')
+ >>> j.add_addr('192.0.2.1/24')
+ >>> j.get_addr()
+ ['192.0.2.1/24', '2001:db8::ffff/64']
+ >>> j.del_addr('192.0.2.1/24')
+ >>> j.get_addr()
+ ['2001:db8::ffff/64']
+ """
+ if addr == 'dhcp':
+ self._del_dhcp()
+ elif addr == 'dhcpv6':
+ self._del_dhcpv6()
+ else:
+ if is_intf_addr_assigned(self.config['ifname'], addr):
+ cmd = 'ip addr del "{}" dev "{}"'.format(addr, self.config['ifname'])
+ return self._cmd(cmd)
+
+
+ def get_dhcp_options(self):
+ """
+ Return dictionary with supported DHCP options.
+
+ Dictionary should be altered and send back via set_dhcp_options()
+ so those options are applied when DHCP is run.
+ """
+ return self._dhcp_options
+
+ def set_dhcp_options(self, options):
+ """
+ Store new DHCP options used by next run of DHCP client.
+ """
+ self._dhcp_options = options
+
+ def get_dhcpv6_options(self):
+ """
+ Return dictionary with supported DHCPv6 options.
+
+ Dictionary should be altered and send back via set_dhcp_options()
+ so those options are applied when DHCP is run.
+ """
+ return self._dhcpv6_options
+
+ def set_dhcpv6_options(self, options):
+ """
+ Store new DHCP options used by next run of DHCP client.
+ """
+ self._dhcpv6_options = options
+
+ # replace dhcpv4/v6 with systemd.networkd?
+ def _set_dhcp(self):
+ """
+ Configure interface as DHCP client. The dhclient binary is automatically
+ started in background!
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.set_dhcp()
+ """
+
+ dhcp = self.get_dhcp_options()
+ if not dhcp['hostname']:
+ # read configured system hostname.
+ # maybe change to vyos hostd client ???
+ with open('/etc/hostname', 'r') as f:
+ dhcp['hostname'] = f.read().rstrip('\n')
+
+ # render DHCP configuration
+ tmpl = jinja2.Template(dhcp_cfg)
+ dhcp_text = tmpl.render(dhcp)
+ with open(self._dhcp_cfg_file, 'w') as f:
+ f.write(dhcp_text)
+
+ cmd = 'start-stop-daemon --start --quiet --pidfile ' + \
+ self._dhcp_pid_file
+ cmd += ' --exec /sbin/dhclient --'
+ # now pass arguments to dhclient binary
+ cmd += ' -4 -nw -cf {} -pf {} -lf {} {}'.format(
+ self._dhcp_cfg_file, self._dhcp_pid_file, self._dhcp_lease_file, self.config['ifname'])
+ return self._cmd(cmd)
+
+
+ def _del_dhcp(self):
+ """
+ De-configure interface as DHCP clinet. All auto generated files like
+ pid, config and lease will be removed.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.del_dhcp()
+ """
+ pid = 0
+ if os.path.isfile(self._dhcp_pid_file):
+ with open(self._dhcp_pid_file, 'r') as f:
+ pid = int(f.read())
+ else:
+ self._debug_msg('No DHCP client PID found')
+ return None
+
+ # stop dhclient, we need to call dhclient and tell it should release the
+ # aquired IP address. tcpdump tells me:
+ # 172.16.35.103.68 > 172.16.35.254.67: [bad udp cksum 0xa0cb -> 0xb943!] BOOTP/DHCP, Request from 00:50:56:9d:11:df, length 300, xid 0x620e6946, Flags [none] (0x0000)
+ # Client-IP 172.16.35.103
+ # Client-Ethernet-Address 00:50:56:9d:11:df
+ # Vendor-rfc1048 Extensions
+ # Magic Cookie 0x63825363
+ # DHCP-Message Option 53, length 1: Release
+ # Server-ID Option 54, length 4: 172.16.35.254
+ # Hostname Option 12, length 10: "vyos"
+ #
+ cmd = '/sbin/dhclient -cf {} -pf {} -lf {} -r {}'.format(
+ self._dhcp_cfg_file, self._dhcp_pid_file, self._dhcp_lease_file, self.config['ifname'])
+ self._cmd(cmd)
+
+ # cleanup old config file
+ if os.path.isfile(self._dhcp_cfg_file):
+ os.remove(self._dhcp_cfg_file)
+
+ # cleanup old pid file
+ if os.path.isfile(self._dhcp_pid_file):
+ os.remove(self._dhcp_pid_file)
+
+ # cleanup old lease file
+ if os.path.isfile(self._dhcp_lease_file):
+ os.remove(self._dhcp_lease_file)
+
+
+ def _set_dhcpv6(self):
+ """
+ Configure interface as DHCPv6 client. The dhclient binary is automatically
+ started in background!
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.set_dhcpv6()
+ """
+ dhcpv6 = self.get_dhcpv6_options()
+
+ # better save then sorry .. should be checked in interface script
+ # but if you missed it we are safe!
+ if dhcpv6['dhcpv6_prm_only'] and dhcpv6['dhcpv6_temporary']:
+ raise Exception('DHCPv6 temporary and parameters-only options are mutually exclusive!')
+
+ # render DHCP configuration
+ tmpl = jinja2.Template(dhcpv6_cfg)
+ dhcpv6_text = tmpl.render(dhcpv6)
+ with open(self._dhcpv6_cfg_file, 'w') as f:
+ f.write(dhcpv6_text)
+
+ # https://bugs.launchpad.net/ubuntu/+source/ifupdown/+bug/1447715
+ #
+ # wee need to wait for IPv6 DAD to finish once and interface is added
+ # this suxx :-(
+ sleep(5)
+
+ # no longer accept router announcements on this interface
+ self._write_sysfs('/proc/sys/net/ipv6/conf/{}/accept_ra'
+ .format(self.config['ifname']), 0)
+
+ # assemble command-line to start DHCPv6 client (dhclient)
+ cmd = 'start-stop-daemon --start --quiet --pidfile ' + \
+ self._dhcpv6_pid_file
+ cmd += ' --exec /sbin/dhclient --'
+ # now pass arguments to dhclient binary
+ cmd += ' -6 -nw -cf {} -pf {} -lf {}'.format(
+ self._dhcpv6_cfg_file, self._dhcpv6_pid_file, self._dhcpv6_lease_file)
+
+ # add optional arguments
+ if dhcpv6['dhcpv6_prm_only']:
+ cmd += ' -S'
+ if dhcpv6['dhcpv6_temporary']:
+ cmd += ' -T'
+
+ cmd += ' {}'.format(self.config['ifname'])
+ return self._cmd(cmd)
+
+
+ def _del_dhcpv6(self):
+ """
+ De-configure interface as DHCPv6 clinet. All auto generated files like
+ pid, config and lease will be removed.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.del_dhcpv6()
+ """
+ pid = 0
+ if os.path.isfile(self._dhcpv6_pid_file):
+ with open(self._dhcpv6_pid_file, 'r') as f:
+ pid = int(f.read())
+ else:
+ self._debug_msg('No DHCPv6 client PID found')
+ return None
+
+ # stop dhclient
+ cmd = 'start-stop-daemon --stop --quiet --pidfile {}'.format(self._dhcpv6_pid_file)
+ self._cmd(cmd)
+
+ # accept router announcements on this interface
+ self._write_sysfs('/proc/sys/net/ipv6/conf/{}/accept_ra'
+ .format(self.config['ifname']), 1)
+
+ # cleanup old config file
+ if os.path.isfile(self._dhcpv6_cfg_file):
+ os.remove(self._dhcpv6_cfg_file)
+
+ # cleanup old pid file
+ if os.path.isfile(self._dhcpv6_pid_file):
+ os.remove(self._dhcpv6_pid_file)
+
+ # cleanup old lease file
+ if os.path.isfile(self._dhcpv6_lease_file):
+ os.remove(self._dhcpv6_lease_file)
+
+ 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/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py
new file mode 100644
index 000000000..491fd24a7
--- /dev/null
+++ b/python/vyos/ifconfig/l2tpv3.py
@@ -0,0 +1,108 @@
+# 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 vyos.ifconfig.interface import Interface
+
+
+class L2TPv3If(Interface):
+ """
+ The Linux bonding driver provides a method for aggregating multiple network
+ interfaces into a single logical "bonded" interface. The behavior of the
+ bonded interfaces depends upon the mode; generally speaking, modes provide
+ either hot standby or load balancing services. Additionally, link integrity
+ monitoring may be performed.
+ """
+
+ options = Interface.options + \
+ ['tunnel_id', 'peer_tunnel_id', 'local_port', 'remote_port',
+ 'encapsulation', 'local_address', 'remote_address']
+ default = {
+ 'type': 'l2tp',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def _create(self):
+ # create tunnel interface
+ cmd = 'ip l2tp add tunnel tunnel_id {} '.format(config['tunnel_id'])
+ cmd += 'peer_tunnel_id {} '.format(config['peer_tunnel_id'])
+ cmd += 'udp_sport {} '.format(config['local_port'])
+ cmd += 'udp_dport {} '.format(config['remote_port'])
+ cmd += 'encap {} '.format(config['encapsulation'])
+ cmd += 'local {} '.format(config['local_address'])
+ cmd += 'remote {} '.format(config['remote_address'])
+ self._cmd(cmd)
+
+ # setup session
+ cmd = 'ip l2tp add session name {} '.format(self.config['ifname'])
+ cmd += 'tunnel_id {} '.format(config['tunnel_id'])
+ cmd += 'session_id {} '.format(config['session_id'])
+ cmd += 'peer_session_id {} '.format(config['peer_session_id'])
+ self._cmd(cmd)
+
+ # interface is always A/D down. It needs to be enabled explicitly
+ self.set_state('down')
+
+ def remove(self):
+ """
+ Remove interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses.
+ Example:
+ >>> from vyos.ifconfig import L2TPv3If
+ >>> i = L2TPv3If('l2tpeth0')
+ >>> i.remove()
+ """
+
+ if os.path.exists('/sys/class/net/{}'.format(self.config['ifname'])):
+ # interface is always A/D down. It needs to be enabled explicitly
+ self.set_state('down')
+
+ if self._config['tunnel_id'] and self._config['session_id']:
+ cmd = 'ip l2tp del session tunnel_id {} '.format(
+ self._config['tunnel_id'])
+ cmd += 'session_id {} '.format(self._config['session_id'])
+ self._cmd(cmd)
+
+ if self._config['tunnel_id']:
+ cmd = 'ip l2tp del tunnel tunnel_id {} '.format(
+ self._config['tunnel_id'])
+ self._cmd(cmd)
+
+ @staticmethod
+ def get_config():
+ """
+ L2TPv3 interfaces require a configuration when they are added using
+ iproute2. This static method will provide the configuration dictionary
+ used by this class.
+
+ Example:
+ >> dict = L2TPv3If().get_config()
+ """
+ config = {
+ 'peer_tunnel_id': '',
+ 'local_port': 0,
+ 'remote_port': 0,
+ 'encapsulation': 'udp',
+ 'local_address': '',
+ 'remote_address': '',
+ 'session_id': '',
+ 'tunnel_id': '',
+ 'peer_session_id': ''
+ }
+ return config
diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py
new file mode 100644
index 000000000..410a19dcf
--- /dev/null
+++ b/python/vyos/ifconfig/loopback.py
@@ -0,0 +1,50 @@
+# 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/>.
+
+
+from vyos.ifconfig.interface import Interface
+
+
+class LoopbackIf(Interface):
+ """
+ The loopback device is a special, virtual network interface that your router
+ uses to communicate with itself.
+ """
+
+ default = {
+ 'type': 'loopback',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def remove(self):
+ """
+ Loopback interface can not be deleted from operating system. We can
+ only remove all assigned IP addresses.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> i = LoopbackIf('lo').remove()
+ """
+ # remove all assigned IP addresses from interface
+ for addr in self.get_addr():
+ if addr in ["127.0.0.1/8", "::1/128"]:
+ # Do not allow deletion of the default loopback addresses as
+ # this will cause weird system behavior like snmp/ssh no longer
+ # operating as expected, see https://phabricator.vyos.net/T2034.
+ continue
+
+ self.del_addr(addr)
diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py
new file mode 100644
index 000000000..a86f84f3e
--- /dev/null
+++ b/python/vyos/ifconfig/macvlan.py
@@ -0,0 +1,61 @@
+# 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/>.
+
+
+from vyos.ifconfig.vlan import VLANIf
+
+
+class MACVLANIf(VLANIf):
+ """
+ Abstraction of a Linux MACvlan interface
+ """
+
+ options = VLANIf.options + ['link', 'mode']
+ default = {
+ 'type': 'macvlan',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def _create(self):
+ cmd = 'ip link add {ifname} link {link} type macvlan mode {mode}'.format(
+ **self.config)
+ self._cmd(cmd)
+
+ @staticmethod
+ def get_config():
+ """
+ VXLAN interfaces require a configuration when they are added using
+ iproute2. This static method will provide the configuration dictionary
+ used by this class.
+
+ Example:
+ >> dict = MACVLANIf().get_config()
+ """
+ config = {
+ 'address': '',
+ 'link': 0,
+ 'mode': ''
+ }
+ return config
+
+ def set_mode(self, mode):
+ """
+ """
+
+ cmd = 'ip link set dev {} type macvlan mode {}'.format(
+ self.config['ifname'], mode)
+ return self._cmd(cmd)
diff --git a/python/vyos/ifconfig/stp.py b/python/vyos/ifconfig/stp.py
new file mode 100644
index 000000000..741322d0d
--- /dev/null
+++ b/python/vyos/ifconfig/stp.py
@@ -0,0 +1,69 @@
+# 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/>.
+
+
+from vyos.ifconfig.interface import Interface
+
+from vyos.validate import *
+
+
+class STPIf(Interface):
+ """
+ A spanning-tree capable interface. This applies only to bridge port member
+ interfaces!
+ """
+ _sysfs_set = {**Interface._sysfs_set, **{
+ 'path_cost': {
+ # XXX: we should set a maximum
+ 'validate': assert_positive,
+ 'location': '/sys/class/net/{ifname}/brport/path_cost',
+ 'errormsg': '{ifname} is not a bridge port member'
+ },
+ 'path_priority': {
+ # XXX: we should set a maximum
+ 'validate': assert_positive,
+ 'location': '/sys/class/net/{ifname}/brport/priority',
+ 'errormsg': '{ifname} is not a bridge port member'
+ },
+ }}
+
+ default = {
+ 'type': 'stp',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def set_path_cost(self, cost):
+ """
+ Set interface path cost, only relevant for STP enabled interfaces
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_path_cost(4)
+ """
+ self.set_interface('path_cost', cost)
+
+ def set_path_priority(self, priority):
+ """
+ Set interface path priority, only relevant for STP enabled interfaces
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').set_path_priority(4)
+ """
+ self.set_interface('path_priority', priority)
diff --git a/python/vyos/ifconfig/vlan.py b/python/vyos/ifconfig/vlan.py
new file mode 100644
index 000000000..ad5d066c4
--- /dev/null
+++ b/python/vyos/ifconfig/vlan.py
@@ -0,0 +1,134 @@
+# 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 re
+
+from vyos.ifconfig.interface import Interface
+
+
+class VLANIf(Interface):
+ """
+ This class handels the creation and removal of a VLAN interface. It serves
+ as base class for BondIf and EthernetIf.
+ """
+
+ default = {
+ 'type': 'vlan',
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def remove(self):
+ """
+ Remove interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses and clear possible DHCP(v6)
+ client processes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> i = Interface('eth0')
+ >>> i.remove()
+ """
+ # Do we have sub interfaces (VLANs)? We apply a regex matching
+ # subinterfaces (indicated by a .) of a parent interface.
+ #
+ # As interfaces need to be deleted "in order" starting from Q-in-Q
+ # we delete them first.
+ vlan_ifs = [f for f in os.listdir(r'/sys/class/net')
+ if re.match(self.config['ifname'] + r'(?:\.\d+)(?:\.\d+)', f)]
+
+ for vlan in vlan_ifs:
+ Interface(vlan).remove()
+
+ # After deleting all Q-in-Q interfaces delete other VLAN interfaces
+ # which probably acted as parent to Q-in-Q or have been regular 802.1q
+ # interface.
+ vlan_ifs = [f for f in os.listdir(r'/sys/class/net')
+ if re.match(self.config['ifname'] + r'(?:\.\d+)', f)]
+
+ for vlan in vlan_ifs:
+ Interface(vlan).remove()
+
+ # All subinterfaces are now removed, continue on the physical interface
+ super().remove()
+
+ def add_vlan(self, vlan_id, ethertype='', ingress_qos='', egress_qos=''):
+ """
+ A virtual LAN (VLAN) is any broadcast domain that is partitioned and
+ isolated in a computer network at the data link layer (OSI layer 2).
+ Use this function to create a new VLAN interface on a given physical
+ interface.
+
+ This function creates both 802.1q and 802.1ad (Q-in-Q) interfaces. Proto
+ parameter is used to indicate VLAN type.
+
+ A new object of type VLANIf is returned once the interface has been
+ created.
+
+ @param ethertype: If specified, create 802.1ad or 802.1q Q-in-Q VLAN
+ interface
+ @param ingress_qos: Defines a mapping of VLAN header prio field to the
+ Linux internal packet priority on incoming frames.
+ @param ingress_qos: Defines a mapping of Linux internal packet priority
+ to VLAN header prio field but for outgoing frames.
+
+ Example:
+ >>> from vyos.ifconfig import VLANIf
+ >>> i = VLANIf('eth0')
+ >>> i.add_vlan(10)
+ """
+ vlan_ifname = self.config['ifname'] + '.' + str(vlan_id)
+ if not os.path.exists('/sys/class/net/{}'.format(vlan_ifname)):
+ self._vlan_id = int(vlan_id)
+
+ if ethertype:
+ self._ethertype = ethertype
+ ethertype = 'proto {}'.format(ethertype)
+
+ # Optional ingress QOS mapping
+ opt_i = ''
+ if ingress_qos:
+ opt_i = 'ingress-qos-map ' + ingress_qos
+ # Optional egress QOS mapping
+ opt_e = ''
+ if egress_qos:
+ opt_e = 'egress-qos-map ' + egress_qos
+
+ # create interface in the system
+ cmd = 'ip link add link {ifname} name {ifname}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \
+ .format(ifname=self.config['ifname'], vlan=self._vlan_id, proto=ethertype, opt_e=opt_e, opt_i=opt_i)
+ self._cmd(cmd)
+
+ # return new object mapping to the newly created interface
+ # we can now work on this object for e.g. IP address setting
+ # or interface description and so on
+ return VLANIf(vlan_ifname)
+
+ def del_vlan(self, vlan_id):
+ """
+ Remove VLAN interface from operating system. Removing the interface
+ deconfigures all assigned IP addresses and clear possible DHCP(v6)
+ client processes.
+
+ Example:
+ >>> from vyos.ifconfig import VLANIf
+ >>> i = VLANIf('eth0.10')
+ >>> i.del_vlan()
+ """
+ vlan_ifname = self.config['ifname'] + '.' + str(vlan_id)
+ VLANIf(vlan_ifname).remove()
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
new file mode 100644
index 000000000..bc2ec508b
--- /dev/null
+++ b/python/vyos/ifconfig/vxlan.py
@@ -0,0 +1,91 @@
+# 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/>.
+
+
+from vyos.ifconfig.interface import Interface
+
+
+class VXLANIf(Interface):
+ """
+ The VXLAN protocol is a tunnelling protocol designed to solve the
+ problem of limited VLAN IDs (4096) in IEEE 802.1q. With VXLAN the
+ size of the identifier is expanded to 24 bits (16777216).
+
+ VXLAN is described by IETF RFC 7348, and has been implemented by a
+ number of vendors. The protocol runs over UDP using a single
+ destination port. This document describes the Linux kernel tunnel
+ device, there is also a separate implementation of VXLAN for
+ Openvswitch.
+
+ Unlike most tunnels, a VXLAN is a 1 to N network, not just point to
+ point. A VXLAN device can learn the IP address of the other endpoint
+ either dynamically in a manner similar to a learning bridge, or make
+ use of statically-configured forwarding entries.
+
+ For more information please refer to:
+ https://www.kernel.org/doc/Documentation/networking/vxlan.txt
+ """
+
+ options = ['group', 'remote', 'dev', 'port', 'vni']
+
+ default = {
+ 'type': 'vxlan',
+ 'vni': 0,
+ 'dev': '',
+ 'group': '',
+ 'remote': '',
+ 'port': 8472, # The Linux implementation of VXLAN pre-dates
+ # the IANA's selection of a standard destination port
+ }
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def _create(self):
+ # we assume that by default a multicast interface is created
+ group = 'group {}'.format(self.config['group'])
+
+ # if remote host is specified we ignore the multicast address
+ if self.config['remote']:
+ group = 'remote {}'.format(self.config['remote'])
+
+ # an underlay device is not always specified
+ dev = ''
+ if self.config['dev']:
+ dev = 'dev {}'.format(self.config['dev'])
+
+ cmd = 'ip link add {ifname} type vxlan id {vni} {group} {dev} dstport {port}'.format(
+ **config)
+ self._cmd(cmd)
+
+ @staticmethod
+ def get_config():
+ """
+ VXLAN interfaces require a configuration when they are added using
+ iproute2. This static method will provide the configuration dictionary
+ used by this class.
+
+ Example:
+ >> dict = VXLANIf().get_config()
+ """
+ config = {
+ 'vni': 0,
+ 'dev': '',
+ 'group': '',
+ 'port': 8472, # The Linux implementation of VXLAN pre-dates
+ # the IANA's selection of a standard destination port
+ 'remote': ''
+ }
+ return config
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
new file mode 100644
index 000000000..2926e72e1
--- /dev/null
+++ b/python/vyos/ifconfig/wireguard.py
@@ -0,0 +1,169 @@
+# 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 vyos.ifconfig.interface import Interface
+
+class WireGuardIf(Interface):
+ options = ['port', 'private-key', 'pubkey', 'psk',
+ 'allowed-ips', 'fwmark', 'endpoint', 'keepalive']
+
+ default = {
+ 'type': 'wireguard',
+ 'port': 0,
+ 'private-key': None,
+ 'pubkey': None,
+ 'psk': '/dev/null',
+ 'allowed-ips': [],
+ 'fwmark': 0x00,
+ 'endpoint': None,
+ 'keepalive': 0
+ }
+
+ """
+ Wireguard interface class, contains a comnfig dictionary since
+ wireguard VPN is being comnfigured via the wg command rather than
+ writing the config into a file. Otherwise if a pre-shared key is used
+ (symetric enryption key), it would we exposed within multiple files.
+ Currently it's only within the config.boot if the config was saved.
+
+ Example:
+ >>> from vyos.ifconfig import WireGuardIf as wg_if
+ >>> wg_intfc = wg_if("wg01")
+ >>> print (wg_intfc.wg_config)
+ {'private-key': None, 'keepalive': 0, 'endpoint': None, 'port': 0,
+ 'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'}
+ >>> wg_intfc.wg_config['keepalive'] = 100
+ >>> print (wg_intfc.wg_config)
+ {'private-key': None, 'keepalive': 100, 'endpoint': None, 'port': 0,
+ 'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'}
+ """
+
+ def __init__(self, ifname, **kargs):
+ super().__init__(ifname, **kargs)
+
+ def update(self):
+ if not self.config['private-key']:
+ raise ValueError("private key required")
+ else:
+ # fmask permission check?
+ pass
+
+ cmd = "wg set {} ".format(self.config['ifname'])
+ cmd += "listen-port {} ".format(self.config['port'])
+ cmd += "fwmark {} ".format(str(self.config['fwmark']))
+ cmd += "private-key {} ".format(self.config['private-key'])
+ cmd += "peer {} ".format(self.config['pubkey'])
+ cmd += " preshared-key {} ".format(self.config['psk'])
+ cmd += " allowed-ips "
+ for aip in self.config['allowed-ips']:
+ if aip != self.config['allowed-ips'][-1]:
+ cmd += aip + ","
+ else:
+ cmd += aip
+ if self.config['endpoint']:
+ cmd += " endpoint {}".format(self.config['endpoint'])
+ cmd += " persistent-keepalive {}".format(self.config['keepalive'])
+
+ self._cmd(cmd)
+
+ # remove psk since it isn't required anymore and is saved in the cli
+ # config only !!
+ if self.config['psk'] != '/dev/null':
+ if os.path.exists(self.config['psk']):
+ os.remove(self.config['psk'])
+
+ def remove_peer(self, peerkey):
+ """
+ Remove a peer of an interface, peers are identified by their public key.
+ Giving it a readable name is a vyos feature, to remove a peer the pubkey
+ and the interface is needed, to remove the entry.
+ """
+ cmd = "wg set {0} peer {1} remove".format(
+ self.config['ifname'], str(peerkey))
+ return self._cmd(cmd)
+
+ def op_show_interface(self):
+ wgdump = vyos.interfaces.wireguard_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()
diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py
index 576bb244a..1d57283ac 100644
--- a/python/vyos/ifconfig_vlan.py
+++ b/python/vyos/ifconfig_vlan.py
@@ -13,7 +13,9 @@
# 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/>.
+from netifaces import interfaces
from vyos.ifconfig import VLANIf
+from vyos import ConfigError
def apply_vlan_config(vlan, config):
"""
@@ -65,6 +67,13 @@ def apply_vlan_config(vlan, config):
vlan.set_arp_ignore(config['ip_enable_arp_ignore'])
# Maximum Transmission Unit (MTU)
vlan.set_mtu(config['mtu'])
+
+ # assign to VRF
+ if config['vrf']:
+ vlan.add_vrf(config['vrf'])
+ else:
+ vlan.del_vrf(config['vrf'])
+
# Change VLAN interface MAC address
if config['mac']:
vlan.set_mac(config['mac'])
@@ -83,3 +92,46 @@ def apply_vlan_config(vlan, config):
for addr in config['address']:
vlan.add_addr(addr)
+def verify_vlan_config(config):
+ """
+ Generic function to verify VLAN config consistency. Instead of re-
+ implementing this function in multiple places use single source \o/
+ """
+
+ for vif in config['vif']:
+ # DHCPv6 parameters-only and temporary address are mutually exclusive
+ if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']:
+ raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
+
+ vrf_name = vif['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
+ # e.g. wireless interface has no vif_s support
+ # thus we bail out eraly.
+ if 'vif_s' not in config.keys():
+ return
+
+ for vif_s in config['vif_s']:
+ for vif in config['vif']:
+ if vif['id'] == vif_s['id']:
+ raise ConfigError('Can not use identical ID on vif and vif-s interface')
+
+ # DHCPv6 parameters-only and temporary address are mutually exclusive
+ if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']:
+ raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
+
+ vrf_name = vif_s['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
+ for vif_c in vif_s['vif_c']:
+ # DHCPv6 parameters-only and temporary address are mutually exclusive
+ if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']:
+ raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
+
+ vrf_name = vif_c['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
+
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index 4dca82dd8..48a18642b 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -187,7 +187,7 @@ def assert_list(s, l):
def assert_number(n):
- if not n.isnumeric():
+ if not str(n).isnumeric():
raise ValueError(f'{n} must be a number')
diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates
index 0383c8982..689d19ece 100755
--- a/scripts/build-command-op-templates
+++ b/scripts/build-command-op-templates
@@ -111,7 +111,7 @@ def get_properties(p):
for i in lists:
comp_exprs.append("echo \"{0}\"".format(i.text))
for i in paths:
- comp_exprs.append("/bin/cli-shell-api listNodes {0}".format(i.text))
+ comp_exprs.append("/bin/cli-shell-api listActiveNodes {0} | sed -e \"s/'//g\"".format(i.text))
for i in scripts:
comp_exprs.append("{0}".format(i.text))
comp_help = " && ".join(comp_exprs)
diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py
index dcb0b59ed..a75beabd1 100755
--- a/src/conf_mode/interfaces-bonding.py
+++ b/src/conf_mode/interfaces-bonding.py
@@ -21,7 +21,7 @@ from sys import exit
from netifaces import interfaces
from vyos.ifconfig import BondIf
-from vyos.ifconfig_vlan import apply_vlan_config
+from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
from vyos.configdict import list_diff, vlan_to_dict
from vyos.config import Config
from vyos import ConfigError
@@ -58,7 +58,8 @@ default_config_data = {
'vif_s': [],
'vif_s_remove': [],
'vif': [],
- 'vif_remove': []
+ 'vif_remove': [],
+ 'vrf': ''
}
@@ -221,8 +222,10 @@ def get_config():
if conf.exists('primary'):
bond['primary'] = conf.return_value('primary')
- # re-set configuration level to parse new nodes
- conf.set_level(cfg_base)
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ bond['vrf'] = conf.return_value('vrf')
+
# get vif-s interfaces (currently effective) - to determine which vif-s
# interface is no longer present and needs to be removed
eff_intf = conf.list_effective_nodes('vif-s')
@@ -265,26 +268,12 @@ def verify(bond):
raise ConfigError('Interface "{}" is not part of the bond' \
.format(bond['primary']))
+ vrf_name = bond['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
- # DHCPv6 parameters-only and temporary address are mutually exclusive
- for vif_s in bond['vif_s']:
- if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']:
- raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
-
- for vif_c in vif_s['vif_c']:
- if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']:
- raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
-
- for vif in bond['vif']:
- if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']:
- raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
-
-
- for vif_s in bond['vif_s']:
- for vif in bond['vif']:
- if vif['id'] == vif_s['id']:
- raise ConfigError('Can not use identical ID on vif and vif-s interface')
-
+ # use common function to verify VLAN configuration
+ verify_vlan_config(bond)
conf = Config()
for intf in bond['member']:
@@ -472,6 +461,12 @@ def apply(bond):
for addr in bond['address']:
b.add_addr(addr)
+ # assign to VRF
+ if bond['vrf']:
+ b.add_vrf(bond['vrf'])
+ else:
+ b.del_vrf(bond['vrf'])
+
# remove no longer required service VLAN interfaces (vif-s)
for vif_s in bond['vif_s_remove']:
b.del_vlan(vif_s)
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 0810d63d6..f189ce36d 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -52,7 +52,8 @@ default_config_data = {
'member': [],
'member_remove': [],
'priority': 32768,
- 'stp': 0
+ 'stp': 0,
+ 'vrf': ''
}
def get_config():
@@ -191,12 +192,20 @@ def get_config():
if conf.exists('stp'):
bridge['stp'] = 1
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ bridge['vrf'] = conf.return_value('vrf')
+
return bridge
def verify(bridge):
if bridge['dhcpv6_prm_only'] and bridge['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
+ vrf_name = bridge['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
conf = Config()
for br in conf.list_nodes('interfaces bridge'):
# it makes no sense to verify ourself in this case
@@ -286,6 +295,12 @@ def apply(bridge):
# store DHCPv6 config dictionary - used later on when addresses are aquired
br.set_dhcpv6_options(opt)
+ # assign to VRF
+ if bridge['vrf']:
+ br.add_vrf(bridge['vrf'])
+ else:
+ br.del_vrf(bridge['vrf'])
+
# Change interface MAC address
if bridge['mac']:
br.set_mac(bridge['mac'])
diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py
index e79e6222d..10cae5d7d 100755
--- a/src/conf_mode/interfaces-dummy.py
+++ b/src/conf_mode/interfaces-dummy.py
@@ -18,6 +18,7 @@ import os
from copy import deepcopy
from sys import exit
+from netifaces import interfaces
from vyos.ifconfig import DummyIf
from vyos.configdict import list_diff
@@ -30,7 +31,8 @@ default_config_data = {
'deleted': False,
'description': '',
'disable': False,
- 'intf': ''
+ 'intf': '',
+ 'vrf': ''
}
def get_config():
@@ -69,9 +71,17 @@ def get_config():
act_addr = conf.return_values('address')
dummy['address_remove'] = list_diff(eff_addr, act_addr)
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ dummy['vrf'] = conf.return_value('vrf')
+
return dummy
def verify(dummy):
+ vrf_name = dummy['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
return None
def generate(dummy):
@@ -95,6 +105,12 @@ def apply(dummy):
for addr in dummy['address']:
d.add_addr(addr)
+ # assign to VRF
+ if dummy['vrf']:
+ d.add_vrf(dummy['vrf'])
+ else:
+ d.del_vrf(dummy['vrf'])
+
# disable interface on demand
if dummy['disable']:
d.set_state('down')
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index 43cc22589..6d779c94c 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -16,11 +16,12 @@
import os
-from copy import deepcopy
from sys import exit
+from copy import deepcopy
+from netifaces import interfaces
from vyos.ifconfig import EthernetIf
-from vyos.ifconfig_vlan import apply_vlan_config
+from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
from vyos.configdict import list_diff, vlan_to_dict
from vyos.config import Config
from vyos import ConfigError
@@ -59,7 +60,8 @@ default_config_data = {
'vif_s': [],
'vif_s_remove': [],
'vif': [],
- 'vif_remove': []
+ 'vif_remove': [],
+ 'vrf': ''
}
def get_config():
@@ -197,6 +199,10 @@ def get_config():
if conf.exists('speed'):
eth['speed'] = conf.return_value('speed')
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ eth['vrf'] = conf.return_value('vrf')
+
# re-set configuration level to parse new nodes
conf.set_level(cfg_base)
# get vif-s interfaces (currently effective) - to determine which vif-s
@@ -243,6 +249,10 @@ def verify(eth):
if eth['dhcpv6_prm_only'] and eth['dhcpv6_temporary']:
raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
+ vrf_name = eth['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
conf = Config()
# some options can not be changed when interface is enslaved to a bond
for bond in conf.list_nodes('interfaces bonding'):
@@ -250,21 +260,10 @@ def verify(eth):
bond_member = conf.return_values('interfaces bonding ' + bond + ' member interface')
if eth['intf'] in bond_member:
if eth['address']:
- raise ConfigError('Can not assign address to interface {} which is a member of {}').format(eth['intf'], bond)
-
- # DHCPv6 parameters-only and temporary address are mutually exclusive
- for vif_s in eth['vif_s']:
- if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']:
- raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
-
- for vif_c in vif_s['vif_c']:
- if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']:
- raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
-
- for vif in eth['vif']:
- if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']:
- raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!')
+ raise ConfigError('Can not assign address to interface {} which is a member of {}'.format(eth['intf'], bond))
+ # use common function to verify VLAN configuration
+ verify_vlan_config(eth)
return None
def generate(eth):
@@ -367,6 +366,12 @@ def apply(eth):
for addr in eth['address']:
e.add_addr(addr)
+ # assign to VRF
+ if eth['vrf']:
+ e.add_vrf(eth['vrf'])
+ else:
+ e.del_vrf(eth['vrf'])
+
# remove no longer required service VLAN interfaces (vif-s)
for vif_s in eth['vif_s_remove']:
e.del_vlan(vif_s)
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index 8ec78bab3..f948070ee 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -20,9 +20,9 @@ from sys import exit
from copy import deepcopy
from jinja2 import Template
from subprocess import Popen, PIPE
-from time import sleep
from pwd import getpwnam
from grp import getgrnam
+from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH
from vyos.config import Config
from vyos.ifconfig import Interface
@@ -30,9 +30,7 @@ from vyos import ConfigError
from netifaces import interfaces
# Please be careful if you edit the template.
-config_pppoe_tmpl = """
-### Autogenerated by interfaces-pppoe.py ###
-
+config_pppoe_tmpl = """### Autogenerated by interfaces-pppoe.py ###
{% if description %}
# {{ description }}
{% endif %}
@@ -92,14 +90,85 @@ usepeerdns
{% endif %}
{% if ipv6_enable -%}
+ipv6
+ipv6cp-use-ipaddr
{% endif %}
{% if service_name -%}
rp_pppoe_service "{{ service_name }}"
{% endif %}
+{% if on_demand %}
+demand
+{% endif %}
"""
-PPP_LOGFILE = '/var/log/vyatta/ppp_{}.log'
+# Please be careful if you edit the template.
+# There must be no blank line at the top pf the script file
+config_pppoe_ipv6_up_tmpl = """#!/bin/sh
+
+# As PPPoE is an "on demand" interface we need to re-configure it when it
+# becomes up
+
+if [ "$6" != "{{ intf }}" ]; then
+ exit
+fi
+
+# add some info to syslog
+DIALER_PID=$(cat /var/run/{{ intf }}.pid)
+logger -t pppd[$DIALER_PID] "executing $0"
+logger -t pppd[$DIALER_PID] "configuring dialer interface $6 via $2"
+
+echo "{{ description }}" > /sys/class/net/{{ intf }}/ifalias
+
+{% if ipv6_autoconf -%}
+
+
+# Configure interface-specific Host/Router behaviour.
+# Note: It is recommended to have the same setting on all interfaces; mixed
+# router/host scenarios are rather uncommon. Possible values are:
+#
+# 0 Forwarding disabled
+# 1 Forwarding enabled
+#
+echo 1 > /proc/sys/net/ipv6/conf/{{ intf }}/forwarding
+
+# Accept Router Advertisements; autoconfigure using them.
+#
+# It also determines whether or not to transmit Router
+# Solicitations. If and only if the functional setting is to
+# accept Router Advertisements, Router Solicitations will be
+# transmitted. Possible values are:
+#
+# 0 Do not accept Router Advertisements.
+# 1 Accept Router Advertisements if forwarding is disabled.
+# 2 Overrule forwarding behaviour. Accept Router Advertisements
+# even if forwarding is enabled.
+#
+echo 2 > /proc/sys/net/ipv6/conf/{{ intf }}/accept_ra
+
+# Autoconfigure addresses using Prefix Information in Router Advertisements.
+echo 1 > /proc/sys/net/ipv6/conf/{{ intf }}/autoconfigure
+{% endif %}
+"""
+
+config_pppoe_ip_pre_up_tmpl = """#!/bin/sh
+
+# As PPPoE is an "on demand" interface we need to re-configure it when it
+# becomes up
+
+if [ "$6" != "pppoe0" ]; then
+ exit
+fi
+
+# add some info to syslog
+DIALER_PID=$(cat /var/run/{{ intf }}.pid)
+logger -t pppd[$DIALER_PID] "executing $0"
+
+{% if vrf -%}
+logger -t pppd[$DIALER_PID] "configuring dialer interface $6 for VRF {{ vrf }}"
+ip link set dev {{ intf }} master {{ vrf }}
+{% endif %}
+
+"""
default_config_data = {
'access_concentrator': '',
@@ -108,7 +177,7 @@ default_config_data = {
'on_demand': False,
'default_route': 'auto',
'deleted': False,
- 'description': '',
+ 'description': '\0',
'disable': False,
'intf': '',
'idle_timeout': '',
@@ -120,7 +189,8 @@ default_config_data = {
'name_server': True,
'remote_address': '',
'service_name': '',
- 'source_interface': ''
+ 'source_interface': '',
+ 'vrf': ''
}
def subprocess_cmd(command):
@@ -137,7 +207,7 @@ def get_config():
raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')
pppoe['intf'] = os.environ['VYOS_TAGNODE_VALUE']
- pppoe['logfile'] = PPP_LOGFILE.format(pppoe['intf'])
+ pppoe['logfile'] = f"/var/log/vyatta/ppp_{pppoe['intf']}.log"
# Check if interface has been removed
if not conf.exists(base_path + [pppoe['intf']]):
@@ -193,7 +263,7 @@ def get_config():
# Physical Interface used for this PPPoE session
if conf.exists(['source-interface']):
- pppoe['source_interface'] = conf.return_value('source-interface')
+ pppoe['source_interface'] = conf.return_value(['source-interface'])
# Maximum Transmission Unit (MTU)
if conf.exists(['mtu']):
@@ -211,6 +281,10 @@ def get_config():
if conf.exists(['service-name']):
pppoe['service_name'] = conf.return_value(['service-name'])
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ pppoe['vrf'] = conf.return_value(['vrf'])
+
return pppoe
def verify(pppoe):
@@ -219,18 +293,24 @@ def verify(pppoe):
return None
if not pppoe['source_interface']:
- raise ConfigError('PPPoE source interface is missing')
+ raise ConfigError('PPPoE source interface missing')
+
+ if not pppoe['source_interface'] in interfaces():
+ raise ConfigError(f"PPPoE source interface {pppoe['source_interface']} does not exist")
- if pppoe['source_interface'] not in interfaces():
- raise ConfigError('PPPoE source interface does not exist')
+ vrf_name = pppoe['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF {vrf_name} does not exist')
return None
def generate(pppoe):
- config_file_pppoe = '/etc/ppp/peers/{}'.format(pppoe['intf'])
+ config_file_pppoe = f"/etc/ppp/peers/{pppoe['intf']}"
+ ip_pre_up_script_file = f"/etc/ppp/ip-pre-up.d/9999-vyos-vrf-{pppoe['intf']}"
+ ipv6_if_up_script_file = f"/etc/ppp/ipv6-up.d/50-vyos-{pppoe['intf']}-autoconf"
# Always hang-up PPPoE connection prior generating new configuration file
- cmd = 'systemctl stop ppp@{}.service'.format(pppoe['intf'])
+ cmd = f"systemctl stop ppp@{pppoe['intf']}.service"
subprocess_cmd(cmd)
if pppoe['deleted']:
@@ -238,6 +318,12 @@ def generate(pppoe):
if os.path.exists(config_file_pppoe):
os.unlink(config_file_pppoe)
+ if os.path.exists(ipv6_if_up_script_file):
+ os.unlink(ipv6_if_up_script_file)
+
+ if os.path.exists(ip_pre_up_script_file):
+ os.unlink(ip_pre_up_script_file)
+
else:
# Create PPP configuration files
tmpl = Template(config_pppoe_tmpl)
@@ -245,6 +331,21 @@ def generate(pppoe):
with open(config_file_pppoe, 'w') as f:
f.write(config_text)
+ tmpl = Template(config_pppoe_ip_pre_up_tmpl)
+ config_text = tmpl.render(pppoe)
+ with open(ip_pre_up_script_file, 'w') as f:
+ f.write(config_text)
+
+ tmpl = Template(config_pppoe_ipv6_up_tmpl)
+ config_text = tmpl.render(pppoe)
+ with open(ipv6_if_up_script_file, 'w') as f:
+ f.write(config_text)
+
+ bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \
+ S_IROTH | S_IXOTH
+ os.chmod(ip_pre_up_script_file, bitmask)
+ os.chmod(ipv6_if_up_script_file, bitmask)
+
return None
def apply(pppoe):
@@ -254,7 +355,7 @@ def apply(pppoe):
if not pppoe['disable']:
# dial PPPoE connection
- cmd = 'systemctl start ppp@{}.service'.format(pppoe['intf'])
+ cmd = f"systemctl start ppp@{pppoe['intf']}.service"
subprocess_cmd(cmd)
# make logfile owned by root / vyattacfg
@@ -263,24 +364,6 @@ def apply(pppoe):
gid = getgrnam('vyattacfg').gr_gid
os.chown(pppoe['logfile'], uid, gid)
- # better late then sorry ... but we can only set interface alias after
- # pppd has been launched and created the interface
- cnt = 0
- while pppoe['intf'] not in interfaces():
- cnt += 1
- if cnt == 50:
- break
-
- # sleep 250ms
- sleep(0.250)
-
- try:
- # we need to catch the exception if the interface is not up due to
- # reason stated above
- Interface(pppoe['intf']).set_alias(pppoe['description'])
- except:
- pass
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index 3d36da226..989b1432b 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -21,7 +21,8 @@ from sys import exit
from netifaces import interfaces
from vyos.ifconfig import MACVLANIf
-from vyos.configdict import list_diff
+from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
+from vyos.configdict import list_diff, vlan_to_dict
from vyos.config import Config
from vyos import ConfigError
@@ -52,7 +53,8 @@ default_config_data = {
'vif_s': [],
'vif_s_remove': [],
'vif': [],
- 'vif_remove': []
+ 'vif_remove': [],
+ 'vrf': ''
}
def get_config():
@@ -158,6 +160,10 @@ def get_config():
if conf.exists(['mode']):
peth['mode'] = conf.return_value(['mode'])
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ peth['vrf'] = conf.return_value('vrf')
+
# re-set configuration level to parse new nodes
conf.set_level(cfg_base)
# get vif-s interfaces (currently effective) - to determine which vif-s
@@ -196,6 +202,15 @@ def verify(peth):
if not peth['link']:
raise ConfigError('Link device must be set for virtual ethernet {}'.format(peth['intf']))
+ if not peth['link'] in interfaces():
+ raise ConfigError('Pseudo-ethernet source interface does not exist')
+
+ vrf_name = peth['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+
+ # use common function to verify VLAN configuration
+ verify_vlan_config(peth)
return None
def generate(peth):
@@ -282,6 +297,12 @@ def apply(peth):
# Enable private VLAN proxy ARP on this interface
p.set_proxy_arp_pvlan(peth['ip_proxy_arp_pvlan'])
+ # assign to VRF
+ if peth['vrf']:
+ p.add_vrf(peth['vrf'])
+ else:
+ p.del_vrf(peth['vrf'])
+
# Change interface MAC address
if peth['mac']:
p.set_mac(peth['mac'])
@@ -303,6 +324,35 @@ def apply(peth):
for addr in peth['address']:
p.add_addr(addr)
+ # remove no longer required service VLAN interfaces (vif-s)
+ for vif_s in peth['vif_s_remove']:
+ p.del_vlan(vif_s)
+
+ # create service VLAN interfaces (vif-s)
+ for vif_s in peth['vif_s']:
+ s_vlan = p.add_vlan(vif_s['id'], ethertype=vif_s['ethertype'])
+ apply_vlan_config(s_vlan, vif_s)
+
+ # remove no longer required client VLAN interfaces (vif-c)
+ # on lower service VLAN interface
+ for vif_c in vif_s['vif_c_remove']:
+ s_vlan.del_vlan(vif_c)
+
+ # create client VLAN interfaces (vif-c)
+ # on lower service VLAN interface
+ for vif_c in vif_s['vif_c']:
+ c_vlan = s_vlan.add_vlan(vif_c['id'])
+ apply_vlan_config(c_vlan, vif_c)
+
+ # remove no longer required VLAN interfaces (vif)
+ for vif in peth['vif_remove']:
+ p.del_vlan(vif)
+
+ # create VLAN interfaces (vif)
+ for vif in peth['vif']:
+ vlan = p.add_vlan(vif['id'])
+ apply_vlan_config(vlan, vif)
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index dabfe4836..c9ef0fe9c 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -134,8 +134,11 @@ def verify(vxlan):
if vxlan['mtu'] < 1500:
print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU')
- if vxlan['group'] and not vxlan['link']:
- raise ConfigError('Multicast VXLAN requires an underlaying interface ')
+ if vxlan['group']:
+ if not vxlan['link']:
+ raise ConfigError('Multicast VXLAN requires an underlaying interface ')
+ if not vxlan['link'] in interfaces():
+ raise ConfigError('VXLAN source interface does not exist')
if not (vxlan['group'] or vxlan['remote']):
raise ConfigError('Group or remote must be configured')
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 5289208d9..19e1f01b8 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -25,9 +25,10 @@ from grp import getgrnam
from subprocess import Popen, PIPE
from psutil import pid_exists
+from netifaces import interfaces
from vyos.ifconfig import EthernetIf
-from vyos.ifconfig_vlan import apply_vlan_config
+from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
from vyos.configdict import list_diff, vlan_to_dict
from vyos.config import Config
from vyos import ConfigError
@@ -640,9 +641,16 @@ wpa_key_mgmt=WPA-EAP
# IP addresses, but this field can be used to force a specific address to be
# used, e.g., when the device has multiple IP addresses.
radius_client_addr={{ sec_wpa_radius_source }}
+
+# The own IP address of the access point (used as NAS-IP-Address)
+own_ip_addr={{ sec_wpa_radius_source }}
+{% else %}
+# The own IP address of the access point (used as NAS-IP-Address)
+own_ip_addr=127.0.0.1
{% endif %}
{% for radius in sec_wpa_radius -%}
+{%- if not radius.disabled -%}
# RADIUS authentication server
auth_server_addr={{ radius.server }}
auth_server_port={{ radius.port }}
@@ -653,6 +661,7 @@ acct_server_addr={{ radius.server }}
acct_server_port={{ radius.acc_port }}
acct_server_shared_secret={{ radius.key }}
{% endif %}
+{% endif %}
{% endfor %}
{% endif %}
@@ -760,6 +769,8 @@ network={
ssid="{{ ssid }}"
{%- if sec_wpa_passphrase %}
psk="{{ sec_wpa_passphrase }}"
+{% else %}
+ key_mgmt=NONE
{% endif %}
}
@@ -836,7 +847,8 @@ default_config_data = {
'ssid' : '',
'type' : 'monitor',
'vif': [],
- 'vif_remove': []
+ 'vif_remove': [],
+ 'vrf': ''
}
def get_conf_file(conf_type, intf):
@@ -1148,6 +1160,10 @@ def get_config():
if conf.exists('mode'):
wifi['mode'] = conf.return_value('mode')
+ # retrieve VRF instance
+ if conf.exists('vrf'):
+ wifi['vrf'] = conf.return_value('vrf')
+
# Wireless physical device
if conf.exists('phy'):
wifi['phy'] = conf.return_value('phy')
@@ -1204,6 +1220,7 @@ def get_config():
radius = {
'server' : server,
'acc_port' : '',
+ 'disabled': False,
'port' : 1812,
'key' : ''
}
@@ -1216,6 +1233,10 @@ def get_config():
if conf.exists('accounting'):
radius['acc_port'] = radius['port'] + 1
+ # Check if RADIUS server was temporary disabled
+ if conf.exists(['disable']):
+ radius['disabled'] = True
+
# RADIUS server shared-secret
if conf.exists('key'):
radius['key'] = conf.return_value('key')
@@ -1248,6 +1269,9 @@ def get_config():
conf.set_level(cfg_base + ' vif ' + vif)
wifi['vif'].append(vlan_to_dict(conf))
+ # disable interface
+ if conf.exists('disable'):
+ wifi['disable'] = True
# retrieve configured regulatory domain
conf.set_level('system')
@@ -1273,7 +1297,6 @@ def verify(wifi):
if not wifi['channel']:
raise ConfigError('Channel must be set for {}'.format(wifi['intf']))
-
if len(wifi['sec_wep_key']) > 4:
raise ConfigError('No more then 4 WEP keys configurable')
@@ -1293,7 +1316,12 @@ def verify(wifi):
if not radius['key']:
raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server']))
+ vrf_name = wifi['vrf']
+ if vrf_name and vrf_name not in interfaces():
+ raise ConfigError(f'VRF "{vrf_name}" does not exist')
+ # use common function to verify VLAN configuration
+ verify_vlan_config(wifi)
return None
@@ -1390,6 +1418,12 @@ def apply(wifi):
# ignore link state changes
w.set_link_detect(wifi['disable_link_detect'])
+ # assign to VRF
+ if wifi['vrf']:
+ w.add_vrf(wifi['vrf'])
+ else:
+ w.del_vrf(wifi['vrf'])
+
# Change interface MAC address - re-set to real hardware address (hw-id)
# if custom mac is removed
if wifi['mac']:
@@ -1406,8 +1440,10 @@ def apply(wifi):
# configure ARP ignore
w.set_arp_ignore(wifi['ip_enable_arp_ignore'])
- # enable interface
- if not wifi['disable']:
+ # Enable/Disable interface
+ if wifi['disable']:
+ w.set_state('down')
+ else:
w.set_state('up')
# Configure interface address(es)
diff --git a/src/conf_mode/system-login-banner.py b/src/conf_mode/system-login-banner.py
index e66d409bb..20cc16f97 100755
--- a/src/conf_mode/system-login-banner.py
+++ b/src/conf_mode/system-login-banner.py
@@ -16,6 +16,7 @@
from sys import exit
from vyos.config import Config
+from vyos import ConfigError
motd="""
The programs included with the Debian GNU/Linux system are free software;
@@ -49,15 +50,25 @@ def get_config():
# Post-Login banner
if conf.exists(['post-login']):
tmp = conf.return_value(['post-login'])
- tmp = tmp.replace('\\n','\n')
- tmp = tmp.replace('\\t','\t')
+ # post-login banner can be empty as well
+ if tmp:
+ tmp = tmp.replace('\\n','\n')
+ tmp = tmp.replace('\\t','\t')
+ else:
+ tmp = ''
+
banner['motd'] = tmp
# Pre-Login banner
if conf.exists(['pre-login']):
tmp = conf.return_value(['pre-login'])
- tmp = tmp.replace('\\n','\n')
- tmp = tmp.replace('\\t','\t')
+ # pre-login banner can be empty as well
+ if tmp:
+ tmp = tmp.replace('\\n','\n')
+ tmp = tmp.replace('\\t','\t')
+ else:
+ tmp = ''
+
banner['issue'] = banner['issue_net'] = tmp
return banner
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index a7fb8ee8f..959e86e5b 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -196,6 +196,14 @@ def verify(login):
if cur_user in login['del_users']:
raise ConfigError('Attempting to delete current user: {}'.format(cur_user))
+ for user in login['add_users']:
+ for key in user['public_keys']:
+ if not key['type']:
+ raise ConfigError('SSH public key type missing for "{}"!'.format(key['name']))
+
+ if not key['key']:
+ raise ConfigError('SSH public key for id "{}" missing!'.format(key['name']))
+
# At lease one RADIUS server must not be disabled
if len(login['radius_server']) > 0:
fail = True
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
new file mode 100755
index 000000000..991c5cb2c
--- /dev/null
+++ b/src/conf_mode/vrf.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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 os
+import jinja2
+
+from sys import exit
+from copy import deepcopy
+from json import loads
+from subprocess import check_output, CalledProcessError
+
+from vyos.config import Config
+from vyos.configdict import list_diff
+from vyos.ifconfig import Interface
+from vyos import ConfigError
+
+config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf'
+
+# Please be careful if you edit the template.
+config_tmpl = """
+### Autogenerated by vrf.py ###
+#
+# Routing table ID to name mapping reference
+
+# id vrf name comment
+{% for vrf in vrf_add -%}
+{{ "%-10s" | format(vrf.table) }} {{ "%-16s" | format(vrf.name) }} # {{ vrf.description }}
+{% endfor -%}
+
+"""
+
+default_config_data = {
+ 'bind_to_all': 0,
+ 'deleted': False,
+ 'vrf_add': [],
+ 'vrf_existing': [],
+ 'vrf_remove': []
+}
+
+def _cmd(command):
+ try:
+ check_output(command.split())
+ except CalledProcessError as e:
+ raise ConfigError(f'Error changing VRF: {e}')
+
+def list_rules():
+ command = 'ip -j -4 rule show'
+ answer = loads(check_output(command.split()).decode())
+ return [_ for _ in answer if _]
+
+def vrf_interfaces(c, match):
+ matched = []
+ old_level = c.get_level()
+ c.set_level(['interfaces'])
+ section = c.get_config_dict([])
+ for type in section:
+ interfaces = section[type]
+ for name in interfaces:
+ interface = interfaces[name]
+ if 'vrf' in interface:
+ v = interface.get('vrf', '')
+ if v == match:
+ matched.append(name)
+
+ c.set_level(old_level)
+ return matched
+
+def vrf_routing(c, match):
+ matched = []
+ old_level = c.get_level()
+ c.set_level(['protocols', 'vrf'])
+ if match in c.list_nodes([]):
+ matched.append(match)
+
+ c.set_level(old_level)
+ return matched
+
+
+def get_config():
+ conf = Config()
+ vrf_config = deepcopy(default_config_data)
+
+ cfg_base = ['vrf']
+ if not conf.exists(cfg_base):
+ # get all currently effetive VRFs and mark them for deletion
+ vrf_config['vrf_remove'] = conf.list_effective_nodes(cfg_base + ['name'])
+ else:
+ # set configuration level base
+ conf.set_level(cfg_base)
+
+ # Should services be allowed to bind to all VRFs?
+ if conf.exists(['bind-to-all']):
+ vrf_config['bind_to_all'] = 1
+
+ # Determine vrf interfaces (currently effective) - to determine which
+ # vrf interface is no longer present and needs to be removed
+ eff_vrf = conf.list_effective_nodes(['name'])
+ act_vrf = conf.list_nodes(['name'])
+ vrf_config['vrf_remove'] = list_diff(eff_vrf, act_vrf)
+
+ # read in individual VRF definition and build up
+ # configuration
+ for name in conf.list_nodes(['name']):
+ vrf_inst = {
+ 'description' : '',
+ 'members': [],
+ 'name' : name,
+ 'table' : '',
+ 'table_mod': False
+ }
+ conf.set_level(cfg_base + ['name', name])
+
+ if conf.exists(['table']):
+ # VRF table can't be changed on demand, thus we need to read in the
+ # current and the effective routing table number
+ act_table = conf.return_value(['table'])
+ eff_table = conf.return_effective_value(['table'])
+ vrf_inst['table'] = act_table
+ if eff_table and eff_table != act_table:
+ vrf_inst['table_mod'] = True
+
+ if conf.exists(['description']):
+ vrf_inst['description'] = conf.return_value(['description'])
+
+ # append individual VRF configuration to global configuration list
+ vrf_config['vrf_add'].append(vrf_inst)
+
+ # set configuration level base
+ conf.set_level(cfg_base)
+
+ # check VRFs which need to be removed as they are not allowed to have
+ # interfaces attached
+ tmp = []
+ for name in vrf_config['vrf_remove']:
+ vrf_inst = {
+ 'interfaces': [],
+ 'name': name,
+ 'routes': []
+ }
+
+ # find member interfaces of this particulat VRF
+ vrf_inst['interfaces'] = vrf_interfaces(conf, name)
+
+ # find routing protocols used by this VRF
+ vrf_inst['routes'] = vrf_routing(conf, name)
+
+ # append individual VRF configuration to temporary configuration list
+ tmp.append(vrf_inst)
+
+ # replace values in vrf_remove with list of dictionaries
+ # as we need it in verify() - we can't delete a VRF with members attached
+ vrf_config['vrf_remove'] = tmp
+ return vrf_config
+
+def verify(vrf_config):
+ # ensure VRF is not assigned to any interface
+ for vrf in vrf_config['vrf_remove']:
+ if len(vrf['interfaces']) > 0:
+ raise ConfigError(f"VRF {vrf['name']} can not be deleted. It has active member interfaces!")
+
+ if len(vrf['routes']) > 0:
+ raise ConfigError(f"VRF {vrf['name']} can not be deleted. It has active routing protocols!")
+
+ table_ids = []
+ for vrf in vrf_config['vrf_add']:
+ # table id is mandatory
+ if not vrf['table']:
+ raise ConfigError(f"VRF {vrf['name']} table id is mandatory!")
+
+ # routing table id can't be changed - OS restriction
+ if vrf['table_mod']:
+ raise ConfigError(f"VRF {vrf['name']} table id modification is not possible!")
+
+ # VRf routing table ID must be unique on the system
+ if vrf['table'] in table_ids:
+ raise ConfigError(f"VRF {vrf['name']} table id {vrf['table']} is not unique!")
+
+ table_ids.append(vrf['table'])
+
+ return None
+
+def generate(vrf_config):
+ tmpl = jinja2.Template(config_tmpl)
+ config_text = tmpl.render(vrf_config)
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+
+ return None
+
+def apply(vrf_config):
+ # Documentation
+ #
+ # - https://github.com/torvalds/linux/blob/master/Documentation/networking/vrf.txt
+ # - https://github.com/Mellanox/mlxsw/wiki/Virtual-Routing-and-Forwarding-(VRF)
+ # - https://netdevconf.info/1.1/proceedings/slides/ahern-vrf-tutorial.pdf
+ # - https://netdevconf.info/1.2/slides/oct6/02_ahern_what_is_l3mdev_slides.pdf
+
+ # set the default VRF global behaviour
+ bind_all = vrf_config['bind_to_all']
+ _cmd(f'sysctl -wq net.ipv4.tcp_l3mdev_accept={bind_all}')
+ _cmd(f'sysctl -wq net.ipv4.udp_l3mdev_accept={bind_all}')
+
+ for vrf_name in vrf_config['vrf_remove']:
+ if os.path.isdir(f'/sys/class/net/{vrf_name}'):
+ _cmd(f'ip link delete dev {vrf_name}')
+
+ for vrf in vrf_config['vrf_add']:
+ name = vrf['name']
+ table = vrf['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
+ _cmd(f'ip link add {name} type vrf table {table}')
+ # Start VRf
+ _cmd(f'ip link set dev {name} up')
+ # The kernel Documentation/networking/vrf.txt also recommends
+ # adding unreachable routes to the VRF routing tables so that routes
+ # afterwards are taken.
+ _cmd(f'ip -4 route add vrf {name} unreachable default metric 4278198272')
+ _cmd(f'ip -6 route add vrf {name} unreachable default metric 4278198272')
+
+ # set VRF description for e.g. SNMP monitoring
+ Interface(name).set_alias(vrf['description'])
+
+ # 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 vrf_config['vrf_add']:
+ for af in ['-4', '-6']:
+ _cmd(f'ip {af} rule add pref 32765 table local')
+ _cmd(f'ip {af} rule del pref 0')
+
+ # return to default lookup preference when no VRF is configured
+ if not vrf_config['vrf_add']:
+ for af in ['-4', '-6']:
+ _cmd(f'ip {af} rule add pref 0 table local')
+ _cmd(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]:
+ _cmd(f'ip {af} rule del pref 1000')
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/etc/ppp/ip-pre-up b/src/etc/ppp/ip-pre-up
new file mode 100755
index 000000000..05840650b
--- /dev/null
+++ b/src/etc/ppp/ip-pre-up
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# This script is run by the pppd when the link is created.
+# It uses run-parts to run scripts in /etc/ppp/ip-pre-up.d, to
+# change name, setup firewall,etc you should create script(s) there.
+#
+# Be aware that other packages may include /etc/ppp/ip-pre-up.d scripts (named
+# after that package), so choose local script names with that in mind.
+#
+# This script is called with the following arguments:
+# Arg Name Example
+# $1 Interface name ppp0
+# $2 The tty ttyS1
+# $3 The link speed 38400
+# $4 Local IP number 12.34.56.78
+# $5 Peer IP number 12.34.56.99
+# $6 Optional ``ipparam'' value foo
+
+# The environment is cleared before executing this script
+# so the path must be reset
+PATH=/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin
+export PATH
+
+# These variables are for the use of the scripts run by run-parts
+PPP_IFACE="$1"
+PPP_TTY="$2"
+PPP_SPEED="$3"
+PPP_LOCAL="$4"
+PPP_REMOTE="$5"
+PPP_IPPARAM="$6"
+export PPP_IFACE PPP_TTY PPP_SPEED PPP_LOCAL PPP_REMOTE PPP_IPPARAM
+
+# as an additional convenience, $PPP_TTYNAME is set to the tty name,
+# stripped of /dev/ (if present) for easier matching.
+PPP_TTYNAME=`/usr/bin/basename "$2"`
+export PPP_TTYNAME
+
+# If /var/log/ppp-ipupdown.log exists use it for logging.
+if [ -e /var/log/ppp-ipupdown.log ]; then
+ exec > /var/log/ppp-ipupdown.log 2>&1
+ echo $0 $*
+ echo
+fi
+
+# This script can be used to override the .d files supplied by other packages.
+if [ -x /etc/ppp/ip-pre-up.local ]; then
+ exec /etc/ppp/ip-pre-up.local "$*"
+fi
+
+run-parts /etc/ppp/ip-pre-up.d \
+ --arg="$1" --arg="$2" --arg="$3" --arg="$4" --arg="$5" --arg="$6"
diff --git a/src/migration-scripts/quagga/3-to-4 b/src/migration-scripts/quagga/3-to-4
index f8c87ce8c..be3528391 100755
--- a/src/migration-scripts/quagga/3-to-4
+++ b/src/migration-scripts/quagga/3-to-4
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2020 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
@@ -16,6 +16,13 @@
#
#
+# Between 1.2.3 and 1.2.4, FRR added per-neighbor enforce-first-as option.
+# Unfortunately they also removed the global enforce-first-as option,
+# which broke all old configs that used to have it.
+#
+# To emulate the effect of the original option, we insert it in every neighbor
+# if the config used to have the original global option
+
import sys
from vyos.configtree import ConfigTree
@@ -45,11 +52,16 @@ else:
# There's actually no BGP, just its empty shell
sys.exit(0)
- # Check if BGP scan-time parameter exist
- scan_time_param = ['protocols', 'bgp', asn, 'parameters', 'scan-time']
- if config.exists(scan_time_param):
- # Delete BGP scan-time parameter
- config.delete(scan_time_param)
+ # Check if BGP enforce-first-as option is set
+ enforce_first_as_path = ['protocols', 'bgp', asn, 'parameters', 'enforce-first-as']
+ if config.exists(enforce_first_as_path):
+ # Delete the obsolete option
+ config.delete(enforce_first_as_path)
+
+ # Now insert it in every peer
+ peers = config.list_nodes(['protocols', 'bgp', asn, 'neighbor'])
+ for p in peers:
+ config.set(['protocols', 'bgp', asn, 'neighbor', p, 'enforce-first-as'])
else:
# Do nothing
sys.exit(0)
@@ -61,3 +73,4 @@ else:
except OSError as e:
print("Failed to save the modified config: {}".format(e))
sys.exit(1)
+
diff --git a/src/migration-scripts/quagga/4-to-5 b/src/migration-scripts/quagga/4-to-5
new file mode 100755
index 000000000..f8c87ce8c
--- /dev/null
+++ b/src/migration-scripts/quagga/4-to-5
@@ -0,0 +1,63 @@
+#!/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 <http://www.gnu.org/licenses/>.
+#
+#
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['protocols', 'bgp']):
+ # Nothing to do
+ sys.exit(0)
+else:
+ # Check if BGP is actually configured and obtain the ASN
+ asn_list = config.list_nodes(['protocols', 'bgp'])
+ if asn_list:
+ # There's always just one BGP node, if any
+ asn = asn_list[0]
+ else:
+ # There's actually no BGP, just its empty shell
+ sys.exit(0)
+
+ # Check if BGP scan-time parameter exist
+ scan_time_param = ['protocols', 'bgp', asn, 'parameters', 'scan-time']
+ if config.exists(scan_time_param):
+ # Delete BGP scan-time parameter
+ config.delete(scan_time_param)
+ else:
+ # Do nothing
+ sys.exit(0)
+
+ # Save a new configuration file
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/op_mode/reset_openvpn.py b/src/op_mode/reset_openvpn.py
index 7043ac261..38eca53cc 100755
--- a/src/op_mode/reset_openvpn.py
+++ b/src/op_mode/reset_openvpn.py
@@ -64,6 +64,7 @@ if __name__ == '__main__':
cmd += ' --exec /usr/sbin/openvpn'
# now pass arguments to openvpn binary
cmd += ' --'
+ cmd += ' --daemon openvpn-' + interface
cmd += ' --config ' + get_config_name(interface)
subprocess_cmd(cmd)
diff --git a/src/op_mode/show_vrf.py b/src/op_mode/show_vrf.py
new file mode 100755
index 000000000..66c33e607
--- /dev/null
+++ b/src/op_mode/show_vrf.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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 jinja2
+
+from subprocess import check_output
+from json import loads
+
+vrf_out_tmpl = """
+VRF name state mac address flags interfaces
+-------- ----- ----------- ----- ----------
+{%- for v in vrf %}
+{{"%-16s"|format(v.ifname)}} {{ "%-8s"|format(v.operstate | lower())}} {{"%-17s"|format(v.address | lower())}} {{ v.flags|join(',')|lower()}} {{v.members|join(',')|lower()}}
+{%- endfor %}
+
+"""
+
+def list_vrfs():
+ command = 'ip -j -br link show type vrf'
+ answer = loads(check_output(command.split()).decode())
+ return [_ for _ in answer if _]
+
+def list_vrf_members(vrf):
+ command = f'ip -j -br link show master {vrf}'
+ answer = loads(check_output(command.split()).decode())
+ return [_ for _ in answer if _]
+
+parser = argparse.ArgumentParser()
+group = parser.add_mutually_exclusive_group()
+group.add_argument("-e", "--extensive", action="store_true",
+ help="provide detailed vrf informatio")
+parser.add_argument('interface', metavar='I', type=str, nargs='?',
+ help='interface to display')
+
+args = parser.parse_args()
+
+if args.extensive:
+ data = { 'vrf': [] }
+ for vrf in list_vrfs():
+ name = vrf['ifname']
+ if args.interface and name != args.interface:
+ continue
+
+ vrf['members'] = []
+ for member in list_vrf_members(name):
+ vrf['members'].append(member['ifname'])
+ data['vrf'].append(vrf)
+
+ tmpl = jinja2.Template(vrf_out_tmpl)
+ print(tmpl.render(data))
+
+else:
+ print(" ".join([vrf['ifname'] for vrf in list_vrfs()]))