net: bridge: mcast: add support for raw L2 multicast groups
authorNikolay Aleksandrov <nikolay@nvidia.com>
Wed, 28 Oct 2020 23:38:31 +0000 (01:38 +0200)
committerJakub Kicinski <kuba@kernel.org>
Sat, 31 Oct 2020 00:49:19 +0000 (17:49 -0700)
Extend the bridge multicast control and data path to configure routes
for L2 (non-IP) multicast groups.

The uapi struct br_mdb_entry union u is extended with another variant,
mac_addr, which does not change the structure size, and which is valid
when the proto field is zero.

To be compatible with the forwarding code that is already in place,
which acts as an IGMP/MLD snooping bridge with querier capabilities, we
need to declare that for L2 MDB entries (for which there exists no such
thing as IGMP/MLD snooping/querying), that there is always a querier.
Otherwise, these entries would be flooded to all bridge ports and not
just to those that are members of the L2 multicast group.

Needless to say, only permanent L2 multicast groups can be installed on
a bridge port.

Signed-off-by: Nikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Link: https://lore.kernel.org/r/20201028233831.610076-1-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
include/linux/if_bridge.h
include/uapi/linux/if_bridge.h
net/bridge/br_device.c
net/bridge/br_input.c
net/bridge/br_mdb.c
net/bridge/br_multicast.c
net/bridge/br_private.h

index 556caed0025849a9cd4b30825d410029c344e41e..b979005ea39c8a43b2d9feb793956cd46bb5948e 100644 (file)
@@ -25,6 +25,7 @@ struct br_ip {
 #if IS_ENABLED(CONFIG_IPV6)
                struct in6_addr ip6;
 #endif
+               unsigned char   mac_addr[ETH_ALEN];
        } dst;
        __be16          proto;
        __u16           vid;
index d975e1223884269d32e95697e87a777f1cfd193a..13d59c51ef5bc7bef9889c83a4c0802f006b8382 100644 (file)
@@ -651,6 +651,7 @@ struct br_mdb_entry {
                union {
                        __be32  ip4;
                        struct in6_addr ip6;
+                       unsigned char mac_addr[ETH_ALEN];
                } u;
                __be16          proto;
        } addr;
index 9b5d62744acc01092cb4836e2ae6332b8ac682cd..2400a66fe76e8366ef0d30db51e3109d92c36f04 100644 (file)
@@ -93,7 +93,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 
                mdst = br_mdb_get(br, skb, vid);
                if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
-                   br_multicast_querier_exists(br, eth_hdr(skb)))
+                   br_multicast_querier_exists(br, eth_hdr(skb), mdst))
                        br_multicast_flood(mdst, skb, false, true);
                else
                        br_flood(br, skb, BR_PKT_MULTICAST, false, true);
index bece03bf83c49fe9355f9972a195c49da8e934eb..21808985f268780d8bbffd7bb6340bb783cb383c 100644 (file)
@@ -134,7 +134,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
        case BR_PKT_MULTICAST:
                mdst = br_mdb_get(br, skb, vid);
                if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
