summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-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
19 files changed, 2525 insertions, 2160 deletions
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')