summaryrefslogtreecommitdiff
path: root/python/vyos/ifconfig.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos/ifconfig.py')
-rw-r--r--python/vyos/ifconfig.py2158
1 files changed, 0 insertions, 2158 deletions
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