From 4b2d2839e93f7df8e61e5f8f8a4ad94bcdb308d5 Mon Sep 17 00:00:00 2001 From: Maple Wang Date: Sat, 18 Apr 2020 01:17:48 +0800 Subject: [T2311] name servers via cmdline can't take effect Please refer to https://phabricator.vyos.net/T2311 --- src/services/vyos-hostsd | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index a655762e9..647cbc8c1 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -25,6 +25,7 @@ import traceback import re import logging import zmq +import collections import jinja2 @@ -79,8 +80,18 @@ resolv_tmpl_source = """ ### Autogenerated by VyOS ### ### Do not edit, your changes will get overwritten ### +# name server from static configuration {% for ns in name_servers -%} +{%- if name_servers[ns]['tag'] == "static" %} nameserver {{ns}} +{%- endif %} +{% endfor -%} + +{% for ns in name_servers -%} +{%- if name_servers[ns]['tag'] != "static" %} +# name server from {{name_servers[ns]['tag']}} +nameserver {{ns}} +{%- endif %} {% endfor -%} {%- if domain_name %} @@ -110,7 +121,7 @@ resolv_tmpl = jinja2.Template(resolv_tmpl_source) # and re-created without having to track what needs # to be changed STATE = { - "name_servers": {}, + "name_servers": collections.OrderedDict({}), "hosts": {}, "host_name": "vyos", "domain_name": "", -- cgit v1.2.3 From a1afabff0fa8be39c917cd2fd879c4b68db49f42 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 11:22:11 +0200 Subject: interface: T2367: optimize flow and detriplicate add/del_addr functions - detriplicate list appending - detriplicate returns - use if-elif-else - move check if address is already added to beginning - move caching in variable to after address assignment so a failed assignment won't cache the address --- python/vyos/ifconfig/interface.py | 48 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index de5ca369f..5b3da228e 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -638,6 +638,10 @@ class Interface(Control): # XXX: normalize/compress with ipaddress if calling functions don't? # is subnet mask always passed, and in the same way? + # do not add same address twice + if addr in self._addr: + return False + # we can't have both DHCP and static IPv4 addresses assigned for a in self._addr: if ( ( addr == 'dhcp' and a != 'dhcpv6' and is_ipv4(a) ) or @@ -646,27 +650,20 @@ class Interface(Control): "Can't configure both static IPv4 and DHCP address " "on the same interface")) - # do not add same address twice - if addr in self._addr: - return False - # add to interface if addr == 'dhcp': - self._addr.append(addr) self.dhcp.v4.set() - return True - - if addr == 'dhcpv6': - self._addr.append(addr) + elif addr == 'dhcpv6': self.dhcp.v6.set() - return True - - if not is_intf_addr_assigned(self.ifname, addr): - self._addr.append(addr) + elif not is_intf_addr_assigned(self.ifname, addr): self._cmd(f'ip addr add "{addr}" dev "{self.ifname}"') - return True + else: + return False - return False + # add to cache + self._addr.append(addr) + + return True def del_addr(self, addr): """ @@ -693,24 +690,21 @@ class Interface(Control): ['2001:db8::ffff/64'] """ - # remove from cache (dhcp, and dhcpv6 can not be in it) - if addr in self._addr: - self._addr.remove(addr) - # remove from interface if addr == 'dhcp': self.dhcp.v4.delete() - return True - - if addr == 'dhcpv6': + elif addr == 'dhcpv6': self.dhcp.v6.delete() - return True - - if is_intf_addr_assigned(self.ifname, addr): + elif is_intf_addr_assigned(self.ifname, addr): self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"') - return True + else: + return False + + # remove from cache + if addr in self._addr: + self._addr.remove(addr) - return False + return True def flush_addrs(self): """ -- cgit v1.2.3 From de5540e49a0e66805204c424686556451592de45 Mon Sep 17 00:00:00 2001 From: DmitriyEshenko Date: Mon, 4 May 2020 12:30:31 +0000 Subject: ipoe: T2294: Fix nodes path --- src/conf_mode/service_ipoe-server.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index b53692d37..84443ade3 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -112,28 +112,30 @@ def get_config(): 'name': interface, 'mac': [] } - for client in conf.list_nodes(base_path + ['authentication', 'interface', interface, 'mac-address']): - mac = { + for mac in conf.list_nodes(['authentication', 'interface', interface, 'mac-address']): + client = { 'address': mac, 'rate_download': '', 'rate_upload': '', 'vlan_id': '' } - conf.set_level(base_path + ['authentication', 'interface', interface, 'mac-address', client]) + conf.set_level(base_path + ['authentication', 'interface', interface, 'mac-address', mac]) if conf.exists(['rate-limit', 'download']): - mac['rate_download'] = conf.return_value(['rate-limit', 'download']) + client['rate_download'] = conf.return_value(['rate-limit', 'download']) if conf.exists(['rate-limit', 'upload']): - mac['rate_upload'] = conf.return_value(['rate-limit', 'upload']) + client['rate_upload'] = conf.return_value(['rate-limit', 'upload']) if conf.exists(['vlan-id']): - mac['vlan'] = conf.return_value(['vlan-id']) + client['vlan'] = conf.return_value(['vlan-id']) - tmp['mac'].append(mac) + tmp['mac'].append(client) ipoe['auth_interfaces'].append(tmp) + conf.set_level(base_path) + # # authentication mode radius servers and settings if conf.exists(['authentication', 'mode', 'radius']): -- cgit v1.2.3 From 208414293f341923990ff3a30f1d0eddbf94b14f Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 11:55:00 +0200 Subject: vlan: T2241: cleanup vlan_to_dict function Remove one unnecessary call to conf.get_level() --- python/vyos/configdict.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index e1b704a31..943092ceb 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -416,13 +416,13 @@ def add_to_dict(conf, disabled, ifdict, section, key): def vlan_to_dict(conf, default=vlan_default): vlan, disabled = intf_to_dict(conf, default) - # get the '100' in 'interfaces bonding bond0 vif-s 100 - vlan['id'] = conf.get_level()[-1] - current_level = conf.get_level() + level = conf.get_level() + # get the '100' in 'interfaces bonding bond0 vif-s 100' + vlan['id'] = level[-1] # if this is a not within vif-s node, we are done - if current_level[-2] != 'vif-s': + if level[-2] != 'vif-s': return vlan # ethertype is mandatory on vif-s nodes and only exists here! -- cgit v1.2.3 From 9bfbb24ae649d7cedc2002f7df95a0aa0fececdc Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:25:56 +0200 Subject: util: T2241: add func that gets interface name from current config level --- python/vyos/util.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/python/vyos/util.py b/python/vyos/util.py index 92b6f7992..c708c1eb4 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -498,3 +498,27 @@ def get_half_cpus(): if cpu > 1: cpu /= 2 return int(cpu) + +def ifname_from_config(conf): + """ + Gets interface name with VLANs from current config level. + Level must be at the interface whose name we want. + + Example: + >>> from vyos.util import ifname_from_config + >>> from vyos.config import Config + >>> conf = Config() + >>> conf.set_level('interfaces ethernet eth0 vif-s 1 vif-c 2') + >>> ifname_from_config(conf) + 'eth0.1.2' + """ + level = conf.get_level() + + # vlans + if level[-2] == 'vif' or level[-2] == 'vif-s': + return level[-3] + '.' + level[-1] + if level[-2] == 'vif-c': + return level[-5] + '.' + level[-3] + '.' + level[-1] + + # no vlans + return level[-1] -- cgit v1.2.3 From c1ad2a6461fc2e767d69567be9647150c3310569 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:28:11 +0200 Subject: configdict: T2241: get interface name in intf/vlan_from_dict This is needed as later functions depend on it --- python/vyos/configdict.py | 2 ++ src/conf_mode/interfaces-bonding.py | 1 - src/conf_mode/interfaces-ethernet.py | 1 - src/conf_mode/interfaces-pseudo-ethernet.py | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 943092ceb..cd3364c94 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -23,6 +23,7 @@ from copy import deepcopy from vyos import ConfigError from vyos.ifconfig import Interface +from vyos.util import ifname_from_config def retrieve_config(path_hash, base_path, config): @@ -197,6 +198,7 @@ def intf_to_dict(conf, default): """ intf = deepcopy(default) + intf['intf'] = ifname_from_config(conf) # retrieve configured interface addresses if conf.exists('address'): diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index a174e33e4..76caefabe 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -119,7 +119,6 @@ def get_config(): conf.set_level(cfg_base) bond, disabled = intf_to_dict(conf, default_config_data) - bond['intf'] = ifname # ARP link monitoring frequency in milliseconds if conf.exists('arp-monitor interval'): diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 3ddd394d7..c7f935ca6 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -92,7 +92,6 @@ def get_config(): conf.set_level(cfg_base) eth, disabled = intf_to_dict(conf, default_config_data) - eth['intf'] = ifname # disable ethernet flow control (pause frames) if conf.exists('disable-flow-control'): diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index f0f893b44..67250ec9f 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -85,7 +85,6 @@ def get_config(): conf.set_level(cfg_base) peth, disabled = intf_to_dict(conf, default_config_data) - peth['intf'] = ifname # ARP cache entry timeout in seconds if conf.exists(['ip', 'arp-cache-timeout']): -- cgit v1.2.3 From 900e75e387939a1d1d4d5b0b79809b8bb2305b91 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:46:47 +0200 Subject: validate: T2241: rewrite is_bridge_member to generic is_member - rewrite the function to support both bridge and bonding interface types, if the type is passed it searches only that type, otherwise it searches both - move is_member check out of the deleted condition - move is_member check to intf_from_dict for interfaces that use it --- python/vyos/configdict.py | 3 ++ python/vyos/validate.py | 44 ++++++++++++++++++++--------- src/conf_mode/interfaces-bonding.py | 3 -- src/conf_mode/interfaces-dummy.py | 7 +++-- src/conf_mode/interfaces-geneve.py | 7 +++-- src/conf_mode/interfaces-openvpn.py | 7 +++-- src/conf_mode/interfaces-pseudo-ethernet.py | 5 +--- src/conf_mode/interfaces-tunnel.py | 4 +-- src/conf_mode/interfaces-vxlan.py | 7 +++-- src/conf_mode/interfaces-wireguard.py | 7 +++-- src/conf_mode/interfaces-wireless.py | 7 +++-- src/conf_mode/interfaces-wirelessmodem.py | 7 +++-- 12 files changed, 65 insertions(+), 43 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index cd3364c94..ab72aac6c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -23,6 +23,7 @@ from copy import deepcopy from vyos import ConfigError from vyos.ifconfig import Interface +from vyos.validate import is_member from vyos.util import ifname_from_config @@ -268,6 +269,8 @@ def intf_to_dict(conf, default): # Media Access Control (MAC) address if conf.exists('mac'): intf['mac'] = conf.return_value('mac') + # check if interface is member of a bridge + intf['is_bridge_member'] = is_member(conf, intf['intf'], 'bridge') # IPv6 Duplicate Address Detection (DAD) tries if conf.exists('ipv6 dup-addr-detect-transmits'): diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 446f6e4ca..eb3f8bf52 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -241,25 +241,43 @@ def assert_mac(m): if octets[:5] == (0, 0, 94, 0, 1): raise ValueError(f'{m} is a VRRP MAC address') -def is_bridge_member(conf, interface): +def is_member(conf, interface, intftype=None): """ - Checks if passed interfaces is part of a bridge device or not. - - Returns a tuple: - None -> Interface not a bridge member - Bridge -> Interface is a member of this bridge + Checks if passed interface is member of other interface of specified type. + intftype is optional, if not passed it will search all known types + (currently bridge and bonding) + + Returns: + None -> Interface is not a member + interface name -> Interface is a member of this interface + False -> interface type cannot have members """ ret_val = None - old_level = conf.get_level() + + if intftype not in ['bonding', 'bridge', None]: + raise ValueError(( + f'unknown interface type "{intftype}" or it cannot ' + f'have member interfaces')) + + intftype = ['bonding', 'bridge'] if intftype == None else [intftype] # set config level to root + old_level = conf.get_level() conf.set_level([]) - base = ['interfaces', 'bridge'] - for bridge in conf.list_nodes(base): - members = conf.list_nodes(base + [bridge, 'member', 'interface']) - if interface in members: - ret_val = bridge - break + + for it in intftype: + base = 'interfaces ' + it + for intf in conf.list_nodes(base): + memberintf = f'{base} {intf} member interface' + if conf.is_tag(memberintf): + if interface in conf.list_nodes(memberintf): + ret_val = intf + break + elif conf.is_leaf(memberintf): + if ( conf.exists(memberintf) and + interface in conf.return_values(memberintf) ): + ret_val = intf + break old_level = conf.set_level(old_level) return ret_val diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 76caefabe..0fc8cfa6c 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -25,7 +25,6 @@ from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.config import Config from vyos.util import call, cmd -from vyos.validate import is_bridge_member from vyos import ConfigError default_config_data = { @@ -111,8 +110,6 @@ def get_config(): bond = deepcopy(default_config_data) bond['intf'] = ifname bond['deleted'] = True - # check if interface is member if a bridge - bond['is_bridge_member'] = is_bridge_member(conf, ifname) return bond # set new configuration level diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 23eaa4ecb..7bc834be5 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -23,7 +23,7 @@ from netifaces import interfaces from vyos.ifconfig import DummyIf from vyos.configdict import list_diff from vyos.config import Config -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -47,11 +47,12 @@ def get_config(): dummy['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if we are a member of any bridge + dummy['is_bridge_member'] = is_member(conf, dummy['intf'], 'bridge') + # Check if interface has been removed if not conf.exists('interfaces dummy ' + dummy['intf']): dummy['deleted'] = True - # check if interface is member if a bridge - dummy['is_bridge_member'] = is_bridge_member(conf, dummy['intf']) return dummy # set new configuration level diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 708a64474..98f0672c5 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -22,7 +22,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.ifconfig import GeneveIf -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -49,11 +49,12 @@ def get_config(): geneve['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if interface is member if a bridge + geneve['is_bridge_member'] = is_member(conf, geneve['intf'], 'bridge') + # Check if interface has been removed if not conf.exists('interfaces geneve ' + geneve['intf']): geneve['deleted'] = True - # check if interface is member if a bridge - geneve['is_bridge_member'] = is_bridge_member(conf, geneve['intf']) return geneve # set new configuration level diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 029bc1d69..23a690bf2 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -29,7 +29,7 @@ from vyos.configdict import list_diff from vyos.ifconfig import VTunIf from vyos.template import render from vyos.util import call, chown, chmod_600, chmod_755 -from vyos.validate import is_addr_assigned, is_bridge_member, is_ipv4 +from vyos.validate import is_addr_assigned, is_member, is_ipv4 from vyos import ConfigError user = 'openvpn' @@ -199,11 +199,12 @@ def get_config(): openvpn['intf'] = os.environ['VYOS_TAGNODE_VALUE'] openvpn['auth_user_pass_file'] = f"/run/openvpn/{openvpn['intf']}.pw" + # check if interface is member of a bridge + openvpn['is_bridge_member'] = is_member(conf, openvpn['intf'], 'bridge') + # Check if interface instance has been removed if not conf.exists('interfaces openvpn ' + openvpn['intf']): openvpn['deleted'] = True - # check if interface is member if a bridge - openvpn['is_bridge_member'] = is_bridge_member(conf, openvpn['intf']) return openvpn # Check if we belong to any bridge interface diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 67250ec9f..f9523ca8b 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -21,10 +21,9 @@ from sys import exit from netifaces import interfaces from vyos.config import Config -from vyos.configdict import list_diff, vlan_to_dict, intf_to_dict, add_to_dict +from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.ifconfig import MACVLANIf, Section from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config -from vyos.validate import is_bridge_member from vyos import ConfigError default_config_data = { @@ -77,8 +76,6 @@ def get_config(): if not conf.exists(cfg_base): peth = deepcopy(default_config_data) peth['deleted'] = True - # check if interface is member if a bridge - peth['is_bridge_member'] = is_bridge_member(conf, ifname) return peth # set new configuration level diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index fc084814a..1916d2de2 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -25,7 +25,7 @@ from vyos.config import Config from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf from vyos.ifconfig.afi import IP4, IP6 from vyos.configdict import list_diff -from vyos.validate import is_ipv4, is_ipv6, is_bridge_member +from vyos.validate import is_ipv4, is_ipv6, is_member from vyos import ConfigError from vyos.dicts import FixedDict @@ -410,7 +410,7 @@ def get_config(): options['tunnel'] = {} # check for bridges - options['bridge'] = is_bridge_member(conf, ifname) + options['bridge'] = is_member(conf, ifname, 'bridge') options['interfaces'] = interfaces() for name in ct: diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 74eae4281..334e418ab 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -22,7 +22,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.ifconfig import VXLANIf, Interface -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -62,11 +62,12 @@ def get_config(): vxlan['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if interface is member if a bridge + vxlan['is_bridge_member'] = is_member(conf, vxlan['intf'], 'bridge') + # Check if interface has been removed if not conf.exists('interfaces vxlan ' + vxlan['intf']): vxlan['deleted'] = True - # check if interface is member if a bridge - vxlan['is_bridge_member'] = is_bridge_member(conf, vxlan['intf']) return vxlan # set new configuration level diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 01f84260d..98d5fcb27 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -25,7 +25,7 @@ from vyos.config import Config from vyos.configdict import list_diff from vyos.ifconfig import WireGuardIf from vyos.util import chown, chmod_750, call -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError kdir = r'/config/auth/wireguard' @@ -78,11 +78,12 @@ def get_config(): wg = deepcopy(default_config_data) wg['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if interface is member if a bridge + wg['is_bridge_member'] = is_member(conf, wg['intf'], 'bridge') + # Check if interface has been removed if not conf.exists(base + [wg['intf']]): wg['deleted'] = True - # check if interface is member if a bridge - wg['is_bridge_member'] = is_bridge_member(conf, wg['intf']) return wg conf.set_level(base + [wg['intf']]) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 148a7f6e0..04125ff31 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -29,7 +29,7 @@ from vyos.ifconfig import WiFiIf, Section from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.template import render from vyos.util import chown, call -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -134,12 +134,13 @@ def get_config(): wifi['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if interface is member if a bridge + wifi['is_bridge_member'] = is_member(conf, wifi['intf'], 'bridge') + # check if wireless interface has been removed cfg_base = 'interfaces wireless ' + wifi['intf'] if not conf.exists(cfg_base): wifi['deleted'] = True - # check if interface is member if a bridge - wifi['is_bridge_member'] = is_bridge_member(conf, wifi['intf']) # we can not bail out early as wireless interface can not be removed # Kernel will complain with: RTNETLINK answers: Operation not supported. # Thus we need to remove individual settings diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index a3a2a2648..03832f345 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -23,7 +23,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.template import render from vyos.util import chown, chmod_755, cmd, call -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -64,11 +64,12 @@ def get_config(): wwan['logfile'] = f"/var/log/vyatta/ppp_{wwan['intf']}.log" wwan['chat_script'] = f"/etc/ppp/peers/chat.{wwan['intf']}" + # check if interface is member if a bridge + wwan['is_bridge_member'] = is_member(conf, wwan['intf'], 'bridge') + # Check if interface has been removed if not conf.exists('interfaces wirelessmodem ' + wwan['intf']): wwan['deleted'] = True - # check if interface is member if a bridge - wwan['is_bridge_member'] = is_bridge_member(conf, wwan['intf']) return wwan # set new configuration level -- cgit v1.2.3 From 64d3d94f35453bfaf596c27a0fc0f3fa78cc7260 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:50:42 +0200 Subject: intf_from_dict: T2241: move getting mac code so it's sorted alphabetically --- python/vyos/configdict.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index ab72aac6c..97ba8937c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -266,9 +266,6 @@ def intf_to_dict(conf, default): if conf.exists('ipv6 disable-forwarding'): intf['ipv6_forwarding'] = 0 - # Media Access Control (MAC) address - if conf.exists('mac'): - intf['mac'] = conf.return_value('mac') # check if interface is member of a bridge intf['is_bridge_member'] = is_member(conf, intf['intf'], 'bridge') @@ -277,6 +274,10 @@ def intf_to_dict(conf, default): intf['ipv6_dup_addr_detect'] = int( conf.return_value('ipv6 dup-addr-detect-transmits')) + # Media Access Control (MAC) address + if conf.exists('mac'): + intf['mac'] = conf.return_value('mac') + # Maximum Transmission Unit (MTU) if conf.exists('mtu'): intf['mtu'] = int(conf.return_value('mtu')) @@ -350,7 +351,7 @@ def intf_to_dict(conf, default): # add the link-local by default to make IPv6 work intf['ipv6_eui64_prefix'].append('fe80::/64') - # Find out if MAC has changed + # If MAC has changed, remove and re-add all IPv6 EUI64 addresses try: interface = Interface(intf['intf'], create=False) if intf['mac'] and intf['mac'] != interface.get_mac(): -- cgit v1.2.3 From da413b6aec002b37a20443632bab08f5db89f854 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:57:35 +0200 Subject: vlan: T2241: add checks for bridge membership --- python/vyos/configdict.py | 2 +- python/vyos/ifconfig_vlan.py | 35 ++++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 97ba8937c..0648d8646 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -26,7 +26,6 @@ from vyos.ifconfig import Interface from vyos.validate import is_member from vyos.util import ifname_from_config - def retrieve_config(path_hash, base_path, config): """ Retrieves a VyOS config as a dict according to a declarative description @@ -130,6 +129,7 @@ vlan_default = { 'ipv6_dup_addr_detect': 1, 'ingress_qos': '', 'ingress_qos_changed': False, + 'is_bridge_member': False, 'mac': '', 'mtu': 1500, 'vif_c': [], diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index ee009f7f9..079118df6 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -103,9 +103,15 @@ def verify_vlan_config(config): if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - vrf_name = vif['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + + if vif['vrf']: + if vif['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{vif["vrf"]}" does not exist') + + if vif['is_bridge_member']: + raise ConfigError(( + f'vif {vif["intf"]} cannot be member of VRF {vif["vrf"]} ' + f'and bridge {vif["is_bridge_member"]} at the same time!')) # e.g. wireless interface has no vif_s support # thus we bail out eraly. @@ -121,17 +127,28 @@ def verify_vlan_config(config): if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - vrf_name = vif_s['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + + if vif_s['vrf']: + if vif_s['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{vif_s["vrf"]}" does not exist') + + if vif_s['is_bridge_member']: + raise ConfigError(( + f'vif-s {vif_s["intf"]} cannot be member of VRF {vif_s["vrf"]} ' + f'and bridge {vif_s["is_bridge_member"]} at the same time!')) for vif_c in vif_s['vif_c']: # DHCPv6 parameters-only and temporary address are mutually exclusive if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - vrf_name = vif_c['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + if vif_c['vrf']: + if vif_c['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{vif_c["vrf"]}" does not exist') + + if vif_c['is_bridge_member']: + raise ConfigError(( + f'vif-c {vif_c["intf"]} cannot be member of VRF {vif_c["vrf"]} ' + f'and bridge {vif_c["is_bridge_member"]} at the same time!')) -- cgit v1.2.3 From 66e150056ad2cfecb302ab7f42d06096395b7517 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 13:38:51 +0200 Subject: openvpn: T2241: remove redundant bridge_member variable --- data/templates/openvpn/server.conf.tmpl | 2 +- src/conf_mode/interfaces-openvpn.py | 34 ++++++++++----------------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl index 75ab602f8..401f8e04b 100644 --- a/data/templates/openvpn/server.conf.tmpl +++ b/data/templates/openvpn/server.conf.tmpl @@ -74,7 +74,7 @@ nobind topology {% if server_topology == 'point-to-point' %}p2p{% else %}{{ server_topology }}{% endif %} {%- endif %} -{%- if bridge_member %} +{%- if is_bridge_member %} mode server tls-server {%- else %} diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 23a690bf2..c0678764f 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -41,7 +41,6 @@ default_config_data = { 'auth_pass': '', 'auth_user_pass_file': '', 'auth': False, - 'bridge_member': [], 'compress_lzo': False, 'deleted': False, 'description': '', @@ -207,14 +206,8 @@ def get_config(): openvpn['deleted'] = True return openvpn - # Check if we belong to any bridge interface - for bridge in conf.list_nodes('interfaces bridge'): - for intf in conf.list_nodes('interfaces bridge {} member interface'.format(bridge)): - if intf == openvpn['intf']: - openvpn['bridge_member'].append(intf) - # bridged server should not have a pool by default (but can be specified manually) - if openvpn['bridge_member']: + if openvpn['is_bridge_member']: openvpn['server_pool'] = False openvpn['server_ipv6_pool'] = False @@ -598,7 +591,7 @@ def get_config(): default_server = getDefaultServer(server_network_v4, openvpn['server_topology'], openvpn['type']) if default_server: # server-bridge doesn't require a pool so don't set defaults for it - if openvpn['server_pool'] and not openvpn['bridge_member']: + if openvpn['server_pool'] and not openvpn['is_bridge_member']: if not openvpn['server_pool_start']: openvpn['server_pool_start'] = default_server['pool_start'] @@ -636,22 +629,15 @@ def get_config(): def verify(openvpn): if openvpn['deleted']: if openvpn['is_bridge_member']: - interface = openvpn['intf'] - bridge = openvpn['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') - - return None + raise ConfigError(( + f'Cannot delete interface "{openvpn["intf"]}" as it is a ' + f'member of bridge "{openvpn["is_bridge_menber"]}"!')) + return None if not openvpn['mode']: raise ConfigError('Must specify OpenVPN operation mode') - # Checks which need to be performed on interface rmeoval - if openvpn['deleted']: - # OpenVPN interface can not be deleted if it's still member of a bridge - if openvpn['bridge_member']: - raise ConfigError('Can not delete {} as it is a member interface of bridge {}!'.format(openvpn['intf'], bridge)) - # Check if we have disabled ncp and at the same time specified ncp-ciphers if openvpn['disable_ncp'] and openvpn['ncp_ciphers']: raise ConfigError('Cannot specify both "encryption disable-ncp" and "encryption ncp-ciphers"') @@ -681,9 +667,9 @@ def verify(openvpn): if openvpn['ncp_ciphers']: raise ConfigError('encryption ncp-ciphers cannot be specified in site-to-site mode, only server or client') - if openvpn['mode'] == 'site-to-site' and not openvpn['bridge_member']: + if openvpn['mode'] == 'site-to-site' and not openvpn['is_bridge_member']: if not (openvpn['local_address'] or openvpn['ipv6_local_address']): - raise ConfigError('Must specify "local-address" or "bridge member interface"') + raise ConfigError('Must specify "local-address" or add interface to bridge') if len(openvpn['local_address']) > 1 or len(openvpn['ipv6_local_address']) > 1: raise ConfigError('Cannot specify more than 1 IPv4 and 1 IPv6 "local-address"') @@ -762,8 +748,8 @@ def verify(openvpn): raise ConfigError(f'Client "{client["name"]}" IP {client["ip"][0]} not in server subnet {subnet}') else: - if not openvpn['bridge_member']: - raise ConfigError('Must specify "server subnet" or "bridge member interface" in server mode') + if not openvpn['is_bridge_member']: + raise ConfigError('Must specify "server subnet" or add interface to bridge in server mode') if openvpn['server_pool']: if not (openvpn['server_pool_start'] and openvpn['server_pool_stop']): -- cgit v1.2.3 From c113977f917ec56fe2b8707bfe8e381b4c181d7d Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 15:38:30 +0200 Subject: util: T2241: add get_bridge_member_config function Function that parses the config of a bridge member into a dict that is needed to apply all port config when adding a port to a bridge. Needed because other interfaces will be adding themselves to the bridge outside of the bridge conf_mode script and they need a common place to get their config. Can't be put as method of BridgeIf as we can't invoke it without it creating the bridge (create=False raises an exception), we need to get the configuration before we create the interface. --- python/vyos/util.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/python/vyos/util.py b/python/vyos/util.py index c708c1eb4..e598e0ff3 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -522,3 +522,50 @@ def ifname_from_config(conf): # no vlans return level[-1] + +def get_bridge_member_config(conf, br, intf): + """ + Gets bridge port (member) configuration + + Arguments: + conf: Config + br: bridge name + intf: interface name + + Returns: + dict with the configuration + False if bridge or bridge port doesn't exist + """ + old_level = conf.get_level() + conf.set_level([]) + + bridge = f'interfaces bridge {br}' + member = f'{bridge} member interface {intf}' + if not ( conf.exists(bridge) and conf.exists(member) ): + return False + + # default bridge port configuration + # cost and priority initialized with linux defaults + # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority} + # after adding interface to bridge after reboot + memberconf = { + 'cost': 100, + 'priority': 32, + 'arp_cache_tmo': 30, + 'disable_link_detect': 1, + } + + if conf.exists(f'{member} cost'): + memberconf['cost'] = int(conf.return_value(f'{member} cost')) + + if conf.exists(f'{member} priority'): + memberconf['priority'] = int(conf.return_value(f'{member} priority')) + + if conf.exists(f'{bridge} ip arp-cache-timeout'): + memberconf['arp_cache_tmo'] = int(conf.return_value(f'{bridge} ip arp-cache-timeout')) + + if conf.exists(f'{bridge} disable-link-detect'): + memberconf['disable_link_detect'] = 2 + + conf.set_level(old_level) + return memberconf -- cgit v1.2.3 From 347347c68687849822912e5e26febc5f92955b8b Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 15:43:16 +0200 Subject: bridge: T2241: use vyos.util get_bridge_member_config function Was previously moved out of this script. --- src/conf_mode/interfaces-bridge.py | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 9d638653c..d46b625d8 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -24,7 +24,7 @@ from vyos.ifconfig import BridgeIf, Section from vyos.ifconfig.stp import STP from vyos.configdict import list_diff from vyos.config import Config -from vyos.util import cmd +from vyos.util import cmd, get_bridge_member_config from vyos import ConfigError default_config_data = { @@ -202,22 +202,12 @@ def get_config(): # Determine bridge member interface (currently configured) for intf in conf.list_nodes('member interface'): - # cost and priority initialized with linux defaults - # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority} - # after adding interface to bridge after reboot - iface = { - 'name': intf, - 'cost': 100, - 'priority': 32 - } - - if conf.exists('member interface {} cost'.format(intf)): - iface['cost'] = int(conf.return_value('member interface {} cost'.format(intf))) - - if conf.exists('member interface {} priority'.format(intf)): - iface['priority'] = int(conf.return_value('member interface {} priority'.format(intf))) - - bridge['member'].append(iface) + # defaults are stored in util.py (they can't be here as all interface + # scripts use the function) + memberconf = get_bridge_member_config(conf, bridge['intf'], intf) + if memberconf: + memberconf['name'] = intf + bridge['member'].append(memberconf) # Determine bridge member interface (currently effective) - to determine which # interfaces is no longer assigend to the bridge and thus can be removed @@ -382,9 +372,9 @@ def apply(bridge): for member in bridge['member']: i = STPBridgeIf(member['name']) # configure ARP cache timeout - i.set_arp_cache_tmo(bridge['arp_cache_tmo']) + i.set_arp_cache_tmo(member['arp_cache_tmo']) # ignore link state changes - i.set_link_detect(bridge['disable_link_detect']) + i.set_link_detect(member['disable_link_detect']) # set bridge port path cost i.set_path_cost(member['cost']) # set bridge port path priority -- cgit v1.2.3 From e35807b209e3de77b09082a74d2b0f0b1a802206 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 15:47:11 +0200 Subject: interface: T2241: add function to add self to bridge Will be called by all interface scripts to re-add themselves to a bridge after deleting and recreating themselves. --- python/vyos/ifconfig/interface.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 5b3da228e..7b42e3399 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -42,6 +42,7 @@ from vyos.ifconfig.control import Control from vyos.ifconfig.dhcp import DHCP from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.operational import Operational +from vyos.ifconfig import Section class Interface(Control): @@ -718,3 +719,22 @@ class Interface(Control): # flush all addresses self._cmd(f'ip addr flush dev "{self.ifname}"') + + def add_to_bridge(self, br): + """ + Adds the interface to the bridge with the passed port config. + + Returns False if bridge doesn't exist. + """ + + # check if the bridge exists (on boot it doesn't) + if br not in Section.interfaces('bridge'): + return False + + self.flush_addrs() + # add interface to bridge - use Section.klass to get BridgeIf class + Section.klass(br)(br, create=False).add_port(self.ifname) + + # TODO: port config (STP) + + return True -- cgit v1.2.3 From 963dcd509fa491a30d0dd6266d827fb60bb9e27b Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 16:01:53 +0200 Subject: vlan: T2241: fix falling out of bridge when changing settings Previously, set_vrf was always called, which uses the same master and nomaster commands as bridge, so it removed the interface from the bridge. - add checks to make VRF and bridge membership mutually exclusive - always re-add the interface back to any bridge it is part of in case it is deleted and recreated (e.g. changing egress/ingress-qos) --- python/vyos/ifconfig_vlan.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 079118df6..bc43ff6db 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -63,8 +63,10 @@ def apply_vlan_config(vlan, config): # Maximum Transmission Unit (MTU) vlan.set_mtu(config['mtu']) - # assign/remove VRF - vlan.set_vrf(config['vrf']) + # assign/remove VRF (ONLY when not a member of a bridge, + # otherwise 'nomaster' removes it from it) + if not config['is_bridge_member']: + vlan.set_vrf(config['vrf']) # Delete old IPv6 EUI64 addresses before changing MAC for addr in config['ipv6_eui64_prefix_remove']: @@ -92,6 +94,10 @@ def apply_vlan_config(vlan, config): for addr in config['address']: vlan.add_addr(addr) + # re-add ourselves to any bridge we might have fallen out of + if config['is_bridge_member']: + vlan.add_to_bridge(config['is_bridge_member']) + def verify_vlan_config(config): """ Generic function to verify VLAN config consistency. Instead of re- @@ -103,7 +109,6 @@ def verify_vlan_config(config): if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - if vif['vrf']: if vif['vrf'] not in interfaces(): raise ConfigError(f'VRF "{vif["vrf"]}" does not exist') @@ -127,7 +132,6 @@ def verify_vlan_config(config): if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - if vif_s['vrf']: if vif_s['vrf'] not in interfaces(): raise ConfigError(f'VRF "{vif_s["vrf"]}" does not exist') @@ -142,7 +146,6 @@ def verify_vlan_config(config): if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - if vif_c['vrf']: if vif_c['vrf'] not in interfaces(): raise ConfigError(f'VRF "{vif_c["vrf"]}" does not exist') -- cgit v1.2.3 From d6e65c5b14b1049ba346cbaa7c05c12cacfd0495 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 22:54:53 +0200 Subject: configdict: T2241: don't add default IPv6 EUI64 if member of a bridge Bridge members should not have addresses assigned. --- python/vyos/configdict.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 0648d8646..5ab831258 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -344,8 +344,9 @@ def intf_to_dict(conf, default): intf['ipv6_eui64_prefix'] = act_eui intf['ipv6_eui64_prefix_remove'] = list_diff(eff_eui, act_eui) - # Remove the default link-local address if set. - if conf.exists('ipv6 address no-default-link-local'): + # Remove the default link-local address if set or if member of a bridge + if ( conf.exists('ipv6 address no-default-link-local') + or intf.get('is_bridge_member') ): intf['ipv6_eui64_prefix_remove'].append('fe80::/64') else: # add the link-local by default to make IPv6 work -- cgit v1.2.3 From faa490ad122eeb1de02947bb4593539f0f476bd3 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 22:56:30 +0200 Subject: vlan: T2241: make address and bridge membership mutually exclusive Bridge members should not have addresses assigned. --- python/vyos/ifconfig_vlan.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index bc43ff6db..eb7a369ec 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -109,6 +109,14 @@ def verify_vlan_config(config): if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + if ( vif['is_bridge_member'] + and ( vif['address'] + or vif['ipv6_eui64_prefix'] + or vif['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to vif interface {vif["intf"]} ' + f'which is a member of bridge {vif["is_bridge_member"]}')) + if vif['vrf']: if vif['vrf'] not in interfaces(): raise ConfigError(f'VRF "{vif["vrf"]}" does not exist') @@ -132,6 +140,14 @@ def verify_vlan_config(config): if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + if ( vif_s['is_bridge_member'] + and ( vif_s['address'] + or vif_s['ipv6_eui64_prefix'] + or vif_s['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to vif-s interface {vif_s["intf"]} ' + f'which is a member of bridge {vif_s["is_bridge_member"]}')) + if vif_s['vrf']: if vif_s['vrf'] not in interfaces(): raise ConfigError(f'VRF "{vif_s["vrf"]}" does not exist') @@ -146,6 +162,14 @@ def verify_vlan_config(config): if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + if ( vif_c['is_bridge_member'] + and ( vif_c['address'] + or vif_c['ipv6_eui64_prefix'] + or vif_c['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to vif-c interface {vif_c["intf"]} ' + f'which is a member of bridge {vif_c["is_bridge_member"]}')) + if vif_c['vrf']: if vif_c['vrf'] not in interfaces(): raise ConfigError(f'VRF "{vif_c["vrf"]}" does not exist') -- cgit v1.2.3 From 5ac80802197e1b5c17db96580b004391bde47bf2 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 16:26:50 +0200 Subject: bonding: T2241: make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-bonding.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 0fc8cfa6c..7681a8c71 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -184,9 +184,15 @@ def verify(bond): raise ConfigError('Mode dependency failed, primary not supported ' \ 'in mode "{}"!'.format(bond['mode'])) - vrf_name = bond['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + if bond['vrf']: + if bond['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{bond["vrf"]}" does not exist') + + if bond['is_bridge_member']: + raise ConfigError(( + f'Interface "{bond["intf"]}" cannot be member of VRF ' + f'"{bond["vrf"]}" and bridge {bond["is_bridge_member"]} ' + f'at the same time!')) # use common function to verify VLAN configuration verify_vlan_config(bond) -- cgit v1.2.3 From 1c93e3895787641a207d09c562f2703fddc77b8f Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 20:59:35 +0200 Subject: validate: T2241: add func that checks if an interface has a configured address --- python/vyos/validate.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/python/vyos/validate.py b/python/vyos/validate.py index eb3f8bf52..e083604c5 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -282,3 +282,25 @@ def is_member(conf, interface, intftype=None): old_level = conf.set_level(old_level) return ret_val +def has_address_configured(conf, intf): + """ + Checks if interface has an address configured. + Checks the following config nodes: + 'address', 'ipv6 address eui64', 'ipv6 address autoconf' + + Returns True if interface has address configured, False if it doesn't. + """ + from vyos.ifconfig import Section + ret = False + + old_level = conf.get_level() + conf.set_level([]) + + intfpath = 'interfaces ' + Section.get_config_path(intf) + if ( conf.exists(f'{intfpath} address') or + conf.exists(f'{intfpath} ipv6 address autoconf') or + conf.exists(f'{intfpath} ipv6 address eui64') ): + ret = True + + conf.set_level(old_level) + return ret -- cgit v1.2.3 From d3ac208e936caa172c575c953d3e8582286f70ef Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 21:01:50 +0200 Subject: bonding: T2241: disallow adding interfaces with addresses to bond --- src/conf_mode/interfaces-bonding.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 7681a8c71..ec9e0bb11 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -25,6 +25,7 @@ from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.config import Config from vyos.util import call, cmd +from vyos.validate import has_address_configured from vyos import ConfigError default_config_data = { @@ -213,9 +214,10 @@ def verify(bond): 'belongs to {}'.format(intf, tmp)) # can not add interfaces with an assigned address to a bond - if conf.exists('interfaces ethernet ' + intf + ' address'): - raise ConfigError('can not enslave interface {} which has an address ' \ - 'assigned'.format(intf)) + if has_address_configured(conf, intf): + raise ConfigError(( + f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' + f'it has an address assigned!')) # bond members are not allowed to be bridge members, too for tmp in conf.list_nodes('interfaces bridge'): -- cgit v1.2.3 From 9998768f0c8edb3e72566f1d26bee67d42adc133 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 21:02:10 +0200 Subject: bonding: T2241: make address and bridge membership mutually exclusive Bridge members should not have addresses assigned. --- src/conf_mode/interfaces-bonding.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index ec9e0bb11..e5626ef6c 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -185,6 +185,14 @@ def verify(bond): raise ConfigError('Mode dependency failed, primary not supported ' \ 'in mode "{}"!'.format(bond['mode'])) + if ( bond['is_bridge_member'] + and ( bond['address'] + or bond['ipv6_eui64_prefix'] + or bond['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to interface "{bond["intf"]}" ' + f'as it is a member of bridge "{bond["is_bridge_member"]}"!')) + if bond['vrf']: if bond['vrf'] not in interfaces(): raise ConfigError(f'VRF "{bond["vrf"]}" does not exist') -- cgit v1.2.3 From 512510b887408d8263865197fc936501ee453064 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 16:31:35 +0200 Subject: bonding: T2241: cleanup verify section - use is_member function instead of checking config directly - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-bonding.py | 66 ++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index e5626ef6c..235b24439 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -25,7 +25,7 @@ from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.config import Config from vyos.util import call, cmd -from vyos.validate import has_address_configured +from vyos.validate import is_member, has_address_configured from vyos import ConfigError default_config_data = { @@ -172,18 +172,20 @@ def get_config(): def verify(bond): if bond['deleted']: if bond['is_bridge_member']: - interface = bond['intf'] - bridge = bond['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + raise ConfigError(( + f'Cannot delete interface "{bond["intf"]}" as it is a ' + f'member of bridge "{bond["is_bridge_member"]}"!')) + return None - if len (bond['arp_mon_tgt']) > 16: - raise ConfigError('The maximum number of targets that can be specified is 16') + if len(bond['arp_mon_tgt']) > 16: + raise ConfigError('The maximum number of arp-monitor targets is 16') if bond['primary']: if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: - raise ConfigError('Mode dependency failed, primary not supported ' \ - 'in mode "{}"!'.format(bond['mode'])) + raise ConfigError(( + 'Mode dependency failed, primary not supported in mode ' + f'"{bond["mode"]}"!')) if ( bond['is_bridge_member'] and ( bond['address'] @@ -210,16 +212,17 @@ def verify(bond): for intf in bond['member']: # check if member interface is "real" if intf not in interfaces(): - raise ConfigError('interface {} does not exist!'.format(intf)) + raise ConfigError(f'Interface {intf} does not exist!') # a bonding member interface is only allowed to be assigned to one bond! all_bonds = conf.list_nodes('interfaces bonding') # We do not need to check our own bond all_bonds.remove(bond['intf']) for tmp in all_bonds: - if conf.exists('interfaces bonding ' + tmp + ' member interface ' + intf): - raise ConfigError('can not enslave interface {} which already ' \ - 'belongs to {}'.format(intf, tmp)) + if conf.exists('interfaces bonding {tmp} member interface {intf}'): + raise ConfigError(( + f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' + f'it is already a member of bond "{tmp}"!')) # can not add interfaces with an assigned address to a bond if has_address_configured(conf, intf): @@ -227,35 +230,37 @@ def verify(bond): f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' f'it has an address assigned!')) - # bond members are not allowed to be bridge members, too - for tmp in conf.list_nodes('interfaces bridge'): - if conf.exists('interfaces bridge ' + tmp + ' member interface ' + intf): - raise ConfigError('can not enslave interface {} which belongs to ' \ - 'bridge {}'.format(intf, tmp)) + # bond members are not allowed to be bridge members + tmp = is_member(conf, intf, 'bridge') + if tmp: + raise ConfigError(( + f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' + f'it is already a member of bridge "{tmp}"!')) - # bond members are not allowed to be vrrp members, too + # bond members are not allowed to be vrrp members for tmp in conf.list_nodes('high-availability vrrp group'): - if conf.exists('high-availability vrrp group ' + tmp + ' interface ' + intf): - raise ConfigError('can not enslave interface {} which belongs to ' \ - 'VRRP group {}'.format(intf, tmp)) + if conf.exists('high-availability vrrp group {tmp} interface {intf}'): + raise ConfigError(( + f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' + f'it is already a member of VRRP group "{tmp}"!')) # bond members are not allowed to be underlaying psuedo-ethernet devices for tmp in conf.list_nodes('interfaces pseudo-ethernet'): - if conf.exists('interfaces pseudo-ethernet ' + tmp + ' link ' + intf): - raise ConfigError('can not enslave interface {} which belongs to ' \ - 'pseudo-ethernet {}'.format(intf, tmp)) + if conf.exists('interfaces pseudo-ethernet {tmp} link {intf}'): + raise ConfigError(( + f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' + f'it is already the link of pseudo-ethernet "{tmp}"!')) # bond members are not allowed to be underlaying vxlan devices for tmp in conf.list_nodes('interfaces vxlan'): - if conf.exists('interfaces vxlan ' + tmp + ' link ' + intf): - raise ConfigError('can not enslave interface {} which belongs to ' \ - 'vxlan {}'.format(intf, tmp)) - + if conf.exists('interfaces vxlan {tmp} link {intf}'): + raise ConfigError(( + f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' + f'it is already the link of VXLAN "{tmp}"!')) if bond['primary']: if bond['primary'] not in bond['member']: - raise ConfigError('primary interface must be a member interface of {}' \ - .format(bond['intf'])) + raise ConfigError(f'Bond "{bond["intf"]}" primary interface must be a member') if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: raise ConfigError('primary interface only works for mode active-backup, ' \ @@ -268,7 +273,6 @@ def verify(bond): return None - def generate(bond): return None -- cgit v1.2.3 From 600b3bc874355237d24bbe38660478a43ce6c946 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 16:33:31 +0200 Subject: bonding: T2241: fix falling out of bridge when changing settings Previously, set_vrf was always called, which uses the same master and nomaster commands as bridge, so it removed the interface from the bridge. - add checks to make VRF and bridge membership mutually exclusive - always re-add the interface back to any bridge it is part of in case it is deleted and recreated --- src/conf_mode/interfaces-bonding.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 235b24439..a8900ac76 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -405,8 +405,14 @@ def apply(bond): for addr in bond['address']: b.add_addr(addr) - # assign/remove VRF - b.set_vrf(bond['vrf']) + # assign/remove VRF (ONLY when not a member of a bridge, + # otherwise 'nomaster' removes it from it) + if not bond['is_bridge_member']: + b.set_vrf(bond['vrf']) + + # re-add ourselves to any bridge we might have fallen out of + if bond['is_bridge_member']: + b.add_to_bridge(bond['is_bridge_member']) # remove no longer required service VLAN interfaces (vif-s) for vif_s in bond['vif_s_remove']: -- cgit v1.2.3 From 6369d56ed5bd5a36c4ae3de21e0e316989f0da8e Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 16:37:59 +0200 Subject: bridge: T2241: cleanup verify section - use is_member function instead of checking config directly - rearrange to join 2 for loops into one - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-bridge.py | 41 +++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index d46b625d8..3f54e0dc3 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -23,6 +23,7 @@ from netifaces import interfaces from vyos.ifconfig import BridgeIf, Section from vyos.ifconfig.stp import STP from vyos.configdict import list_diff +from vyos.validate import is_member from vyos.config import Config from vyos.util import cmd, get_bridge_member_config from vyos import ConfigError @@ -238,30 +239,34 @@ def verify(bridge): raise ConfigError(f'VRF "{vrf_name}" does not exist') conf = Config() - for br in conf.list_nodes('interfaces bridge'): - # it makes no sense to verify ourself in this case - if br == bridge['intf']: - continue - - for intf in bridge['member']: - tmp = conf.list_nodes('interfaces bridge {} member interface'.format(br)) - if intf['name'] in tmp: - raise ConfigError('Interface "{}" belongs to bridge "{}" and can not be enslaved.'.format(intf['name'], bridge['intf'])) - - # the interface must exist prior adding it to a bridge for intf in bridge['member']: + # the interface must exist prior adding it to a bridge if intf['name'] not in interfaces(): - raise ConfigError('Can not add non existing interface "{}" to bridge "{}"'.format(intf['name'], bridge['intf'])) + raise ConfigError(( + f'Cannot add nonexistent interface "{intf["name"]}" ' + f'to bridge "{bridge["intf"]}"')) if intf['name'] == 'lo': raise ConfigError('Loopback interface "lo" can not be added to a bridge') - # bridge members are not allowed to be bond members, too - for intf in bridge['member']: - for bond in conf.list_nodes('interfaces bonding'): - if conf.exists('interfaces bonding ' + bond + ' member interface'): - if intf['name'] in conf.return_values('interfaces bonding ' + bond + ' member interface'): - raise ConfigError('Interface {} belongs to bond {}, can not add it to {}'.format(intf['name'], bond, bridge['intf'])) + # bridge members aren't allowed to be members of another bridge + for br in conf.list_nodes('interfaces bridge'): + # it makes no sense to verify ourself in this case + if br == bridge['intf']: + continue + + tmp = conf.list_nodes(f'interfaces bridge {br} member interface') + if intf['name'] in tmp: + raise ConfigError(( + f'Cannot add interface "{intf["name"]}" to bridge ' + f'"{bridge["intf"]}", it is already a member of bridge "{br}"!')) + + # bridge members are not allowed to be bond members + tmp = is_member(conf, intf['name'], 'bonding') + if tmp: + raise ConfigError(( + f'Cannot add interface "{intf["name"]}" to bridge ' + f'"{bridge["intf"]}", it is already a member of bond "{tmp}"!')) return None -- cgit v1.2.3 From 109dbf9789bdc0b1e35b2b5ebd2fd0ce2a1ebe4a Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 17:36:47 +0200 Subject: ifconfig: T2241: fix section _basename vlan stripping Previously the function returned the correct basename only for vif interfaces as it stopped at the 2nd dot. If we had a vif-s vif-c interface 'eth0.1.2' it would return 'eth0.'. It is now fixed to strip both vif-s and vif-c if 'vlan=True' (default). --- python/vyos/ifconfig/section.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 092236fef..2d77c42cc 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -54,7 +54,7 @@ class Section: name = name.rstrip('0123456789') name = name.rstrip('.') if vlan: - name = name.rstrip('0123456789') + name = name.rstrip('0123456789.') return name @classmethod -- cgit v1.2.3 From 6f784231283ec3a63335145e914f2b15e28795dc Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 17:40:58 +0200 Subject: ifconfig: section: T2241: add get_config_path function Add a function that converts an interface name to its config path. For example: 'eth0.1.2' -> 'ethernet eth0 vif-s 1 vif-c 2' --- python/vyos/ifconfig/section.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 2d77c42cc..926c22e8a 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -137,3 +137,22 @@ class Section: eth, lo, vxlan, dum, ... """ return list(cls._prefixes.keys()) + + @classmethod + def get_config_path(cls, name): + """ + get config path to interface with .vif or .vif-s.vif-c + example: eth0.1.2 -> 'ethernet eth0 vif-s 1 vif-c 2' + Returns False if interface name is invalid (not found in sections) + """ + sect = cls.section(name) + if sect: + splinterface = name.split('.') + intfpath = f'{sect} {splinterface[0]}' + if len(splinterface) == 2: + intfpath += f' vif {splinterface[1]}' + elif len(splinterface) == 3: + intfpath += f' vif-s {splinterface[1]} vif-c {splinterface[2]}' + return intfpath + else: + return False -- cgit v1.2.3 From f81ca22c1988495943531cba11add7cfc9441033 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 17:45:20 +0200 Subject: bridge: T2241: disallow adding interfaces with addresses to bridge --- src/conf_mode/interfaces-bridge.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 3f54e0dc3..fe3675190 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -23,7 +23,7 @@ from netifaces import interfaces from vyos.ifconfig import BridgeIf, Section from vyos.ifconfig.stp import STP from vyos.configdict import list_diff -from vyos.validate import is_member +from vyos.validate import is_member, has_address_configured from vyos.config import Config from vyos.util import cmd, get_bridge_member_config from vyos import ConfigError @@ -268,6 +268,12 @@ def verify(bridge): f'Cannot add interface "{intf["name"]}" to bridge ' f'"{bridge["intf"]}", it is already a member of bond "{tmp}"!')) + # bridge members must not have an assigned address + if has_address_configured(conf, intf['name']): + raise ConfigError(( + f'Cannot add interface "{intf["name"]}" to bridge ' + f'"{bridge["intf"]}", it has an address assigned!')) + return None def generate(bridge): -- cgit v1.2.3 From 0ea1abae11dfa5d376f4aff80cd87486370211ed Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:00:28 +0200 Subject: dummy: T2241: make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-dummy.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 7bc834be5..712f867e1 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -91,9 +91,15 @@ def verify(dummy): return None - vrf_name = dummy['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + if dummy['vrf']: + if dummy['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{dummy["vrf"]}" does not exist') + + if dummy['is_bridge_member']: + raise ConfigError(( + f'Interface "{dummy["intf"]}" cannot be member of VRF ' + f'"{dummy["vrf"]}" and bridge "{dummy["is_bridge_member"]}" ' + f'at the same time!')) return None -- cgit v1.2.3 From 571b162345aa18f440ccd3f02f8b9f4b62a16c8a Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:01:14 +0200 Subject: dummy: T2241: make address and bridge membership mutually exclusive Bridge members should not have any addresses assigned. --- src/conf_mode/interfaces-dummy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 712f867e1..7d266e693 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -101,6 +101,11 @@ def verify(dummy): f'"{dummy["vrf"]}" and bridge "{dummy["is_bridge_member"]}" ' f'at the same time!')) + if dummy['is_bridge_member'] and dummy['address']: + raise ConfigError(( + f'Cannot assign address to interface "{dummy["intf"]}" ' + f'as it is a member of bridge "{dummy["is_bridge_member"]}"!')) + return None def generate(dummy): -- cgit v1.2.3 From ea7ba8a636189bb8e2d87f0de995484be55a016e Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:02:05 +0200 Subject: dummy: T2241: fix falling out of bridge when changing settings Previously, set_vrf was always called, which uses the same master and nomaster commands as bridge, so it removed the interface from the bridge. - add checks to make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-dummy.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 7d266e693..0049442cd 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -129,8 +129,10 @@ def apply(dummy): for addr in dummy['address']: d.add_addr(addr) - # assign/remove VRF - d.set_vrf(dummy['vrf']) + # assign/remove VRF (ONLY when not a member of a bridge, + # otherwise 'nomaster' removes it from it) + if not dummy['is_bridge_member']: + d.set_vrf(dummy['vrf']) # disable interface on demand if dummy['disable']: -- cgit v1.2.3 From c6914e2595172a24d376c0b65c6a2c520a2aaf80 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:04:11 +0200 Subject: dummy: T2241: cleanup verify section - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-dummy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 0049442cd..4a77b0c1a 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -85,9 +85,9 @@ def get_config(): def verify(dummy): if dummy['deleted']: if dummy['is_bridge_member']: - interface = dummy['intf'] - bridge = dummy['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + raise ConfigError(( + f'Interface "{dummy["intf"]}" cannot be deleted as it is a ' + f'member of bridge "{dummy["is_bridge_member"]}"!')) return None -- cgit v1.2.3 From be55968dfbe66477bbef4492abc0875c5da5b797 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:08:14 +0200 Subject: ethernet: T2241: add checks for bridge and bond membership --- src/conf_mode/interfaces-ethernet.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index c7f935ca6..cc6a824dc 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -23,6 +23,7 @@ from netifaces import interfaces from vyos.ifconfig import EthernetIf, Section from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.configdict import list_diff, intf_to_dict, add_to_dict +from vyos.validate import is_member from vyos.config import Config from vyos import ConfigError @@ -53,6 +54,8 @@ default_config_data = { 'ipv6_eui64_prefix_remove': [], 'ipv6_forwarding': 1, 'ipv6_dup_addr_detect': 1, + 'is_bridge_member': False, + 'is_bond_member': False, 'intf': '', 'mac': '', 'mtu': 1500, @@ -113,6 +116,9 @@ def get_config(): if conf.exists('ip proxy-arp-pvlan'): eth['ip_proxy_arp_pvlan'] = 1 + # check if we are a member of any bond + eth['is_bond_member'] = is_member(conf, eth['intf'], 'bonding') + # GRO (generic receive offload) if conf.exists('offload-options generic-receive'): eth['offload_gro'] = conf.return_value('offload-options generic-receive') -- cgit v1.2.3 From 2b002b2b2309942f3ee137e5c2e8427c44038935 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:11:13 +0200 Subject: ethernet: T2241: make VRF and bond/bridge membership mutually exclusive --- src/conf_mode/interfaces-ethernet.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index cc6a824dc..468a9040a 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -167,9 +167,7 @@ def verify(eth): if eth['dhcpv6_prm_only'] and eth['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - vrf_name = eth['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + memberof = eth['is_bridge_member'] if eth['is_bridge_member'] else eth['is_bond_member'] conf = Config() # some options can not be changed when interface is enslaved to a bond @@ -180,6 +178,15 @@ def verify(eth): if eth['address']: raise ConfigError(f"Can not assign address to interface {eth['intf']} which is a member of {bond}") + if eth['vrf']: + if eth['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{eth["vrf"]}" does not exist') + + if memberof: + raise ConfigError(( + f'Interface "{eth["intf"]}" cannot be member of VRF "{eth["vrf"]}" ' + f'and "{memberof}" at the same time!')) + # use common function to verify VLAN configuration verify_vlan_config(eth) return None -- cgit v1.2.3 From 41a2e2f7de7bf281b4233d82d3b43d6039019bc7 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:12:29 +0200 Subject: ethernet: T2241: make address and bond membership exclusive Bond members should not have any addresses assigned. --- src/conf_mode/interfaces-ethernet.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 468a9040a..314e696ca 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -143,6 +143,11 @@ def get_config(): if conf.exists('speed'): eth['speed'] = conf.return_value('speed') + # remove default IPv6 link-local address if member of a bond + if eth['is_bond_member'] and 'fe80::/64' in eth['ipv6_eui64_prefix']: + eth['ipv6_eui64_prefix'].remove('fe80::/64') + eth['ipv6_eui64_prefix_remove'].append('fe80::/64') + add_to_dict(conf, disabled, eth, 'vif', 'vif') add_to_dict(conf, disabled, eth, 'vif-s', 'vif_s') @@ -169,14 +174,13 @@ def verify(eth): memberof = eth['is_bridge_member'] if eth['is_bridge_member'] else eth['is_bond_member'] - conf = Config() - # some options can not be changed when interface is enslaved to a bond - for bond in conf.list_nodes('interfaces bonding'): - if conf.exists('interfaces bonding ' + bond + ' member interface'): - bond_member = conf.return_values('interfaces bonding ' + bond + ' member interface') - if eth['intf'] in bond_member: - if eth['address']: - raise ConfigError(f"Can not assign address to interface {eth['intf']} which is a member of {bond}") + if ( memberof + and ( eth['address'] + or eth['ipv6_eui64_prefix'] + or eth['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to interface "{eth["intf"]}" ' + f'as it is a member of "{memberof}"!')) if eth['vrf']: if eth['vrf'] not in interfaces(): -- cgit v1.2.3 From ad0448e6e213dd964d44fac450c4a428d035f635 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:13:59 +0200 Subject: ethernet: T2241: fix falling out of bridge when changing settings Previously, set_vrf was always called, which uses the same master and nomaster commands as bridge, so it removed the interface from the bridge. - add checks to make VRF and bridge membership mutually exclusive - always re-add the interface back to any bridge it is part of --- src/conf_mode/interfaces-ethernet.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 314e696ca..9b9ae931c 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -20,7 +20,7 @@ from sys import exit from copy import deepcopy from netifaces import interfaces -from vyos.ifconfig import EthernetIf, Section +from vyos.ifconfig import EthernetIf from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.validate import is_member @@ -297,8 +297,14 @@ def apply(eth): for addr in eth['address']: e.add_addr(addr) - # assign/remove VRF - e.set_vrf(eth['vrf']) + # assign/remove VRF (ONLY when not a member of a bridge or bond, + # otherwise 'nomaster' removes it from it) + if not ( eth['is_bridge_member'] or eth['is_bond_member'] ): + e.set_vrf(eth['vrf']) + + # re-add ourselves to any bridge we might have fallen out of + if eth['is_bridge_member']: + e.add_to_bridge(eth['is_bridge_member']) # remove no longer required service VLAN interfaces (vif-s) for vif_s in eth['vif_s_remove']: -- cgit v1.2.3 From 5a0101bdd4b117001e0b0f77acae09b96865c815 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:16:47 +0200 Subject: geneve: T2241: make address and bridge membership mutually exclusive Bridge members should not have any addresses assigned. --- src/conf_mode/interfaces-geneve.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 98f0672c5..ab1bd7d7b 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -104,6 +104,11 @@ def verify(geneve): return None + if geneve['is_bridge_member'] and geneve['address']: + raise ConfigError(( + f'Cannot assign address to interface "{geneve["intf"]}" ' + f'as it is a member of bridge "{geneve["is_bridge_member"]}"!')) + if not geneve['remote']: raise ConfigError('GENEVE remote must be configured') -- cgit v1.2.3 From 9979a6ef4f45ffc6f3686e6c63e50818ea32b610 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:17:47 +0200 Subject: geneve: T2241: fix falling out of bridge when changing settings Previously, the interface was always deleted and recreated, which removed it from the bridge. - always re-add the interface back to any bridge it is part of in case it is deleted and recreated --- src/conf_mode/interfaces-geneve.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index ab1bd7d7b..11d6cc2f1 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -164,6 +164,10 @@ def apply(geneve): if not geneve['disable']: g.set_admin_state('up') + # re-add ourselves to any bridge we might have fallen out of + if geneve['is_bridge_member']: + g.add_to_bridge(geneve['is_bridge_member']) + return None -- cgit v1.2.3 From 65d4d4c669650ccf727541306330a43da7dceb6f Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:19:29 +0200 Subject: geneve: T2241: cleanup verify section - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-geneve.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 11d6cc2f1..e4109a221 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -98,9 +98,9 @@ def get_config(): def verify(geneve): if geneve['deleted']: if geneve['is_bridge_member']: - interface = geneve['intf'] - bridge = geneve['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + raise ConfigError(( + f'Cannot delete interface "{geneve["intf"]}" as it is a ' + f'member of bridge "{geneve["is_bridge_member"]}"!')) return None -- cgit v1.2.3 From f25f65e38ecde122409534bdc648b768e89b8c8f Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:44:12 +0200 Subject: l2tpv3: T2241: add check for bridge membership --- src/conf_mode/interfaces-l2tpv3.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 33cf62f70..1f249c5d0 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -24,7 +24,7 @@ from vyos.config import Config from vyos.ifconfig import L2TPv3If, Interface from vyos import ConfigError from vyos.util import call -from vyos.validate import is_bridge_member, is_addr_assigned +from vyos.validate import is_member, is_addr_assigned default_config_data = { 'address': [], @@ -66,12 +66,13 @@ def get_config(): l2tpv3['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if interface is member of a bridge + l2tpv3['is_bridge_member'] = is_member(conf, l2tpv3['intf'], 'bridge') + # Check if interface has been removed if not conf.exists('interfaces l2tpv3 ' + l2tpv3['intf']): l2tpv3['deleted'] = True interface = l2tpv3['intf'] - # check if interface is member if a bridge - l2tpv3['is_bridge_member'] = is_bridge_member(conf, interface) # to delete the l2tpv3 interface we need the current tunnel_id and session_id if conf.exists_effective(f'interfaces l2tpv3 {interface} tunnel-id'): -- cgit v1.2.3 From c63ee72e797d29a756c413174ada98d301631bac Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:45:33 +0200 Subject: l2tpv3: T2241: make address and bridge membership mutually exclusive Bridge members should not have any addresses assigned. --- src/conf_mode/interfaces-l2tpv3.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 1f249c5d0..90a7ecdc8 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -119,7 +119,8 @@ def get_config(): l2tpv3['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') # Remove the default link-local address if set. - if not conf.exists('ipv6 address no-default-link-local'): + if not ( conf.exists('ipv6 address no-default-link-local') or + l2tpv3['is_bridge_member'] ): # add the link-local by default to make IPv6 work l2tpv3['ipv6_eui64_prefix'].append('fe80::/64') @@ -194,6 +195,14 @@ def verify(l2tpv3): if not l2tpv3['peer_session_id']: raise ConfigError(f'Must configure the l2tpv3 peer-session-id for {interface}') + if ( l2tpv3['is_bridge_member'] + and ( l2tpv3['address'] + or l2tpv3['ipv6_eui64_prefix'] + or l2tpv3['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to interface "{l2tpv3["intf"]}" ' + f'as it is a member of bridge "{l2tpv3["is_bridge_member"]}"!')) + return None -- cgit v1.2.3 From ea6fd38d526f95a4e8524dfb3fc3ccc4460276e0 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:49:57 +0200 Subject: l2tpv3: T2241: fix falling out of bridge when changing settings Previously, the interface was always deleted and recreated, which removed it from the bridge. - always re-add the interface back to any bridge it is part of in case it is deleted and recreated --- src/conf_mode/interfaces-l2tpv3.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 90a7ecdc8..fe5de3431 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -264,6 +264,10 @@ def apply(l2tpv3): if not l2tpv3['disable']: l.set_admin_state('up') + # re-add ourselves to any bridge we might have fallen out of + if l2tpv3['is_bridge_member']: + l.add_to_bridge(l2tpv3['is_bridge_member']) + return None if __name__ == '__main__': -- cgit v1.2.3 From 141d45d39d83fbd1eab025bcd00dabcadd6f4e61 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 18:53:27 +0200 Subject: l2tpv3: T2241: cleanup verify section - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-l2tpv3.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index fe5de3431..26bb537e5 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -168,9 +168,9 @@ def verify(l2tpv3): if l2tpv3['deleted']: if l2tpv3['is_bridge_member']: - interface = l2tpv3['intf'] - bridge = l2tpv3['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + raise ConfigError(( + f'Interface "{l2tpv3["intf"]}" cannot be deleted as it is a ' + f'member of bridge "{l2tpv3["is_bridge_member"]}"!')) return None -- cgit v1.2.3 From f11ce49a670507f814183e27c4f3e50e29e54e6e Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:41:42 +0200 Subject: pseudo-ethernet: T2241: make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-pseudo-ethernet.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index f9523ca8b..9f01cf0c1 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -122,9 +122,15 @@ def verify(peth): if not peth['source_interface'] in interfaces(): raise ConfigError('Pseudo-ethernet source-interface does not exist') - vrf_name = peth['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + if peth['vrf']: + if peth['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{peth["vrf"]}" does not exist') + + if peth['is_bridge_member']: + raise ConfigError(( + f'Interface "{peth["intf"]}" cannot be member of VRF ' + f'"{peth["vrf"]}" and bridge {peth["is_bridge_member"]} ' + f'at the same time!')) # use common function to verify VLAN configuration verify_vlan_config(peth) -- cgit v1.2.3 From 50ca89433c2a29367a36f5f049821a9bedb39d24 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:44:17 +0200 Subject: pseudo-ethernet: T2241: make address and bridge membership mutually exclusive Bridge members should not have any addresses assigned. --- src/conf_mode/interfaces-pseudo-ethernet.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 9f01cf0c1..e06f14a95 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -122,6 +122,14 @@ def verify(peth): if not peth['source_interface'] in interfaces(): raise ConfigError('Pseudo-ethernet source-interface does not exist') + if ( peth['is_bridge_member'] + and ( peth['address'] + or peth['ipv6_eui64_prefix'] + or peth['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to interface "{peth["intf"]}" ' + f'as it is a member of bridge "{peth["is_bridge_member"]}"!')) + if peth['vrf']: if peth['vrf'] not in interfaces(): raise ConfigError(f'VRF "{peth["vrf"]}" does not exist') -- cgit v1.2.3 From f45c165ca53d4c9e8017d36940de768d7419e097 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:45:11 +0200 Subject: pseudo-ethernet: T2241: fix falling out of bridge when changing settings Previously, the interface was always deleted and recreated, which removed it from the bridge. - add checks to make VRF and bridge membership mutually exclusive - always re-add the interface back to any bridge it is part of --- src/conf_mode/interfaces-pseudo-ethernet.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index e06f14a95..bd0942fd8 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -213,8 +213,10 @@ def apply(peth): # IPv6 Duplicate Address Detection (DAD) tries p.set_ipv6_dad_messages(peth['ipv6_dup_addr_detect']) - # assign/remove VRF - p.set_vrf(peth['vrf']) + # assign/remove VRF (ONLY when not a member of a bridge, + # otherwise 'nomaster' removes it from it) + if not peth['is_bridge_member']: + p.set_vrf(peth['vrf']) # Delete old IPv6 EUI64 addresses before changing MAC for addr in peth['ipv6_eui64_prefix_remove']: @@ -245,6 +247,10 @@ def apply(peth): for addr in peth['address']: p.add_addr(addr) + # re-add ourselves to any bridge we might have fallen out of + if peth['is_bridge_member']: + p.add_to_bridge(peth['is_bridge_member']) + # remove no longer required service VLAN interfaces (vif-s) for vif_s in peth['vif_s_remove']: p.del_vlan(vif_s) -- cgit v1.2.3 From ee58d8ef363d989ab7c63441b84489a6c45cfc31 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:48:48 +0200 Subject: pseudo-ethernet: T2241: cleanup verify section - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-pseudo-ethernet.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index bd0942fd8..1d04d6dbe 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -110,17 +110,19 @@ def get_config(): def verify(peth): if peth['deleted']: if peth['is_bridge_member']: - interface = peth['intf'] - bridge = peth['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + raise ConfigError(( + f'Cannot delete interface "{peth["intf"]}" as it is a ' + f'member of bridge "{peth["is_bridge_member"]}"!')) return None if not peth['source_interface']: - raise ConfigError('source-interface must be set for virtual ethernet {}'.format(peth['intf'])) + raise ConfigError(( + f'Link device must be set for pseudo-ethernet "{peth["intf"]}"')) if not peth['source_interface'] in interfaces(): - raise ConfigError('Pseudo-ethernet source-interface does not exist') + raise ConfigError(( + f'Pseudo-ethernet "{peth["intf"]}" link device does not exist') if ( peth['is_bridge_member'] and ( peth['address'] -- cgit v1.2.3 From f5ebaaa3ea3091f3228c509991c06300269883ef Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:50:21 +0200 Subject: tunnel: T2241: make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-tunnel.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 1916d2de2..363daaf4f 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -525,10 +525,15 @@ def verify(conf): print(f'Should not use IPv6 addresses on tunnel {iftype} {ifname}') # vrf check - - vrf = options['vrf'] - if vrf and vrf not in options['interfaces']: - raise ConfigError(f'VRF "{vrf}" does not exist') + if options['vrf']: + if options['vrf'] not in options['interfaces']: + raise ConfigError(f'VRF "{options["vrf"]}" does not exist') + + if options['bridge']: + raise ConfigError(( + f'Interface "{options["ifname"]}" cannot be member of VRF ' + f'"{options["vrf"]}" and bridge {options["bridge"]} ' + f'at the same time!')) # source-interface check -- cgit v1.2.3 From c6fba800f088ca389bd73386793b5444ef185c22 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:51:09 +0200 Subject: tunnel: T2241: make address and bridge membership mutually exclusive Bridge members should not have any addresses assigned. --- src/conf_mode/interfaces-tunnel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 363daaf4f..2ef1017c9 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -535,6 +535,14 @@ def verify(conf): f'"{options["vrf"]}" and bridge {options["bridge"]} ' f'at the same time!')) + # bridge and address check + if ( options['bridge'] + and ( options['addresses-add'] + or options['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to interface "{options["name"]}" ' + f'as it is a member of bridge "{options["bridge"]}"!')) + # source-interface check if tun_dev and tun_dev not in options['interfaces']: -- cgit v1.2.3 From 6866efd1081c39b50a473b58aea61188f4eb6d6e Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:51:56 +0200 Subject: tunnel: T2241: fix falling out of bridge when changing settings Previously, set_vrf was always called, which uses the same master and nomaster commands as bridge, so it removed the interface from the bridge. - add checks to make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-tunnel.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 2ef1017c9..57bc3b8e0 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -633,12 +633,17 @@ def apply(conf): # set other interface properties for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast', - 'vrf', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'): + 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'): if not options[option]: # should never happen but better safe continue tunnel.set_interface(option, options[option]) + # assign/remove VRF (ONLY when not a member of a bridge, + # otherwise 'nomaster' removes it from it) + if not options['bridge']: + tunnel.set_vrf(options['vrf']) + # Configure interface address(es) for addr in options['addresses-del']: tunnel.del_addr(addr) -- cgit v1.2.3 From dd50da5f22d18745adafb482ae19ac2455f86cba Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:53:41 +0200 Subject: tunnel: T2241: cleanup verify section - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-tunnel.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 57bc3b8e0..f4cd53981 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -436,11 +436,14 @@ def verify(conf): if changes['section'] == 'delete': if ifname in options['nhrp']: - raise ConfigError(f'Can not delete interface tunnel {iftype} {ifname}, it is used by nhrp') + raise ConfigError(( + f'Cannot delete interface tunnel {iftype} {ifname}, ' + 'it is used by NHRP')) - bridge = options['bridge'] - if bridge: - raise ConfigError(f'Interface "{ifname}" can not be deleted as it belongs to bridge "{bridge}"!') + if options['bridge']: + raise ConfigError(( + f'Cannot delete interface "{options["ifname"]}" as it is a ' + f'member of bridge "{options["bridge"]}"!')) # done, bail out early return None -- cgit v1.2.3 From 1a170e9172eac154322c49d8c196e09f5804bc4a Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:54:53 +0200 Subject: vxlan: T2241: make address and bridge membership mutually exclusive Bridge members should not have any addresses assigned. --- src/conf_mode/interfaces-vxlan.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 334e418ab..5bf1f2fb6 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -122,7 +122,8 @@ def get_config(): vxlan['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') # Remove the default link-local address if set. - if not conf.exists('ipv6 address no-default-link-local'): + if not ( conf.exists('ipv6 address no-default-link-local') + or vxlan['is_bridge_member'] ): # add the link-local by default to make IPv6 work vxlan['ipv6_eui64_prefix'].append('fe80::/64') @@ -194,6 +195,14 @@ def verify(vxlan): raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \ 'MTU is to small ({})'.format(underlay_mtu)) + if ( vxlan['is_bridge_member'] + and ( vxlan['address'] + or vxlan['ipv6_eui64_prefix'] + or vxlan['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to interface "{vxlan["intf"]}" ' + f'as it is a member of bridge "{vxlan["is_bridge_member"]}"!')) + return None -- cgit v1.2.3 From 3a3b3c3ffaea0a6e77f6dbf5ee6a3de121b5b9a9 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:55:48 +0200 Subject: vxlan: T2241: fix falling out of bridge when changing settings Previously, the interface was always deleted and recreated, which removed it from the bridge. - always re-add the interface back to any bridge it is part of --- src/conf_mode/interfaces-vxlan.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 5bf1f2fb6..8abd2b46e 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -274,6 +274,10 @@ def apply(vxlan): if not vxlan['disable']: v.set_admin_state('up') + # re-add ourselves to any bridge we might have fallen out of + if vxlan['is_bridge_member']: + v.add_to_bridge(vxlan['is_bridge_member']) + return None -- cgit v1.2.3 From 08bb34e392989ccefa4706ac017449148e8f61fe Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:56:47 +0200 Subject: vxlan: T2241: cleanup verify section - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-vxlan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 8abd2b46e..fabfaa9df 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -165,9 +165,9 @@ def get_config(): def verify(vxlan): if vxlan['deleted']: if vxlan['is_bridge_member']: - interface = vxlan['intf'] - bridge = vxlan['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + raise ConfigError(( + f'Cannot delete interface "{vxlan["intf"]}" as it is a ' + f'member of bridge "{vxlan["is_bridge_member"]}"!') return None -- cgit v1.2.3 From 4efdaa11523d4fed36f2ea73bd1aed4c4186faa7 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:58:30 +0200 Subject: wireguard: T2241: make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-wireguard.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 98d5fcb27..3fb527401 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -38,8 +38,8 @@ default_config_data = { 'listen_port': '', 'deleted': False, 'disable': False, - 'is_bridge_member': False, 'fwmark': 0, + 'is_bridge_member': False, 'mtu': 1420, 'peer': [], 'peer_remove': [], # stores public keys of peers to remove @@ -200,9 +200,15 @@ def verify(wg): return None - vrf_name = wg['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + if wg['vrf']: + if wg['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{wg["vrf"]}" does not exist') + + if wg['is_bridge_member']: + raise ConfigError(( + f'Interface "{wg["intf"]}" cannot be member of VRF ' + f'"{wg["vrf"]}" and bridge {wg["is_bridge_member"]} ' + f'at the same time!')) if not os.path.exists(wg['pk']): raise ConfigError('No keys found, generate them by executing:\n' \ -- cgit v1.2.3 From 56caf9cc1e980f5b2e03b930a5411a94190fbba7 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:59:17 +0200 Subject: wireguard: T2241: make address and bridge membership mutually exclusive Bridge members should not have any addresses assigned. --- src/conf_mode/interfaces-wireguard.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 3fb527401..3b18f8a15 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -200,6 +200,11 @@ def verify(wg): return None + if wg['is_bridge_member'] and wg['address']: + raise ConfigError(( + f'Cannot assign address to interface "{wg["intf"]}" ' + f'as it is a member of bridge "{wg["is_bridge_member"]}"!')) + if wg['vrf']: if wg['vrf'] not in interfaces(): raise ConfigError(f'VRF "{wg["vrf"]}" does not exist') -- cgit v1.2.3 From 5b628a395fca837f9e0b3fde180c11ce0eac16a5 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 19:59:53 +0200 Subject: wireguard: T2241: fix falling out of bridge when changing settings Previously, set_vrf was always called, which uses the same master and nomaster commands as bridge, so it removed the interface from the bridge. - add checks to make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-wireguard.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 3b18f8a15..2065278fd 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -264,8 +264,10 @@ def apply(wg): # update interface description used e.g. within SNMP w.set_alias(wg['description']) - # assign/remove VRF - w.set_vrf(wg['vrf']) + # assign/remove VRF (ONLY when not a member of a bridge, + # otherwise 'nomaster' removes it from it) + if not wg['is_bridge_member']: + w.set_vrf(wg['vrf']) # remove peers for pub_key in wg['peer_remove']: -- cgit v1.2.3 From 8bdbf5fa8bf4a0134d373412bfca0a562c615a5d Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 20:00:48 +0200 Subject: wireguard: T2241: cleanup verify section - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-wireguard.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 2065278fd..820b0a724 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -190,13 +190,11 @@ def get_config(): def verify(wg): - interface = wg['intf'] - if wg['deleted']: if wg['is_bridge_member']: - interface = wg['intf'] - bridge = wg['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + raise ConfigError(( + f'Cannot delete interface "{wg["intf"]}" as it is a member ' + f'of bridge "{wg["is_bridge_member"]}"!')) return None @@ -220,25 +218,24 @@ def verify(wg): '"run generate wireguard [keypair|named-keypairs]"') if not wg['address']: - raise ConfigError(f'IP address required for interface "{interface}"!') + raise ConfigError(f'IP address required for interface "{wg["intf"]}"!') if not wg['peer']: - raise ConfigError(f'Peer required for interface "{interface}"!') + raise ConfigError(f'Peer required for interface "{wg["intf"]}"!') # run checks on individual configured WireGuard peer for peer in wg['peer']: - peer_name = peer['name'] if not peer['allowed-ips']: - raise ConfigError(f'Peer allowed-ips required for peer "{peer_name}"!') + raise ConfigError(f'Peer allowed-ips required for peer "{peer["name"]}"!') if not peer['pubkey']: - raise ConfigError(f'Peer public-key required for peer "{peer_name}"!') + raise ConfigError(f'Peer public-key required for peer "{peer["name"]}"!') if peer['address'] and not peer['port']: - raise ConfigError(f'Peer "{peer_name}" port must be defined if address is defined!') + raise ConfigError(f'Peer "{peer["name"]}" port must be defined if address is defined!') if not peer['address'] and peer['port']: - raise ConfigError(f'Peer "{peer_name}" address must be defined if port is defined!') + raise ConfigError(f'Peer "{peer["name"]}" address must be defined if port is defined!') def apply(wg): -- cgit v1.2.3 From 366a53072f40f9ffbf05800787dc7faef5e489e2 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 20:01:54 +0200 Subject: wireless: T2241: make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-wireless.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 04125ff31..319685fbd 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -599,9 +599,15 @@ def verify(wifi): if not radius['key']: raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server'])) - vrf_name = wifi['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + if wifi['vrf']: + if wifi['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{wifi["vrf"]}" does not exist') + + if wifi['is_bridge_member']: + raise ConfigError(( + f'Interface "{wifi["intf"]}" cannot be member of VRF ' + f'"{wifi["vrf"]}" and bridge {wifi["is_bridge_member"]} ' + f'at the same time!')) # use common function to verify VLAN configuration verify_vlan_config(wifi) -- cgit v1.2.3 From 91762a9c10d3cc047c5a0938fe9827688fcdd5c0 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 20:02:34 +0200 Subject: wireless: T2241: make address and bridge membership mutually exclusive Bridge members should not have any addresses assigned. --- src/conf_mode/interfaces-wireless.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 319685fbd..99f5323d6 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -379,8 +379,8 @@ def get_config(): eff_addr = conf.return_effective_values('ipv6 address eui64') wifi['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, wifi['ipv6_eui64_prefix']) - # Remove the default link-local address if set. - if conf.exists('ipv6 address no-default-link-local'): + # Remove the default link-local address if set or if member of a bridge + if conf.exists('ipv6 address no-default-link-local') or wifi['is_bridge_member']: wifi['ipv6_eui64_prefix_remove'].append('fe80::/64') else: # add the link-local by default to make IPv6 work @@ -599,6 +599,14 @@ def verify(wifi): if not radius['key']: raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server'])) + if ( wifi['is_bridge_member'] + and ( wifi['address'] + or wifi['ipv6_eui64_prefix'] + or wifi['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to interface "{wifi["intf"]}" ' + f'as it is a member of bridge "{wifi["is_bridge_member"]}"!')) + if wifi['vrf']: if wifi['vrf'] not in interfaces(): raise ConfigError(f'VRF "{wifi["vrf"]}" does not exist') -- cgit v1.2.3 From 0e369db49f7b51c175a458dbc32215fbed4ad5a0 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 20:03:02 +0200 Subject: wireless: T2241: fix falling out of bridge when changing settings Previously, set_vrf was always called, which uses the same master and nomaster commands as bridge, so it removed the interface from the bridge. - add checks to make VRF and bridge membership mutually exclusive --- src/conf_mode/interfaces-wireless.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 99f5323d6..962238273 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -706,8 +706,10 @@ def apply(wifi): # Finally create the new interface w = WiFiIf(interface, **conf) - # assign/remove VRF - w.set_vrf(wifi['vrf']) + # assign/remove VRF (ONLY when not a member of a bridge, + # otherwise 'nomaster' removes it from it) + if not wifi['is_bridge_member']: + w.set_vrf(wifi['vrf']) # update interface description used e.g. within SNMP w.set_alias(wifi['description']) -- cgit v1.2.3 From ab478417b027ca650563552c11af97742930a451 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 20:04:28 +0200 Subject: wireless: T2241: cleanup verify section - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-wireless.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 962238273..4d61dc303 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -552,9 +552,9 @@ def get_config(): def verify(wifi): if wifi['deleted']: if wifi['is_bridge_member']: - interface = wifi['intf'] - bridge = wifi['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + raise ConfigError(( + f'Cannot delete interface "{wifi["intf"]}" as it is a ' + f'member of bridge "{wifi["is_bridge_member"]}"!')) return None -- cgit v1.2.3 From 2cb8062719280b9695b3e50c6cfa7c6997309bbc Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 20:05:17 +0200 Subject: wirelessmodem: T2241: make VRF and bond/bridge membership mutually exclusive --- src/conf_mode/interfaces-wirelessmodem.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 03832f345..2e25c33a9 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -134,9 +134,15 @@ def verify(wwan): if not os.path.exists(f"/dev/{wwan['device']}"): raise ConfigError(f"Device {wwan['device']} does not exist") - vrf_name = wwan['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF {vrf_name} does not exist') + if wwan['vrf']: + if wwan['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{wwan["vrf"]}" does not exist') + + if wwan['is_bridge_member']: + raise ConfigError(( + f'Interface "{wwan["intf"]}" cannot be member of VRF ' + f'"{wwan["vrf"]}" and bridge {wwan["is_bridge_member"]} ' + f'at the same time!')) return None -- cgit v1.2.3 From 9282b705afa350c49bfef4bdd46bf96c19aef5a0 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 20:06:42 +0200 Subject: wirelessmodem: T2241: make address and bridge membership mutually exclusive Bridge members should not have any addresses assigned. --- src/conf_mode/interfaces-wirelessmodem.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 2e25c33a9..313d19470 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -134,6 +134,11 @@ def verify(wwan): if not os.path.exists(f"/dev/{wwan['device']}"): raise ConfigError(f"Device {wwan['device']} does not exist") + if wwan['is_bridge_member'] and wwan['address']: + raise ConfigError(( + f'Cannot assign address to interface "{wwan["intf"]}" ' + f'as it is a member of bridge "{wwan["is_bridge_member"]}"!')) + if wwan['vrf']: if wwan['vrf'] not in interfaces(): raise ConfigError(f'VRF "{wwan["vrf"]}" does not exist') -- cgit v1.2.3 From bca5864a1b1182b9c2b2949cddfb715f51535ffb Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 20:07:25 +0200 Subject: wirelessmodem: T2241: fix falling out of bridge when changing settings Previously, the interface was always deleted and recreated, which removed it from the bridge. - always re-add the interface back to any bridge it is part of --- src/conf_mode/interfaces-wirelessmodem.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 313d19470..cf2945593 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -21,6 +21,7 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config +from vyos.ifconfig import BridgeIf, Section from vyos.template import render from vyos.util import chown, chmod_755, cmd, call from vyos.validate import is_member @@ -205,6 +206,12 @@ def apply(wwan): # make logfile owned by root / vyattacfg chown(wwan['logfile'], 'root', 'vyattacfg') + # re-add ourselves to any bridge we might have fallen out of + # FIXME: wwan isn't under vyos.ifconfig so we can't call + # Interfaces.add_to_bridge() so STP settings won't get applied + if wwan['is_bridge_member'] in Section.interfaces('bridge'): + BridgeIf(wwan['is_bridge_member'], create=False).add_port(wwan['intf']) + return None if __name__ == '__main__': -- cgit v1.2.3 From 6c8c129c78d084dd3e3b4dfeb7e7aa1b386370b8 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 20:08:06 +0200 Subject: wirelessmodem: T2241: cleanup verify section - make error output more user friendly - replace .format with f-strings - split into lines less than ~80 characters long --- src/conf_mode/interfaces-wirelessmodem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index cf2945593..975e21d9f 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -121,9 +121,9 @@ def get_config(): def verify(wwan): if wwan['deleted']: if wwan['is_bridge_member']: - interface = wwan['intf'] - bridge = wwan['is_bridge_member'] - raise ConfigError(f'Interface "{interface}" can not be deleted as it belongs to bridge "{bridge}"!') + raise ConfigError(( + f'Cannot delete interface "{wwan["intf"]}" as it is a ' + f'member of bridge "{wwan["is_bridge_member"]}"!')) return None -- cgit v1.2.3 From b38326721ce6448a865100a7da200a291388c029 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sat, 2 May 2020 15:11:48 +0200 Subject: bonding: T2367: use simple 'ip addr flush' to flush member addresses We've already verified that all member interfaces don't have any addresses configured, so it should be safe to simply call 'ip addr flush' on them to flush the remaining addresses (e.g. IPv6 link-local) --- src/conf_mode/interfaces-bonding.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index a8900ac76..82b7c0f2a 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -20,7 +20,7 @@ from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BondIf, Section +from vyos.ifconfig import BondIf from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.config import Config @@ -381,12 +381,9 @@ def apply(bond): # Add (enslave) interfaces to bond for intf in bond['member']: - # flushes only children of Interfaces class (e.g. vlan are not) - if intf in Section.interfaces(): - klass = Section.klass(intf, vlan=False) - klass(intf, create=False).flush_addrs() - # flushes also vlan interfaces - call(f'ip addr flush dev "{intf}"') + # if we've come here we already verified the interface doesn't + # have addresses configured so just flush any remaining ones + cmd(f'ip addr flush dev "{intf}"') b.add_port(intf) # As the bond interface is always disabled first when changing -- cgit v1.2.3 From b16dc5044eb9735c51ea211ea00fa35297d921f3 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sat, 2 May 2020 15:54:42 +0200 Subject: bridge: T2367: use simple 'ip addr flush' to flush member addresses We've already verified that all member interfaces don't have any addresses configured, so it should be safe to simply call 'ip addr flush' on them to flush the remaining addresses (e.g. IPv6 link-local) --- src/conf_mode/interfaces-bridge.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index fe3675190..c43fae78b 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -348,12 +348,8 @@ def apply(bridge): # add interfaces to bridge for member in bridge['member']: - # flushes address of only children of Interfaces class - # (e.g. vlan are not) - if member['name'] in Section.interfaces(): - klass = Section.klass(member['name'], vlan=False) - klass(member['name'], create=False).flush_addrs() - # flushes all interfaces + # if we've come here we already verified the interface doesn't + # have addresses configured so just flush any remaining ones cmd(f'ip addr flush dev "{member["name"]}"') br.add_port(member['name']) -- cgit v1.2.3 From 5fdd21714536a69348adbfdb97370c3201b401be Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 14:50:59 +0200 Subject: config: T2427: always return copies of lists Since lists in python are assigned by reference, taking the return value from these functions and modifying it will modify all other return values of functions that called the function before and did not explicitly copy it. To be safe, always make a copy of lists before returning them. --- python/vyos/config.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/python/vyos/config.py b/python/vyos/config.py index 75055a603..0bc6be12a 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -155,7 +155,7 @@ class Config(object): ``exists("system name-server"`` without ``set_level``. Args: - path (str): relative config path + path (str|list): relative config path """ # Make sure there's always a space between default path (level) # and path supplied as method argument @@ -166,7 +166,7 @@ class Config(object): else: self._level = [] elif isinstance(path, list): - self._level = path + self._level = path.copy() else: raise TypeError("Level path must be either a whitespace-separated string or a list") @@ -177,7 +177,7 @@ class Config(object): Returns: str: current edit level """ - return(self._level) + return(self._level.copy()) def exists(self, path): """ @@ -386,7 +386,7 @@ class Config(object): values = [] if not values: - return(default) + return(default.copy()) else: return(values) @@ -407,7 +407,7 @@ class Config(object): nodes = [] if not nodes: - return(default) + return(default.copy()) else: return(nodes) @@ -448,7 +448,6 @@ class Config(object): else: return(value) - def return_effective_values(self, path, default=[]): """ Retrieve all values of a multi-value node in a running (effective) config @@ -465,7 +464,7 @@ class Config(object): values = [] if not values: - return(default) + return(default.copy()) else: return(values) @@ -488,6 +487,6 @@ class Config(object): nodes = [] if not nodes: - return(default) + return(default.copy()) else: return(nodes) -- cgit v1.2.3 From 7a1c1fef7677e68d5a92c8538eda74a45b47a3c9 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 15:48:47 +0200 Subject: configdict: T2427: do not remove all addresses when disabling interface Commit 3fdf0093a introduced code that removed all addresses from an interface when that interface is disabled. This is wrong, as other configured services may be listening on these addresses and may fail to start if their configured address isn't present. It also caused a commit error when applying dhcp-server configuration: DHCP server configuration error! None of configured DHCP subnets does not have appropriate primary IP address on any broadcast interface. This commit reverts it to prior behavior, which was to just put the interface admin down and leave all addresses configured, other than the IPv6 'fe80::EUI-64/64' link-local, which it deletes, as the interface may not have a MAC if it's put down. --- python/vyos/configdict.py | 55 ++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 5ab831258..ceacf86fc 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -201,10 +201,6 @@ def intf_to_dict(conf, default): intf = deepcopy(default) intf['intf'] = ifname_from_config(conf) - # retrieve configured interface addresses - if conf.exists('address'): - intf['address'] = conf.return_values('address') - # retrieve interface description if conf.exists('description'): intf['description'] = conf.return_value('description') @@ -258,10 +254,6 @@ def intf_to_dict(conf, default): if conf.exists('ipv6 address autoconf'): intf['ipv6_autoconf'] = 1 - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - intf['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - # Disable IPv6 forwarding on this interface if conf.exists('ipv6 disable-forwarding'): intf['ipv6_forwarding'] = 0 @@ -304,49 +296,44 @@ def intf_to_dict(conf, default): if intf['ingress_qos'] != conf.return_effective_value('ingress-qos'): intf['ingress_qos_changed'] = True - disabled = disable_state(conf) + # Get the interface addresses + intf['address'] = conf.return_values('address') - # Get the interface IPs - eff_addr = conf.return_effective_values('address') - act_addr = conf.return_values('address') + # addresses to remove - difference between effective and working config + intf['address_remove'] = list_diff( + conf.return_effective_values('address'), + intf['address'] + ) # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - eff_eui = conf.return_effective_values('ipv6 address eui64') - act_eui = conf.return_values('ipv6 address eui64') + intf['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') + + # EUI64 to remove - difference between effective and working config + intf['ipv6_eui64_prefix_remove'] = list_diff( + conf.return_effective_values('ipv6 address eui64'), + intf['ipv6_eui64_prefix'] + ) - # Determine what should stay or be removed + # Determine if the interface should be disabled + disabled = disable_state(conf) if disabled == disable.both: # was and is still disabled intf['disable'] = True - intf['address'] = [] - intf['address_remove'] = [] - intf['ipv6_eui64_prefix'] = [] - intf['ipv6_eui64_prefix_remove'] = [] elif disabled == disable.now: # it is now disable but was not before intf['disable'] = True - intf['address'] = [] - intf['address_remove'] = eff_addr - intf['ipv6_eui64_prefix'] = [] - intf['ipv6_eui64_prefix_remove'] = eff_eui elif disabled == disable.was: # it was disable but not anymore intf['disable'] = False - intf['address'] = act_addr - intf['address_remove'] = [] - intf['ipv6_eui64_prefix'] = act_eui - intf['ipv6_eui64_prefix_remove'] = [] else: # normal change intf['disable'] = False - intf['address'] = act_addr - intf['address_remove'] = list_diff(eff_addr, act_addr) - intf['ipv6_eui64_prefix'] = act_eui - intf['ipv6_eui64_prefix_remove'] = list_diff(eff_eui, act_eui) - # Remove the default link-local address if set or if member of a bridge + # Remove the default link-local address if no-default-link-local is set, + # if member of a bridge or if disabled (it may not have a MAC if it's down) if ( conf.exists('ipv6 address no-default-link-local') - or intf.get('is_bridge_member') ): + or intf.get('is_bridge_member') + or intf['disable'] ): intf['ipv6_eui64_prefix_remove'].append('fe80::/64') else: # add the link-local by default to make IPv6 work @@ -358,7 +345,7 @@ def intf_to_dict(conf, default): if intf['mac'] and intf['mac'] != interface.get_mac(): intf['ipv6_eui64_prefix_remove'] += intf['ipv6_eui64_prefix'] except Exception: - # If the interface does not exists, it can not have changed + # If the interface does not exist, it could not have changed pass return intf, disable -- cgit v1.2.3 From 023b6b772ee6d6853e1c0e585b7f7d207e9926ce Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 15:57:31 +0200 Subject: vlan: T2427: convert vlan config variables from lists to dicts Previously all vlan configs, which are dicts, were appended to a simple list, with the distinguishing 'id' stored inside the dicts themselves. This worked, but wasn't ideal. This commit converts them to dicts, where the key is the VLAN ID and value the config dict of that VLAN. This makes it posible to access single VLANs by their ID (key) and we can for-loop and get both the ID and config with: 'for vif_id, vif in conf["vif"].items():' --- python/vyos/configdict.py | 12 ++++-------- python/vyos/ifconfig_vlan.py | 25 +++++++++++++++++-------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index ceacf86fc..666497389 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -132,7 +132,7 @@ vlan_default = { 'is_bridge_member': False, 'mac': '', 'mtu': 1500, - 'vif_c': [], + 'vif_c': {}, 'vif_c_remove': [], 'vrf': '' } @@ -400,7 +400,8 @@ def add_to_dict(conf, disabled, ifdict, section, key): for s in sections: # set config level to vif interface conf.set_level(current_level + [section, s]) - ifdict[f'{key}'].append(vlan_to_dict(conf)) + # add the vlan config as a key (vlan id) - value (config) pair + ifdict[key][s] = vlan_to_dict(conf) # re-set configuration level to leave things as found conf.set_level(current_level) @@ -411,12 +412,8 @@ def add_to_dict(conf, disabled, ifdict, section, key): def vlan_to_dict(conf, default=vlan_default): vlan, disabled = intf_to_dict(conf, default) - level = conf.get_level() - # get the '100' in 'interfaces bonding bond0 vif-s 100' - vlan['id'] = level[-1] - # if this is a not within vif-s node, we are done - if level[-2] != 'vif-s': + if conf.get_level()[-2] != 'vif-s': return vlan # ethertype is mandatory on vif-s nodes and only exists here! @@ -428,7 +425,6 @@ def vlan_to_dict(conf, default=vlan_default): # check if there is a Q-in-Q vlan customer interface # and call this function recursively - add_to_dict(conf, disable, vlan, 'vif-c', 'vif_c') return vlan diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index eb7a369ec..54736ec6f 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -104,7 +104,8 @@ def verify_vlan_config(config): implementing this function in multiple places use single source \o/ """ - for vif in config['vif']: + # config['vif'] is a dict with ids as keys and config dicts as values + for vif in config['vif'].values(): # DHCPv6 parameters-only and temporary address are mutually exclusive if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') @@ -131,14 +132,19 @@ def verify_vlan_config(config): if 'vif_s' not in config.keys(): return - for vif_s in config['vif_s']: - for vif in config['vif']: - if vif['id'] == vif_s['id']: - raise ConfigError('Can not use identical ID on vif and vif-s interface') + # config['vif_s'] is a dict with ids as keys and config dicts as values + for vif_s_id, vif_s in config['vif_s'].items(): + for vif_id, vif in config['vif'].items(): + if vif_id == vif_s_id: + raise ConfigError(( + f'Cannot use identical ID on vif "{vif["intf"]}" ' + f'and vif-s "{vif_s}"')) # DHCPv6 parameters-only and temporary address are mutually exclusive if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + raise ConfigError(( + 'DHCPv6 temporary and parameters-only options are mutually ' + 'exclusive!')) if ( vif_s['is_bridge_member'] and ( vif_s['address'] @@ -157,10 +163,13 @@ def verify_vlan_config(config): f'vif-s {vif_s["intf"]} cannot be member of VRF {vif_s["vrf"]} ' f'and bridge {vif_s["is_bridge_member"]} at the same time!')) - for vif_c in vif_s['vif_c']: + # vif_c is a dict with ids as keys and config dicts as values + for vif_c in vif_s['vif_c'].values(): # DHCPv6 parameters-only and temporary address are mutually exclusive if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + raise ConfigError(( + 'DHCPv6 temporary and parameters-only options are ' + 'mutually exclusive!')) if ( vif_c['is_bridge_member'] and ( vif_c['address'] -- cgit v1.2.3 From 312cffce31cf3a411a87559bf99f78cde5facd60 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 16:03:49 +0200 Subject: vlan: T2427: move code that applies VLANs to interface to common function --- python/vyos/ifconfig_vlan.py | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 54736ec6f..09fb8c802 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -16,6 +16,53 @@ from netifaces import interfaces from vyos import ConfigError +def apply_all_vlans(intf, intfconfig): + """ + Function applies all VLANs to the passed interface. + + intf: object of Interface class + intfconfig: dict with interface configuration + """ + # remove no longer required service VLAN interfaces (vif-s) + for vif_s in intfconfig['vif_s_remove']: + intf.del_vlan(vif_s) + + # create service VLAN interfaces (vif-s) + for vif_s_id, vif_s in intfconfig['vif_s'].items(): + s_vlan = intf.add_vlan(vif_s_id, ethertype=vif_s['ethertype']) + apply_vlan_config(s_vlan, vif_s) + + # remove no longer required client VLAN interfaces (vif-c) + # on lower service VLAN interface + for vif_c in intfconfig['vif_c_remove']: + s_vlan.del_vlan(vif_c) + + # create client VLAN interfaces (vif-c) + # on lower service VLAN interface + for vif_c_id, vif_c in vif_s['vif_c'].items(): + c_vlan = s_vlan.add_vlan(vif_c_id) + apply_vlan_config(c_vlan, vif_c) + + # remove no longer required VLAN interfaces (vif) + for vif in intfconfig['vif_remove']: + intf.del_vlan(vif) + + # create VLAN interfaces (vif) + for vif_id, vif in intfconfig['vif'].items(): + # QoS priority mapping can only be set during interface creation + # so we delete the interface first if required. + if vif['egress_qos_changed'] or vif['ingress_qos_changed']: + try: + # on system bootup the above condition is true but the interface + # does not exists, which throws an exception, but that's legal + intf.del_vlan(vif_id) + except: + pass + + vlan = intf.add_vlan(vif_id, ingress_qos=vif['ingress_qos'], egress_qos=vif['egress_qos']) + apply_vlan_config(vlan, vif) + + def apply_vlan_config(vlan, config): """ Generic function to apply a VLAN configuration from a dictionary -- cgit v1.2.3 From 0a3147631094dba44523bd439586086c1bf6a93b Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 16:04:54 +0200 Subject: configdict: T2427: clarify code comments --- python/vyos/configdict.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 666497389..5ca369f66 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -375,8 +375,7 @@ def add_to_dict(conf, disabled, ifdict, section, key): # the section to parse for vlan sections = [] - # Determine interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed from the bond + # determine which interfaces to add or remove based on disable state if disabled == disable.both: # was and is still disabled ifdict[f'{key}_remove'] = [] @@ -389,7 +388,7 @@ def add_to_dict(conf, disabled, ifdict, section, key): sections = active else: # normal change - # get vif-s interfaces (currently effective) - to determine which vif-s + # get interfaces (currently effective) - to determine which # interface is no longer present and needs to be removed ifdict[f'{key}_remove'] = list_diff(effect, active) sections = active -- cgit v1.2.3 From d637a1cd2af0bc6bb2d47b8b661da262892046a2 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 16:07:24 +0200 Subject: bonding: T2427: move VLAN adding to common function --- src/conf_mode/interfaces-bonding.py | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 82b7c0f2a..5a2ff9eef 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -21,7 +21,7 @@ from sys import exit from netifaces import interfaces from vyos.ifconfig import BondIf -from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config +from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.config import Config from vyos.util import call, cmd @@ -63,9 +63,9 @@ default_config_data = { 'shutdown_required': False, 'mtu': 1500, 'primary': '', - 'vif_s': [], + 'vif_s': {}, 'vif_s_remove': [], - 'vif': [], + 'vif': {}, 'vif_remove': [], 'vrf': '' } @@ -411,34 +411,8 @@ def apply(bond): if bond['is_bridge_member']: b.add_to_bridge(bond['is_bridge_member']) - # remove no longer required service VLAN interfaces (vif-s) - for vif_s in bond['vif_s_remove']: - b.del_vlan(vif_s) - - # create service VLAN interfaces (vif-s) - for vif_s in bond['vif_s']: - s_vlan = b.add_vlan(vif_s['id'], ethertype=vif_s['ethertype']) - apply_vlan_config(s_vlan, vif_s) - - # remove no longer required client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c in vif_s['vif_c_remove']: - s_vlan.del_vlan(vif_c) - - # create client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c in vif_s['vif_c']: - c_vlan = s_vlan.add_vlan(vif_c['id']) - apply_vlan_config(c_vlan, vif_c) - - # remove no longer required VLAN interfaces (vif) - for vif in bond['vif_remove']: - b.del_vlan(vif) - - # create VLAN interfaces (vif) - for vif in bond['vif']: - vlan = b.add_vlan(vif['id']) - apply_vlan_config(vlan, vif) + # apply all vlans to interface + apply_all_vlans(b, bond) return None -- cgit v1.2.3 From 8abba78e5571d3fbcb2c51a1165b0e879a5dbd02 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 16:08:28 +0200 Subject: ethernet: T2427: move VLAN adding to common function --- src/conf_mode/interfaces-ethernet.py | 46 ++++-------------------------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 9b9ae931c..955022042 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -21,7 +21,7 @@ from copy import deepcopy from netifaces import interfaces from vyos.ifconfig import EthernetIf -from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config +from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.validate import is_member from vyos.config import Config @@ -65,9 +65,9 @@ default_config_data = { 'offload_tso': 'off', 'offload_ufo': 'off', 'speed': 'auto', - 'vif_s': [], + 'vif_s': {}, 'vif_s_remove': [], - 'vif': [], + 'vif': {}, 'vif_remove': [], 'vrf': '' } @@ -306,44 +306,8 @@ def apply(eth): if eth['is_bridge_member']: e.add_to_bridge(eth['is_bridge_member']) - # remove no longer required service VLAN interfaces (vif-s) - for vif_s in eth['vif_s_remove']: - e.del_vlan(vif_s) - - # create service VLAN interfaces (vif-s) - for vif_s in eth['vif_s']: - s_vlan = e.add_vlan(vif_s['id'], ethertype=vif_s['ethertype']) - apply_vlan_config(s_vlan, vif_s) - - # remove no longer required client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c in vif_s['vif_c_remove']: - s_vlan.del_vlan(vif_c) - - # create client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c in vif_s['vif_c']: - c_vlan = s_vlan.add_vlan(vif_c['id']) - apply_vlan_config(c_vlan, vif_c) - - # remove no longer required VLAN interfaces (vif) - for vif in eth['vif_remove']: - e.del_vlan(vif) - - # create VLAN interfaces (vif) - for vif in eth['vif']: - # QoS priority mapping can only be set during interface creation - # so we delete the interface first if required. - if vif['egress_qos_changed'] or vif['ingress_qos_changed']: - try: - # on system bootup the above condition is true but the interface - # does not exists, which throws an exception, but that's legal - e.del_vlan(vif['id']) - except: - pass - - vlan = e.add_vlan(vif['id'], ingress_qos=vif['ingress_qos'], egress_qos=vif['egress_qos']) - apply_vlan_config(vlan, vif) + # apply all vlans to interface + apply_all_vlans(e, eth) return None -- cgit v1.2.3 From d6a93991d1a3a5e4575ccd3d586e9f8f48234544 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 16:09:42 +0200 Subject: pseudo-ethernet: T2427: move VLAN adding to common function --- src/conf_mode/interfaces-pseudo-ethernet.py | 36 ++++------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 1d04d6dbe..4063b85b0 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -23,7 +23,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.ifconfig import MACVLANIf, Section -from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config +from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config from vyos import ConfigError default_config_data = { @@ -56,9 +56,9 @@ default_config_data = { 'source_interface_changed': False, 'mac': '', 'mode': 'private', - 'vif_s': [], + 'vif_s': {}, 'vif_s_remove': [], - 'vif': [], + 'vif': {}, 'vif_remove': [], 'vrf': '' } @@ -253,34 +253,8 @@ def apply(peth): if peth['is_bridge_member']: p.add_to_bridge(peth['is_bridge_member']) - # remove no longer required service VLAN interfaces (vif-s) - for vif_s in peth['vif_s_remove']: - p.del_vlan(vif_s) - - # create service VLAN interfaces (vif-s) - for vif_s in peth['vif_s']: - s_vlan = p.add_vlan(vif_s['id'], ethertype=vif_s['ethertype']) - apply_vlan_config(s_vlan, vif_s) - - # remove no longer required client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c in vif_s['vif_c_remove']: - s_vlan.del_vlan(vif_c) - - # create client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c in vif_s['vif_c']: - c_vlan = s_vlan.add_vlan(vif_c['id']) - apply_vlan_config(c_vlan, vif_c) - - # remove no longer required VLAN interfaces (vif) - for vif in peth['vif_remove']: - p.del_vlan(vif) - - # create VLAN interfaces (vif) - for vif in peth['vif']: - vlan = p.add_vlan(vif['id']) - apply_vlan_config(vlan, vif) + # apply all vlans to interface + apply_all_vlans(b, bond) return None -- cgit v1.2.3 From faec542493eedf989f0703a1b4cedb74025bd9e2 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Wed, 6 May 2020 16:24:46 +0100 Subject: debug: T2426: remove invisible characters when printing --- python/vyos/debug.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/python/vyos/debug.py b/python/vyos/debug.py index 1a042cbb4..91431a7bb 100644 --- a/python/vyos/debug.py +++ b/python/vyos/debug.py @@ -81,10 +81,17 @@ def enabled(flag): return _fromenv(flag) or _fromfile(flag) +def _remove_invisible(string): + for char in ('\0', '\a', '\b', '\f', '\v'): + string = string.replace(char, '') + return string + + def _format(flag, message): """ format a log message """ + message = _remove_invisible(message) return f'DEBUG/{flag.upper():<7} {message}\n' -- cgit v1.2.3 From d55050dcb8806a982b0394dcde00c1814499d9f3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 6 May 2020 21:41:15 +0200 Subject: sstp: T2392: add initial IPv6 support New commands added: * set vpn sstp network-settings client-ipv6-pool prefix 2001:db8::/64 mask 112 * set vpn sstp network-settings client-ipv6-pool delegate 2001:db8:100::/48 delegation-prefix 64 --- data/templates/accel-ppp/sstp.config.tmpl | 21 +++++++++++++++ interface-definitions/vpn_sstp.xml.in | 1 + src/conf_mode/vpn_sstp.py | 43 ++++++++++++++++++++++++++++--- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/data/templates/accel-ppp/sstp.config.tmpl b/data/templates/accel-ppp/sstp.config.tmpl index c3dc83429..e0a48a44e 100644 --- a/data/templates/accel-ppp/sstp.config.tmpl +++ b/data/templates/accel-ppp/sstp.config.tmpl @@ -9,6 +9,9 @@ chap-secrets radius {% endif -%} ippool +ipv6pool +ipv6_nd +ipv6_dhcp {% for proto in auth_proto %} {{proto}} @@ -87,6 +90,9 @@ check-ip=1 {% if mtu %} mtu={{ mtu }} {% endif -%} +{% if client_ipv6_pool %} +ipv6=allow +{% endif %} {% if ppp_mppe %} mppe={{ ppp_mppe }} @@ -101,6 +107,21 @@ lcp-echo-failure={{ ppp_echo_failure }} lcp-echo-timeout={{ ppp_echo_timeout }} {% endif %} +{% if client_ipv6_pool %} +[ipv6-pool] +{% for p in client_ipv6_pool %} +{{ p.prefix }},{{ p.mask }} +{% endfor %} +{% for p in client_ipv6_delegate_prefix %} +delegate={{ p.prefix }},{{ p.mask }} +{% endfor %} +{% endif %} + +{% if client_ipv6_delegate_prefix %} +[ipv6-dhcp] +verbose=1 +{% endif %} + {% if radius_shaper_attr %} [shaper] verbose=1 diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn_sstp.xml.in index 7e4471015..4ce231e0f 100644 --- a/interface-definitions/vpn_sstp.xml.in +++ b/interface-definitions/vpn_sstp.xml.in @@ -220,6 +220,7 @@ + #include #include diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index d250cd3b0..6d9496012 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -35,7 +35,11 @@ default_config_data = { 'auth_mode' : 'local', 'auth_proto' : ['auth_mschap_v2'], 'chap_secrets_file': sstp_chap_secrets, # used in Jinja2 template + 'client_ip_pool' : [], + 'client_ipv6_pool': [], + 'client_ipv6_delegate_prefix': [], 'client_gateway': '', + 'dnsv4' : [], 'radius_server' : [], 'radius_acct_tmo' : '3', 'radius_max_try' : '3', @@ -49,8 +53,6 @@ default_config_data = { 'ssl_ca' : '', 'ssl_cert' : '', 'ssl_key' : '', - 'client_ip_pool' : [], - 'dnsv4' : [], 'mtu' : '', 'ppp_mppe' : 'prefer', 'ppp_echo_failure' : '', @@ -210,7 +212,7 @@ def get_config(): # - # read in client ip pool settings + # read in client IPv4 pool conf.set_level(base_path + ['network-settings', 'client-ip-settings']) if conf.exists(['subnet']): sstp['client_ip_pool'] = conf.return_values(['subnet']) @@ -218,6 +220,33 @@ def get_config(): if conf.exists(['gateway-address']): sstp['client_gateway'] = conf.return_value(['gateway-address']) + # + # read in client IPv6 pool + conf.set_level(base_path + ['network-settings', 'client-ipv6-pool']) + if conf.exists(['prefix']): + for prefix in conf.list_nodes(['prefix']): + tmp = { + 'prefix': prefix, + 'mask': '64' + } + + if conf.exists(['prefix', prefix, 'mask']): + tmp['mask'] = conf.return_value(['prefix', prefix, 'mask']) + + sstp['client_ipv6_pool'].append(tmp) + + if conf.exists(['delegate']): + for prefix in conf.list_nodes(['delegate']): + tmp = { + 'prefix': prefix, + 'mask': '' + } + + if conf.exists(['delegate', prefix, 'delegation-prefix']): + tmp['mask'] = conf.return_value(['delegate', prefix, 'delegation-prefix']) + + sstp['client_ipv6_delegate_prefix'].append(tmp) + # # read in network settings conf.set_level(base_path + ['network-settings']) @@ -275,6 +304,14 @@ def verify(sstp): if len(sstp['dnsv4']) > 2: raise ConfigError('Not more then two IPv4 DNS name-servers can be configured') + # check ipv6 + if sstp['client_ipv6_delegate_prefix'] and not sstp['client_ipv6_pool']: + raise ConfigError('IPv6 prefix delegation requires client-ipv6-pool prefix') + + for prefix in sstp['client_ipv6_delegate_prefix']: + if not prefix['mask']: + raise ConfigError('Delegation-prefix required for individual delegated networks') + if not sstp['ssl_ca'] or not sstp['ssl_cert'] or not sstp['ssl_key']: raise ConfigError('One or more SSL certificates missing') -- cgit v1.2.3 From ed22334321d3b6f27b5d695a4f984257b909f78b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 6 May 2020 21:44:07 +0200 Subject: sstp: T2392: add IPv6 DNS support New command added: * set vpn sstp network-settings name-server 2001:db8::1111 --- data/templates/accel-ppp/sstp.config.tmpl | 8 ++++++++ interface-definitions/vpn_sstp.xml.in | 14 +------------- src/conf_mode/vpn_sstp.py | 13 +++++++++---- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/data/templates/accel-ppp/sstp.config.tmpl b/data/templates/accel-ppp/sstp.config.tmpl index e0a48a44e..411fca489 100644 --- a/data/templates/accel-ppp/sstp.config.tmpl +++ b/data/templates/accel-ppp/sstp.config.tmpl @@ -54,6 +54,14 @@ dns{{ loop.index }}={{ dns }} {% endfor -%} {% endif %} +{% if dnsv6 %} +[ipv6-dns] +{% for dns in dnsv6 -%} +{{ dns }} +{% endfor -%} +{% endif %} + + {% if auth_mode == 'local' %} [chap-secrets] chap-secrets={{ chap_secrets_file }} diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn_sstp.xml.in index 4ce231e0f..f0c93b882 100644 --- a/interface-definitions/vpn_sstp.xml.in +++ b/interface-definitions/vpn_sstp.xml.in @@ -207,20 +207,8 @@ - - - DNS servers propagated to clients - - ipv4 - IPv4 address - - - - - - - #include + #include #include diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 6d9496012..7c3e3f515 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -22,10 +22,10 @@ from copy import deepcopy from stat import S_IRUSR, S_IWUSR, S_IRGRP from vyos.config import Config -from vyos import ConfigError -from vyos.util import call, run, get_half_cpus from vyos.template import render - +from vyos.util import call, run, get_half_cpus +from vyos.validate import is_ipv4 +from vyos import ConfigError sstp_conf = '/run/accel-pppd/sstp.conf' sstp_chap_secrets = '/run/accel-pppd/sstp.chap-secrets' @@ -40,6 +40,7 @@ default_config_data = { 'client_ipv6_delegate_prefix': [], 'client_gateway': '', 'dnsv4' : [], + 'dnsv6' : [], 'radius_server' : [], 'radius_acct_tmo' : '3', 'radius_max_try' : '3', @@ -251,7 +252,11 @@ def get_config(): # read in network settings conf.set_level(base_path + ['network-settings']) if conf.exists(['name-server']): - sstp['dnsv4'] = conf.return_values(['name-server']) + for name_server in conf.return_values(['name-server']): + if is_ipv4(name_server): + sstp['dnsv4'].append(name_server) + else: + sstp['dnsv6'].append(name_server) if conf.exists(['mtu']): sstp['mtu'] = conf.return_value(['mtu']) -- cgit v1.2.3