diff options
Diffstat (limited to 'net/core/fib_rules.c')
| -rw-r--r-- | net/core/fib_rules.c | 112 | 
1 files changed, 76 insertions, 36 deletions
diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 82a4369ae15..185c341fafb 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -12,6 +12,7 @@  #include <linux/kernel.h>  #include <linux/slab.h>  #include <linux/list.h> +#include <linux/module.h>  #include <net/net_namespace.h>  #include <net/sock.h>  #include <net/fib_rules.h> @@ -32,6 +33,9 @@ int fib_default_rule_add(struct fib_rules_ops *ops,  	r->flags = flags;  	r->fr_net = hold_net(ops->fro_net); +	r->suppress_prefixlen = -1; +	r->suppress_ifgroup = -1; +  	/* The lock is not required here, the list in unreacheable  	 * at the moment this function is called */  	list_add_tail(&r->list, &ops->rules_list); @@ -150,6 +154,8 @@ static void fib_rules_cleanup_ops(struct fib_rules_ops *ops)  	list_for_each_entry_safe(rule, tmp, &ops->rules_list, list) {  		list_del_rcu(&rule->list); +		if (ops->delete) +			ops->delete(rule);  		fib_rule_put(rule);  	}  } @@ -181,14 +187,13 @@ static int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops,  {  	int ret = 0; -	if (rule->iifindex && (rule->iifindex != fl->iif) && -	    !(fl->flags & FLOWI_FLAG_MATCH_ANY_IIF)) +	if (rule->iifindex && (rule->iifindex != fl->flowi_iif))  		goto out; -	if (rule->oifindex && (rule->oifindex != fl->oif)) +	if (rule->oifindex && (rule->oifindex != fl->flowi_oif))  		goto out; -	if ((rule->mark ^ fl->mark) & rule->mark_mask) +	if ((rule->mark ^ fl->flowi_mark) & rule->mark_mask)  		goto out;  	ret = ops->match(rule, fl, flags); @@ -224,6 +229,9 @@ jumped:  		else  			err = ops->action(rule, fl, flags, arg); +		if (!err && ops->suppress && ops->suppress(rule, arg)) +			continue; +  		if (err != -EAGAIN) {  			if ((arg->flags & FIB_LOOKUP_NOREF) ||  			    likely(atomic_inc_not_zero(&rule->refcnt))) { @@ -264,7 +272,7 @@ errout:  	return err;  } -static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) +static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh)  {  	struct net *net = sock_net(skb->sk);  	struct fib_rule_hdr *frh = nlmsg_data(nlh); @@ -335,6 +343,15 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)  	rule->action = frh->action;  	rule->flags = frh->flags;  	rule->table = frh_get_table(frh, tb); +	if (tb[FRA_SUPPRESS_PREFIXLEN]) +		rule->suppress_prefixlen = nla_get_u32(tb[FRA_SUPPRESS_PREFIXLEN]); +	else +		rule->suppress_prefixlen = -1; + +	if (tb[FRA_SUPPRESS_IFGROUP]) +		rule->suppress_ifgroup = nla_get_u32(tb[FRA_SUPPRESS_IFGROUP]); +	else +		rule->suppress_ifgroup = -1;  	if (!tb[FRA_PRIORITY] && ops->default_pref)  		rule->pref = ops->default_pref(ops); @@ -385,8 +402,8 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)  		 */  		list_for_each_entry(r, &ops->rules_list, list) {  			if (r->action == FR_ACT_GOTO && -			    r->target == rule->pref) { -				BUG_ON(rtnl_dereference(r->ctarget) != NULL); +			    r->target == rule->pref && +			    rtnl_dereference(r->ctarget) == NULL) {  				rcu_assign_pointer(r->ctarget, rule);  				if (--ops->unresolved_rules == 0)  					break; @@ -400,7 +417,7 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)  	if (unresolved)  		ops->unresolved_rules++; -	notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).pid); +	notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).portid);  	flush_route_cache(ops);  	rules_ops_put(ops);  	return 0; @@ -413,7 +430,7 @@ errout:  	return err;  } -static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) +static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh)  {  	struct net *net = sock_net(skb->sk);  	struct fib_rule_hdr *frh = nlmsg_data(nlh); @@ -443,7 +460,8 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)  		if (frh->action && (frh->action != rule->action))  			continue; -		if (frh->table && (frh_get_table(frh, tb) != rule->table)) +		if (frh_get_table(frh, tb) && +		    (frh_get_table(frh, tb) != rule->table))  			continue;  		if (tb[FRA_PRIORITY] && @@ -476,8 +494,11 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)  		list_del_rcu(&rule->list); -		if (rule->action == FR_ACT_GOTO) +		if (rule->action == FR_ACT_GOTO) {  			ops->nr_goto_rules--; +			if (rtnl_dereference(rule->ctarget) == NULL) +				ops->unresolved_rules--; +		}  		/*  		 * Check if this rule is a target to any of them. If so, @@ -488,14 +509,16 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)  		if (ops->nr_goto_rules > 0) {  			list_for_each_entry(tmp, &ops->rules_list, list) {  				if (rtnl_dereference(tmp->ctarget) == rule) { -					rcu_assign_pointer(tmp->ctarget, NULL); +					RCU_INIT_POINTER(tmp->ctarget, NULL);  					ops->unresolved_rules++;  				}  			}  		}  		notify_rule_change(RTM_DELRULE, rule, ops, nlh, -				   NETLINK_CB(skb).pid); +				   NETLINK_CB(skb).portid); +		if (ops->delete) +			ops->delete(rule);  		fib_rule_put(rule);  		flush_route_cache(ops);  		rules_ops_put(ops); @@ -516,6 +539,8 @@ static inline size_t fib_rule_nlmsg_size(struct fib_rules_ops *ops,  			 + nla_total_size(IFNAMSIZ) /* FRA_OIFNAME */  			 + nla_total_size(4) /* FRA_PRIORITY */  			 + nla_total_size(4) /* FRA_TABLE */ +			 + nla_total_size(4) /* FRA_SUPPRESS_PREFIXLEN */ +			 + nla_total_size(4) /* FRA_SUPPRESS_IFGROUP */  			 + nla_total_size(4) /* FRA_FWMARK */  			 + nla_total_size(4); /* FRA_FWMASK */ @@ -539,41 +564,47 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule,  	frh = nlmsg_data(nlh);  	frh->family = ops->family;  	frh->table = rule->table; -	NLA_PUT_U32(skb, FRA_TABLE, rule->table); +	if (nla_put_u32(skb, FRA_TABLE, rule->table)) +		goto nla_put_failure; +	if (nla_put_u32(skb, FRA_SUPPRESS_PREFIXLEN, rule->suppress_prefixlen)) +		goto nla_put_failure;  	frh->res1 = 0;  	frh->res2 = 0;  	frh->action = rule->action;  	frh->flags = rule->flags;  	if (rule->action == FR_ACT_GOTO && -	    rcu_dereference_raw(rule->ctarget) == NULL) +	    rcu_access_pointer(rule->ctarget) == NULL)  		frh->flags |= FIB_RULE_UNRESOLVED;  	if (rule->iifname[0]) { -		NLA_PUT_STRING(skb, FRA_IIFNAME, rule->iifname); - +		if (nla_put_string(skb, FRA_IIFNAME, rule->iifname)) +			goto nla_put_failure;  		if (rule->iifindex == -1)  			frh->flags |= FIB_RULE_IIF_DETACHED;  	}  	if (rule->oifname[0]) { -		NLA_PUT_STRING(skb, FRA_OIFNAME, rule->oifname); - +		if (nla_put_string(skb, FRA_OIFNAME, rule->oifname)) +			goto nla_put_failure;  		if (rule->oifindex == -1)  			frh->flags |= FIB_RULE_OIF_DETACHED;  	} -	if (rule->pref) -		NLA_PUT_U32(skb, FRA_PRIORITY, rule->pref); - -	if (rule->mark) -		NLA_PUT_U32(skb, FRA_FWMARK, rule->mark); - -	if (rule->mark_mask || rule->mark) -		NLA_PUT_U32(skb, FRA_FWMASK, rule->mark_mask); +	if ((rule->pref && +	     nla_put_u32(skb, FRA_PRIORITY, rule->pref)) || +	    (rule->mark && +	     nla_put_u32(skb, FRA_FWMARK, rule->mark)) || +	    ((rule->mark_mask || rule->mark) && +	     nla_put_u32(skb, FRA_FWMASK, rule->mark_mask)) || +	    (rule->target && +	     nla_put_u32(skb, FRA_GOTO, rule->target))) +		goto nla_put_failure; -	if (rule->target) -		NLA_PUT_U32(skb, FRA_GOTO, rule->target); +	if (rule->suppress_ifgroup != -1) { +		if (nla_put_u32(skb, FRA_SUPPRESS_IFGROUP, rule->suppress_ifgroup)) +			goto nla_put_failure; +	}  	if (ops->fill(rule, skb, frh) < 0)  		goto nla_put_failure; @@ -591,17 +622,19 @@ static int dump_rules(struct sk_buff *skb, struct netlink_callback *cb,  	int idx = 0;  	struct fib_rule *rule; -	list_for_each_entry(rule, &ops->rules_list, list) { +	rcu_read_lock(); +	list_for_each_entry_rcu(rule, &ops->rules_list, list) {  		if (idx < cb->args[1])  			goto skip; -		if (fib_nl_fill_rule(skb, rule, NETLINK_CB(cb->skb).pid, +		if (fib_nl_fill_rule(skb, rule, NETLINK_CB(cb->skb).portid,  				     cb->nlh->nlmsg_seq, RTM_NEWRULE,  				     NLM_F_MULTI, ops) < 0)  			break;  skip:  		idx++;  	} +	rcu_read_unlock();  	cb->args[1] = idx;  	rules_ops_put(ops); @@ -698,9 +731,9 @@ static void detach_rules(struct list_head *rules, struct net_device *dev)  static int fib_rules_event(struct notifier_block *this, unsigned long event, -			    void *ptr) +			   void *ptr)  { -	struct net_device *dev = ptr; +	struct net_device *dev = netdev_notifier_info_to_dev(ptr);  	struct net *net = dev_net(dev);  	struct fib_rules_ops *ops; @@ -712,6 +745,13 @@ static int fib_rules_event(struct notifier_block *this, unsigned long event,  			attach_rules(&ops->rules_list, dev);  		break; +	case NETDEV_CHANGENAME: +		list_for_each_entry(ops, &net->rules_ops, list) { +			detach_rules(&ops->rules_list, dev); +			attach_rules(&ops->rules_list, dev); +		} +		break; +  	case NETDEV_UNREGISTER:  		list_for_each_entry(ops, &net->rules_ops, list)  			detach_rules(&ops->rules_list, dev); @@ -739,9 +779,9 @@ static struct pernet_operations fib_rules_net_ops = {  static int __init fib_rules_init(void)  {  	int err; -	rtnl_register(PF_UNSPEC, RTM_NEWRULE, fib_nl_newrule, NULL); -	rtnl_register(PF_UNSPEC, RTM_DELRULE, fib_nl_delrule, NULL); -	rtnl_register(PF_UNSPEC, RTM_GETRULE, NULL, fib_nl_dumprule); +	rtnl_register(PF_UNSPEC, RTM_NEWRULE, fib_nl_newrule, NULL, NULL); +	rtnl_register(PF_UNSPEC, RTM_DELRULE, fib_nl_delrule, NULL, NULL); +	rtnl_register(PF_UNSPEC, RTM_GETRULE, NULL, fib_nl_dumprule, NULL);  	err = register_pernet_subsys(&fib_rules_net_ops);  	if (err < 0)  | 
