summaryrefslogtreecommitdiff
path: root/python/vyos/ifconfig/interface.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos/ifconfig/interface.py')
-rw-r--r--python/vyos/ifconfig/interface.py252
1 files changed, 115 insertions, 137 deletions
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 4c05ac613..ff05cab0e 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2021 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
@@ -60,7 +60,6 @@ class Interface(Control):
options = ['debug', 'create']
required = []
default = {
- 'type': '',
'debug': True,
'create': True,
}
@@ -79,6 +78,14 @@ class Interface(Control):
'shellcmd': 'ip -json link show dev {ifname}',
'format': lambda j: 'up' if 'UP' in jmespath.search('[*].flags | [0]', json.loads(j)) else 'down',
},
+ 'alias': {
+ 'shellcmd': 'ip -json -detail link list dev {ifname}',
+ 'format': lambda j: jmespath.search('[*].ifalias | [0]', json.loads(j)) or '',
+ },
+ 'mac': {
+ 'shellcmd': 'ip -json -detail link list dev {ifname}',
+ 'format': lambda j: jmespath.search('[*].address | [0]', json.loads(j)),
+ },
'min_mtu': {
'shellcmd': 'ip -json -detail link list dev {ifname}',
'format': lambda j: jmespath.search('[*].min_mtu | [0]', json.loads(j)),
@@ -87,6 +94,14 @@ class Interface(Control):
'shellcmd': 'ip -json -detail link list dev {ifname}',
'format': lambda j: jmespath.search('[*].max_mtu | [0]', json.loads(j)),
},
+ 'mtu': {
+ 'shellcmd': 'ip -json -detail link list dev {ifname}',
+ 'format': lambda j: jmespath.search('[*].mtu | [0]', json.loads(j)),
+ },
+ 'oper_state': {
+ 'shellcmd': 'ip -json -detail link list dev {ifname}',
+ 'format': lambda j: jmespath.search('[*].operstate | [0]', json.loads(j)),
+ },
}
_command_set = {
@@ -94,40 +109,29 @@ class Interface(Control):
'validate': lambda v: assert_list(v, ['up', 'down']),
'shellcmd': 'ip link set dev {ifname} {value}',
},
+ 'alias': {
+ 'convert': lambda name: name if name else '',
+ 'shellcmd': 'ip link set dev {ifname} alias "{value}"',
+ },
+ 'bridge_port_isolation': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': 'bridge link set dev {ifname} isolated {value}',
+ },
'mac': {
'validate': assert_mac,
'shellcmd': 'ip link set dev {ifname} address {value}',
},
+ 'mtu': {
+ 'validate': assert_mtu,
+ 'shellcmd': 'ip link set dev {ifname} mtu {value}',
+ },
'vrf': {
'convert': lambda v: f'master {v}' if v else 'nomaster',
'shellcmd': 'ip link set dev {ifname} {value}',
},
}
- _sysfs_get = {
- 'alias': {
- 'location': '/sys/class/net/{ifname}/ifalias',
- },
- 'mac': {
- 'location': '/sys/class/net/{ifname}/address',
- },
- 'mtu': {
- 'location': '/sys/class/net/{ifname}/mtu',
- },
- 'oper_state':{
- 'location': '/sys/class/net/{ifname}/operstate',
- },
- }
-
_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',
@@ -231,26 +235,21 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> i = Interface('eth0')
"""
+ self.config = deepcopy(kargs)
+ self.config['ifname'] = self.ifname = ifname
- self.config = deepcopy(self.default)
- for k in self.options:
- if k in kargs:
- self.config[k] = kargs[k]
-
- # make sure the ifname is the first argument and not from the dict
- self.config['ifname'] = ifname
self._admin_state_down_cnt = 0
# we must have updated config before initialising the Interface
super().__init__(**kargs)
- self.ifname = ifname
if not self.exists(ifname):
- # Any instance of Interface, such as Interface('eth0')
- # can be used safely to access the generic function in this class
- # as 'type' is unset, the class can not be created
- if not self.config['type']:
+ # Any instance of Interface, such as Interface('eth0') can be used
+ # safely to access the generic function in this class as 'type' is
+ # unset, the class can not be created
+ if not self.iftype:
raise Exception(f'interface "{ifname}" not found')
+ self.config['type'] = self.iftype
# Should an Instance of a child class (EthernetIf, DummyIf, ..)
# be required, then create should be set to False to not accidentally create it.
@@ -694,6 +693,20 @@ class Interface(Control):
"""
self.set_interface('path_priority', priority)
+ def set_port_isolation(self, on_or_off):
+ """
+ Controls whether a given port will be isolated, which means it will be
+ able to communicate with non-isolated ports only. By default this flag
+ is off.
+
+ Use enable=1 to enable or enable=0 to disable
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth1').set_port_isolation('on')
+ """
+ self.set_interface('bridge_port_isolation', on_or_off)
+
def set_proxy_arp(self, enable):
"""
Set per interface proxy ARP configuration
@@ -899,49 +912,42 @@ class Interface(Control):
if 'priority' in bridge_config:
self.set_path_cost(bridge_config['priority'])
- vlan_filter = 0
- vlan_add = set()
-
- del_ifname_vlan_ids = get_vlan_ids(ifname)
bridge_vlan_filter = Section.klass(bridge)(bridge, create=True).get_vlan_filter()
- if bridge_vlan_filter:
- if 1 in del_ifname_vlan_ids:
- del_ifname_vlan_ids.remove(1)
- vlan_filter = 1
-
- for vlan in del_ifname_vlan_ids:
- cmd = f'bridge vlan del dev {ifname} vid {vlan}'
- self._cmd(cmd)
-
- if 'native_vlan' in bridge_config:
- vlan_filter = 1
- cmd = f'bridge vlan del dev {self.ifname} vid 1'
- self._cmd(cmd)
- vlan_id = bridge_config['native_vlan']
- cmd = f'bridge vlan add dev {self.ifname} vid {vlan_id} pvid untagged master'
- self._cmd(cmd)
- vlan_add.add(vlan_id)
-
- if 'allowed_vlan' in bridge_config:
- vlan_filter = 1
- if 'native_vlan' not in bridge_config:
- cmd = f'bridge vlan del dev {self.ifname} vid 1'
+ if int(bridge_vlan_filter):
+ cur_vlan_ids = get_vlan_ids(ifname)
+ add_vlan = []
+ native_vlan_id = None
+ allowed_vlan_ids= []
+
+ if 'native_vlan' in bridge_config:
+ vlan_id = bridge_config['native_vlan']
+ add_vlan.append(vlan_id)
+ native_vlan_id = vlan_id
+
+ if 'allowed_vlan' in bridge_config:
+ for vlan in bridge_config['allowed_vlan']:
+ vlan_range = vlan.split('-')
+ if len(vlan_range) == 2:
+ for vlan_add in range(int(vlan_range[0]),int(vlan_range[1]) + 1):
+ add_vlan.append(str(vlan_add))
+ allowed_vlan_ids.append(str(vlan_add))
+ else:
+ add_vlan.append(vlan)
+ allowed_vlan_ids.append(vlan)
+
+ # Remove redundant VLANs from the system
+ for vlan in list_diff(cur_vlan_ids, add_vlan):
+ cmd = f'bridge vlan del dev {ifname} vid {vlan} master'
self._cmd(cmd)
- for vlan in bridge_config['allowed_vlan']:
- cmd = f'bridge vlan add dev {self.ifname} vid {vlan} master'
- self._cmd(cmd)
- vlan_add.add(vlan)
- if vlan_filter:
- # Setting VLAN ID for the bridge
- for vlan in vlan_add:
- cmd = f'bridge vlan add dev {bridge} vid {vlan} self'
+ for vlan in allowed_vlan_ids:
+ cmd = f'bridge vlan add dev {ifname} vid {vlan} master'
+ self._cmd(cmd)
+ # Setting native VLAN to system
+ if native_vlan_id:
+ cmd = f'bridge vlan add dev {ifname} vid {native_vlan_id} pvid untagged master'
self._cmd(cmd)
-
- # enable/disable Vlan Filter
- # When the VLAN aware option is not detected, the setting of `bridge` should not be overwritten
- Section.klass(bridge)(bridge, create=True).set_vlan_filter(vlan_filter)
def set_dhcp(self, enable):
"""
@@ -957,6 +963,9 @@ class Interface(Control):
pid_file = f'{config_base}_{ifname}.pid'
lease_file = f'{config_base}_{ifname}.leases'
+ # Stop client with old config files to get the right IF_METRIC.
+ self._cmd(f'systemctl stop dhclient@{ifname}.service')
+
if enable and 'disable' not in self._config:
if dict_search('dhcp_options.host_name', self._config) == None:
# read configured system hostname.
@@ -975,10 +984,8 @@ class Interface(Control):
# 'up' check is mandatory b/c even if the interface is A/D, as soon as
# the DHCP client is started the interface will be placed in u/u state.
# This is not what we intended to do when disabling an interface.
- return self._cmd(f'systemctl restart dhclient@{ifname}.service')
+ return self._cmd(f'systemctl start dhclient@{ifname}.service')
else:
- self._cmd(f'systemctl stop dhclient@{ifname}.service')
-
# cleanup old config files
for file in [config_file, options_file, pid_file, lease_file]:
if os.path.isfile(file):
@@ -1021,9 +1028,11 @@ class Interface(Control):
source_if = next(iter(self._config['is_mirror_intf']))
config = self._config['is_mirror_intf'][source_if].get('mirror', None)
+ # Please do not clear the 'set $? = 0 '. It's meant to force a return of 0
# Remove existing mirroring rules
- delete_tc_cmd = f'tc qdisc del dev {source_if} handle ffff: ingress; '
- delete_tc_cmd += f'tc qdisc del dev {source_if} handle 1: root prio'
+ delete_tc_cmd = f'tc qdisc del dev {source_if} handle ffff: ingress 2> /dev/null;'
+ delete_tc_cmd += f'tc qdisc del dev {source_if} handle 1: root prio 2> /dev/null;'
+ delete_tc_cmd += 'set $?=0'
self._popen(delete_tc_cmd)
# Bail out early if nothing needs to be configured
@@ -1060,6 +1069,10 @@ class Interface(Control):
if not isinstance(state, bool):
raise ValueError("Value out of range")
+ # https://phabricator.vyos.net/T3448 - there is (yet) no RPI support for XDP
+ if not os.path.exists('/usr/sbin/xdp_loader'):
+ return
+
ifname = self.config['ifname']
cmd = f'xdp_loader -d {ifname} -U --auto-mode'
if state:
@@ -1078,6 +1091,10 @@ class Interface(Control):
interface setup code and provide a single point of entry when workin
on any interface. """
+ if self.debug:
+ import pprint
+ pprint.pprint(config)
+
# Cache the configuration - it will be reused inside e.g. DHCP handler
# XXX: maybe pass the option via __init__ in the future and rename this
# method to apply()?
@@ -1108,9 +1125,10 @@ class Interface(Control):
self.del_addr('dhcp')
# always ensure DHCPv6 client is stopped (when not configured as client
- # for IPv6 address or prefix delegation
+ # for IPv6 address or prefix delegation)
dhcpv6pd = dict_search('dhcpv6_options.pd', config)
- if 'dhcpv6' not in new_addr or dhcpv6pd == None:
+ dhcpv6pd = dhcpv6pd != None and len(dhcpv6pd) != 0
+ if 'dhcpv6' not in new_addr and not dhcpv6pd:
self.del_addr('dhcpv6')
# determine IP addresses which are assigned to the interface and build a
@@ -1130,7 +1148,7 @@ class Interface(Control):
self.add_addr(addr)
# start DHCPv6 client when only PD was configured
- if dhcpv6pd != None:
+ if dhcpv6pd:
self.set_dhcpv6(True)
# There are some items in the configuration which can only be applied
@@ -1246,6 +1264,16 @@ class Interface(Control):
# configure port mirror
self.set_mirror()
+ # Enable/Disable of an interface must always be done at the end of the
+ # derived class to make use of the ref-counting set_admin_state()
+ # function. We will only enable the interface if 'up' was called as
+ # often as 'down'. This is required by some interface implementations
+ # as certain parameters can only be changed when the interface is
+ # in admin-down state. This ensures the link does not flap during
+ # reconfiguration.
+ state = 'down' if 'disable' in config else 'up'
+ self.set_admin_state(state)
+
# remove no longer required 802.1ad (Q-in-Q VLANs)
ifname = config['ifname']
for vif_s_id in config.get('vif_s_remove', {}):
@@ -1299,38 +1327,7 @@ class Interface(Control):
class VLANIf(Interface):
""" Specific class which abstracts 802.1q and 802.1ad (Q-in-Q) VLAN interfaces """
- default = {
- 'type': 'vlan',
- 'source_interface': '',
- 'vlan_id': '',
- 'protocol': '',
- 'ingress_qos': '',
- 'egress_qos': '',
- }
-
- options = Interface.options + \
- ['source_interface', 'vlan_id', 'protocol', 'ingress_qos', 'egress_qos']
-
- 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
- >>> VLANIf('eth0.10').remove
- """
- # Do we have sub interfaces (VLANs)? As interfaces need to be deleted
- # "in order" starting from Q-in-Q we delete them first.
- for upper in glob(f'/sys/class/net/{self.ifname}/upper*'):
- # an upper interface could be named: upper_bond0.1000.1100, thus
- # we need top drop the upper_ prefix
- vif_c = os.path.basename(upper)
- vif_c = vif_c.replace('upper_', '')
- VLANIf(vif_c).remove()
-
- super().remove()
+ iftype = 'vlan'
def _create(self):
# bail out early if interface already exists
@@ -1338,11 +1335,11 @@ class VLANIf(Interface):
return
cmd = 'ip link add link {source_interface} name {ifname} type vlan id {vlan_id}'
- if self.config['protocol']:
+ if 'protocol' in self.config:
cmd += ' protocol {protocol}'
- if self.config['ingress_qos']:
+ if 'ingress_qos' in self.config:
cmd += ' ingress-qos-map {ingress_qos}'
- if self.config['egress_qos']:
+ if 'egress_qos' in self.config:
cmd += ' egress-qos-map {egress_qos}'
self._cmd(cmd.format(**self.config))
@@ -1374,22 +1371,3 @@ class VLANIf(Interface):
def set_mirror(self):
return
-
- def update(self, config):
- """ General helper function which works on a dictionary retrived by
- get_config_dict(). It's main intention is to consolidate the scattered
- interface setup code and provide a single point of entry when workin
- on any interface. """
-
- # call base class first
- super().update(config)
-
- # Enable/Disable of an interface must always be done at the end of the
- # derived class to make use of the ref-counting set_admin_state()
- # function. We will only enable the interface if 'up' was called as
- # often as 'down'. This is required by some interface implementations
- # as certain parameters can only be changed when the interface is
- # in admin-down state. This ensures the link does not flap during
- # reconfiguration.
- state = 'down' if 'disable' in config else 'up'
- self.set_admin_state(state)