768 lines · 16.9 KB · C
RawDownload
/* * net/core/fib_rules.c		Generic Routing Rules * *	This program is free software; you can redistribute it and/or *	modify it under the terms of the GNU General Public License as *	published by the Free Software Foundation, version 2. * * Authors:	Thomas Graf <tgraf@suug.ch> */ #include <linux/types.h>#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> int fib_default_rule_add(struct fib_rules_ops *ops,			 u32 pref, u32 table, u32 flags){	struct fib_rule *r; 	r = kzalloc(ops->rule_size, GFP_KERNEL);	if (r == NULL)		return -ENOMEM; 	atomic_set(&r->refcnt, 1);	r->action = FR_ACT_TO_TBL;	r->pref = pref;	r->table = table;	r->flags = flags;	r->fr_net = hold_net(ops->fro_net); 	/* 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);	return 0;}EXPORT_SYMBOL(fib_default_rule_add); u32 fib_default_rule_pref(struct fib_rules_ops *ops){	struct list_head *pos;	struct fib_rule *rule; 	if (!list_empty(&ops->rules_list)) {		pos = ops->rules_list.next;		if (pos->next != &ops->rules_list) {			rule = list_entry(pos->next, struct fib_rule, list);			if (rule->pref)				return rule->pref - 1;		}	} 	return 0;}EXPORT_SYMBOL(fib_default_rule_pref); static void notify_rule_change(int event, struct fib_rule *rule,			       struct fib_rules_ops *ops, struct nlmsghdr *nlh,			       u32 pid); static struct fib_rules_ops *lookup_rules_ops(struct net *net, int family){	struct fib_rules_ops *ops; 	rcu_read_lock();	list_for_each_entry_rcu(ops, &net->rules_ops, list) {		if (ops->family == family) {			if (!try_module_get(ops->owner))				ops = NULL;			rcu_read_unlock();			return ops;		}	}	rcu_read_unlock(); 	return NULL;} static void rules_ops_put(struct fib_rules_ops *ops){	if (ops)		module_put(ops->owner);} static void flush_route_cache(struct fib_rules_ops *ops){	if (ops->flush_cache)		ops->flush_cache(ops);} static int __fib_rules_register(struct fib_rules_ops *ops){	int err = -EEXIST;	struct fib_rules_ops *o;	struct net *net; 	net = ops->fro_net; 	if (ops->rule_size < sizeof(struct fib_rule))		return -EINVAL; 	if (ops->match == NULL || ops->configure == NULL ||	    ops->compare == NULL || ops->fill == NULL ||	    ops->action == NULL)		return -EINVAL; 	spin_lock(&net->rules_mod_lock);	list_for_each_entry(o, &net->rules_ops, list)		if (ops->family == o->family)			goto errout; 	hold_net(net);	list_add_tail_rcu(&ops->list, &net->rules_ops);	err = 0;errout:	spin_unlock(&net->rules_mod_lock); 	return err;} struct fib_rules_ops *fib_rules_register(const struct fib_rules_ops *tmpl, struct net *net){	struct fib_rules_ops *ops;	int err; 	ops = kmemdup(tmpl, sizeof(*ops), GFP_KERNEL);	if (ops == NULL)		return ERR_PTR(-ENOMEM); 	INIT_LIST_HEAD(&ops->rules_list);	ops->fro_net = net; 	err = __fib_rules_register(ops);	if (err) {		kfree(ops);		ops = ERR_PTR(err);	} 	return ops;}EXPORT_SYMBOL_GPL(fib_rules_register); static void fib_rules_cleanup_ops(struct fib_rules_ops *ops){	struct fib_rule *rule, *tmp; 	list_for_each_entry_safe(rule, tmp, &ops->rules_list, list) {		list_del_rcu(&rule->list);		fib_rule_put(rule);	}} static void fib_rules_put_rcu(struct rcu_head *head){	struct fib_rules_ops *ops = container_of(head, struct fib_rules_ops, rcu);	struct net *net = ops->fro_net; 	release_net(net);	kfree(ops);} void fib_rules_unregister(struct fib_rules_ops *ops){	struct net *net = ops->fro_net; 	spin_lock(&net->rules_mod_lock);	list_del_rcu(&ops->list);	fib_rules_cleanup_ops(ops);	spin_unlock(&net->rules_mod_lock); 	call_rcu(&ops->rcu, fib_rules_put_rcu);}EXPORT_SYMBOL_GPL(fib_rules_unregister); static int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops,			  struct flowi *fl, int flags){	int ret = 0; 	if (rule->iifindex && (rule->iifindex != fl->flowi_iif))		goto out; 	if (rule->oifindex && (rule->oifindex != fl->flowi_oif))		goto out; 	if ((rule->mark ^ fl->flowi_mark) & rule->mark_mask)		goto out; 	ret = ops->match(rule, fl, flags);out:	return (rule->flags & FIB_RULE_INVERT) ? !ret : ret;} int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl,		     int flags, struct fib_lookup_arg *arg){	struct fib_rule *rule;	int err; 	rcu_read_lock(); 	list_for_each_entry_rcu(rule, &ops->rules_list, list) {jumped:		if (!fib_rule_match(rule, ops, fl, flags))			continue; 		if (rule->action == FR_ACT_GOTO) {			struct fib_rule *target; 			target = rcu_dereference(rule->ctarget);			if (target == NULL) {				continue;			} else {				rule = target;				goto jumped;			}		} else if (rule->action == FR_ACT_NOP)			continue;		else			err = ops->action(rule, fl, flags, arg); 		if (err != -EAGAIN) {			if ((arg->flags & FIB_LOOKUP_NOREF) ||			    likely(atomic_inc_not_zero(&rule->refcnt))) {				arg->rule = rule;				goto out;			}			break;		}	} 	err = -ESRCH;out:	rcu_read_unlock(); 	return err;}EXPORT_SYMBOL_GPL(fib_rules_lookup); static int validate_rulemsg(struct fib_rule_hdr *frh, struct nlattr **tb,			    struct fib_rules_ops *ops){	int err = -EINVAL; 	if (frh->src_len)		if (tb[FRA_SRC] == NULL ||		    frh->src_len > (ops->addr_size * 8) ||		    nla_len(tb[FRA_SRC]) != ops->addr_size)			goto errout; 	if (frh->dst_len)		if (tb[FRA_DST] == NULL ||		    frh->dst_len > (ops->addr_size * 8) ||		    nla_len(tb[FRA_DST]) != ops->addr_size)			goto errout; 	err = 0;errout:	return err;} static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg){	struct net *net = sock_net(skb->sk);	struct fib_rule_hdr *frh = nlmsg_data(nlh);	struct fib_rules_ops *ops = NULL;	struct fib_rule *rule, *r, *last = NULL;	struct nlattr *tb[FRA_MAX+1];	int err = -EINVAL, unresolved = 0; 	if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh)))		goto errout; 	ops = lookup_rules_ops(net, frh->family);	if (ops == NULL) {		err = -EAFNOSUPPORT;		goto errout;	} 	err = nlmsg_parse(nlh, sizeof(*frh), tb, FRA_MAX, ops->policy);	if (err < 0)		goto errout; 	err = validate_rulemsg(frh, tb, ops);	if (err < 0)		goto errout; 	rule = kzalloc(ops->rule_size, GFP_KERNEL);	if (rule == NULL) {		err = -ENOMEM;		goto errout;	}	rule->fr_net = hold_net(net); 	if (tb[FRA_PRIORITY])		rule->pref = nla_get_u32(tb[FRA_PRIORITY]); 	if (tb[FRA_IIFNAME]) {		struct net_device *dev; 		rule->iifindex = -1;		nla_strlcpy(rule->iifname, tb[FRA_IIFNAME], IFNAMSIZ);		dev = __dev_get_by_name(net, rule->iifname);		if (dev)			rule->iifindex = dev->ifindex;	} 	if (tb[FRA_OIFNAME]) {		struct net_device *dev; 		rule->oifindex = -1;		nla_strlcpy(rule->oifname, tb[FRA_OIFNAME], IFNAMSIZ);		dev = __dev_get_by_name(net, rule->oifname);		if (dev)			rule->oifindex = dev->ifindex;	} 	if (tb[FRA_FWMARK]) {		rule->mark = nla_get_u32(tb[FRA_FWMARK]);		if (rule->mark)			/* compatibility: if the mark value is non-zero all bits			 * are compared unless a mask is explicitly specified.			 */			rule->mark_mask = 0xFFFFFFFF;	} 	if (tb[FRA_FWMASK])		rule->mark_mask = nla_get_u32(tb[FRA_FWMASK]); 	rule->action = frh->action;	rule->flags = frh->flags;	rule->table = frh_get_table(frh, tb); 	if (!tb[FRA_PRIORITY] && ops->default_pref)		rule->pref = ops->default_pref(ops); 	err = -EINVAL;	if (tb[FRA_GOTO]) {		if (rule->action != FR_ACT_GOTO)			goto errout_free; 		rule->target = nla_get_u32(tb[FRA_GOTO]);		/* Backward jumps are prohibited to avoid endless loops */		if (rule->target <= rule->pref)			goto errout_free; 		list_for_each_entry(r, &ops->rules_list, list) {			if (r->pref == rule->target) {				RCU_INIT_POINTER(rule->ctarget, r);				break;			}		} 		if (rcu_dereference_protected(rule->ctarget, 1) == NULL)			unresolved = 1;	} else if (rule->action == FR_ACT_GOTO)		goto errout_free; 	err = ops->configure(rule, skb, frh, tb);	if (err < 0)		goto errout_free; 	list_for_each_entry(r, &ops->rules_list, list) {		if (r->pref > rule->pref)			break;		last = r;	} 	fib_rule_get(rule); 	if (last)		list_add_rcu(&rule->list, &last->list);	else		list_add_rcu(&rule->list, &ops->rules_list); 	if (ops->unresolved_rules) {		/*		 * There are unresolved goto rules in the list, check if		 * any of them are pointing to this new rule.		 */		list_for_each_entry(r, &ops->rules_list, list) {			if (r->action == FR_ACT_GOTO &&			    r->target == rule->pref &&			    rtnl_dereference(r->ctarget) == NULL) {				rcu_assign_pointer(r->ctarget, rule);				if (--ops->unresolved_rules == 0)					break;			}		}	} 	if (rule->action == FR_ACT_GOTO)		ops->nr_goto_rules++; 	if (unresolved)		ops->unresolved_rules++; 	notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).pid);	flush_route_cache(ops);	rules_ops_put(ops);	return 0; errout_free:	release_net(rule->fr_net);	kfree(rule);errout:	rules_ops_put(ops);	return err;} static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg){	struct net *net = sock_net(skb->sk);	struct fib_rule_hdr *frh = nlmsg_data(nlh);	struct fib_rules_ops *ops = NULL;	struct fib_rule *rule, *tmp;	struct nlattr *tb[FRA_MAX+1];	int err = -EINVAL; 	if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh)))		goto errout; 	ops = lookup_rules_ops(net, frh->family);	if (ops == NULL) {		err = -EAFNOSUPPORT;		goto errout;	} 	err = nlmsg_parse(nlh, sizeof(*frh), tb, FRA_MAX, ops->policy);	if (err < 0)		goto errout; 	err = validate_rulemsg(frh, tb, ops);	if (err < 0)		goto errout; 	list_for_each_entry(rule, &ops->rules_list, list) {		if (frh->action && (frh->action != rule->action))			continue; 		if (frh->table && (frh_get_table(frh, tb) != rule->table))			continue; 		if (tb[FRA_PRIORITY] &&		    (rule->pref != nla_get_u32(tb[FRA_PRIORITY])))			continue; 		if (tb[FRA_IIFNAME] &&		    nla_strcmp(tb[FRA_IIFNAME], rule->iifname))			continue; 		if (tb[FRA_OIFNAME] &&		    nla_strcmp(tb[FRA_OIFNAME], rule->oifname))			continue; 		if (tb[FRA_FWMARK] &&		    (rule->mark != nla_get_u32(tb[FRA_FWMARK])))			continue; 		if (tb[FRA_FWMASK] &&		    (rule->mark_mask != nla_get_u32(tb[FRA_FWMASK])))			continue; 		if (!ops->compare(rule, frh, tb))			continue; 		if (rule->flags & FIB_RULE_PERMANENT) {			err = -EPERM;			goto errout;		} 		list_del_rcu(&rule->list); 		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,		 * disable them. As this operation is eventually very		 * expensive, it is only performed if goto rules have		 * actually been added.		 */		if (ops->nr_goto_rules > 0) {			list_for_each_entry(tmp, &ops->rules_list, list) {				if (rtnl_dereference(tmp->ctarget) == rule) {					RCU_INIT_POINTER(tmp->ctarget, NULL);					ops->unresolved_rules++;				}			}		} 		notify_rule_change(RTM_DELRULE, rule, ops, nlh,

Lines 1–500 of 768.