Commit 88d4bd18 authored by Nikolay Aleksandrov's avatar Nikolay Aleksandrov Committed by David S. Miller

net: bridge: mdb: add support for add/del/dump of entries with source

Add new mdb attributes (MDBE_ATTR_SOURCE for setting,
MDBA_MDB_EATTR_SOURCE for dumping) to allow add/del and dump of mdb
entries with a source address (S,G). New S,G entries are created with
filter mode of MCAST_INCLUDE. The same attributes are used for IPv4 and
IPv6, they're validated and parsed based on their protocol.
S,G host joined entries which are added by user are not allowed yet.
Signed-off-by: default avatarNikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 9c4258c7
...@@ -457,6 +457,7 @@ enum { ...@@ -457,6 +457,7 @@ enum {
MDBA_MDB_EATTR_TIMER, MDBA_MDB_EATTR_TIMER,
MDBA_MDB_EATTR_SRC_LIST, MDBA_MDB_EATTR_SRC_LIST,
MDBA_MDB_EATTR_GROUP_MODE, MDBA_MDB_EATTR_GROUP_MODE,
MDBA_MDB_EATTR_SOURCE,
__MDBA_MDB_EATTR_MAX __MDBA_MDB_EATTR_MAX
}; };
#define MDBA_MDB_EATTR_MAX (__MDBA_MDB_EATTR_MAX - 1) #define MDBA_MDB_EATTR_MAX (__MDBA_MDB_EATTR_MAX - 1)
...@@ -542,6 +543,7 @@ enum { ...@@ -542,6 +543,7 @@ enum {
*/ */
enum { enum {
MDBE_ATTR_UNSPEC, MDBE_ATTR_UNSPEC,
MDBE_ATTR_SOURCE,
__MDBE_ATTR_MAX, __MDBE_ATTR_MAX,
}; };
#define MDBE_ATTR_MAX (__MDBE_ATTR_MAX - 1) #define MDBE_ATTR_MAX (__MDBE_ATTR_MAX - 1)
......
...@@ -64,17 +64,27 @@ static void __mdb_entry_fill_flags(struct br_mdb_entry *e, unsigned char flags) ...@@ -64,17 +64,27 @@ static void __mdb_entry_fill_flags(struct br_mdb_entry *e, unsigned char flags)
e->flags |= MDB_FLAGS_FAST_LEAVE; e->flags |= MDB_FLAGS_FAST_LEAVE;
} }
static void __mdb_entry_to_br_ip(struct br_mdb_entry *entry, struct br_ip *ip) static void __mdb_entry_to_br_ip(struct br_mdb_entry *entry, struct br_ip *ip,
struct nlattr **mdb_attrs)
{ {
memset(ip, 0, sizeof(struct br_ip)); memset(ip, 0, sizeof(struct br_ip));
ip->vid = entry->vid; ip->vid = entry->vid;
ip->proto = entry->addr.proto; ip->proto = entry->addr.proto;
if (ip->proto == htons(ETH_P_IP)) switch (ip->proto) {
case htons(ETH_P_IP):
ip->dst.ip4 = entry->addr.u.ip4; ip->dst.ip4 = entry->addr.u.ip4;
if (mdb_attrs && mdb_attrs[MDBE_ATTR_SOURCE])
ip->src.ip4 = nla_get_in_addr(mdb_attrs[MDBE_ATTR_SOURCE]);
break;
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
else case htons(ETH_P_IPV6):
ip->dst.ip6 = entry->addr.u.ip6; ip->dst.ip6 = entry->addr.u.ip6;
if (mdb_attrs && mdb_attrs[MDBE_ATTR_SOURCE])
ip->src.ip6 = nla_get_in6_addr(mdb_attrs[MDBE_ATTR_SOURCE]);
break;
#endif #endif
}
} }
static int __mdb_fill_srcs(struct sk_buff *skb, static int __mdb_fill_srcs(struct sk_buff *skb,
...@@ -172,30 +182,41 @@ static int __mdb_fill_info(struct sk_buff *skb, ...@@ -172,30 +182,41 @@ static int __mdb_fill_info(struct sk_buff *skb,
if (nla_put_nohdr(skb, sizeof(e), &e) || if (nla_put_nohdr(skb, sizeof(e), &e) ||
nla_put_u32(skb, nla_put_u32(skb,
MDBA_MDB_EATTR_TIMER, MDBA_MDB_EATTR_TIMER,
br_timer_value(mtimer))) { br_timer_value(mtimer)))
nla_nest_cancel(skb, nest_ent); goto nest_err;
return -EMSGSIZE;
}
switch (mp->addr.proto) { switch (mp->addr.proto) {
case htons(ETH_P_IP): case htons(ETH_P_IP):
dump_srcs_mode = !!(p && mp->br->multicast_igmp_version == 3); dump_srcs_mode = !!(mp->br->multicast_igmp_version == 3);
if (mp->addr.src.ip4) {
if (nla_put_in_addr(skb, MDBA_MDB_EATTR_SOURCE,
mp->addr.src.ip4))
goto nest_err;
break;
}
break; break;
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6): case htons(ETH_P_IPV6):
dump_srcs_mode = !!(p && mp->br->multicast_mld_version == 2); dump_srcs_mode = !!(mp->br->multicast_mld_version == 2);
if (!ipv6_addr_any(&mp->addr.src.ip6)) {
if (nla_put_in6_addr(skb, MDBA_MDB_EATTR_SOURCE,
&mp->addr.src.ip6))
goto nest_err;
break;
}
break; break;
#endif #endif
} }
if (dump_srcs_mode && if (p && dump_srcs_mode &&
(__mdb_fill_srcs(skb, p) || (__mdb_fill_srcs(skb, p) ||
nla_put_u8(skb, MDBA_MDB_EATTR_GROUP_MODE, p->filter_mode))) { nla_put_u8(skb, MDBA_MDB_EATTR_GROUP_MODE, p->filter_mode)))
nla_nest_cancel(skb, nest_ent); goto nest_err;
return -EMSGSIZE;
}
nla_nest_end(skb, nest_ent); nla_nest_end(skb, nest_ent);
return 0; return 0;
nest_err:
nla_nest_cancel(skb, nest_ent);
return -EMSGSIZE;
} }
static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb, static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
...@@ -395,12 +416,18 @@ static size_t rtnl_mdb_nlmsg_size(struct net_bridge_port_group *pg) ...@@ -395,12 +416,18 @@ static size_t rtnl_mdb_nlmsg_size(struct net_bridge_port_group *pg)
switch (pg->addr.proto) { switch (pg->addr.proto) {
case htons(ETH_P_IP): case htons(ETH_P_IP):
/* MDBA_MDB_EATTR_SOURCE */
if (pg->addr.src.ip4)
nlmsg_size += nla_total_size(sizeof(__be32));
if (pg->port->br->multicast_igmp_version == 2) if (pg->port->br->multicast_igmp_version == 2)
goto out; goto out;
addr_size = sizeof(__be32); addr_size = sizeof(__be32);
break; break;
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6): case htons(ETH_P_IPV6):
/* MDBA_MDB_EATTR_SOURCE */
if (!ipv6_addr_any(&pg->addr.src.ip6))
nlmsg_size += nla_total_size(sizeof(struct in6_addr));
if (pg->port->br->multicast_mld_version == 1) if (pg->port->br->multicast_mld_version == 1)
goto out; goto out;
addr_size = sizeof(struct in6_addr); addr_size = sizeof(struct in6_addr);
...@@ -670,7 +697,48 @@ static bool is_valid_mdb_entry(struct br_mdb_entry *entry, ...@@ -670,7 +697,48 @@ static bool is_valid_mdb_entry(struct br_mdb_entry *entry,
return true; return true;
} }
static bool is_valid_mdb_source(struct nlattr *attr, __be16 proto,
struct netlink_ext_ack *extack)
{
switch (proto) {
case htons(ETH_P_IP):
if (nla_len(attr) != sizeof(struct in_addr)) {
NL_SET_ERR_MSG_MOD(extack, "IPv4 invalid source address length");
return false;
}
if (ipv4_is_multicast(nla_get_in_addr(attr))) {
NL_SET_ERR_MSG_MOD(extack, "IPv4 multicast source address is not allowed");
return false;
}
break;
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6): {
struct in6_addr src;
if (nla_len(attr) != sizeof(struct in6_addr)) {
NL_SET_ERR_MSG_MOD(extack, "IPv6 invalid source address length");
return false;
}
src = nla_get_in6_addr(attr);
if (ipv6_addr_is_multicast(&src)) {
NL_SET_ERR_MSG_MOD(extack, "IPv6 multicast source address is not allowed");
return false;
}
break;
}
#endif
default:
NL_SET_ERR_MSG_MOD(extack, "Invalid protocol used with source address");
return false;
}
return true;
}
static const struct nla_policy br_mdbe_attrs_pol[MDBE_ATTR_MAX + 1] = { static const struct nla_policy br_mdbe_attrs_pol[MDBE_ATTR_MAX + 1] = {
[MDBE_ATTR_SOURCE] = NLA_POLICY_RANGE(NLA_BINARY,
sizeof(struct in_addr),
sizeof(struct in6_addr)),
}; };
static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh, static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh,
...@@ -728,6 +796,10 @@ static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -728,6 +796,10 @@ static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh,
br_mdbe_attrs_pol, extack); br_mdbe_attrs_pol, extack);
if (err) if (err)
return err; return err;
if (mdb_attrs[MDBE_ATTR_SOURCE] &&
!is_valid_mdb_source(mdb_attrs[MDBE_ATTR_SOURCE],
entry->addr.proto, extack))
return -EINVAL;
} else { } else {
memset(mdb_attrs, 0, memset(mdb_attrs, 0,
sizeof(struct nlattr *) * (MDBE_ATTR_MAX + 1)); sizeof(struct nlattr *) * (MDBE_ATTR_MAX + 1));
...@@ -744,8 +816,22 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port, ...@@ -744,8 +816,22 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
struct net_bridge_port_group *p; struct net_bridge_port_group *p;
struct net_bridge_port_group __rcu **pp; struct net_bridge_port_group __rcu **pp;
unsigned long now = jiffies; unsigned long now = jiffies;
u8 filter_mode;
int err; int err;
/* host join errors which can happen before creating the group */
if (!port) {
/* don't allow any flags for host-joined groups */
if (entry->state) {
NL_SET_ERR_MSG_MOD(extack, "Flags are not allowed for host groups");
return -EINVAL;
}
if (!br_multicast_is_star_g(group)) {
NL_SET_ERR_MSG_MOD(extack, "Groups with sources cannot be manually host joined");
return -EINVAL;
}
}
mp = br_mdb_ip_get(br, group); mp = br_mdb_ip_get(br, group);
if (!mp) { if (!mp) {
mp = br_multicast_new_group(br, group); mp = br_multicast_new_group(br, group);
...@@ -756,11 +842,6 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port, ...@@ -756,11 +842,6 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
/* host join */ /* host join */
if (!port) { if (!port) {
/* don't allow any flags for host-joined groups */
if (entry->state) {
NL_SET_ERR_MSG_MOD(extack, "Flags are not allowed for host groups");
return -EINVAL;
}
if (mp->host_joined) { if (mp->host_joined) {
NL_SET_ERR_MSG_MOD(extack, "Group is already joined by host"); NL_SET_ERR_MSG_MOD(extack, "Group is already joined by host");
return -EEXIST; return -EEXIST;
...@@ -783,8 +864,11 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port, ...@@ -783,8 +864,11 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
break; break;
} }
filter_mode = br_multicast_is_star_g(group) ? MCAST_EXCLUDE :
MCAST_INCLUDE;
p = br_multicast_new_port_group(port, group, *pp, entry->state, NULL, p = br_multicast_new_port_group(port, group, *pp, entry->state, NULL,
MCAST_EXCLUDE); filter_mode);
if (unlikely(!p)) { if (unlikely(!p)) {
NL_SET_ERR_MSG_MOD(extack, "Couldn't allocate new port group"); NL_SET_ERR_MSG_MOD(extack, "Couldn't allocate new port group");
return -ENOMEM; return -ENOMEM;
...@@ -800,12 +884,13 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port, ...@@ -800,12 +884,13 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
static int __br_mdb_add(struct net *net, struct net_bridge *br, static int __br_mdb_add(struct net *net, struct net_bridge *br,
struct net_bridge_port *p, struct net_bridge_port *p,
struct br_mdb_entry *entry, struct br_mdb_entry *entry,
struct nlattr **mdb_attrs,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
struct br_ip ip; struct br_ip ip;
int ret; int ret;
__mdb_entry_to_br_ip(entry, &ip); __mdb_entry_to_br_ip(entry, &ip, mdb_attrs);
spin_lock_bh(&br->multicast_lock); spin_lock_bh(&br->multicast_lock);
ret = br_mdb_add_group(br, p, &ip, entry, extack); ret = br_mdb_add_group(br, p, &ip, entry, extack);
...@@ -875,18 +960,19 @@ static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -875,18 +960,19 @@ static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh,
if (br_vlan_enabled(br->dev) && vg && entry->vid == 0) { if (br_vlan_enabled(br->dev) && vg && entry->vid == 0) {
list_for_each_entry(v, &vg->vlan_list, vlist) { list_for_each_entry(v, &vg->vlan_list, vlist) {
entry->vid = v->vid; entry->vid = v->vid;
err = __br_mdb_add(net, br, p, entry, extack); err = __br_mdb_add(net, br, p, entry, mdb_attrs, extack);
if (err) if (err)
break; break;
} }
} else { } else {
err = __br_mdb_add(net, br, p, entry, extack); err = __br_mdb_add(net, br, p, entry, mdb_attrs, extack);
} }
return err; return err;
} }
static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry) static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry,
struct nlattr **mdb_attrs)
{ {
struct net_bridge_mdb_entry *mp; struct net_bridge_mdb_entry *mp;
struct net_bridge_port_group *p; struct net_bridge_port_group *p;
...@@ -897,7 +983,7 @@ static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry) ...@@ -897,7 +983,7 @@ static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry)
if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED)) if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
return -EINVAL; return -EINVAL;
__mdb_entry_to_br_ip(entry, &ip); __mdb_entry_to_br_ip(entry, &ip, mdb_attrs);
spin_lock_bh(&br->multicast_lock); spin_lock_bh(&br->multicast_lock);
mp = br_mdb_ip_get(br, &ip); mp = br_mdb_ip_get(br, &ip);
...@@ -971,10 +1057,10 @@ static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -971,10 +1057,10 @@ static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
if (br_vlan_enabled(br->dev) && vg && entry->vid == 0) { if (br_vlan_enabled(br->dev) && vg && entry->vid == 0) {
list_for_each_entry(v, &vg->vlan_list, vlist) { list_for_each_entry(v, &vg->vlan_list, vlist) {
entry->vid = v->vid; entry->vid = v->vid;
err = __br_mdb_del(br, entry); err = __br_mdb_del(br, entry, mdb_attrs);
} }
} else { } else {
err = __br_mdb_del(br, entry); err = __br_mdb_del(br, entry, mdb_attrs);
} }
return err; return err;
......
...@@ -873,6 +873,20 @@ static inline bool br_multicast_querier_exists(struct net_bridge *br, ...@@ -873,6 +873,20 @@ static inline bool br_multicast_querier_exists(struct net_bridge *br,
} }
} }
static inline bool br_multicast_is_star_g(const struct br_ip *ip)
{
switch (ip->proto) {
case htons(ETH_P_IP):
return ipv4_is_zeronet(ip->src.ip4);
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6):
return ipv6_addr_any(&ip->src.ip6);
#endif
default:
return false;
}
}
static inline int br_multicast_igmp_type(const struct sk_buff *skb) static inline int br_multicast_igmp_type(const struct sk_buff *skb)
{ {
return BR_INPUT_SKB_CB(skb)->igmp; return BR_INPUT_SKB_CB(skb)->igmp;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment