]> git.proxmox.com Git - mirror_ifupdown2.git/commitdiff
addons: vlan: new attribute: vlan-bridge-binding
authorJulien Fortin <julien@cumulusnetworks.com>
Thu, 16 Jul 2020 23:08:59 +0000 (01:08 +0200)
committerJulien Fortin <julien@cumulusnetworks.com>
Mon, 25 Jan 2021 20:30:40 +0000 (21:30 +0100)
From Mike Manning:
In the case of vlan filtering on bridges, the bridge may also have the
corresponding vlan devices as upper devices. Currently the link state
of vlan devices is transferred from the lower device. So this is up if
the bridge is in admin up state and there is at least one bridge port
that is up, regardless of the vlan that the port is a member of.

The link state of the vlan device may need to track only the state of
the subset of ports that are also members of the corresponding vlan,
rather than that of all ports.

Add a flag to specify a vlan bridge binding mode, by which the link
state is no longer automatically transferred from the lower device,
but is instead determined by the bridge ports that are members of the
vlan.
----

Signed-off-by: Julien Fortin <julien@cumulusnetworks.com>
ifupdown2/addons/vlan.py
ifupdown2/lib/nlcache.py
ifupdown2/nlmanager/nlpacket.py

index 5a2965efd0521c3c947e6ed1117c8d0c0260f931..86192317d3862ee4193e4991a6e7d4c30a04a7af 100644 (file)
@@ -9,13 +9,17 @@ try:
     from ifupdown2.ifupdown.iface import *
     from ifupdown2.nlmanager.nlmanager import Link
     from ifupdown2.ifupdownaddons.modulebase import moduleBase
+    from ifupdown2.ifupdown.utils import utils
     import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
+    import ifupdown2.ifupdown.policymanager as policymanager
 except (ImportError, ModuleNotFoundError):
     from lib.addon import Addon
     from ifupdown.iface import *
     from nlmanager.nlmanager import Link
     from ifupdownaddons.modulebase import moduleBase
+    from ifupdown.utils import utils
     import ifupdown.ifupdownflags as ifupdownflags
+    import ifupdown.policymanager as policymanager
 
 
 class vlan(Addon, moduleBase):
@@ -41,6 +45,16 @@ class vlan(Addon, moduleBase):
                 "validvals": ["802.1q", "802.1ad"],
                 "example": ["vlan-protocol 802.1q"]
             },
+            "vlan-bridge-binding": {
+                "help": "The link state of the vlan device may need to track only the state of the subset of ports "
+                        "that are also members of the corresponding vlan, rather than that of all ports. Add a flag to "
+                        "specify a vlan bridge binding mode, by which the link state is no longer automatically "
+                        "transferred from the lower device, but is instead determined by the bridge ports that are "
+                        "members of the vlan.",
+                "default": "off",
+                "validvals": ["on", "off"],
+                "example": ["vlan-bridge-binding on"]
+            }
         }
     }
 
@@ -123,6 +137,16 @@ class vlan(Addon, moduleBase):
         else:
             cached_vlan_ifla_info_data = self.cache.get_link_info_data(ifname)
 
+        vlan_bridge_binding = ifaceobj.get_attr_value_first("vlan-bridge-binding")
+
+        if not vlan_bridge_binding:
+            vlan_bridge_binding = policymanager.policymanager_api.get_attr_default(
+                self.__class__.__name__,
+                "vlan-bridge-binding"
+            ) or self.get_attr_default_value("vlan-bridge-binding")
+
+        bool_vlan_bridge_binding = utils.get_boolean_from_string(vlan_bridge_binding)
+
         vlan_protocol = ifaceobj.get_attr_value_first('vlan-protocol')
         cached_vlan_protocol = cached_vlan_ifla_info_data.get(Link.IFLA_VLAN_PROTOCOL)
 
@@ -154,10 +178,16 @@ class vlan(Addon, moduleBase):
                 else:
                     raise Exception('rawdevice %s not present' % vlanrawdevice)
             if vlan_exists:
