diff options
Diffstat (limited to 'net/sched/cls_flower.c')
| -rw-r--r-- | net/sched/cls_flower.c | 337 | 
1 files changed, 279 insertions, 58 deletions
| diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c index f6f40fba599b..e040c5140f61 100644 --- a/net/sched/cls_flower.c +++ b/net/sched/cls_flower.c @@ -13,6 +13,7 @@  #include <linux/init.h>  #include <linux/module.h>  #include <linux/rhashtable.h> +#include <linux/workqueue.h>  #include <linux/if_ether.h>  #include <linux/in6.h> @@ -38,11 +39,13 @@ struct fl_flow_key {  		struct flow_dissector_key_ipv6_addrs ipv6;  	};  	struct flow_dissector_key_ports tp; +	struct flow_dissector_key_icmp icmp;  	struct flow_dissector_key_keyid enc_key_id;  	union {  		struct flow_dissector_key_ipv4_addrs enc_ipv4;  		struct flow_dissector_key_ipv6_addrs enc_ipv6;  	}; +	struct flow_dissector_key_ports enc_tp;  } __aligned(BITS_PER_LONG / 8); /* Ensure that we can do comparisons as longs. */  struct fl_flow_mask_range { @@ -64,7 +67,10 @@ struct cls_fl_head {  	bool mask_assigned;  	struct list_head filters;  	struct rhashtable_params ht_params; -	struct rcu_head rcu; +	union { +		struct work_struct work; +		struct rcu_head	rcu; +	};  };  struct cls_fl_filter { @@ -77,6 +83,8 @@ struct cls_fl_filter {  	u32 handle;  	u32 flags;  	struct rcu_head	rcu; +	struct tc_to_netdev tc; +	struct net_device *hw_dev;  };  static unsigned short int fl_mask_range(const struct fl_flow_mask *mask) @@ -155,6 +163,8 @@ static int fl_classify(struct sk_buff *skb, const struct tcf_proto *tp,  		}  		skb_key.enc_key_id.keyid = tunnel_id_to_key32(key->tun_id); +		skb_key.enc_tp.src = key->tp_src; +		skb_key.enc_tp.dst = key->tp_dst;  	}  	skb_key.indev_ifindex = skb->skb_iif; @@ -198,75 +208,108 @@ static void fl_destroy_filter(struct rcu_head *head)  	kfree(f);  } -static void fl_hw_destroy_filter(struct tcf_proto *tp, unsigned long cookie) +static void fl_hw_destroy_filter(struct tcf_proto *tp, struct cls_fl_filter *f)  { -	struct net_device *dev = tp->q->dev_queue->dev;  	struct tc_cls_flower_offload offload = {0}; -	struct tc_to_netdev tc; +	struct net_device *dev = f->hw_dev; +	struct tc_to_netdev *tc = &f->tc; -	if (!tc_should_offload(dev, tp, 0)) +	if (!tc_can_offload(dev, tp))  		return;  	offload.command = TC_CLSFLOWER_DESTROY; -	offload.cookie = cookie; +	offload.cookie = (unsigned long)f; -	tc.type = TC_SETUP_CLSFLOWER; -	tc.cls_flower = &offload; +	tc->type = TC_SETUP_CLSFLOWER; +	tc->cls_flower = &offload; -	dev->netdev_ops->ndo_setup_tc(dev, tp->q->handle, tp->protocol, &tc); +	dev->netdev_ops->ndo_setup_tc(dev, tp->q->handle, tp->protocol, tc);  }  static int fl_hw_replace_filter(struct tcf_proto *tp,  				struct flow_dissector *dissector,  				struct fl_flow_key *mask, -				struct fl_flow_key *key, -				struct tcf_exts *actions, -				unsigned long cookie, u32 flags) +				struct cls_fl_filter *f)  {  	struct net_device *dev = tp->q->dev_queue->dev;  	struct tc_cls_flower_offload offload = {0}; -	struct tc_to_netdev tc; +	struct tc_to_netdev *tc = &f->tc;  	int err; -	if (!tc_should_offload(dev, tp, flags)) -		return tc_skip_sw(flags) ? -EINVAL : 0; +	if (!tc_can_offload(dev, tp)) { +		if (tcf_exts_get_dev(dev, &f->exts, &f->hw_dev) || +		    (f->hw_dev && !tc_can_offload(f->hw_dev, tp))) { +			f->hw_dev = dev; +			return tc_skip_sw(f->flags) ? -EINVAL : 0; +		} +		dev = f->hw_dev; +		tc->egress_dev = true; +	} else { +		f->hw_dev = dev; +	}  	offload.command = TC_CLSFLOWER_REPLACE; -	offload.cookie = cookie; +	offload.cookie = (unsigned long)f;  	offload.dissector = dissector;  	offload.mask = mask; -	offload.key = key; -	offload.exts = actions; +	offload.key = &f->key; +	offload.exts = &f->exts; -	tc.type = TC_SETUP_CLSFLOWER; -	tc.cls_flower = &offload; +	tc->type = TC_SETUP_CLSFLOWER; +	tc->cls_flower = &offload;  	err = dev->netdev_ops->ndo_setup_tc(dev, tp->q->handle, tp->protocol, -					    &tc); +					    tc); -	if (tc_skip_sw(flags)) +	if (tc_skip_sw(f->flags))  		return err; -  	return 0;  }  static void fl_hw_update_stats(struct tcf_proto *tp, struct cls_fl_filter *f)  { -	struct net_device *dev = tp->q->dev_queue->dev;  	struct tc_cls_flower_offload offload = {0}; -	struct tc_to_netdev tc; +	struct net_device *dev = f->hw_dev; +	struct tc_to_netdev *tc = &f->tc; -	if (!tc_should_offload(dev, tp, 0)) +	if (!tc_can_offload(dev, tp))  		return;  	offload.command = TC_CLSFLOWER_STATS;  	offload.cookie = (unsigned long)f;  	offload.exts = &f->exts; -	tc.type = TC_SETUP_CLSFLOWER; -	tc.cls_flower = &offload; +	tc->type = TC_SETUP_CLSFLOWER; +	tc->cls_flower = &offload; -	dev->netdev_ops->ndo_setup_tc(dev, tp->q->handle, tp->protocol, &tc); +	dev->netdev_ops->ndo_setup_tc(dev, tp->q->handle, tp->protocol, tc); +} + +static void __fl_delete(struct tcf_proto *tp, struct cls_fl_filter *f) +{ +	list_del_rcu(&f->list); +	if (!tc_skip_hw(f->flags)) +		fl_hw_destroy_filter(tp, f); +	tcf_unbind_filter(tp, &f->res); +	call_rcu(&f->rcu, fl_destroy_filter); +} + +static void fl_destroy_sleepable(struct work_struct *work) +{ +	struct cls_fl_head *head = container_of(work, struct cls_fl_head, +						work); +	if (head->mask_assigned) +		rhashtable_destroy(&head->ht); +	kfree(head); +	module_put(THIS_MODULE); +} + +static void fl_destroy_rcu(struct rcu_head *rcu) +{ +	struct cls_fl_head *head = container_of(rcu, struct cls_fl_head, rcu); + +	INIT_WORK(&head->work, fl_destroy_sleepable); +	schedule_work(&head->work);  }  static bool fl_destroy(struct tcf_proto *tp, bool force) @@ -277,15 +320,12 @@ static bool fl_destroy(struct tcf_proto *tp, bool force)  	if (!force && !list_empty(&head->filters))  		return false; -	list_for_each_entry_safe(f, next, &head->filters, list) { -		fl_hw_destroy_filter(tp, (unsigned long)f); -		list_del_rcu(&f->list); -		call_rcu(&f->rcu, fl_destroy_filter); -	} -	RCU_INIT_POINTER(tp->root, NULL); -	if (head->mask_assigned) -		rhashtable_destroy(&head->ht); -	kfree_rcu(head, rcu); +	list_for_each_entry_safe(f, next, &head->filters, list) +		__fl_delete(tp, f); + +	__module_get(THIS_MODULE); +	call_rcu(&head->rcu, fl_destroy_rcu); +  	return true;  } @@ -339,6 +379,24 @@ static const struct nla_policy fl_policy[TCA_FLOWER_MAX + 1] = {  	[TCA_FLOWER_KEY_TCP_DST_MASK]	= { .type = NLA_U16 },  	[TCA_FLOWER_KEY_UDP_SRC_MASK]	= { .type = NLA_U16 },  	[TCA_FLOWER_KEY_UDP_DST_MASK]	= { .type = NLA_U16 }, +	[TCA_FLOWER_KEY_SCTP_SRC_MASK]	= { .type = NLA_U16 }, +	[TCA_FLOWER_KEY_SCTP_DST_MASK]	= { .type = NLA_U16 }, +	[TCA_FLOWER_KEY_SCTP_SRC]	= { .type = NLA_U16 }, +	[TCA_FLOWER_KEY_SCTP_DST]	= { .type = NLA_U16 }, +	[TCA_FLOWER_KEY_ENC_UDP_SRC_PORT]	= { .type = NLA_U16 }, +	[TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK]	= { .type = NLA_U16 }, +	[TCA_FLOWER_KEY_ENC_UDP_DST_PORT]	= { .type = NLA_U16 }, +	[TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK]	= { .type = NLA_U16 }, +	[TCA_FLOWER_KEY_FLAGS]		= { .type = NLA_U32 }, +	[TCA_FLOWER_KEY_FLAGS_MASK]	= { .type = NLA_U32 }, +	[TCA_FLOWER_KEY_ICMPV4_TYPE]	= { .type = NLA_U8 }, +	[TCA_FLOWER_KEY_ICMPV4_TYPE_MASK] = { .type = NLA_U8 }, +	[TCA_FLOWER_KEY_ICMPV4_CODE]	= { .type = NLA_U8 }, +	[TCA_FLOWER_KEY_ICMPV4_CODE_MASK] = { .type = NLA_U8 }, +	[TCA_FLOWER_KEY_ICMPV6_TYPE]	= { .type = NLA_U8 }, +	[TCA_FLOWER_KEY_ICMPV6_TYPE_MASK] = { .type = NLA_U8 }, +	[TCA_FLOWER_KEY_ICMPV6_CODE]	= { .type = NLA_U8 }, +	[TCA_FLOWER_KEY_ICMPV6_CODE_MASK] = { .type = NLA_U8 },  };  static void fl_set_key_val(struct nlattr **tb, @@ -373,6 +431,39 @@ static void fl_set_key_vlan(struct nlattr **tb,  	}  } +static void fl_set_key_flag(u32 flower_key, u32 flower_mask, +			    u32 *dissector_key, u32 *dissector_mask, +			    u32 flower_flag_bit, u32 dissector_flag_bit) +{ +	if (flower_mask & flower_flag_bit) { +		*dissector_mask |= dissector_flag_bit; +		if (flower_key & flower_flag_bit) +			*dissector_key |= dissector_flag_bit; +	} +} + +static void fl_set_key_flags(struct nlattr **tb, +			     u32 *flags_key, u32 *flags_mask) +{ +	u32 key, mask; + +	if (!tb[TCA_FLOWER_KEY_FLAGS]) +		return; + +	key = be32_to_cpu(nla_get_u32(tb[TCA_FLOWER_KEY_FLAGS])); + +	if (!tb[TCA_FLOWER_KEY_FLAGS_MASK]) +		mask = ~0; +	else +		mask = be32_to_cpu(nla_get_u32(tb[TCA_FLOWER_KEY_FLAGS_MASK])); + +	*flags_key  = 0; +	*flags_mask = 0; + +	fl_set_key_flag(key, mask, flags_key, flags_mask, +			TCA_FLOWER_KEY_FLAGS_IS_FRAGMENT, FLOW_DIS_IS_FRAGMENT); +} +  static int fl_set_key(struct net *net, struct nlattr **tb,  		      struct fl_flow_key *key, struct fl_flow_key *mask)  { @@ -448,6 +539,33 @@ static int fl_set_key(struct net *net, struct nlattr **tb,  		fl_set_key_val(tb, &key->tp.dst, TCA_FLOWER_KEY_UDP_DST,  			       &mask->tp.dst, TCA_FLOWER_KEY_UDP_DST_MASK,  			       sizeof(key->tp.dst)); +	} else if (key->basic.ip_proto == IPPROTO_SCTP) { +		fl_set_key_val(tb, &key->tp.src, TCA_FLOWER_KEY_SCTP_SRC, +			       &mask->tp.src, TCA_FLOWER_KEY_SCTP_SRC_MASK, +			       sizeof(key->tp.src)); +		fl_set_key_val(tb, &key->tp.dst, TCA_FLOWER_KEY_SCTP_DST, +			       &mask->tp.dst, TCA_FLOWER_KEY_SCTP_DST_MASK, +			       sizeof(key->tp.dst)); +	} else if (key->basic.n_proto == htons(ETH_P_IP) && +		   key->basic.ip_proto == IPPROTO_ICMP) { +		fl_set_key_val(tb, &key->icmp.type, TCA_FLOWER_KEY_ICMPV4_TYPE, +			       &mask->icmp.type, +			       TCA_FLOWER_KEY_ICMPV4_TYPE_MASK, +			       sizeof(key->icmp.type)); +		fl_set_key_val(tb, &key->icmp.code, TCA_FLOWER_KEY_ICMPV4_CODE, +			       &mask->icmp.code, +			       TCA_FLOWER_KEY_ICMPV4_CODE_MASK, +			       sizeof(key->icmp.code)); +	} else if (key->basic.n_proto == htons(ETH_P_IPV6) && +		   key->basic.ip_proto == IPPROTO_ICMPV6) { +		fl_set_key_val(tb, &key->icmp.type, TCA_FLOWER_KEY_ICMPV6_TYPE, +			       &mask->icmp.type, +			       TCA_FLOWER_KEY_ICMPV6_TYPE_MASK, +			       sizeof(key->icmp.type)); +		fl_set_key_val(tb, &key->icmp.code, TCA_FLOWER_KEY_ICMPV4_CODE, +			       &mask->icmp.code, +			       TCA_FLOWER_KEY_ICMPV4_CODE_MASK, +			       sizeof(key->icmp.code));  	}  	if (tb[TCA_FLOWER_KEY_ENC_IPV4_SRC] || @@ -484,6 +602,16 @@ static int fl_set_key(struct net *net, struct nlattr **tb,  		       &mask->enc_key_id.keyid, TCA_FLOWER_UNSPEC,  		       sizeof(key->enc_key_id.keyid)); +	fl_set_key_val(tb, &key->enc_tp.src, TCA_FLOWER_KEY_ENC_UDP_SRC_PORT, +		       &mask->enc_tp.src, TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK, +		       sizeof(key->enc_tp.src)); + +	fl_set_key_val(tb, &key->enc_tp.dst, TCA_FLOWER_KEY_ENC_UDP_DST_PORT, +		       &mask->enc_tp.dst, TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK, +		       sizeof(key->enc_tp.dst)); + +	fl_set_key_flags(tb, &key->control.flags, &mask->control.flags); +  	return 0;  } @@ -550,7 +678,21 @@ static void fl_init_dissector(struct cls_fl_head *head,  	FL_KEY_SET_IF_MASKED(&mask->key, keys, cnt,  			     FLOW_DISSECTOR_KEY_PORTS, tp);  	FL_KEY_SET_IF_MASKED(&mask->key, keys, cnt, +			     FLOW_DISSECTOR_KEY_ICMP, icmp); +	FL_KEY_SET_IF_MASKED(&mask->key, keys, cnt,  			     FLOW_DISSECTOR_KEY_VLAN, vlan); +	FL_KEY_SET_IF_MASKED(&mask->key, keys, cnt, +			     FLOW_DISSECTOR_KEY_ENC_KEYID, enc_key_id); +	FL_KEY_SET_IF_MASKED(&mask->key, keys, cnt, +			     FLOW_DISSECTOR_KEY_ENC_IPV4_ADDRS, enc_ipv4); +	FL_KEY_SET_IF_MASKED(&mask->key, keys, cnt, +			     FLOW_DISSECTOR_KEY_ENC_IPV6_ADDRS, enc_ipv6); +	if (FL_KEY_IS_MASKED(&mask->key, enc_ipv4) || +	    FL_KEY_IS_MASKED(&mask->key, enc_ipv6)) +		FL_KEY_SET(keys, cnt, FLOW_DISSECTOR_KEY_ENC_CONTROL, +			   enc_control); +	FL_KEY_SET_IF_MASKED(&mask->key, keys, cnt, +			     FLOW_DISSECTOR_KEY_ENC_PORTS, enc_tp);  	skb_flow_dissector_init(&head->dissector, keys, cnt);  } @@ -700,20 +842,21 @@ static int fl_change(struct net *net, struct sk_buff *in_skb,  			goto errout;  	} -	err = fl_hw_replace_filter(tp, -				   &head->dissector, -				   &mask.key, -				   &fnew->key, -				   &fnew->exts, -				   (unsigned long)fnew, -				   fnew->flags); -	if (err) -		goto errout; +	if (!tc_skip_hw(fnew->flags)) { +		err = fl_hw_replace_filter(tp, +					   &head->dissector, +					   &mask.key, +					   fnew); +		if (err) +			goto errout; +	}  	if (fold) { -		rhashtable_remove_fast(&head->ht, &fold->ht_node, -				       head->ht_params); -		fl_hw_destroy_filter(tp, (unsigned long)fold); +		if (!tc_skip_sw(fold->flags)) +			rhashtable_remove_fast(&head->ht, &fold->ht_node, +					       head->ht_params); +		if (!tc_skip_hw(fold->flags)) +			fl_hw_destroy_filter(tp, fold);  	}  	*arg = (unsigned long) fnew; @@ -739,12 +882,10 @@ static int fl_delete(struct tcf_proto *tp, unsigned long arg)  	struct cls_fl_head *head = rtnl_dereference(tp->root);  	struct cls_fl_filter *f = (struct cls_fl_filter *) arg; -	rhashtable_remove_fast(&head->ht, &f->ht_node, -			       head->ht_params); -	list_del_rcu(&f->list); -	fl_hw_destroy_filter(tp, (unsigned long)f); -	tcf_unbind_filter(tp, &f->res); -	call_rcu(&f->rcu, fl_destroy_filter); +	if (!tc_skip_sw(f->flags)) +		rhashtable_remove_fast(&head->ht, &f->ht_node, +				       head->ht_params); +	__fl_delete(tp, f);  	return 0;  } @@ -807,6 +948,42 @@ static int fl_dump_key_vlan(struct sk_buff *skb,  	return 0;  } +static void fl_get_key_flag(u32 dissector_key, u32 dissector_mask, +			    u32 *flower_key, u32 *flower_mask, +			    u32 flower_flag_bit, u32 dissector_flag_bit) +{ +	if (dissector_mask & dissector_flag_bit) { +		*flower_mask |= flower_flag_bit; +		if (dissector_key & dissector_flag_bit) +			*flower_key |= flower_flag_bit; +	} +} + +static int fl_dump_key_flags(struct sk_buff *skb, u32 flags_key, u32 flags_mask) +{ +	u32 key, mask; +	__be32 _key, _mask; +	int err; + +	if (!memchr_inv(&flags_mask, 0, sizeof(flags_mask))) +		return 0; + +	key = 0; +	mask = 0; + +	fl_get_key_flag(flags_key, flags_mask, &key, &mask, +			TCA_FLOWER_KEY_FLAGS_IS_FRAGMENT, FLOW_DIS_IS_FRAGMENT); + +	_key = cpu_to_be32(key); +	_mask = cpu_to_be32(mask); + +	err = nla_put(skb, TCA_FLOWER_KEY_FLAGS, 4, &_key); +	if (err) +		return err; + +	return nla_put(skb, TCA_FLOWER_KEY_FLAGS_MASK, 4, &_mask); +} +  static int fl_dump(struct net *net, struct tcf_proto *tp, unsigned long fh,  		   struct sk_buff *skb, struct tcmsg *t)  { @@ -839,7 +1016,8 @@ static int fl_dump(struct net *net, struct tcf_proto *tp, unsigned long fh,  			goto nla_put_failure;  	} -	fl_hw_update_stats(tp, f); +	if (!tc_skip_hw(f->flags)) +		fl_hw_update_stats(tp, f);  	if (fl_dump_key_val(skb, key->eth.dst, TCA_FLOWER_KEY_ETH_DST,  			    mask->eth.dst, TCA_FLOWER_KEY_ETH_DST_MASK, @@ -895,6 +1073,36 @@ static int fl_dump(struct net *net, struct tcf_proto *tp, unsigned long fh,  				  &mask->tp.dst, TCA_FLOWER_KEY_UDP_DST_MASK,  				  sizeof(key->tp.dst))))  		goto nla_put_failure; +	else if (key->basic.ip_proto == IPPROTO_SCTP && +		 (fl_dump_key_val(skb, &key->tp.src, TCA_FLOWER_KEY_SCTP_SRC, +				  &mask->tp.src, TCA_FLOWER_KEY_SCTP_SRC_MASK, +				  sizeof(key->tp.src)) || +		  fl_dump_key_val(skb, &key->tp.dst, TCA_FLOWER_KEY_SCTP_DST, +				  &mask->tp.dst, TCA_FLOWER_KEY_SCTP_DST_MASK, +				  sizeof(key->tp.dst)))) +		goto nla_put_failure; +	else if (key->basic.n_proto == htons(ETH_P_IP) && +		 key->basic.ip_proto == IPPROTO_ICMP && +		 (fl_dump_key_val(skb, &key->icmp.type, +				  TCA_FLOWER_KEY_ICMPV4_TYPE, &mask->icmp.type, +				  TCA_FLOWER_KEY_ICMPV4_TYPE_MASK, +				  sizeof(key->icmp.type)) || +		  fl_dump_key_val(skb, &key->icmp.code, +				  TCA_FLOWER_KEY_ICMPV4_CODE, &mask->icmp.code, +				  TCA_FLOWER_KEY_ICMPV4_CODE_MASK, +				  sizeof(key->icmp.code)))) +		goto nla_put_failure; +	else if (key->basic.n_proto == htons(ETH_P_IPV6) && +		 key->basic.ip_proto == IPPROTO_ICMPV6 && +		 (fl_dump_key_val(skb, &key->icmp.type, +				  TCA_FLOWER_KEY_ICMPV6_TYPE, &mask->icmp.type, +				  TCA_FLOWER_KEY_ICMPV6_TYPE_MASK, +				  sizeof(key->icmp.type)) || +		  fl_dump_key_val(skb, &key->icmp.code, +				  TCA_FLOWER_KEY_ICMPV6_CODE, &mask->icmp.code, +				  TCA_FLOWER_KEY_ICMPV6_CODE_MASK, +				  sizeof(key->icmp.code)))) +		goto nla_put_failure;  	if (key->enc_control.addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS &&  	    (fl_dump_key_val(skb, &key->enc_ipv4.src, @@ -920,7 +1128,20 @@ static int fl_dump(struct net *net, struct tcf_proto *tp, unsigned long fh,  	if (fl_dump_key_val(skb, &key->enc_key_id, TCA_FLOWER_KEY_ENC_KEY_ID,  			    &mask->enc_key_id, TCA_FLOWER_UNSPEC, -			    sizeof(key->enc_key_id))) +			    sizeof(key->enc_key_id)) || +	    fl_dump_key_val(skb, &key->enc_tp.src, +			    TCA_FLOWER_KEY_ENC_UDP_SRC_PORT, +			    &mask->enc_tp.src, +			    TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK, +			    sizeof(key->enc_tp.src)) || +	    fl_dump_key_val(skb, &key->enc_tp.dst, +			    TCA_FLOWER_KEY_ENC_UDP_DST_PORT, +			    &mask->enc_tp.dst, +			    TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK, +			    sizeof(key->enc_tp.dst))) +		goto nla_put_failure; + +	if (fl_dump_key_flags(skb, key->control.flags, mask->control.flags))  		goto nla_put_failure;  	nla_put_u32(skb, TCA_FLOWER_FLAGS, f->flags); |