net: bridge: mcast: add support for src list and filter mode dumping
Support per port group src list (address and timer) and filter mode dumping. Protected by either multicast_lock or rcu. v3: add IPv6 support v2: require RCU or multicast_lock to traverse src groups Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
		
							parent
							
								
									8b671779b7
								
							
						
					
					
						commit
						5205e919c9
					
				| @ -455,10 +455,31 @@ enum { | |||||||
| enum { | enum { | ||||||
| 	MDBA_MDB_EATTR_UNSPEC, | 	MDBA_MDB_EATTR_UNSPEC, | ||||||
| 	MDBA_MDB_EATTR_TIMER, | 	MDBA_MDB_EATTR_TIMER, | ||||||
|  | 	MDBA_MDB_EATTR_SRC_LIST, | ||||||
|  | 	MDBA_MDB_EATTR_GROUP_MODE, | ||||||
| 	__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) | ||||||
| 
 | 
 | ||||||
|  | /* per mdb entry source */ | ||||||
|  | enum { | ||||||
|  | 	MDBA_MDB_SRCLIST_UNSPEC, | ||||||
|  | 	MDBA_MDB_SRCLIST_ENTRY, | ||||||
|  | 	__MDBA_MDB_SRCLIST_MAX | ||||||
|  | }; | ||||||
|  | #define MDBA_MDB_SRCLIST_MAX (__MDBA_MDB_SRCLIST_MAX - 1) | ||||||
|  | 
 | ||||||
|  | /* per mdb entry per source attributes
 | ||||||
|  |  * these are embedded in MDBA_MDB_SRCLIST_ENTRY | ||||||
|  |  */ | ||||||
|  | enum { | ||||||
|  | 	MDBA_MDB_SRCATTR_UNSPEC, | ||||||
|  | 	MDBA_MDB_SRCATTR_ADDRESS, | ||||||
|  | 	MDBA_MDB_SRCATTR_TIMER, | ||||||
|  | 	__MDBA_MDB_SRCATTR_MAX | ||||||
|  | }; | ||||||
|  | #define MDBA_MDB_SRCATTR_MAX (__MDBA_MDB_SRCATTR_MAX - 1) | ||||||
|  | 
 | ||||||
| /* multicast router types */ | /* multicast router types */ | ||||||
| enum { | enum { | ||||||
| 	MDB_RTR_TYPE_DISABLED, | 	MDB_RTR_TYPE_DISABLED, | ||||||
|  | |||||||
| @ -77,10 +77,67 @@ static void __mdb_entry_to_br_ip(struct br_mdb_entry *entry, struct br_ip *ip) | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int __mdb_fill_srcs(struct sk_buff *skb, | ||||||
|  | 			   struct net_bridge_port_group *p) | ||||||
|  | { | ||||||
|  | 	struct net_bridge_group_src *ent; | ||||||
|  | 	struct nlattr *nest, *nest_ent; | ||||||
|  | 
 | ||||||
|  | 	if (hlist_empty(&p->src_list)) | ||||||
|  | 		return 0; | ||||||
|  | 
 | ||||||
|  | 	nest = nla_nest_start(skb, MDBA_MDB_EATTR_SRC_LIST); | ||||||
|  | 	if (!nest) | ||||||
|  | 		return -EMSGSIZE; | ||||||
|  | 
 | ||||||
|  | 	hlist_for_each_entry_rcu(ent, &p->src_list, node, | ||||||
|  | 				 lockdep_is_held(&p->port->br->multicast_lock)) { | ||||||
|  | 		nest_ent = nla_nest_start(skb, MDBA_MDB_SRCLIST_ENTRY); | ||||||
|  | 		if (!nest_ent) | ||||||
|  | 			goto out_cancel_err; | ||||||
|  | 		switch (ent->addr.proto) { | ||||||
|  | 		case htons(ETH_P_IP): | ||||||
|  | 			if (nla_put_in_addr(skb, MDBA_MDB_SRCATTR_ADDRESS, | ||||||
|  | 					    ent->addr.u.ip4)) { | ||||||
|  | 				nla_nest_cancel(skb, nest_ent); | ||||||
|  | 				goto out_cancel_err; | ||||||
|  | 			} | ||||||
|  | 			break; | ||||||
|  | #if IS_ENABLED(CONFIG_IPV6) | ||||||
|  | 		case htons(ETH_P_IPV6): | ||||||
|  | 			if (nla_put_in6_addr(skb, MDBA_MDB_SRCATTR_ADDRESS, | ||||||
|  | 					     &ent->addr.u.ip6)) { | ||||||
|  | 				nla_nest_cancel(skb, nest_ent); | ||||||
|  | 				goto out_cancel_err; | ||||||
|  | 			} | ||||||
|  | 			break; | ||||||
|  | #endif | ||||||
|  | 		default: | ||||||
|  | 			nla_nest_cancel(skb, nest_ent); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  | 		if (nla_put_u32(skb, MDBA_MDB_SRCATTR_TIMER, | ||||||
|  | 				br_timer_value(&ent->timer))) { | ||||||
|  | 			nla_nest_cancel(skb, nest_ent); | ||||||
|  | 			goto out_cancel_err; | ||||||
|  | 		} | ||||||
|  | 		nla_nest_end(skb, nest_ent); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	nla_nest_end(skb, nest); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | 
 | ||||||
|  | out_cancel_err: | ||||||
|  | 	nla_nest_cancel(skb, nest); | ||||||
|  | 	return -EMSGSIZE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int __mdb_fill_info(struct sk_buff *skb, | static int __mdb_fill_info(struct sk_buff *skb, | ||||||
| 			   struct net_bridge_mdb_entry *mp, | 			   struct net_bridge_mdb_entry *mp, | ||||||
| 			   struct net_bridge_port_group *p) | 			   struct net_bridge_port_group *p) | ||||||
| { | { | ||||||
|  | 	bool dump_srcs_mode = false; | ||||||
| 	struct timer_list *mtimer; | 	struct timer_list *mtimer; | ||||||
| 	struct nlattr *nest_ent; | 	struct nlattr *nest_ent; | ||||||
| 	struct br_mdb_entry e; | 	struct br_mdb_entry e; | ||||||
| @ -119,6 +176,23 @@ static int __mdb_fill_info(struct sk_buff *skb, | |||||||
| 		nla_nest_cancel(skb, nest_ent); | 		nla_nest_cancel(skb, nest_ent); | ||||||
| 		return -EMSGSIZE; | 		return -EMSGSIZE; | ||||||
| 	} | 	} | ||||||
|  | 	switch (mp->addr.proto) { | ||||||
|  | 	case htons(ETH_P_IP): | ||||||
|  | 		dump_srcs_mode = !!(p && mp->br->multicast_igmp_version == 3); | ||||||
|  | 		break; | ||||||
|  | #if IS_ENABLED(CONFIG_IPV6) | ||||||
|  | 	case htons(ETH_P_IPV6): | ||||||
|  | 		dump_srcs_mode = !!(p && mp->br->multicast_mld_version == 2); | ||||||
|  | 		break; | ||||||
|  | #endif | ||||||
|  | 	} | ||||||
|  | 	if (dump_srcs_mode && | ||||||
|  | 	    (__mdb_fill_srcs(skb, p) || | ||||||
|  | 	     nla_put_u8(skb, MDBA_MDB_EATTR_GROUP_MODE, p->filter_mode))) { | ||||||
|  | 		nla_nest_cancel(skb, nest_ent); | ||||||
|  | 		return -EMSGSIZE; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	nla_nest_end(skb, nest_ent); | 	nla_nest_end(skb, nest_ent); | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| @ -127,7 +201,7 @@ static int __mdb_fill_info(struct sk_buff *skb, | |||||||
| 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, | ||||||
| 			    struct net_device *dev) | 			    struct net_device *dev) | ||||||
| { | { | ||||||
| 	int idx = 0, s_idx = cb->args[1], err = 0; | 	int idx = 0, s_idx = cb->args[1], err = 0, pidx = 0, s_pidx = cb->args[2]; | ||||||
| 	struct net_bridge *br = netdev_priv(dev); | 	struct net_bridge *br = netdev_priv(dev); | ||||||
| 	struct net_bridge_mdb_entry *mp; | 	struct net_bridge_mdb_entry *mp; | ||||||
| 	struct nlattr *nest, *nest2; | 	struct nlattr *nest, *nest2; | ||||||
| @ -152,7 +226,7 @@ static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb, | |||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if (mp->host_joined) { | 		if (!s_pidx && mp->host_joined) { | ||||||
| 			err = __mdb_fill_info(skb, mp, NULL); | 			err = __mdb_fill_info(skb, mp, NULL); | ||||||
| 			if (err) { | 			if (err) { | ||||||
| 				nla_nest_cancel(skb, nest2); | 				nla_nest_cancel(skb, nest2); | ||||||
| @ -164,13 +238,19 @@ static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb, | |||||||
| 		      pp = &p->next) { | 		      pp = &p->next) { | ||||||
| 			if (!p->port) | 			if (!p->port) | ||||||
| 				continue; | 				continue; | ||||||
|  | 			if (pidx < s_pidx) | ||||||
|  | 				goto skip_pg; | ||||||
| 
 | 
 | ||||||
| 			err = __mdb_fill_info(skb, mp, p); | 			err = __mdb_fill_info(skb, mp, p); | ||||||
| 			if (err) { | 			if (err) { | ||||||
| 				nla_nest_cancel(skb, nest2); | 				nla_nest_cancel(skb, nest2); | ||||||
| 				goto out; | 				goto out; | ||||||
| 			} | 			} | ||||||
|  | skip_pg: | ||||||
|  | 			pidx++; | ||||||
| 		} | 		} | ||||||
|  | 		pidx = 0; | ||||||
|  | 		s_pidx = 0; | ||||||
| 		nla_nest_end(skb, nest2); | 		nla_nest_end(skb, nest2); | ||||||
| skip: | skip: | ||||||
| 		idx++; | 		idx++; | ||||||
| @ -178,6 +258,7 @@ skip: | |||||||
| 
 | 
 | ||||||
| out: | out: | ||||||
| 	cb->args[1] = idx; | 	cb->args[1] = idx; | ||||||
|  | 	cb->args[2] = pidx; | ||||||
| 	nla_nest_end(skb, nest); | 	nla_nest_end(skb, nest); | ||||||
| 	return err; | 	return err; | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user