1487 lines · 35.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * net/core/fib_rules.c		Generic Routing Rules * * 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/inet_dscp.h>#include <net/sock.h>#include <net/fib_rules.h>#include <net/ip_tunnels.h>#include <linux/indirect_call_wrapper.h> #if defined(CONFIG_IPV6) && defined(CONFIG_IPV6_MULTIPLE_TABLES)#ifdef CONFIG_IP_MULTIPLE_TABLES#define INDIRECT_CALL_MT(f, f2, f1, ...) \	INDIRECT_CALL_INET(f, f2, f1, __VA_ARGS__)#else#define INDIRECT_CALL_MT(f, f2, f1, ...) INDIRECT_CALL_1(f, f2, __VA_ARGS__)#endif#elif defined(CONFIG_IP_MULTIPLE_TABLES)#define INDIRECT_CALL_MT(f, f2, f1, ...) INDIRECT_CALL_1(f, f1, __VA_ARGS__)#else#define INDIRECT_CALL_MT(f, f2, f1, ...) f(__VA_ARGS__)#endif static const struct fib_kuid_range fib_kuid_range_unset = {	KUIDT_INIT(0),	KUIDT_INIT(~0),}; bool fib_rule_matchall(const struct fib_rule *rule){	if (READ_ONCE(rule->iifindex) || READ_ONCE(rule->oifindex) ||	    rule->mark || rule->tun_id || rule->flags)		return false;	if (rule->suppress_ifgroup != -1 || rule->suppress_prefixlen != -1)		return false;	if (!uid_eq(rule->uid_range.start, fib_kuid_range_unset.start) ||	    !uid_eq(rule->uid_range.end, fib_kuid_range_unset.end))		return false;	if (fib_rule_port_range_set(&rule->sport_range))		return false;	if (fib_rule_port_range_set(&rule->dport_range))		return false;	return true;} int fib_default_rule_add(struct fib_rules_ops *ops,			 u32 pref, u32 table){	struct fib_rule *r; 	r = kzalloc(ops->rule_size, GFP_KERNEL_ACCOUNT);	if (r == NULL)		return -ENOMEM; 	refcount_set(&r->refcnt, 1);	r->action = FR_ACT_TO_TBL;	r->pref = pref;	r->table = table;	r->proto = RTPROT_KERNEL;	r->fr_net = ops->fro_net;	r->uid_range = fib_kuid_range_unset; 	r->suppress_prefixlen = -1;	r->suppress_ifgroup = -1; 	/* The lock is not required here, the list in unreachable	 * at the moment this function is called */	list_add_tail(&r->list, &ops->rules_list);	return 0;} static 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;} 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(const 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; 	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);	mutex_init(&ops->lock);	ops->fro_net = net; 	err = __fib_rules_register(ops);	if (err) {		kfree(ops);		ops = ERR_PTR(err);	} 	return ops;} 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);		if (ops->delete)			ops->delete(rule);		fib_rule_put(rule);	}} 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);	spin_unlock(&net->rules_mod_lock); 	fib_rules_cleanup_ops(ops);	mutex_destroy(&ops->lock);	kfree_rcu(ops, rcu);} static int uid_range_set(struct fib_kuid_range *range){	return uid_valid(range->start) && uid_valid(range->end);} static struct fib_kuid_range nla_get_kuid_range(struct nlattr **tb){	struct fib_rule_uid_range *in;	struct fib_kuid_range out; 	in = (struct fib_rule_uid_range *)nla_data(tb[FRA_UID_RANGE]); 	out.start = make_kuid(current_user_ns(), in->start);	out.end = make_kuid(current_user_ns(), in->end); 	return out;} static int nla_put_uid_range(struct sk_buff *skb, struct fib_kuid_range *range){	struct fib_rule_uid_range out = {		from_kuid_munged(current_user_ns(), range->start),		from_kuid_munged(current_user_ns(), range->end)	}; 	return nla_put(skb, FRA_UID_RANGE, sizeof(out), &out);} static int nla_get_port_range(struct nlattr *pattr,			      struct fib_rule_port_range *port_range){	const struct fib_rule_port_range *pr = nla_data(pattr); 	if (!fib_rule_port_range_valid(pr))		return -EINVAL; 	port_range->start = pr->start;	port_range->end = pr->end; 	return 0;} static int nla_put_port_range(struct sk_buff *skb, int attrtype,			      struct fib_rule_port_range *range){	return nla_put(skb, attrtype, sizeof(*range), range);} static bool fib_rule_iif_match(const struct fib_rule *rule, int iifindex,			       const struct flowi *fl){	u8 iif_is_l3_master = READ_ONCE(rule->iif_is_l3_master); 	return iif_is_l3_master ? l3mdev_fib_rule_iif_match(fl, iifindex) :				  fl->flowi_iif == iifindex;} static bool fib_rule_oif_match(const struct fib_rule *rule, int oifindex,			       const struct flowi *fl){	u8 oif_is_l3_master = READ_ONCE(rule->oif_is_l3_master); 	return oif_is_l3_master ? l3mdev_fib_rule_oif_match(fl, oifindex) :				  fl->flowi_oif == oifindex;} static int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops,			  struct flowi *fl, int flags,			  struct fib_lookup_arg *arg){	int iifindex, oifindex, ret = 0; 	iifindex = READ_ONCE(rule->iifindex);	if (iifindex && !fib_rule_iif_match(rule, iifindex, fl))		goto out; 	oifindex = READ_ONCE(rule->oifindex);	if (oifindex && !fib_rule_oif_match(rule, oifindex, fl))		goto out; 	if ((rule->mark ^ fl->flowi_mark) & rule->mark_mask)		goto out; 	if (rule->tun_id && (rule->tun_id != fl->flowi_tun_key.tun_id))		goto out; 	if (rule->l3mdev && !l3mdev_fib_rule_match(rule->fr_net, fl, arg))		goto out; 	if (uid_lt(fl->flowi_uid, rule->uid_range.start) ||	    uid_gt(fl->flowi_uid, rule->uid_range.end))		goto out; 	ret = INDIRECT_CALL_MT(ops->match,			       fib6_rule_match,			       fib4_rule_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, arg))			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 = INDIRECT_CALL_MT(ops->action,					       fib6_rule_action,					       fib4_rule_action,					       rule, fl, flags, arg); 		if (!err && ops->suppress && INDIRECT_CALL_MT(ops->suppress,							      fib6_rule_suppress,							      fib4_rule_suppress,							      rule, flags, arg))			continue; 		if (err != -EAGAIN) {			if ((arg->flags & FIB_LOOKUP_NOREF) ||			    likely(fib_rule_get_safe(rule))) {				arg->rule = rule;				goto out;			}			break;		}	} 	err = -ESRCH;out:	rcu_read_unlock(); 	return err;} static int call_fib_rule_notifier(struct notifier_block *nb,				  enum fib_event_type event_type,				  struct fib_rule *rule, int family,				  struct netlink_ext_ack *extack){	struct fib_rule_notifier_info info = {		.info.family = family,		.info.extack = extack,		.rule = rule,	}; 	return call_fib_notifier(nb, event_type, &info.info);} static int call_fib_rule_notifiers(struct net *net,				   enum fib_event_type event_type,				   struct fib_rule *rule,				   struct fib_rules_ops *ops,				   struct netlink_ext_ack *extack){	struct fib_rule_notifier_info info = {		.info.family = ops->family,		.info.extack = extack,		.rule = rule,	}; 	lockdep_assert_held(&ops->lock); 	/* Paired with READ_ONCE() in fib_rules_seq() */	WRITE_ONCE(ops->fib_rules_seq, ops->fib_rules_seq + 1);	return call_fib_notifiers(net, event_type, &info.info);} /* Called with rcu_read_lock() */int fib_rules_dump(struct net *net, struct notifier_block *nb, int family,		   struct netlink_ext_ack *extack){	struct fib_rules_ops *ops;	struct fib_rule *rule;	int err = 0; 	ops = lookup_rules_ops(net, family);	if (!ops)		return -EAFNOSUPPORT;	list_for_each_entry_rcu(rule, &ops->rules_list, list) {		if (!fib_rule_get_safe(rule))			continue; 		err = call_fib_rule_notifier(nb, FIB_EVENT_RULE_ADD,					     rule, family, extack);		fib_rule_put(rule);		if (err)			break;	}	rules_ops_put(ops); 	return err;} unsigned int fib_rules_seq_read(const struct net *net, int family){	unsigned int fib_rules_seq;	struct fib_rules_ops *ops; 	ops = lookup_rules_ops(net, family);	if (!ops)		return 0;	/* Paired with WRITE_ONCE() in call_fib_rule_notifiers() */	fib_rules_seq = READ_ONCE(ops->fib_rules_seq);	rules_ops_put(ops); 	return fib_rules_seq;} static struct fib_rule *rule_find(struct fib_rules_ops *ops,				  struct fib_rule_hdr *frh,				  struct nlattr **tb,				  struct fib_rule *rule,				  bool user_priority){	struct fib_rule *r; 	list_for_each_entry(r, &ops->rules_list, list) {		if (rule->action && r->action != rule->action)			continue; 		if (rule->table && r->table != rule->table)			continue; 		if (user_priority && r->pref != rule->pref)			continue; 		if (rule->iifname[0] &&		    memcmp(r->iifname, rule->iifname, IFNAMSIZ))			continue; 		if (rule->oifname[0] &&		    memcmp(r->oifname, rule->oifname, IFNAMSIZ))			continue; 		if (rule->mark && r->mark != rule->mark)			continue; 		if (rule->suppress_ifgroup != -1 &&		    r->suppress_ifgroup != rule->suppress_ifgroup)			continue; 		if (rule->suppress_prefixlen != -1 &&		    r->suppress_prefixlen != rule->suppress_prefixlen)			continue; 		if (rule->mark_mask && r->mark_mask != rule->mark_mask)			continue; 		if (rule->tun_id && r->tun_id != rule->tun_id)			continue; 		if (rule->l3mdev && r->l3mdev != rule->l3mdev)			continue; 		if (uid_range_set(&rule->uid_range) &&		    (!uid_eq(r->uid_range.start, rule->uid_range.start) ||		    !uid_eq(r->uid_range.end, rule->uid_range.end)))			continue; 		if (rule->ip_proto && r->ip_proto != rule->ip_proto)			continue; 		if (rule->proto && r->proto != rule->proto)			continue; 		if (fib_rule_port_range_set(&rule->sport_range) &&		    !fib_rule_port_range_compare(&r->sport_range,						 &rule->sport_range))			continue; 

Lines 1–500 of 1487.