-                   br_multicast_querier_exists(br, eth_hdr(skb))) {
+                   br_multicast_querier_exists(br, eth_hdr(skb), mdst)) {
                        if ((mdst && mdst->host_joined) ||
                            br_multicast_is_router(br)) {
                                local_rcv = true;
index e15bab19a012db6af43ece93819525a31579dc9f..3c8863418d0b0dfd62d1d86efa566f4f7784e678 100644 (file)
@@ -87,6 +87,8 @@ static void __mdb_entry_to_br_ip(struct br_mdb_entry *entry, struct br_ip *ip,
                        ip->src.ip6 = nla_get_in6_addr(mdb_attrs[MDBE_ATTR_SOURCE]);
                break;
 #endif
+       default:
+               ether_addr_copy(ip->dst.mac_addr, entry->addr.u.mac_addr);
        }
 
 }
@@ -174,9 +176,11 @@ static int __mdb_fill_info(struct sk_buff *skb,
        if (mp->addr.proto == htons(ETH_P_IP))
                e.addr.u.ip4 = mp->addr.dst.ip4;
 #if IS_ENABLED(CONFIG_IPV6)
-       if (mp->addr.proto == htons(ETH_P_IPV6))
+       else if (mp->addr.proto == htons(ETH_P_IPV6))
                e.addr.u.ip6 = mp->addr.dst.ip6;
 #endif
+       else
+               ether_addr_copy(e.addr.u.mac_addr, mp->addr.dst.mac_addr);
        e.addr.proto = mp->addr.proto;
        nest_ent = nla_nest_start_noflag(skb,
                                         MDBA_MDB_ENTRY_INFO);
@@ -210,6 +214,8 @@ static int __mdb_fill_info(struct sk_buff *skb,
                }
                break;
 #endif
+       default:
+               ether_addr_copy(e.addr.u.mac_addr, mp->addr.dst.mac_addr);
        }
        if (p) {
                if (nla_put_u8(skb, MDBA_MDB_EATTR_RTPROT, p->rt_protocol))
@@ -562,9 +568,12 @@ void br_mdb_notify(struct net_device *dev,
                if (mp->addr.proto == htons(ETH_P_IP))
                        ip_eth_mc_map(mp->addr.dst.ip4, mdb.addr);
 #if IS_ENABLED(CONFIG_IPV6)
-               else
+               else if (mp->addr.proto == htons(ETH_P_IPV6))
                        ipv6_eth_mc_map(&mp->addr.dst.ip6, mdb.addr);
 #endif
+               else
+                       ether_addr_copy(mdb.addr, mp->addr.dst.mac_addr);
+
                mdb.obj.orig_dev = pg->key.port->dev;
                switch (type) {
                case RTM_NEWMDB:
@@ -693,6 +702,12 @@ static bool is_valid_mdb_entry(struct br_mdb_entry *entry,
                        return false;
                }
 #endif
+       } else if (entry->addr.proto == 0) {
+               /* L2 mdb */
+               if (!is_multicast_ether_addr(entry->addr.u.mac_addr)) {
+                       NL_SET_ERR_MSG_MOD(extack, "L2 entry group is not multicast");
+                       return false;
+               }
        } else {
                NL_SET_ERR_MSG_MOD(extack, "Unknown entry protocol");
                return false;
@@ -849,6 +864,11 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
                }
        }
 
+       if (br_group_is_l2(&group) && entry->state != MDB_PERMANENT) {
+               NL_SET_ERR_MSG_MOD(extack, "Only permanent L2 entries allowed");
+               return -EINVAL;
+       }
+
        mp = br_mdb_ip_get(br, &group);
        if (!mp) {
                mp = br_multicast_new_group(br, &group);
index eae898c3cff7971473ba54bd5bbfc320a9a61352..484820c223a3d2d2df36e86c841706cf387cec38 100644 (file)
@@ -179,7 +179,8 @@ struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
                break;
 #endif
        default:
-               return NULL;
+               ip.proto = 0;
+               ether_addr_copy(ip.dst.mac_addr, eth_hdr(skb)->h_dest);
        }
 
        return br_mdb_ip_get_rcu(br, &ip);
@@ -1203,6 +1204,10 @@ void br_multicast_host_join(struct net_bridge_mdb_entry *mp, bool notify)
                if (notify)
                        br_mdb_notify(mp->br->dev, mp, NULL, RTM_NEWMDB);
        }
+
+       if (br_group_is_l2(&mp->addr))
+               return;
+
        mod_timer(&mp->timer, jiffies + mp->br->multicast_membership_interval);
 }
 
@@ -1254,8 +1259,8 @@ __br_multicast_add_group(struct net_bridge *br,
                        break;
        }
 
-       p = br_multicast_new_port_group(port, group, *pp, 0, src, filter_mode,
-                                       RTPROT_KERNEL);
+       p = br_multicast_new_port_group(port, group, *pp, 0, src,
+                                       filter_mode, RTPROT_KERNEL);
        if (unlikely(!p)) {
                p = ERR_PTR(-ENOMEM);
                goto out;
@@ -3690,7 +3695,7 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto)
        memset(&eth, 0, sizeof(eth));
        eth.h_proto = htons(proto);
 
-       ret = br_multicast_querier_exists(br, &eth);
+       ret = br_multicast_querier_exists(br, &eth, NULL);
 
 unlock:
        rcu_read_unlock();
index 905d406a2fc73532d5c65b72b5a590bac67e5b6b..4c691c371884bcd966cb922f77687c709df9656a 100644 (file)
@@ -854,6 +854,11 @@ void br_multicast_star_g_handle_mode(struct net_bridge_port_group *pg,
 void br_multicast_sg_add_exclude_ports(struct net_bridge_mdb_entry *star_mp,
                                       struct net_bridge_port_group *sg);
 
+static inline bool br_group_is_l2(const struct br_ip *group)
+{
+       return group->proto == 0;
+}
+
 #define mlock_dereference(X, br) \
        rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock))
 
@@ -885,7 +890,8 @@ __br_multicast_querier_exists(struct net_bridge *br,
 }
 
 static inline bool br_multicast_querier_exists(struct net_bridge *br,
-                                              struct ethhdr *eth)
+                                              struct ethhdr *eth,
+                                              const struct net_bridge_mdb_entry *mdb)
 {
        switch (eth->h_proto) {
        case (htons(ETH_P_IP)):
@@ -897,7 +903,7 @@ static inline bool br_multicast_querier_exists(struct net_bridge *br,
                        &br->ip6_other_query, true);
 #endif
        default:
-               return false;
+               return !!mdb && br_group_is_l2(&mdb->addr);
        }
 }