+
+                # vlan-bridge-binding has changed we need to update it
+                if vlan_bridge_binding is not None and bool_vlan_bridge_binding != cached_vlan_ifla_info_data.get(Link.IFLA_VLAN_FLAGS, {}).get(Link.VLAN_FLAG_BRIDGE_BINDING, False):
+                    self.logger.info("%s: mismatch detected: resetting: vlan-bridge-binding %s" % (ifname, vlan_bridge_binding))
+                    self.netlink.link_add_vlan(vlanrawdevice, ifaceobj.name, vlanid, vlan_protocol, bool_vlan_bridge_binding)
+
                 self._bridge_vid_add_del(vlanrawdevice, vlanid)
                 return
 
-        self.netlink.link_add_vlan(vlanrawdevice, ifaceobj.name, vlanid, vlan_protocol)
+        self.netlink.link_add_vlan(vlanrawdevice, ifaceobj.name, vlanid, vlan_protocol, bool_vlan_bridge_binding if vlan_bridge_binding is not None else None)
         self._bridge_vid_add_del(vlanrawdevice, vlanid)
 
     def _down(self, ifaceobj):
@@ -227,6 +257,20 @@ class vlan(Addon, moduleBase):
                         0
                     )
 
+            #
+            # vlan-bridge-binding
+            #
+            vlan_bridge_binding = ifaceobj.get_attr_value_first("vlan-bridge-binding")
+            if vlan_bridge_binding:
+                cached_vlan_bridge_binding = cached_vlan_info_data.get(Link.IFLA_VLAN_FLAGS, {}).get(
+                    Link.VLAN_FLAG_BRIDGE_BINDING, False)
+
+                ifaceobjcurr.update_config_with_status(
+                    "vlan-bridge-binding",
+                    "on" if cached_vlan_bridge_binding else "off",
+                    cached_vlan_bridge_binding != utils.get_boolean_from_string(vlan_bridge_binding)
+                )
+
             self._bridge_vid_check(ifaceobjcurr, cached_vlan_raw_device, cached_vlan_id)
 
     def _query_running(self, ifaceobjrunning):
@@ -253,6 +297,9 @@ class vlan(Addon, moduleBase):
 
         ifaceobjrunning.update_config('vlan-raw-device', self.cache.get_lower_device_ifname(ifname))
 
