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.py407
1 files changed, 336 insertions, 71 deletions
diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py
index 0793fad39..777b185a6 100644
--- a/python/vyos/ifconfig.py
+++ b/python/vyos/ifconfig.py
@@ -15,12 +15,12 @@
import os
import re
-import subprocess
import jinja2
from vyos.validate import *
from ipaddress import IPv4Network, IPv6Address
from netifaces import ifaddresses, AF_INET, AF_INET6
+from subprocess import Popen, PIPE, STDOUT
from time import sleep
dhcp_cfg = """
@@ -43,7 +43,6 @@ dhclient_base = r'/var/lib/dhcp/dhclient_'
class Interface:
-
def __init__(self, ifname, type=None):
"""
This is the base interface class which supports basic IP/MAC address
@@ -84,48 +83,15 @@ class Interface:
if os.path.isfile('/tmp/vyos.ifconfig.debug'):
print('DEBUG/{:<6} {}'.format(self._ifname, msg))
- 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. 'bond0(?:\.\d+){1,2}' will match vif and vif-s/vif-c
- # subinterfaces
- vlan_ifs = [f for f in os.listdir(r'/sys/class/net') \
- if re.match(self._ifname + r'(?:\.\d+){1,2}', f)]
-
- for vlan in vlan_ifs:
- Interface(vlan).remove()
-
- # All subinterfaces are now removed, continue on the physical interface
-
- # stop DHCP(v6) if running
- self._del_dhcp()
- self._del_dhcpv6()
-
- # 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._ifname)
- self._cmd(cmd)
-
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()))
- process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
- proc_stdout = process.communicate()[0].strip()
-
- # add exception handling code
- pass
+ # do we need some error checking code here?
+ return tmp
def _read_sysfs(self, filename):
"""
@@ -148,6 +114,37 @@ class Interface:
return None
+ 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)
+
+ # Ethernet interfaces can not be removed
+ if type(self) == type(EthernetIf(self._ifname)):
+ return
+
+ # 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._ifname)
+ self._cmd(cmd)
+
@property
def mtu(self):
"""
@@ -158,7 +155,7 @@ class Interface:
>>> Interface('eth0').mtu
'1500'
"""
- return self._read_sysfs('/sys/class/net/{0}/mtu'
+ return self._read_sysfs('/sys/class/net/{}/mtu'
.format(self._ifname))
@mtu.setter
@@ -175,7 +172,7 @@ class Interface:
if mtu < 68 or mtu > 9000:
raise ValueError('Invalid MTU size: "{}"'.format(mru))
- return self._write_sysfs('/sys/class/net/{0}/mtu'
+ return self._write_sysfs('/sys/class/net/{}/mtu'
.format(self._ifname), mtu)
@property
@@ -188,7 +185,7 @@ class Interface:
>>> Interface('eth0').mac
'00:0c:29:11:aa:cc'
"""
- return self._read_sysfs('/sys/class/net/{0}/address'
+ return self._read_sysfs('/sys/class/net/{}/address'
.format(self._ifname))
@mac.setter
@@ -202,6 +199,10 @@ class Interface:
>>> Interface('eth0').mac
'00:90:43:fe:fe:1b'
"""
+ # on interface removal (ethernet) an empty string is passed - ignore it
+ if not mac:
+ return None
+
# a mac address consits out of 6 octets
octets = len(mac.split(':'))
if octets != 6:
@@ -303,7 +304,7 @@ class Interface:
>>> Interface('eth0').ifalias
''
"""
- return self._read_sysfs('/sys/class/net/{0}/ifalias'
+ return self._read_sysfs('/sys/class/net/{}/ifalias'
.format(self._ifname))
@ifalias.setter
@@ -327,7 +328,7 @@ class Interface:
# clear interface alias
ifalias = '\0'
- self._write_sysfs('/sys/class/net/{0}/ifalias'
+ self._write_sysfs('/sys/class/net/{}/ifalias'
.format(self._ifname), ifalias)
@property
@@ -340,7 +341,7 @@ class Interface:
>>> Interface('eth0').state
'up'
"""
- return self._read_sysfs('/sys/class/net/{0}/operstate'
+ return self._read_sysfs('/sys/class/net/{}/operstate'
.format(self._ifname))
@state.setter
@@ -759,7 +760,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').aging_time
'300'
"""
- return (self._read_sysfs('/sys/class/net/{0}/bridge/ageing_time'
+ return (self._read_sysfs('/sys/class/net/{}/bridge/ageing_time'
.format(self._ifname)) / 100)
@ageing_time.setter
@@ -773,7 +774,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').ageing_time = 2
"""
time = int(time) * 100
- return self._write_sysfs('/sys/class/net/{0}/bridge/ageing_time'
+ return self._write_sysfs('/sys/class/net/{}/bridge/ageing_time'
.format(self._ifname), time)
@property
@@ -787,7 +788,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').ageing_time
'3'
"""
- return (self._read_sysfs('/sys/class/net/{0}/bridge/forward_delay'
+ return (self._read_sysfs('/sys/class/net/{}/bridge/forward_delay'
.format(self._ifname)) / 100)
@forward_delay.setter
@@ -800,7 +801,7 @@ class BridgeIf(Interface):
>>> from vyos.ifconfig import Interface
>>> BridgeIf('br0').forward_delay = 15
"""
- return self._write_sysfs('/sys/class/net/{0}/bridge/forward_delay'
+ return self._write_sysfs('/sys/class/net/{}/bridge/forward_delay'
.format(self._ifname), (int(time) * 100))
@property
@@ -814,7 +815,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').hello_time
'2'
"""
- return (self._read_sysfs('/sys/class/net/{0}/bridge/hello_time'
+ return (self._read_sysfs('/sys/class/net/{}/bridge/hello_time'
.format(self._ifname)) / 100)
@hello_time.setter
@@ -827,7 +828,7 @@ class BridgeIf(Interface):
>>> from vyos.ifconfig import Interface
>>> BridgeIf('br0').hello_time = 2
"""
- return self._write_sysfs('/sys/class/net/{0}/bridge/hello_time'
+ return self._write_sysfs('/sys/class/net/{}/bridge/hello_time'
.format(self._ifname), (int(time) * 100))
@property
@@ -842,7 +843,7 @@ class BridgeIf(Interface):
'20'
"""
- return (self._read_sysfs('/sys/class/net/{0}/bridge/max_age'
+ return (self._read_sysfs('/sys/class/net/{}/bridge/max_age'
.format(self._ifname)) / 100)
@max_age.setter
@@ -855,7 +856,7 @@ class BridgeIf(Interface):
>>> from vyos.ifconfig import Interface
>>> BridgeIf('br0').max_age = 30
"""
- return self._write_sysfs('/sys/class/net/{0}/bridge/max_age'
+ return self._write_sysfs('/sys/class/net/{}/bridge/max_age'
.format(self._ifname), (int(time) * 100))
@property
@@ -868,7 +869,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').priority
'32768'
"""
- return self._read_sysfs('/sys/class/net/{0}/bridge/priority'
+ return self._read_sysfs('/sys/class/net/{}/bridge/priority'
.format(self._ifname))
@priority.setter
@@ -880,7 +881,7 @@ class BridgeIf(Interface):
>>> from vyos.ifconfig import Interface
>>> BridgeIf('br0').priority = 8192
"""
- return self._write_sysfs('/sys/class/net/{0}/bridge/priority'
+ return self._write_sysfs('/sys/class/net/{}/bridge/priority'
.format(self._ifname), priority)
@property
@@ -895,7 +896,7 @@ class BridgeIf(Interface):
"""
state = 0
- with open('/sys/class/net/{0}/bridge/stp_state'.format(self._ifname), 'r') as f:
+ with open('/sys/class/net/{}/bridge/stp_state'.format(self._ifname), 'r') as f:
state = int(f.read().rstrip('\n'))
return state
@@ -911,7 +912,7 @@ class BridgeIf(Interface):
"""
if int(state) >= 0 and int(state) <= 1:
- return self._write_sysfs('/sys/class/net/{0}/bridge/stp_state'
+ return self._write_sysfs('/sys/class/net/{}/bridge/stp_state'
.format(self._ifname), state)
else:
raise ValueError("Value out of range")
@@ -926,7 +927,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').multicast_querier
'0'
"""
- return self._read_sysfs('/sys/class/net/{0}/bridge/multicast_querier'
+ return self._read_sysfs('/sys/class/net/{}/bridge/multicast_querier'
.format(self._ifname))
@multicast_querier.setter
@@ -944,7 +945,7 @@ class BridgeIf(Interface):
>>> BridgeIf('br0').multicast_querier = 1
"""
if int(enable) >= 0 and int(enable) <= 1:
- return self._write_sysfs('/sys/class/net/{0}/bridge/multicast_querier'
+ return self._write_sysfs('/sys/class/net/{}/bridge/multicast_querier'
.format(self._ifname), enable)
else:
raise ValueError("Value out of range")
@@ -997,12 +998,40 @@ class BridgeIf(Interface):
.format(self._ifname, interface), priority)
-class EthernetIf(Interface):
-
+class VLANIf(Interface):
+ """
+ This class handels the creation and removal of a VLAN interface. It serves
+ as base class for BondIf and EthernetIf.
+ """
def __init__(self, ifname, type=None):
super().__init__(ifname, type)
- def add_vlan(self, vlan_id, ethertype=''):
+ 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. 'bond0(?:\.\d+){1,2}' will match vif and vif-s/vif-c
+ # subinterfaces
+ vlan_ifs = [f for f in os.listdir(r'/sys/class/net') \
+ if re.match(self._ifname + r'(?:\.\d+){1,2}', 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).
@@ -1012,8 +1041,20 @@ class EthernetIf(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 EthernetIf is returned once the interface has been
+ 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._ifname + '.' + str(vlan_id)
if not os.path.exists('/sys/class/net/{}'.format(vlan_ifname)):
@@ -1023,29 +1064,224 @@ class EthernetIf(Interface):
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}'.format(
- intf=self._ifname, vlan=self._vlan_id, proto=ethertype)
+ cmd = 'ip link add link {intf} name {intf}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \
+ .format(intf=self._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 EthernetIf(vlan_ifname)
+ 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._ifname + '.' + str(vlan_id)
- tmp = EthernetIf(vlan_ifname)
+ tmp = VLANIf(vlan_ifname)
tmp.remove()
-class BondIf(EthernetIf):
+class EthernetIf(VLANIf):
+ """
+ Abstraction of a Linux Ethernet Interface
+ """
+ def __init__(self, ifname):
+ super().__init__(ifname)
+
+ 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'
+ """
+ link = os.readlink('/sys/class/net/{}/device/driver/module'.format(self._ifname))
+ return os.path.basename(link)
+
+
+ def has_autoneg(self):
+ """
+ Not all drivers support autonegotiation.
+
+ returns True -> Autonegotiation is supported by driver
+ False -> Autonegotiation is not supported by driver
+
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.has_autoneg()
+ 'True'
+ """
+ regex = 'Supports auto-negotiation:[ ]\w+'
+ tmp = self._cmd('/sbin/ethtool {}'.format(self._ifname))
+ tmp = re.search(regex, tmp.decode())
+
+ # Output is either 'Supports auto-negotiation: Yes' or
+ # 'Supports auto-negotiation: No'
+ if tmp.group().split(':')[1].lstrip() == "Yes":
+ return True
+ else:
+ return False
+
+
+ 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
+
+ # 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._ifname, enable)
+ try:
+ # An exception will be thrown if the settings are not changed
+ 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
+
+
+ cmd = '/sbin/ethtool -s {}'.format(self._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')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} gro {}'.format(self._ifname, state)
+ return self._cmd(cmd)
+
+ def set_gso(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_gso('on')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} gso {}'.format(self._ifname, state)
+ return self._cmd(cmd)
+
+
+ def set_sg(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_sg('on')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} sg {}'.format(self._ifname, state)
+ return self._cmd(cmd)
+
+
+ def set_tso(self, state):
+ """
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_tso('on')
+ """
+ if state not in ['on', 'off']:
+ raise ValueError('state must be "on" or "off"')
+
+ cmd = '/sbin/ethtool -K {} tso {}'.format(self._ifname, state)
+ return self._cmd(cmd)
+
+
+ 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._ifname, state)
+ 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
@@ -1053,10 +1289,39 @@ class BondIf(EthernetIf):
either hot standby or load balancing services. Additionally, link integrity
monitoring may be performed.
"""
-
def __init__(self, ifname):
super().__init__(ifname, type='bond')
+ 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).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.state = slave['state']
+
@property
def xmit_hash_policy(self):
"""