162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * net/core/fib_rules.c		Generic Routing Rules
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:	Thomas Graf <tgraf@suug.ch>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/types.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/list.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <net/net_namespace.h>
1462306a36Sopenharmony_ci#include <net/sock.h>
1562306a36Sopenharmony_ci#include <net/fib_rules.h>
1662306a36Sopenharmony_ci#include <net/ip_tunnels.h>
1762306a36Sopenharmony_ci#include <linux/indirect_call_wrapper.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#if defined(CONFIG_IPV6) && defined(CONFIG_IPV6_MULTIPLE_TABLES)
2062306a36Sopenharmony_ci#ifdef CONFIG_IP_MULTIPLE_TABLES
2162306a36Sopenharmony_ci#define INDIRECT_CALL_MT(f, f2, f1, ...) \
2262306a36Sopenharmony_ci	INDIRECT_CALL_INET(f, f2, f1, __VA_ARGS__)
2362306a36Sopenharmony_ci#else
2462306a36Sopenharmony_ci#define INDIRECT_CALL_MT(f, f2, f1, ...) INDIRECT_CALL_1(f, f2, __VA_ARGS__)
2562306a36Sopenharmony_ci#endif
2662306a36Sopenharmony_ci#elif defined(CONFIG_IP_MULTIPLE_TABLES)
2762306a36Sopenharmony_ci#define INDIRECT_CALL_MT(f, f2, f1, ...) INDIRECT_CALL_1(f, f1, __VA_ARGS__)
2862306a36Sopenharmony_ci#else
2962306a36Sopenharmony_ci#define INDIRECT_CALL_MT(f, f2, f1, ...) f(__VA_ARGS__)
3062306a36Sopenharmony_ci#endif
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic const struct fib_kuid_range fib_kuid_range_unset = {
3362306a36Sopenharmony_ci	KUIDT_INIT(0),
3462306a36Sopenharmony_ci	KUIDT_INIT(~0),
3562306a36Sopenharmony_ci};
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cibool fib_rule_matchall(const struct fib_rule *rule)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	if (rule->iifindex || rule->oifindex || rule->mark || rule->tun_id ||
4062306a36Sopenharmony_ci	    rule->flags)
4162306a36Sopenharmony_ci		return false;
4262306a36Sopenharmony_ci	if (rule->suppress_ifgroup != -1 || rule->suppress_prefixlen != -1)
4362306a36Sopenharmony_ci		return false;
4462306a36Sopenharmony_ci	if (!uid_eq(rule->uid_range.start, fib_kuid_range_unset.start) ||
4562306a36Sopenharmony_ci	    !uid_eq(rule->uid_range.end, fib_kuid_range_unset.end))
4662306a36Sopenharmony_ci		return false;
4762306a36Sopenharmony_ci	if (fib_rule_port_range_set(&rule->sport_range))
4862306a36Sopenharmony_ci		return false;
4962306a36Sopenharmony_ci	if (fib_rule_port_range_set(&rule->dport_range))
5062306a36Sopenharmony_ci		return false;
5162306a36Sopenharmony_ci	return true;
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(fib_rule_matchall);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ciint fib_default_rule_add(struct fib_rules_ops *ops,
5662306a36Sopenharmony_ci			 u32 pref, u32 table, u32 flags)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	struct fib_rule *r;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	r = kzalloc(ops->rule_size, GFP_KERNEL_ACCOUNT);
6162306a36Sopenharmony_ci	if (r == NULL)
6262306a36Sopenharmony_ci		return -ENOMEM;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	refcount_set(&r->refcnt, 1);
6562306a36Sopenharmony_ci	r->action = FR_ACT_TO_TBL;
6662306a36Sopenharmony_ci	r->pref = pref;
6762306a36Sopenharmony_ci	r->table = table;
6862306a36Sopenharmony_ci	r->flags = flags;
6962306a36Sopenharmony_ci	r->proto = RTPROT_KERNEL;
7062306a36Sopenharmony_ci	r->fr_net = ops->fro_net;
7162306a36Sopenharmony_ci	r->uid_range = fib_kuid_range_unset;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	r->suppress_prefixlen = -1;
7462306a36Sopenharmony_ci	r->suppress_ifgroup = -1;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	/* The lock is not required here, the list in unreacheable
7762306a36Sopenharmony_ci	 * at the moment this function is called */
7862306a36Sopenharmony_ci	list_add_tail(&r->list, &ops->rules_list);
7962306a36Sopenharmony_ci	return 0;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ciEXPORT_SYMBOL(fib_default_rule_add);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic u32 fib_default_rule_pref(struct fib_rules_ops *ops)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct list_head *pos;
8662306a36Sopenharmony_ci	struct fib_rule *rule;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if (!list_empty(&ops->rules_list)) {
8962306a36Sopenharmony_ci		pos = ops->rules_list.next;
9062306a36Sopenharmony_ci		if (pos->next != &ops->rules_list) {
9162306a36Sopenharmony_ci			rule = list_entry(pos->next, struct fib_rule, list);
9262306a36Sopenharmony_ci			if (rule->pref)
9362306a36Sopenharmony_ci				return rule->pref - 1;
9462306a36Sopenharmony_ci		}
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	return 0;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic void notify_rule_change(int event, struct fib_rule *rule,
10162306a36Sopenharmony_ci			       struct fib_rules_ops *ops, struct nlmsghdr *nlh,
10262306a36Sopenharmony_ci			       u32 pid);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistatic struct fib_rules_ops *lookup_rules_ops(struct net *net, int family)
10562306a36Sopenharmony_ci{
10662306a36Sopenharmony_ci	struct fib_rules_ops *ops;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	rcu_read_lock();
10962306a36Sopenharmony_ci	list_for_each_entry_rcu(ops, &net->rules_ops, list) {
11062306a36Sopenharmony_ci		if (ops->family == family) {
11162306a36Sopenharmony_ci			if (!try_module_get(ops->owner))
11262306a36Sopenharmony_ci				ops = NULL;
11362306a36Sopenharmony_ci			rcu_read_unlock();
11462306a36Sopenharmony_ci			return ops;
11562306a36Sopenharmony_ci		}
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci	rcu_read_unlock();
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	return NULL;
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic void rules_ops_put(struct fib_rules_ops *ops)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	if (ops)
12562306a36Sopenharmony_ci		module_put(ops->owner);
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic void flush_route_cache(struct fib_rules_ops *ops)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	if (ops->flush_cache)
13162306a36Sopenharmony_ci		ops->flush_cache(ops);
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic int __fib_rules_register(struct fib_rules_ops *ops)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	int err = -EEXIST;
13762306a36Sopenharmony_ci	struct fib_rules_ops *o;
13862306a36Sopenharmony_ci	struct net *net;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	net = ops->fro_net;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (ops->rule_size < sizeof(struct fib_rule))
14362306a36Sopenharmony_ci		return -EINVAL;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	if (ops->match == NULL || ops->configure == NULL ||
14662306a36Sopenharmony_ci	    ops->compare == NULL || ops->fill == NULL ||
14762306a36Sopenharmony_ci	    ops->action == NULL)
14862306a36Sopenharmony_ci		return -EINVAL;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	spin_lock(&net->rules_mod_lock);
15162306a36Sopenharmony_ci	list_for_each_entry(o, &net->rules_ops, list)
15262306a36Sopenharmony_ci		if (ops->family == o->family)
15362306a36Sopenharmony_ci			goto errout;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	list_add_tail_rcu(&ops->list, &net->rules_ops);
15662306a36Sopenharmony_ci	err = 0;
15762306a36Sopenharmony_cierrout:
15862306a36Sopenharmony_ci	spin_unlock(&net->rules_mod_lock);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	return err;
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistruct fib_rules_ops *
16462306a36Sopenharmony_cifib_rules_register(const struct fib_rules_ops *tmpl, struct net *net)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	struct fib_rules_ops *ops;
16762306a36Sopenharmony_ci	int err;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	ops = kmemdup(tmpl, sizeof(*ops), GFP_KERNEL);
17062306a36Sopenharmony_ci	if (ops == NULL)
17162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	INIT_LIST_HEAD(&ops->rules_list);
17462306a36Sopenharmony_ci	ops->fro_net = net;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	err = __fib_rules_register(ops);
17762306a36Sopenharmony_ci	if (err) {
17862306a36Sopenharmony_ci		kfree(ops);
17962306a36Sopenharmony_ci		ops = ERR_PTR(err);
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	return ops;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(fib_rules_register);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic void fib_rules_cleanup_ops(struct fib_rules_ops *ops)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	struct fib_rule *rule, *tmp;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	list_for_each_entry_safe(rule, tmp, &ops->rules_list, list) {
19162306a36Sopenharmony_ci		list_del_rcu(&rule->list);
19262306a36Sopenharmony_ci		if (ops->delete)
19362306a36Sopenharmony_ci			ops->delete(rule);
19462306a36Sopenharmony_ci		fib_rule_put(rule);
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_civoid fib_rules_unregister(struct fib_rules_ops *ops)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	struct net *net = ops->fro_net;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	spin_lock(&net->rules_mod_lock);
20362306a36Sopenharmony_ci	list_del_rcu(&ops->list);
20462306a36Sopenharmony_ci	spin_unlock(&net->rules_mod_lock);
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	fib_rules_cleanup_ops(ops);
20762306a36Sopenharmony_ci	kfree_rcu(ops, rcu);
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(fib_rules_unregister);
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic int uid_range_set(struct fib_kuid_range *range)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	return uid_valid(range->start) && uid_valid(range->end);
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic struct fib_kuid_range nla_get_kuid_range(struct nlattr **tb)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	struct fib_rule_uid_range *in;
21962306a36Sopenharmony_ci	struct fib_kuid_range out;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	in = (struct fib_rule_uid_range *)nla_data(tb[FRA_UID_RANGE]);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	out.start = make_kuid(current_user_ns(), in->start);
22462306a36Sopenharmony_ci	out.end = make_kuid(current_user_ns(), in->end);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	return out;
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic int nla_put_uid_range(struct sk_buff *skb, struct fib_kuid_range *range)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	struct fib_rule_uid_range out = {
23262306a36Sopenharmony_ci		from_kuid_munged(current_user_ns(), range->start),
23362306a36Sopenharmony_ci		from_kuid_munged(current_user_ns(), range->end)
23462306a36Sopenharmony_ci	};
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	return nla_put(skb, FRA_UID_RANGE, sizeof(out), &out);
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_cistatic int nla_get_port_range(struct nlattr *pattr,
24062306a36Sopenharmony_ci			      struct fib_rule_port_range *port_range)
24162306a36Sopenharmony_ci{
24262306a36Sopenharmony_ci	const struct fib_rule_port_range *pr = nla_data(pattr);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	if (!fib_rule_port_range_valid(pr))
24562306a36Sopenharmony_ci		return -EINVAL;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	port_range->start = pr->start;
24862306a36Sopenharmony_ci	port_range->end = pr->end;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	return 0;
25162306a36Sopenharmony_ci}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_cistatic int nla_put_port_range(struct sk_buff *skb, int attrtype,
25462306a36Sopenharmony_ci			      struct fib_rule_port_range *range)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	return nla_put(skb, attrtype, sizeof(*range), range);
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops,
26062306a36Sopenharmony_ci			  struct flowi *fl, int flags,
26162306a36Sopenharmony_ci			  struct fib_lookup_arg *arg)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	int ret = 0;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	if (rule->iifindex && (rule->iifindex != fl->flowi_iif))
26662306a36Sopenharmony_ci		goto out;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	if (rule->oifindex && (rule->oifindex != fl->flowi_oif))
26962306a36Sopenharmony_ci		goto out;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	if ((rule->mark ^ fl->flowi_mark) & rule->mark_mask)
27262306a36Sopenharmony_ci		goto out;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	if (rule->tun_id && (rule->tun_id != fl->flowi_tun_key.tun_id))
27562306a36Sopenharmony_ci		goto out;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	if (rule->l3mdev && !l3mdev_fib_rule_match(rule->fr_net, fl, arg))
27862306a36Sopenharmony_ci		goto out;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	if (uid_lt(fl->flowi_uid, rule->uid_range.start) ||
28162306a36Sopenharmony_ci	    uid_gt(fl->flowi_uid, rule->uid_range.end))
28262306a36Sopenharmony_ci		goto out;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	ret = INDIRECT_CALL_MT(ops->match,
28562306a36Sopenharmony_ci			       fib6_rule_match,
28662306a36Sopenharmony_ci			       fib4_rule_match,
28762306a36Sopenharmony_ci			       rule, fl, flags);
28862306a36Sopenharmony_ciout:
28962306a36Sopenharmony_ci	return (rule->flags & FIB_RULE_INVERT) ? !ret : ret;
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ciint fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl,
29362306a36Sopenharmony_ci		     int flags, struct fib_lookup_arg *arg)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	struct fib_rule *rule;
29662306a36Sopenharmony_ci	int err;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	rcu_read_lock();
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	list_for_each_entry_rcu(rule, &ops->rules_list, list) {
30162306a36Sopenharmony_cijumped:
30262306a36Sopenharmony_ci		if (!fib_rule_match(rule, ops, fl, flags, arg))
30362306a36Sopenharmony_ci			continue;
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci		if (rule->action == FR_ACT_GOTO) {
30662306a36Sopenharmony_ci			struct fib_rule *target;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci			target = rcu_dereference(rule->ctarget);
30962306a36Sopenharmony_ci			if (target == NULL) {
31062306a36Sopenharmony_ci				continue;
31162306a36Sopenharmony_ci			} else {
31262306a36Sopenharmony_ci				rule = target;
31362306a36Sopenharmony_ci				goto jumped;
31462306a36Sopenharmony_ci			}
31562306a36Sopenharmony_ci		} else if (rule->action == FR_ACT_NOP)
31662306a36Sopenharmony_ci			continue;
31762306a36Sopenharmony_ci		else
31862306a36Sopenharmony_ci			err = INDIRECT_CALL_MT(ops->action,
31962306a36Sopenharmony_ci					       fib6_rule_action,
32062306a36Sopenharmony_ci					       fib4_rule_action,
32162306a36Sopenharmony_ci					       rule, fl, flags, arg);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci		if (!err && ops->suppress && INDIRECT_CALL_MT(ops->suppress,
32462306a36Sopenharmony_ci							      fib6_rule_suppress,
32562306a36Sopenharmony_ci							      fib4_rule_suppress,
32662306a36Sopenharmony_ci							      rule, flags, arg))
32762306a36Sopenharmony_ci			continue;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci		if (err != -EAGAIN) {
33062306a36Sopenharmony_ci			if ((arg->flags & FIB_LOOKUP_NOREF) ||
33162306a36Sopenharmony_ci			    likely(refcount_inc_not_zero(&rule->refcnt))) {
33262306a36Sopenharmony_ci				arg->rule = rule;
33362306a36Sopenharmony_ci				goto out;
33462306a36Sopenharmony_ci			}
33562306a36Sopenharmony_ci			break;
33662306a36Sopenharmony_ci		}
33762306a36Sopenharmony_ci	}
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	err = -ESRCH;
34062306a36Sopenharmony_ciout:
34162306a36Sopenharmony_ci	rcu_read_unlock();
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	return err;
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(fib_rules_lookup);
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic int call_fib_rule_notifier(struct notifier_block *nb,
34862306a36Sopenharmony_ci				  enum fib_event_type event_type,
34962306a36Sopenharmony_ci				  struct fib_rule *rule, int family,
35062306a36Sopenharmony_ci				  struct netlink_ext_ack *extack)
35162306a36Sopenharmony_ci{
35262306a36Sopenharmony_ci	struct fib_rule_notifier_info info = {
35362306a36Sopenharmony_ci		.info.family = family,
35462306a36Sopenharmony_ci		.info.extack = extack,
35562306a36Sopenharmony_ci		.rule = rule,
35662306a36Sopenharmony_ci	};
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	return call_fib_notifier(nb, event_type, &info.info);
35962306a36Sopenharmony_ci}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cistatic int call_fib_rule_notifiers(struct net *net,
36262306a36Sopenharmony_ci				   enum fib_event_type event_type,
36362306a36Sopenharmony_ci				   struct fib_rule *rule,
36462306a36Sopenharmony_ci				   struct fib_rules_ops *ops,
36562306a36Sopenharmony_ci				   struct netlink_ext_ack *extack)
36662306a36Sopenharmony_ci{
36762306a36Sopenharmony_ci	struct fib_rule_notifier_info info = {
36862306a36Sopenharmony_ci		.info.family = ops->family,
36962306a36Sopenharmony_ci		.info.extack = extack,
37062306a36Sopenharmony_ci		.rule = rule,
37162306a36Sopenharmony_ci	};
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	ops->fib_rules_seq++;
37462306a36Sopenharmony_ci	return call_fib_notifiers(net, event_type, &info.info);
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci/* Called with rcu_read_lock() */
37862306a36Sopenharmony_ciint fib_rules_dump(struct net *net, struct notifier_block *nb, int family,
37962306a36Sopenharmony_ci		   struct netlink_ext_ack *extack)
38062306a36Sopenharmony_ci{
38162306a36Sopenharmony_ci	struct fib_rules_ops *ops;
38262306a36Sopenharmony_ci	struct fib_rule *rule;
38362306a36Sopenharmony_ci	int err = 0;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	ops = lookup_rules_ops(net, family);
38662306a36Sopenharmony_ci	if (!ops)
38762306a36Sopenharmony_ci		return -EAFNOSUPPORT;
38862306a36Sopenharmony_ci	list_for_each_entry_rcu(rule, &ops->rules_list, list) {
38962306a36Sopenharmony_ci		err = call_fib_rule_notifier(nb, FIB_EVENT_RULE_ADD,
39062306a36Sopenharmony_ci					     rule, family, extack);
39162306a36Sopenharmony_ci		if (err)
39262306a36Sopenharmony_ci			break;
39362306a36Sopenharmony_ci	}
39462306a36Sopenharmony_ci	rules_ops_put(ops);
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	return err;
39762306a36Sopenharmony_ci}
39862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(fib_rules_dump);
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ciunsigned int fib_rules_seq_read(struct net *net, int family)
40162306a36Sopenharmony_ci{
40262306a36Sopenharmony_ci	unsigned int fib_rules_seq;
40362306a36Sopenharmony_ci	struct fib_rules_ops *ops;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	ASSERT_RTNL();
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	ops = lookup_rules_ops(net, family);
40862306a36Sopenharmony_ci	if (!ops)
40962306a36Sopenharmony_ci		return 0;
41062306a36Sopenharmony_ci	fib_rules_seq = ops->fib_rules_seq;
41162306a36Sopenharmony_ci	rules_ops_put(ops);
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	return fib_rules_seq;
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(fib_rules_seq_read);
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_cistatic struct fib_rule *rule_find(struct fib_rules_ops *ops,
41862306a36Sopenharmony_ci				  struct fib_rule_hdr *frh,
41962306a36Sopenharmony_ci				  struct nlattr **tb,
42062306a36Sopenharmony_ci				  struct fib_rule *rule,
42162306a36Sopenharmony_ci				  bool user_priority)
42262306a36Sopenharmony_ci{
42362306a36Sopenharmony_ci	struct fib_rule *r;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	list_for_each_entry(r, &ops->rules_list, list) {
42662306a36Sopenharmony_ci		if (rule->action && r->action != rule->action)
42762306a36Sopenharmony_ci			continue;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci		if (rule->table && r->table != rule->table)
43062306a36Sopenharmony_ci			continue;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci		if (user_priority && r->pref != rule->pref)
43362306a36Sopenharmony_ci			continue;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci		if (rule->iifname[0] &&
43662306a36Sopenharmony_ci		    memcmp(r->iifname, rule->iifname, IFNAMSIZ))
43762306a36Sopenharmony_ci			continue;
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci		if (rule->oifname[0] &&
44062306a36Sopenharmony_ci		    memcmp(r->oifname, rule->oifname, IFNAMSIZ))
44162306a36Sopenharmony_ci			continue;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci		if (rule->mark && r->mark != rule->mark)
44462306a36Sopenharmony_ci			continue;
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci		if (rule->suppress_ifgroup != -1 &&
44762306a36Sopenharmony_ci		    r->suppress_ifgroup != rule->suppress_ifgroup)
44862306a36Sopenharmony_ci			continue;
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci		if (rule->suppress_prefixlen != -1 &&
45162306a36Sopenharmony_ci		    r->suppress_prefixlen != rule->suppress_prefixlen)
45262306a36Sopenharmony_ci			continue;
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci		if (rule->mark_mask && r->mark_mask != rule->mark_mask)
45562306a36Sopenharmony_ci			continue;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci		if (rule->tun_id && r->tun_id != rule->tun_id)
45862306a36Sopenharmony_ci			continue;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci		if (r->fr_net != rule->fr_net)
46162306a36Sopenharmony_ci			continue;
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci		if (rule->l3mdev && r->l3mdev != rule->l3mdev)
46462306a36Sopenharmony_ci			continue;
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci		if (uid_range_set(&rule->uid_range) &&
46762306a36Sopenharmony_ci		    (!uid_eq(r->uid_range.start, rule->uid_range.start) ||
46862306a36Sopenharmony_ci		    !uid_eq(r->uid_range.end, rule->uid_range.end)))
46962306a36Sopenharmony_ci			continue;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci		if (rule->ip_proto && r->ip_proto != rule->ip_proto)
47262306a36Sopenharmony_ci			continue;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci		if (rule->proto && r->proto != rule->proto)
47562306a36Sopenharmony_ci			continue;
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci		if (fib_rule_port_range_set(&rule->sport_range) &&
47862306a36Sopenharmony_ci		    !fib_rule_port_range_compare(&r->sport_range,
47962306a36Sopenharmony_ci						 &rule->sport_range))
48062306a36Sopenharmony_ci			continue;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci		if (fib_rule_port_range_set(&rule->dport_range) &&
48362306a36Sopenharmony_ci		    !fib_rule_port_range_compare(&r->dport_range,
48462306a36Sopenharmony_ci						 &rule->dport_range))
48562306a36Sopenharmony_ci			continue;
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci		if (!ops->compare(r, frh, tb))
48862306a36Sopenharmony_ci			continue;
48962306a36Sopenharmony_ci		return r;
49062306a36Sopenharmony_ci	}
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	return NULL;
49362306a36Sopenharmony_ci}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci#ifdef CONFIG_NET_L3_MASTER_DEV
49662306a36Sopenharmony_cistatic int fib_nl2rule_l3mdev(struct nlattr *nla, struct fib_rule *nlrule,
49762306a36Sopenharmony_ci			      struct netlink_ext_ack *extack)
49862306a36Sopenharmony_ci{
49962306a36Sopenharmony_ci	nlrule->l3mdev = nla_get_u8(nla);
50062306a36Sopenharmony_ci	if (nlrule->l3mdev != 1) {
50162306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid l3mdev attribute");
50262306a36Sopenharmony_ci		return -1;
50362306a36Sopenharmony_ci	}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	return 0;
50662306a36Sopenharmony_ci}
50762306a36Sopenharmony_ci#else
50862306a36Sopenharmony_cistatic int fib_nl2rule_l3mdev(struct nlattr *nla, struct fib_rule *nlrule,
50962306a36Sopenharmony_ci			      struct netlink_ext_ack *extack)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	NL_SET_ERR_MSG(extack, "l3mdev support is not enabled in kernel");
51262306a36Sopenharmony_ci	return -1;
51362306a36Sopenharmony_ci}
51462306a36Sopenharmony_ci#endif
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_cistatic int fib_nl2rule(struct sk_buff *skb, struct nlmsghdr *nlh,
51762306a36Sopenharmony_ci		       struct netlink_ext_ack *extack,
51862306a36Sopenharmony_ci		       struct fib_rules_ops *ops,
51962306a36Sopenharmony_ci		       struct nlattr *tb[],
52062306a36Sopenharmony_ci		       struct fib_rule **rule,
52162306a36Sopenharmony_ci		       bool *user_priority)
52262306a36Sopenharmony_ci{
52362306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
52462306a36Sopenharmony_ci	struct fib_rule_hdr *frh = nlmsg_data(nlh);
52562306a36Sopenharmony_ci	struct fib_rule *nlrule = NULL;
52662306a36Sopenharmony_ci	int err = -EINVAL;
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci	if (frh->src_len)
52962306a36Sopenharmony_ci		if (!tb[FRA_SRC] ||
53062306a36Sopenharmony_ci		    frh->src_len > (ops->addr_size * 8) ||
53162306a36Sopenharmony_ci		    nla_len(tb[FRA_SRC]) != ops->addr_size) {
53262306a36Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid source address");
53362306a36Sopenharmony_ci			goto errout;
53462306a36Sopenharmony_ci	}
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	if (frh->dst_len)
53762306a36Sopenharmony_ci		if (!tb[FRA_DST] ||
53862306a36Sopenharmony_ci		    frh->dst_len > (ops->addr_size * 8) ||
53962306a36Sopenharmony_ci		    nla_len(tb[FRA_DST]) != ops->addr_size) {
54062306a36Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid dst address");
54162306a36Sopenharmony_ci			goto errout;
54262306a36Sopenharmony_ci	}
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci	nlrule = kzalloc(ops->rule_size, GFP_KERNEL_ACCOUNT);
54562306a36Sopenharmony_ci	if (!nlrule) {
54662306a36Sopenharmony_ci		err = -ENOMEM;
54762306a36Sopenharmony_ci		goto errout;
54862306a36Sopenharmony_ci	}
54962306a36Sopenharmony_ci	refcount_set(&nlrule->refcnt, 1);
55062306a36Sopenharmony_ci	nlrule->fr_net = net;
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	if (tb[FRA_PRIORITY]) {
55362306a36Sopenharmony_ci		nlrule->pref = nla_get_u32(tb[FRA_PRIORITY]);
55462306a36Sopenharmony_ci		*user_priority = true;
55562306a36Sopenharmony_ci	} else {
55662306a36Sopenharmony_ci		nlrule->pref = fib_default_rule_pref(ops);
55762306a36Sopenharmony_ci	}
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci	nlrule->proto = tb[FRA_PROTOCOL] ?
56062306a36Sopenharmony_ci		nla_get_u8(tb[FRA_PROTOCOL]) : RTPROT_UNSPEC;
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_ci	if (tb[FRA_IIFNAME]) {
56362306a36Sopenharmony_ci		struct net_device *dev;
56462306a36Sopenharmony_ci
56562306a36Sopenharmony_ci		nlrule->iifindex = -1;
56662306a36Sopenharmony_ci		nla_strscpy(nlrule->iifname, tb[FRA_IIFNAME], IFNAMSIZ);
56762306a36Sopenharmony_ci		dev = __dev_get_by_name(net, nlrule->iifname);
56862306a36Sopenharmony_ci		if (dev)
56962306a36Sopenharmony_ci			nlrule->iifindex = dev->ifindex;
57062306a36Sopenharmony_ci	}
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	if (tb[FRA_OIFNAME]) {
57362306a36Sopenharmony_ci		struct net_device *dev;
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci		nlrule->oifindex = -1;
57662306a36Sopenharmony_ci		nla_strscpy(nlrule->oifname, tb[FRA_OIFNAME], IFNAMSIZ);
57762306a36Sopenharmony_ci		dev = __dev_get_by_name(net, nlrule->oifname);
57862306a36Sopenharmony_ci		if (dev)
57962306a36Sopenharmony_ci			nlrule->oifindex = dev->ifindex;
58062306a36Sopenharmony_ci	}
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	if (tb[FRA_FWMARK]) {
58362306a36Sopenharmony_ci		nlrule->mark = nla_get_u32(tb[FRA_FWMARK]);
58462306a36Sopenharmony_ci		if (nlrule->mark)
58562306a36Sopenharmony_ci			/* compatibility: if the mark value is non-zero all bits
58662306a36Sopenharmony_ci			 * are compared unless a mask is explicitly specified.
58762306a36Sopenharmony_ci			 */
58862306a36Sopenharmony_ci			nlrule->mark_mask = 0xFFFFFFFF;
58962306a36Sopenharmony_ci	}
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	if (tb[FRA_FWMASK])
59262306a36Sopenharmony_ci		nlrule->mark_mask = nla_get_u32(tb[FRA_FWMASK]);
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	if (tb[FRA_TUN_ID])
59562306a36Sopenharmony_ci		nlrule->tun_id = nla_get_be64(tb[FRA_TUN_ID]);
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	err = -EINVAL;
59862306a36Sopenharmony_ci	if (tb[FRA_L3MDEV] &&
59962306a36Sopenharmony_ci	    fib_nl2rule_l3mdev(tb[FRA_L3MDEV], nlrule, extack) < 0)
60062306a36Sopenharmony_ci		goto errout_free;
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	nlrule->action = frh->action;
60362306a36Sopenharmony_ci	nlrule->flags = frh->flags;
60462306a36Sopenharmony_ci	nlrule->table = frh_get_table(frh, tb);
60562306a36Sopenharmony_ci	if (tb[FRA_SUPPRESS_PREFIXLEN])
60662306a36Sopenharmony_ci		nlrule->suppress_prefixlen = nla_get_u32(tb[FRA_SUPPRESS_PREFIXLEN]);
60762306a36Sopenharmony_ci	else
60862306a36Sopenharmony_ci		nlrule->suppress_prefixlen = -1;
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	if (tb[FRA_SUPPRESS_IFGROUP])
61162306a36Sopenharmony_ci		nlrule->suppress_ifgroup = nla_get_u32(tb[FRA_SUPPRESS_IFGROUP]);
61262306a36Sopenharmony_ci	else
61362306a36Sopenharmony_ci		nlrule->suppress_ifgroup = -1;
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	if (tb[FRA_GOTO]) {
61662306a36Sopenharmony_ci		if (nlrule->action != FR_ACT_GOTO) {
61762306a36Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Unexpected goto");
61862306a36Sopenharmony_ci			goto errout_free;
61962306a36Sopenharmony_ci		}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci		nlrule->target = nla_get_u32(tb[FRA_GOTO]);
62262306a36Sopenharmony_ci		/* Backward jumps are prohibited to avoid endless loops */
62362306a36Sopenharmony_ci		if (nlrule->target <= nlrule->pref) {
62462306a36Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Backward goto not supported");
62562306a36Sopenharmony_ci			goto errout_free;
62662306a36Sopenharmony_ci		}
62762306a36Sopenharmony_ci	} else if (nlrule->action == FR_ACT_GOTO) {
62862306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Missing goto target for action goto");
62962306a36Sopenharmony_ci		goto errout_free;
63062306a36Sopenharmony_ci	}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	if (nlrule->l3mdev && nlrule->table) {
63362306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "l3mdev and table are mutually exclusive");
63462306a36Sopenharmony_ci		goto errout_free;
63562306a36Sopenharmony_ci	}
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	if (tb[FRA_UID_RANGE]) {
63862306a36Sopenharmony_ci		if (current_user_ns() != net->user_ns) {
63962306a36Sopenharmony_ci			err = -EPERM;
64062306a36Sopenharmony_ci			NL_SET_ERR_MSG(extack, "No permission to set uid");
64162306a36Sopenharmony_ci			goto errout_free;
64262306a36Sopenharmony_ci		}
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci		nlrule->uid_range = nla_get_kuid_range(tb);
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci		if (!uid_range_set(&nlrule->uid_range) ||
64762306a36Sopenharmony_ci		    !uid_lte(nlrule->uid_range.start, nlrule->uid_range.end)) {
64862306a36Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid uid range");
64962306a36Sopenharmony_ci			goto errout_free;
65062306a36Sopenharmony_ci		}
65162306a36Sopenharmony_ci	} else {
65262306a36Sopenharmony_ci		nlrule->uid_range = fib_kuid_range_unset;
65362306a36Sopenharmony_ci	}
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	if (tb[FRA_IP_PROTO])
65662306a36Sopenharmony_ci		nlrule->ip_proto = nla_get_u8(tb[FRA_IP_PROTO]);
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	if (tb[FRA_SPORT_RANGE]) {
65962306a36Sopenharmony_ci		err = nla_get_port_range(tb[FRA_SPORT_RANGE],
66062306a36Sopenharmony_ci					 &nlrule->sport_range);
66162306a36Sopenharmony_ci		if (err) {
66262306a36Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid sport range");
66362306a36Sopenharmony_ci			goto errout_free;
66462306a36Sopenharmony_ci		}
66562306a36Sopenharmony_ci	}
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci	if (tb[FRA_DPORT_RANGE]) {
66862306a36Sopenharmony_ci		err = nla_get_port_range(tb[FRA_DPORT_RANGE],
66962306a36Sopenharmony_ci					 &nlrule->dport_range);
67062306a36Sopenharmony_ci		if (err) {
67162306a36Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid dport range");
67262306a36Sopenharmony_ci			goto errout_free;
67362306a36Sopenharmony_ci		}
67462306a36Sopenharmony_ci	}
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	*rule = nlrule;
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	return 0;
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_cierrout_free:
68162306a36Sopenharmony_ci	kfree(nlrule);
68262306a36Sopenharmony_cierrout:
68362306a36Sopenharmony_ci	return err;
68462306a36Sopenharmony_ci}
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_cistatic int rule_exists(struct fib_rules_ops *ops, struct fib_rule_hdr *frh,
68762306a36Sopenharmony_ci		       struct nlattr **tb, struct fib_rule *rule)
68862306a36Sopenharmony_ci{
68962306a36Sopenharmony_ci	struct fib_rule *r;
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	list_for_each_entry(r, &ops->rules_list, list) {
69262306a36Sopenharmony_ci		if (r->action != rule->action)
69362306a36Sopenharmony_ci			continue;
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci		if (r->table != rule->table)
69662306a36Sopenharmony_ci			continue;
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ci		if (r->pref != rule->pref)
69962306a36Sopenharmony_ci			continue;
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_ci		if (memcmp(r->iifname, rule->iifname, IFNAMSIZ))
70262306a36Sopenharmony_ci			continue;
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_ci		if (memcmp(r->oifname, rule->oifname, IFNAMSIZ))
70562306a36Sopenharmony_ci			continue;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci		if (r->mark != rule->mark)
70862306a36Sopenharmony_ci			continue;
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci		if (r->suppress_ifgroup != rule->suppress_ifgroup)
71162306a36Sopenharmony_ci			continue;
71262306a36Sopenharmony_ci
71362306a36Sopenharmony_ci		if (r->suppress_prefixlen != rule->suppress_prefixlen)
71462306a36Sopenharmony_ci			continue;
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_ci		if (r->mark_mask != rule->mark_mask)
71762306a36Sopenharmony_ci			continue;
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_ci		if (r->tun_id != rule->tun_id)
72062306a36Sopenharmony_ci			continue;
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_ci		if (r->fr_net != rule->fr_net)
72362306a36Sopenharmony_ci			continue;
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci		if (r->l3mdev != rule->l3mdev)
72662306a36Sopenharmony_ci			continue;
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci		if (!uid_eq(r->uid_range.start, rule->uid_range.start) ||
72962306a36Sopenharmony_ci		    !uid_eq(r->uid_range.end, rule->uid_range.end))
73062306a36Sopenharmony_ci			continue;
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_ci		if (r->ip_proto != rule->ip_proto)
73362306a36Sopenharmony_ci			continue;
73462306a36Sopenharmony_ci
73562306a36Sopenharmony_ci		if (r->proto != rule->proto)
73662306a36Sopenharmony_ci			continue;
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci		if (!fib_rule_port_range_compare(&r->sport_range,
73962306a36Sopenharmony_ci						 &rule->sport_range))
74062306a36Sopenharmony_ci			continue;
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci		if (!fib_rule_port_range_compare(&r->dport_range,
74362306a36Sopenharmony_ci						 &rule->dport_range))
74462306a36Sopenharmony_ci			continue;
74562306a36Sopenharmony_ci
74662306a36Sopenharmony_ci		if (!ops->compare(r, frh, tb))
74762306a36Sopenharmony_ci			continue;
74862306a36Sopenharmony_ci		return 1;
74962306a36Sopenharmony_ci	}
75062306a36Sopenharmony_ci	return 0;
75162306a36Sopenharmony_ci}
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_cistatic const struct nla_policy fib_rule_policy[FRA_MAX + 1] = {
75462306a36Sopenharmony_ci	[FRA_UNSPEC]	= { .strict_start_type = FRA_DPORT_RANGE + 1 },
75562306a36Sopenharmony_ci	[FRA_IIFNAME]	= { .type = NLA_STRING, .len = IFNAMSIZ - 1 },
75662306a36Sopenharmony_ci	[FRA_OIFNAME]	= { .type = NLA_STRING, .len = IFNAMSIZ - 1 },
75762306a36Sopenharmony_ci	[FRA_PRIORITY]	= { .type = NLA_U32 },
75862306a36Sopenharmony_ci	[FRA_FWMARK]	= { .type = NLA_U32 },
75962306a36Sopenharmony_ci	[FRA_FLOW]	= { .type = NLA_U32 },
76062306a36Sopenharmony_ci	[FRA_TUN_ID]	= { .type = NLA_U64 },
76162306a36Sopenharmony_ci	[FRA_FWMASK]	= { .type = NLA_U32 },
76262306a36Sopenharmony_ci	[FRA_TABLE]     = { .type = NLA_U32 },
76362306a36Sopenharmony_ci	[FRA_SUPPRESS_PREFIXLEN] = { .type = NLA_U32 },
76462306a36Sopenharmony_ci	[FRA_SUPPRESS_IFGROUP] = { .type = NLA_U32 },
76562306a36Sopenharmony_ci	[FRA_GOTO]	= { .type = NLA_U32 },
76662306a36Sopenharmony_ci	[FRA_L3MDEV]	= { .type = NLA_U8 },
76762306a36Sopenharmony_ci	[FRA_UID_RANGE]	= { .len = sizeof(struct fib_rule_uid_range) },
76862306a36Sopenharmony_ci	[FRA_PROTOCOL]  = { .type = NLA_U8 },
76962306a36Sopenharmony_ci	[FRA_IP_PROTO]  = { .type = NLA_U8 },
77062306a36Sopenharmony_ci	[FRA_SPORT_RANGE] = { .len = sizeof(struct fib_rule_port_range) },
77162306a36Sopenharmony_ci	[FRA_DPORT_RANGE] = { .len = sizeof(struct fib_rule_port_range) }
77262306a36Sopenharmony_ci};
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ciint fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr *nlh,
77562306a36Sopenharmony_ci		   struct netlink_ext_ack *extack)
77662306a36Sopenharmony_ci{
77762306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
77862306a36Sopenharmony_ci	struct fib_rule_hdr *frh = nlmsg_data(nlh);
77962306a36Sopenharmony_ci	struct fib_rules_ops *ops = NULL;
78062306a36Sopenharmony_ci	struct fib_rule *rule = NULL, *r, *last = NULL;
78162306a36Sopenharmony_ci	struct nlattr *tb[FRA_MAX + 1];
78262306a36Sopenharmony_ci	int err = -EINVAL, unresolved = 0;
78362306a36Sopenharmony_ci	bool user_priority = false;
78462306a36Sopenharmony_ci
78562306a36Sopenharmony_ci	if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) {
78662306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid msg length");
78762306a36Sopenharmony_ci		goto errout;
78862306a36Sopenharmony_ci	}
78962306a36Sopenharmony_ci
79062306a36Sopenharmony_ci	ops = lookup_rules_ops(net, frh->family);
79162306a36Sopenharmony_ci	if (!ops) {
79262306a36Sopenharmony_ci		err = -EAFNOSUPPORT;
79362306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Rule family not supported");
79462306a36Sopenharmony_ci		goto errout;
79562306a36Sopenharmony_ci	}
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_ci	err = nlmsg_parse_deprecated(nlh, sizeof(*frh), tb, FRA_MAX,
79862306a36Sopenharmony_ci				     fib_rule_policy, extack);
79962306a36Sopenharmony_ci	if (err < 0) {
80062306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Error parsing msg");
80162306a36Sopenharmony_ci		goto errout;
80262306a36Sopenharmony_ci	}
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci	err = fib_nl2rule(skb, nlh, extack, ops, tb, &rule, &user_priority);
80562306a36Sopenharmony_ci	if (err)
80662306a36Sopenharmony_ci		goto errout;
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci	if ((nlh->nlmsg_flags & NLM_F_EXCL) &&
80962306a36Sopenharmony_ci	    rule_exists(ops, frh, tb, rule)) {
81062306a36Sopenharmony_ci		err = -EEXIST;
81162306a36Sopenharmony_ci		goto errout_free;
81262306a36Sopenharmony_ci	}
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_ci	err = ops->configure(rule, skb, frh, tb, extack);
81562306a36Sopenharmony_ci	if (err < 0)
81662306a36Sopenharmony_ci		goto errout_free;
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_ci	err = call_fib_rule_notifiers(net, FIB_EVENT_RULE_ADD, rule, ops,
81962306a36Sopenharmony_ci				      extack);
82062306a36Sopenharmony_ci	if (err < 0)
82162306a36Sopenharmony_ci		goto errout_free;
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ci	list_for_each_entry(r, &ops->rules_list, list) {
82462306a36Sopenharmony_ci		if (r->pref == rule->target) {
82562306a36Sopenharmony_ci			RCU_INIT_POINTER(rule->ctarget, r);
82662306a36Sopenharmony_ci			break;
82762306a36Sopenharmony_ci		}
82862306a36Sopenharmony_ci	}
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_ci	if (rcu_dereference_protected(rule->ctarget, 1) == NULL)
83162306a36Sopenharmony_ci		unresolved = 1;
83262306a36Sopenharmony_ci
83362306a36Sopenharmony_ci	list_for_each_entry(r, &ops->rules_list, list) {
83462306a36Sopenharmony_ci		if (r->pref > rule->pref)
83562306a36Sopenharmony_ci			break;
83662306a36Sopenharmony_ci		last = r;
83762306a36Sopenharmony_ci	}
83862306a36Sopenharmony_ci
83962306a36Sopenharmony_ci	if (last)
84062306a36Sopenharmony_ci		list_add_rcu(&rule->list, &last->list);
84162306a36Sopenharmony_ci	else
84262306a36Sopenharmony_ci		list_add_rcu(&rule->list, &ops->rules_list);
84362306a36Sopenharmony_ci
84462306a36Sopenharmony_ci	if (ops->unresolved_rules) {
84562306a36Sopenharmony_ci		/*
84662306a36Sopenharmony_ci		 * There are unresolved goto rules in the list, check if
84762306a36Sopenharmony_ci		 * any of them are pointing to this new rule.
84862306a36Sopenharmony_ci		 */
84962306a36Sopenharmony_ci		list_for_each_entry(r, &ops->rules_list, list) {
85062306a36Sopenharmony_ci			if (r->action == FR_ACT_GOTO &&
85162306a36Sopenharmony_ci			    r->target == rule->pref &&
85262306a36Sopenharmony_ci			    rtnl_dereference(r->ctarget) == NULL) {
85362306a36Sopenharmony_ci				rcu_assign_pointer(r->ctarget, rule);
85462306a36Sopenharmony_ci				if (--ops->unresolved_rules == 0)
85562306a36Sopenharmony_ci					break;
85662306a36Sopenharmony_ci			}
85762306a36Sopenharmony_ci		}
85862306a36Sopenharmony_ci	}
85962306a36Sopenharmony_ci
86062306a36Sopenharmony_ci	if (rule->action == FR_ACT_GOTO)
86162306a36Sopenharmony_ci		ops->nr_goto_rules++;
86262306a36Sopenharmony_ci
86362306a36Sopenharmony_ci	if (unresolved)
86462306a36Sopenharmony_ci		ops->unresolved_rules++;
86562306a36Sopenharmony_ci
86662306a36Sopenharmony_ci	if (rule->tun_id)
86762306a36Sopenharmony_ci		ip_tunnel_need_metadata();
86862306a36Sopenharmony_ci
86962306a36Sopenharmony_ci	notify_rule_change(RTM_NEWRULE, rule, ops, nlh, NETLINK_CB(skb).portid);
87062306a36Sopenharmony_ci	flush_route_cache(ops);
87162306a36Sopenharmony_ci	rules_ops_put(ops);
87262306a36Sopenharmony_ci	return 0;
87362306a36Sopenharmony_ci
87462306a36Sopenharmony_cierrout_free:
87562306a36Sopenharmony_ci	kfree(rule);
87662306a36Sopenharmony_cierrout:
87762306a36Sopenharmony_ci	rules_ops_put(ops);
87862306a36Sopenharmony_ci	return err;
87962306a36Sopenharmony_ci}
88062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(fib_nl_newrule);
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ciint fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr *nlh,
88362306a36Sopenharmony_ci		   struct netlink_ext_ack *extack)
88462306a36Sopenharmony_ci{
88562306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
88662306a36Sopenharmony_ci	struct fib_rule_hdr *frh = nlmsg_data(nlh);
88762306a36Sopenharmony_ci	struct fib_rules_ops *ops = NULL;
88862306a36Sopenharmony_ci	struct fib_rule *rule = NULL, *r, *nlrule = NULL;
88962306a36Sopenharmony_ci	struct nlattr *tb[FRA_MAX+1];
89062306a36Sopenharmony_ci	int err = -EINVAL;
89162306a36Sopenharmony_ci	bool user_priority = false;
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci	if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) {
89462306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid msg length");
89562306a36Sopenharmony_ci		goto errout;
89662306a36Sopenharmony_ci	}
89762306a36Sopenharmony_ci
89862306a36Sopenharmony_ci	ops = lookup_rules_ops(net, frh->family);
89962306a36Sopenharmony_ci	if (ops == NULL) {
90062306a36Sopenharmony_ci		err = -EAFNOSUPPORT;
90162306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Rule family not supported");
90262306a36Sopenharmony_ci		goto errout;
90362306a36Sopenharmony_ci	}
90462306a36Sopenharmony_ci
90562306a36Sopenharmony_ci	err = nlmsg_parse_deprecated(nlh, sizeof(*frh), tb, FRA_MAX,
90662306a36Sopenharmony_ci				     fib_rule_policy, extack);
90762306a36Sopenharmony_ci	if (err < 0) {
90862306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Error parsing msg");
90962306a36Sopenharmony_ci		goto errout;
91062306a36Sopenharmony_ci	}
91162306a36Sopenharmony_ci
91262306a36Sopenharmony_ci	err = fib_nl2rule(skb, nlh, extack, ops, tb, &nlrule, &user_priority);
91362306a36Sopenharmony_ci	if (err)
91462306a36Sopenharmony_ci		goto errout;
91562306a36Sopenharmony_ci
91662306a36Sopenharmony_ci	rule = rule_find(ops, frh, tb, nlrule, user_priority);
91762306a36Sopenharmony_ci	if (!rule) {
91862306a36Sopenharmony_ci		err = -ENOENT;
91962306a36Sopenharmony_ci		goto errout;
92062306a36Sopenharmony_ci	}
92162306a36Sopenharmony_ci
92262306a36Sopenharmony_ci	if (rule->flags & FIB_RULE_PERMANENT) {
92362306a36Sopenharmony_ci		err = -EPERM;
92462306a36Sopenharmony_ci		goto errout;
92562306a36Sopenharmony_ci	}
92662306a36Sopenharmony_ci
92762306a36Sopenharmony_ci	if (ops->delete) {
92862306a36Sopenharmony_ci		err = ops->delete(rule);
92962306a36Sopenharmony_ci		if (err)
93062306a36Sopenharmony_ci			goto errout;
93162306a36Sopenharmony_ci	}
93262306a36Sopenharmony_ci
93362306a36Sopenharmony_ci	if (rule->tun_id)
93462306a36Sopenharmony_ci		ip_tunnel_unneed_metadata();
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_ci	list_del_rcu(&rule->list);
93762306a36Sopenharmony_ci
93862306a36Sopenharmony_ci	if (rule->action == FR_ACT_GOTO) {
93962306a36Sopenharmony_ci		ops->nr_goto_rules--;
94062306a36Sopenharmony_ci		if (rtnl_dereference(rule->ctarget) == NULL)
94162306a36Sopenharmony_ci			ops->unresolved_rules--;
94262306a36Sopenharmony_ci	}
94362306a36Sopenharmony_ci
94462306a36Sopenharmony_ci	/*
94562306a36Sopenharmony_ci	 * Check if this rule is a target to any of them. If so,
94662306a36Sopenharmony_ci	 * adjust to the next one with the same preference or
94762306a36Sopenharmony_ci	 * disable them. As this operation is eventually very
94862306a36Sopenharmony_ci	 * expensive, it is only performed if goto rules, except
94962306a36Sopenharmony_ci	 * current if it is goto rule, have actually been added.
95062306a36Sopenharmony_ci	 */
95162306a36Sopenharmony_ci	if (ops->nr_goto_rules > 0) {
95262306a36Sopenharmony_ci		struct fib_rule *n;
95362306a36Sopenharmony_ci
95462306a36Sopenharmony_ci		n = list_next_entry(rule, list);
95562306a36Sopenharmony_ci		if (&n->list == &ops->rules_list || n->pref != rule->pref)
95662306a36Sopenharmony_ci			n = NULL;
95762306a36Sopenharmony_ci		list_for_each_entry(r, &ops->rules_list, list) {
95862306a36Sopenharmony_ci			if (rtnl_dereference(r->ctarget) != rule)
95962306a36Sopenharmony_ci				continue;
96062306a36Sopenharmony_ci			rcu_assign_pointer(r->ctarget, n);
96162306a36Sopenharmony_ci			if (!n)
96262306a36Sopenharmony_ci				ops->unresolved_rules++;
96362306a36Sopenharmony_ci		}
96462306a36Sopenharmony_ci	}
96562306a36Sopenharmony_ci
96662306a36Sopenharmony_ci	call_fib_rule_notifiers(net, FIB_EVENT_RULE_DEL, rule, ops,
96762306a36Sopenharmony_ci				NULL);
96862306a36Sopenharmony_ci	notify_rule_change(RTM_DELRULE, rule, ops, nlh,
96962306a36Sopenharmony_ci			   NETLINK_CB(skb).portid);
97062306a36Sopenharmony_ci	fib_rule_put(rule);
97162306a36Sopenharmony_ci	flush_route_cache(ops);
97262306a36Sopenharmony_ci	rules_ops_put(ops);
97362306a36Sopenharmony_ci	kfree(nlrule);
97462306a36Sopenharmony_ci	return 0;
97562306a36Sopenharmony_ci
97662306a36Sopenharmony_cierrout:
97762306a36Sopenharmony_ci	kfree(nlrule);
97862306a36Sopenharmony_ci	rules_ops_put(ops);
97962306a36Sopenharmony_ci	return err;
98062306a36Sopenharmony_ci}
98162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(fib_nl_delrule);
98262306a36Sopenharmony_ci
98362306a36Sopenharmony_cistatic inline size_t fib_rule_nlmsg_size(struct fib_rules_ops *ops,
98462306a36Sopenharmony_ci					 struct fib_rule *rule)
98562306a36Sopenharmony_ci{
98662306a36Sopenharmony_ci	size_t payload = NLMSG_ALIGN(sizeof(struct fib_rule_hdr))
98762306a36Sopenharmony_ci			 + nla_total_size(IFNAMSIZ) /* FRA_IIFNAME */
98862306a36Sopenharmony_ci			 + nla_total_size(IFNAMSIZ) /* FRA_OIFNAME */
98962306a36Sopenharmony_ci			 + nla_total_size(4) /* FRA_PRIORITY */
99062306a36Sopenharmony_ci			 + nla_total_size(4) /* FRA_TABLE */
99162306a36Sopenharmony_ci			 + nla_total_size(4) /* FRA_SUPPRESS_PREFIXLEN */
99262306a36Sopenharmony_ci			 + nla_total_size(4) /* FRA_SUPPRESS_IFGROUP */
99362306a36Sopenharmony_ci			 + nla_total_size(4) /* FRA_FWMARK */
99462306a36Sopenharmony_ci			 + nla_total_size(4) /* FRA_FWMASK */
99562306a36Sopenharmony_ci			 + nla_total_size_64bit(8) /* FRA_TUN_ID */
99662306a36Sopenharmony_ci			 + nla_total_size(sizeof(struct fib_kuid_range))
99762306a36Sopenharmony_ci			 + nla_total_size(1) /* FRA_PROTOCOL */
99862306a36Sopenharmony_ci			 + nla_total_size(1) /* FRA_IP_PROTO */
99962306a36Sopenharmony_ci			 + nla_total_size(sizeof(struct fib_rule_port_range)) /* FRA_SPORT_RANGE */
100062306a36Sopenharmony_ci			 + nla_total_size(sizeof(struct fib_rule_port_range)); /* FRA_DPORT_RANGE */
100162306a36Sopenharmony_ci
100262306a36Sopenharmony_ci	if (ops->nlmsg_payload)
100362306a36Sopenharmony_ci		payload += ops->nlmsg_payload(rule);
100462306a36Sopenharmony_ci
100562306a36Sopenharmony_ci	return payload;
100662306a36Sopenharmony_ci}
100762306a36Sopenharmony_ci
100862306a36Sopenharmony_cistatic int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule,
100962306a36Sopenharmony_ci			    u32 pid, u32 seq, int type, int flags,
101062306a36Sopenharmony_ci			    struct fib_rules_ops *ops)
101162306a36Sopenharmony_ci{
101262306a36Sopenharmony_ci	struct nlmsghdr *nlh;
101362306a36Sopenharmony_ci	struct fib_rule_hdr *frh;
101462306a36Sopenharmony_ci
101562306a36Sopenharmony_ci	nlh = nlmsg_put(skb, pid, seq, type, sizeof(*frh), flags);
101662306a36Sopenharmony_ci	if (nlh == NULL)
101762306a36Sopenharmony_ci		return -EMSGSIZE;
101862306a36Sopenharmony_ci
101962306a36Sopenharmony_ci	frh = nlmsg_data(nlh);
102062306a36Sopenharmony_ci	frh->family = ops->family;
102162306a36Sopenharmony_ci	frh->table = rule->table < 256 ? rule->table : RT_TABLE_COMPAT;
102262306a36Sopenharmony_ci	if (nla_put_u32(skb, FRA_TABLE, rule->table))
102362306a36Sopenharmony_ci		goto nla_put_failure;
102462306a36Sopenharmony_ci	if (nla_put_u32(skb, FRA_SUPPRESS_PREFIXLEN, rule->suppress_prefixlen))
102562306a36Sopenharmony_ci		goto nla_put_failure;
102662306a36Sopenharmony_ci	frh->res1 = 0;
102762306a36Sopenharmony_ci	frh->res2 = 0;
102862306a36Sopenharmony_ci	frh->action = rule->action;
102962306a36Sopenharmony_ci	frh->flags = rule->flags;
103062306a36Sopenharmony_ci
103162306a36Sopenharmony_ci	if (nla_put_u8(skb, FRA_PROTOCOL, rule->proto))
103262306a36Sopenharmony_ci		goto nla_put_failure;
103362306a36Sopenharmony_ci
103462306a36Sopenharmony_ci	if (rule->action == FR_ACT_GOTO &&
103562306a36Sopenharmony_ci	    rcu_access_pointer(rule->ctarget) == NULL)
103662306a36Sopenharmony_ci		frh->flags |= FIB_RULE_UNRESOLVED;
103762306a36Sopenharmony_ci
103862306a36Sopenharmony_ci	if (rule->iifname[0]) {
103962306a36Sopenharmony_ci		if (nla_put_string(skb, FRA_IIFNAME, rule->iifname))
104062306a36Sopenharmony_ci			goto nla_put_failure;
104162306a36Sopenharmony_ci		if (rule->iifindex == -1)
104262306a36Sopenharmony_ci			frh->flags |= FIB_RULE_IIF_DETACHED;
104362306a36Sopenharmony_ci	}
104462306a36Sopenharmony_ci
104562306a36Sopenharmony_ci	if (rule->oifname[0]) {
104662306a36Sopenharmony_ci		if (nla_put_string(skb, FRA_OIFNAME, rule->oifname))
104762306a36Sopenharmony_ci			goto nla_put_failure;
104862306a36Sopenharmony_ci		if (rule->oifindex == -1)
104962306a36Sopenharmony_ci			frh->flags |= FIB_RULE_OIF_DETACHED;
105062306a36Sopenharmony_ci	}
105162306a36Sopenharmony_ci
105262306a36Sopenharmony_ci	if ((rule->pref &&
105362306a36Sopenharmony_ci	     nla_put_u32(skb, FRA_PRIORITY, rule->pref)) ||
105462306a36Sopenharmony_ci	    (rule->mark &&
105562306a36Sopenharmony_ci	     nla_put_u32(skb, FRA_FWMARK, rule->mark)) ||
105662306a36Sopenharmony_ci	    ((rule->mark_mask || rule->mark) &&
105762306a36Sopenharmony_ci	     nla_put_u32(skb, FRA_FWMASK, rule->mark_mask)) ||
105862306a36Sopenharmony_ci	    (rule->target &&
105962306a36Sopenharmony_ci	     nla_put_u32(skb, FRA_GOTO, rule->target)) ||
106062306a36Sopenharmony_ci	    (rule->tun_id &&
106162306a36Sopenharmony_ci	     nla_put_be64(skb, FRA_TUN_ID, rule->tun_id, FRA_PAD)) ||
106262306a36Sopenharmony_ci	    (rule->l3mdev &&
106362306a36Sopenharmony_ci	     nla_put_u8(skb, FRA_L3MDEV, rule->l3mdev)) ||
106462306a36Sopenharmony_ci	    (uid_range_set(&rule->uid_range) &&
106562306a36Sopenharmony_ci	     nla_put_uid_range(skb, &rule->uid_range)) ||
106662306a36Sopenharmony_ci	    (fib_rule_port_range_set(&rule->sport_range) &&
106762306a36Sopenharmony_ci	     nla_put_port_range(skb, FRA_SPORT_RANGE, &rule->sport_range)) ||
106862306a36Sopenharmony_ci	    (fib_rule_port_range_set(&rule->dport_range) &&
106962306a36Sopenharmony_ci	     nla_put_port_range(skb, FRA_DPORT_RANGE, &rule->dport_range)) ||
107062306a36Sopenharmony_ci	    (rule->ip_proto && nla_put_u8(skb, FRA_IP_PROTO, rule->ip_proto)))
107162306a36Sopenharmony_ci		goto nla_put_failure;
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_ci	if (rule->suppress_ifgroup != -1) {
107462306a36Sopenharmony_ci		if (nla_put_u32(skb, FRA_SUPPRESS_IFGROUP, rule->suppress_ifgroup))
107562306a36Sopenharmony_ci			goto nla_put_failure;
107662306a36Sopenharmony_ci	}
107762306a36Sopenharmony_ci
107862306a36Sopenharmony_ci	if (ops->fill(rule, skb, frh) < 0)
107962306a36Sopenharmony_ci		goto nla_put_failure;
108062306a36Sopenharmony_ci
108162306a36Sopenharmony_ci	nlmsg_end(skb, nlh);
108262306a36Sopenharmony_ci	return 0;
108362306a36Sopenharmony_ci
108462306a36Sopenharmony_cinla_put_failure:
108562306a36Sopenharmony_ci	nlmsg_cancel(skb, nlh);
108662306a36Sopenharmony_ci	return -EMSGSIZE;
108762306a36Sopenharmony_ci}
108862306a36Sopenharmony_ci
108962306a36Sopenharmony_cistatic int dump_rules(struct sk_buff *skb, struct netlink_callback *cb,
109062306a36Sopenharmony_ci		      struct fib_rules_ops *ops)
109162306a36Sopenharmony_ci{
109262306a36Sopenharmony_ci	int idx = 0;
109362306a36Sopenharmony_ci	struct fib_rule *rule;
109462306a36Sopenharmony_ci	int err = 0;
109562306a36Sopenharmony_ci
109662306a36Sopenharmony_ci	rcu_read_lock();
109762306a36Sopenharmony_ci	list_for_each_entry_rcu(rule, &ops->rules_list, list) {
109862306a36Sopenharmony_ci		if (idx < cb->args[1])
109962306a36Sopenharmony_ci			goto skip;
110062306a36Sopenharmony_ci
110162306a36Sopenharmony_ci		err = fib_nl_fill_rule(skb, rule, NETLINK_CB(cb->skb).portid,
110262306a36Sopenharmony_ci				       cb->nlh->nlmsg_seq, RTM_NEWRULE,
110362306a36Sopenharmony_ci				       NLM_F_MULTI, ops);
110462306a36Sopenharmony_ci		if (err)
110562306a36Sopenharmony_ci			break;
110662306a36Sopenharmony_ciskip:
110762306a36Sopenharmony_ci		idx++;
110862306a36Sopenharmony_ci	}
110962306a36Sopenharmony_ci	rcu_read_unlock();
111062306a36Sopenharmony_ci	cb->args[1] = idx;
111162306a36Sopenharmony_ci	rules_ops_put(ops);
111262306a36Sopenharmony_ci
111362306a36Sopenharmony_ci	return err;
111462306a36Sopenharmony_ci}
111562306a36Sopenharmony_ci
111662306a36Sopenharmony_cistatic int fib_valid_dumprule_req(const struct nlmsghdr *nlh,
111762306a36Sopenharmony_ci				   struct netlink_ext_ack *extack)
111862306a36Sopenharmony_ci{
111962306a36Sopenharmony_ci	struct fib_rule_hdr *frh;
112062306a36Sopenharmony_ci
112162306a36Sopenharmony_ci	if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) {
112262306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid header for fib rule dump request");
112362306a36Sopenharmony_ci		return -EINVAL;
112462306a36Sopenharmony_ci	}
112562306a36Sopenharmony_ci
112662306a36Sopenharmony_ci	frh = nlmsg_data(nlh);
112762306a36Sopenharmony_ci	if (frh->dst_len || frh->src_len || frh->tos || frh->table ||
112862306a36Sopenharmony_ci	    frh->res1 || frh->res2 || frh->action || frh->flags) {
112962306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack,
113062306a36Sopenharmony_ci			       "Invalid values in header for fib rule dump request");
113162306a36Sopenharmony_ci		return -EINVAL;
113262306a36Sopenharmony_ci	}
113362306a36Sopenharmony_ci
113462306a36Sopenharmony_ci	if (nlmsg_attrlen(nlh, sizeof(*frh))) {
113562306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid data after header in fib rule dump request");
113662306a36Sopenharmony_ci		return -EINVAL;
113762306a36Sopenharmony_ci	}
113862306a36Sopenharmony_ci
113962306a36Sopenharmony_ci	return 0;
114062306a36Sopenharmony_ci}
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_cistatic int fib_nl_dumprule(struct sk_buff *skb, struct netlink_callback *cb)
114362306a36Sopenharmony_ci{
114462306a36Sopenharmony_ci	const struct nlmsghdr *nlh = cb->nlh;
114562306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
114662306a36Sopenharmony_ci	struct fib_rules_ops *ops;
114762306a36Sopenharmony_ci	int idx = 0, family;
114862306a36Sopenharmony_ci
114962306a36Sopenharmony_ci	if (cb->strict_check) {
115062306a36Sopenharmony_ci		int err = fib_valid_dumprule_req(nlh, cb->extack);
115162306a36Sopenharmony_ci
115262306a36Sopenharmony_ci		if (err < 0)
115362306a36Sopenharmony_ci			return err;
115462306a36Sopenharmony_ci	}
115562306a36Sopenharmony_ci
115662306a36Sopenharmony_ci	family = rtnl_msg_family(nlh);
115762306a36Sopenharmony_ci	if (family != AF_UNSPEC) {
115862306a36Sopenharmony_ci		/* Protocol specific dump request */
115962306a36Sopenharmony_ci		ops = lookup_rules_ops(net, family);
116062306a36Sopenharmony_ci		if (ops == NULL)
116162306a36Sopenharmony_ci			return -EAFNOSUPPORT;
116262306a36Sopenharmony_ci
116362306a36Sopenharmony_ci		dump_rules(skb, cb, ops);
116462306a36Sopenharmony_ci
116562306a36Sopenharmony_ci		return skb->len;
116662306a36Sopenharmony_ci	}
116762306a36Sopenharmony_ci
116862306a36Sopenharmony_ci	rcu_read_lock();
116962306a36Sopenharmony_ci	list_for_each_entry_rcu(ops, &net->rules_ops, list) {
117062306a36Sopenharmony_ci		if (idx < cb->args[0] || !try_module_get(ops->owner))
117162306a36Sopenharmony_ci			goto skip;
117262306a36Sopenharmony_ci
117362306a36Sopenharmony_ci		if (dump_rules(skb, cb, ops) < 0)
117462306a36Sopenharmony_ci			break;
117562306a36Sopenharmony_ci
117662306a36Sopenharmony_ci		cb->args[1] = 0;
117762306a36Sopenharmony_ciskip:
117862306a36Sopenharmony_ci		idx++;
117962306a36Sopenharmony_ci	}
118062306a36Sopenharmony_ci	rcu_read_unlock();
118162306a36Sopenharmony_ci	cb->args[0] = idx;
118262306a36Sopenharmony_ci
118362306a36Sopenharmony_ci	return skb->len;
118462306a36Sopenharmony_ci}
118562306a36Sopenharmony_ci
118662306a36Sopenharmony_cistatic void notify_rule_change(int event, struct fib_rule *rule,
118762306a36Sopenharmony_ci			       struct fib_rules_ops *ops, struct nlmsghdr *nlh,
118862306a36Sopenharmony_ci			       u32 pid)
118962306a36Sopenharmony_ci{
119062306a36Sopenharmony_ci	struct net *net;
119162306a36Sopenharmony_ci	struct sk_buff *skb;
119262306a36Sopenharmony_ci	int err = -ENOMEM;
119362306a36Sopenharmony_ci
119462306a36Sopenharmony_ci	net = ops->fro_net;
119562306a36Sopenharmony_ci	skb = nlmsg_new(fib_rule_nlmsg_size(ops, rule), GFP_KERNEL);
119662306a36Sopenharmony_ci	if (skb == NULL)
119762306a36Sopenharmony_ci		goto errout;
119862306a36Sopenharmony_ci
119962306a36Sopenharmony_ci	err = fib_nl_fill_rule(skb, rule, pid, nlh->nlmsg_seq, event, 0, ops);
120062306a36Sopenharmony_ci	if (err < 0) {
120162306a36Sopenharmony_ci		/* -EMSGSIZE implies BUG in fib_rule_nlmsg_size() */
120262306a36Sopenharmony_ci		WARN_ON(err == -EMSGSIZE);
120362306a36Sopenharmony_ci		kfree_skb(skb);
120462306a36Sopenharmony_ci		goto errout;
120562306a36Sopenharmony_ci	}
120662306a36Sopenharmony_ci
120762306a36Sopenharmony_ci	rtnl_notify(skb, net, pid, ops->nlgroup, nlh, GFP_KERNEL);
120862306a36Sopenharmony_ci	return;
120962306a36Sopenharmony_cierrout:
121062306a36Sopenharmony_ci	if (err < 0)
121162306a36Sopenharmony_ci		rtnl_set_sk_err(net, ops->nlgroup, err);
121262306a36Sopenharmony_ci}
121362306a36Sopenharmony_ci
121462306a36Sopenharmony_cistatic void attach_rules(struct list_head *rules, struct net_device *dev)
121562306a36Sopenharmony_ci{
121662306a36Sopenharmony_ci	struct fib_rule *rule;
121762306a36Sopenharmony_ci
121862306a36Sopenharmony_ci	list_for_each_entry(rule, rules, list) {
121962306a36Sopenharmony_ci		if (rule->iifindex == -1 &&
122062306a36Sopenharmony_ci		    strcmp(dev->name, rule->iifname) == 0)
122162306a36Sopenharmony_ci			rule->iifindex = dev->ifindex;
122262306a36Sopenharmony_ci		if (rule->oifindex == -1 &&
122362306a36Sopenharmony_ci		    strcmp(dev->name, rule->oifname) == 0)
122462306a36Sopenharmony_ci			rule->oifindex = dev->ifindex;
122562306a36Sopenharmony_ci	}
122662306a36Sopenharmony_ci}
122762306a36Sopenharmony_ci
122862306a36Sopenharmony_cistatic void detach_rules(struct list_head *rules, struct net_device *dev)
122962306a36Sopenharmony_ci{
123062306a36Sopenharmony_ci	struct fib_rule *rule;
123162306a36Sopenharmony_ci
123262306a36Sopenharmony_ci	list_for_each_entry(rule, rules, list) {
123362306a36Sopenharmony_ci		if (rule->iifindex == dev->ifindex)
123462306a36Sopenharmony_ci			rule->iifindex = -1;
123562306a36Sopenharmony_ci		if (rule->oifindex == dev->ifindex)
123662306a36Sopenharmony_ci			rule->oifindex = -1;
123762306a36Sopenharmony_ci	}
123862306a36Sopenharmony_ci}
123962306a36Sopenharmony_ci
124062306a36Sopenharmony_ci
124162306a36Sopenharmony_cistatic int fib_rules_event(struct notifier_block *this, unsigned long event,
124262306a36Sopenharmony_ci			   void *ptr)
124362306a36Sopenharmony_ci{
124462306a36Sopenharmony_ci	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
124562306a36Sopenharmony_ci	struct net *net = dev_net(dev);
124662306a36Sopenharmony_ci	struct fib_rules_ops *ops;
124762306a36Sopenharmony_ci
124862306a36Sopenharmony_ci	ASSERT_RTNL();
124962306a36Sopenharmony_ci
125062306a36Sopenharmony_ci	switch (event) {
125162306a36Sopenharmony_ci	case NETDEV_REGISTER:
125262306a36Sopenharmony_ci		list_for_each_entry(ops, &net->rules_ops, list)
125362306a36Sopenharmony_ci			attach_rules(&ops->rules_list, dev);
125462306a36Sopenharmony_ci		break;
125562306a36Sopenharmony_ci
125662306a36Sopenharmony_ci	case NETDEV_CHANGENAME:
125762306a36Sopenharmony_ci		list_for_each_entry(ops, &net->rules_ops, list) {
125862306a36Sopenharmony_ci			detach_rules(&ops->rules_list, dev);
125962306a36Sopenharmony_ci			attach_rules(&ops->rules_list, dev);
126062306a36Sopenharmony_ci		}
126162306a36Sopenharmony_ci		break;
126262306a36Sopenharmony_ci
126362306a36Sopenharmony_ci	case NETDEV_UNREGISTER:
126462306a36Sopenharmony_ci		list_for_each_entry(ops, &net->rules_ops, list)
126562306a36Sopenharmony_ci			detach_rules(&ops->rules_list, dev);
126662306a36Sopenharmony_ci		break;
126762306a36Sopenharmony_ci	}
126862306a36Sopenharmony_ci
126962306a36Sopenharmony_ci	return NOTIFY_DONE;
127062306a36Sopenharmony_ci}
127162306a36Sopenharmony_ci
127262306a36Sopenharmony_cistatic struct notifier_block fib_rules_notifier = {
127362306a36Sopenharmony_ci	.notifier_call = fib_rules_event,
127462306a36Sopenharmony_ci};
127562306a36Sopenharmony_ci
127662306a36Sopenharmony_cistatic int __net_init fib_rules_net_init(struct net *net)
127762306a36Sopenharmony_ci{
127862306a36Sopenharmony_ci	INIT_LIST_HEAD(&net->rules_ops);
127962306a36Sopenharmony_ci	spin_lock_init(&net->rules_mod_lock);
128062306a36Sopenharmony_ci	return 0;
128162306a36Sopenharmony_ci}
128262306a36Sopenharmony_ci
128362306a36Sopenharmony_cistatic void __net_exit fib_rules_net_exit(struct net *net)
128462306a36Sopenharmony_ci{
128562306a36Sopenharmony_ci	WARN_ON_ONCE(!list_empty(&net->rules_ops));
128662306a36Sopenharmony_ci}
128762306a36Sopenharmony_ci
128862306a36Sopenharmony_cistatic struct pernet_operations fib_rules_net_ops = {
128962306a36Sopenharmony_ci	.init = fib_rules_net_init,
129062306a36Sopenharmony_ci	.exit = fib_rules_net_exit,
129162306a36Sopenharmony_ci};
129262306a36Sopenharmony_ci
129362306a36Sopenharmony_cistatic int __init fib_rules_init(void)
129462306a36Sopenharmony_ci{
129562306a36Sopenharmony_ci	int err;
129662306a36Sopenharmony_ci	rtnl_register(PF_UNSPEC, RTM_NEWRULE, fib_nl_newrule, NULL, 0);
129762306a36Sopenharmony_ci	rtnl_register(PF_UNSPEC, RTM_DELRULE, fib_nl_delrule, NULL, 0);
129862306a36Sopenharmony_ci	rtnl_register(PF_UNSPEC, RTM_GETRULE, NULL, fib_nl_dumprule, 0);
129962306a36Sopenharmony_ci
130062306a36Sopenharmony_ci	err = register_pernet_subsys(&fib_rules_net_ops);
130162306a36Sopenharmony_ci	if (err < 0)
130262306a36Sopenharmony_ci		goto fail;
130362306a36Sopenharmony_ci
130462306a36Sopenharmony_ci	err = register_netdevice_notifier(&fib_rules_notifier);
130562306a36Sopenharmony_ci	if (err < 0)
130662306a36Sopenharmony_ci		goto fail_unregister;
130762306a36Sopenharmony_ci
130862306a36Sopenharmony_ci	return 0;
130962306a36Sopenharmony_ci
131062306a36Sopenharmony_cifail_unregister:
131162306a36Sopenharmony_ci	unregister_pernet_subsys(&fib_rules_net_ops);
131262306a36Sopenharmony_cifail:
131362306a36Sopenharmony_ci	rtnl_unregister(PF_UNSPEC, RTM_NEWRULE);
131462306a36Sopenharmony_ci	rtnl_unregister(PF_UNSPEC, RTM_DELRULE);
131562306a36Sopenharmony_ci	rtnl_unregister(PF_UNSPEC, RTM_GETRULE);
131662306a36Sopenharmony_ci	return err;
131762306a36Sopenharmony_ci}
131862306a36Sopenharmony_ci
131962306a36Sopenharmony_cisubsys_initcall(fib_rules_init);
1320