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