+        if cached_vlan_info_data.get(Link.IFLA_VLAN_FLAGS, {}).get(Link.VLAN_FLAG_BRIDGE_BINDING, False):
+            ifaceobjrunning.update_config("vlan-bridge-binding", "on")
+
     _run_ops = {
         "pre-up": _up,
         "post-down": _down,
index 04549939ce7c4757e90c3cf2ae055865bd01c775..471699b7fb8405e735cad2fc665d89441697bdca 100644 (file)
@@ -2406,6 +2406,30 @@ class NetlinkListenerWithCache(nllistener.NetlinkManagerWithListener, BaseObject
         link.build_message(next(self.sequence), self.pid)
         return self.tx_nlpacket_get_response_with_error(link)
 
+    def vlan_set_bridge_binding(self, ifname, bridge_binding=True):
+        """
+        Set VLAN_FLAG_BRIDGE_BINDING on vlan interface
+        :param ifname: the vlan interface
+        :param bridge_binding: True to set the flag, False to unset
+        """
+        self.logger.info("%s: netlink: ip link set dev %s type vlan bridge_binding %s" % (ifname, ifname, "on" if bridge_binding else "off"))
+
+        debug = RTM_NEWLINK in self.debug
+
+        link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
+        link.flags = NLM_F_REQUEST | NLM_F_ACK
+        link.body = struct.pack('=BxxxiLL', socket.AF_UNSPEC, 0, 0, 0)
+
+        link.add_attribute(Link.IFLA_IFNAME, ifname)
+        info_data = {Link.IFLA_VLAN_FLAGS: {Link.VLAN_FLAG_BRIDGE_BINDING: bridge_binding}}
+        link.add_attribute(Link.IFLA_LINKINFO, {
+            Link.IFLA_INFO_KIND: "vlan",
+            Link.IFLA_INFO_DATA: info_data
+        })
+
+        link.build_message(next(self.sequence), self.pid)
+        return self.tx_nlpacket_get_response_with_error(link)
+
     #############################################################################################################
     # Netlink API ###############################################################################################
     #############################################################################################################
@@ -2833,7 +2857,7 @@ class NetlinkListenerWithCache(nllistener.NetlinkManagerWithListener, BaseObject
 
     ###
 
-    def link_add_vlan(self, vlan_raw_device, ifname, vlan_id, vlan_protocol=None):
+    def link_add_vlan(self, vlan_raw_device, ifname, vlan_id, vlan_protocol=None, bridge_binding=None):
         """
         ifindex is the index of the parent interface that this sub-interface
         is being added to
@@ -2845,13 +2869,18 @@ class NetlinkListenerWithCache(nllistener.NetlinkManagerWithListener, BaseObject
         Do this check here so we can provide a more intuitive error
         """
         try:
+            vlan_iproute2_cmd = ["ip link add link %s name %s type vlan id %s" % (vlan_raw_device, ifname, vlan_id)]
+            ifla_info_data = {Link.IFLA_VLAN_ID: vlan_id}
+
             if vlan_protocol:
-                self.logger.info("%s: netlink: ip link add link %s name %s type vlan id %s protocol %s"
-                                 % (ifname, vlan_raw_device, ifname, vlan_id, vlan_protocol))
+                vlan_iproute2_cmd.append("protocol %s" % vlan_protocol)
+                ifla_info_data[Link.IFLA_VLAN_PROTOCOL] = vlan_protocol
 
-            else:
-                self.logger.info("%s: netlink: ip link add link %s name %s type vlan id %s"
-                                 % (ifname, vlan_raw_device, ifname, vlan_id))
+            if bridge_binding is not None:
+                vlan_iproute2_cmd.append("bridge_binding %s" % ("on" if bridge_binding else "off"))
+                ifla_info_data[Link.IFLA_VLAN_FLAGS] = {Link.VLAN_FLAG_BRIDGE_BINDING: bridge_binding}
+
+            self.logger.info("%s: netlink: %s" % (ifname, " ".join(vlan_iproute2_cmd)))
 
             if "." in ifname:
                 ifname_vlanid = int(ifname.split(".")[-1])
@@ -2864,11 +2893,6 @@ class NetlinkListenerWithCache(nllistener.NetlinkManagerWithListener, BaseObject
 
             ifindex = self.cache.get_ifindex(vlan_raw_device)
 
-            ifla_info_data = {Link.IFLA_VLAN_ID: vlan_id}
-
-            if vlan_protocol:
-                ifla_info_data[Link.IFLA_VLAN_PROTOCOL] = vlan_protocol
-
             debug = RTM_NEWLINK in self.debug
 
             link = Link(RTM_NEWLINK, debug, use_color=self.use_color)
index 495930a175227c2c3fcadb4e7f8a522db9f41258..852522b76152e78d1f624093ec5486238fffcbe1 100644 (file)
@@ -298,6 +298,20 @@ class NetlinkPacket_IFLA_LINKINFO_Attributes:
         0x88A8:     '802.1ad'
     }
 
+    VLAN_FLAG_REORDER_HDR    = 0x1
+    VLAN_FLAG_GVRP           = 0x2
+    VLAN_FLAG_LOOSE_BINDING  = 0x4
+    VLAN_FLAG_MVRP           = 0x8
+    VLAN_FLAG_BRIDGE_BINDING = 0x10
+
+    vlan_flags_to_string = {
+        VLAN_FLAG_REORDER_HDR    : "REORDER_HDR",
+        VLAN_FLAG_GVRP           : "GVRP",
+        VLAN_FLAG_LOOSE_BINDING  : "LOOSE_BINDING",
+        VLAN_FLAG_MVRP           : "MVRP",
+        VLAN_FLAG_BRIDGE_BINDING : "BRIDGE_BINDING",
+    }
+
     # =========================================
     # IFLA_INFO_DATA attributes for macvlan
     # =========================================
@@ -1081,6 +1095,26 @@ class Attribute(object):
     def decode_vlan_protocol_attribute(data, _=None):
         return Link.ifla_vlan_protocol_dict.get(unpack(">H", data[4:6])[0])
 
+    @staticmethod
+    def decode_vlan_flags_attribute(data, _=None):
+        vlan_flags = unpack('=I', data[4:8])[0]
+        vlan_flags_dict = {}
+
+        # iterate over bits set to 1
+        def bits(n):
+            while n:
+                b = n & (~n + 1)
+                yield b
+                n ^= b
+
+        for vlan_flag in bits(vlan_flags):
+            if vlan_flag in Link.vlan_flags_to_string:
+                vlan_flags_dict[vlan_flag] = True
+            #else:
+            #    self.log.warning('Unknown vlan flag %d in IFLA_VLAN_FLAGS' % vlan_flag)
+
+        return vlan_flags_dict
+
     ############################################################################
     # encode methods
     ############################################################################
@@ -1216,6 +1250,25 @@ class Attribute(object):
         for mbyte in info_data_value.replace(".", " ").replace(":", " ").split():
             sub_attr_payload.append(int(mbyte, 16))
 
+    @staticmethod
+    def encode_vlan_flags_attribute(sub_attr_pack_layout, sub_attr_payload, info_data_type, info_data_value):
+        sub_attr_pack_layout.append('HH')
+        sub_attr_payload.append(12)
+        sub_attr_payload.append(info_data_type)
+
+        # vlan flags and mask
+        sub_attr_pack_layout.append('II')
+        vlan_flags = 0
+        vlan_flags_mask = 0
+
+        for (vlan_flag, flag_set) in info_data_value.items():
+            vlan_flags_mask |= vlan_flag
+            if flag_set:
+                vlan_flags |= vlan_flag
+
+        sub_attr_payload.append(vlan_flags)
+        sub_attr_payload.append(vlan_flags_mask)
+
 
 class AttributeCACHEINFO(Attribute):
     """
@@ -2128,7 +2181,9 @@ class AttributeIFLA_LINKINFO(Attribute):
                 NetlinkPacket_IFLA_LINKINFO_Attributes.IFLA_VLAN_ID: Attribute.decode_two_bytes_attribute,
 
                 # vlan-protocol attribute ######################################
-                NetlinkPacket_IFLA_LINKINFO_Attributes.IFLA_VLAN_PROTOCOL: Attribute.decode_vlan_protocol_attribute
+                NetlinkPacket_IFLA_LINKINFO_Attributes.IFLA_VLAN_PROTOCOL: Attribute.decode_vlan_protocol_attribute,
+
+                NetlinkPacket_IFLA_LINKINFO_Attributes.IFLA_VLAN_FLAGS: Attribute.decode_vlan_flags_attribute
             },
             "macvlan": {
                 # 4 bytes attributes ###########################################
@@ -2468,6 +2523,8 @@ class AttributeIFLA_LINKINFO(Attribute):
 
                 # vlan-protocol attribute ######################################
                 NetlinkPacket_IFLA_LINKINFO_Attributes.IFLA_VLAN_PROTOCOL: Attribute.encode_vlan_protocol_attribute,
+
+                NetlinkPacket_IFLA_LINKINFO_Attributes.IFLA_VLAN_FLAGS: Attribute.encode_vlan_flags_attribute,
             },
             "macvlan": {
                 # 4 bytes attributes ###########################################