162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * net/ipv6/fib6_rules.c IPv6 Routing Policy Rules 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C)2003-2006 Helsinki University of Technology 662306a36Sopenharmony_ci * Copyright (C)2003-2006 USAGI/WIDE Project 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Authors 962306a36Sopenharmony_ci * Thomas Graf <tgraf@suug.ch> 1062306a36Sopenharmony_ci * Ville Nuorvala <vnuorval@tcs.hut.fi> 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/netdevice.h> 1462306a36Sopenharmony_ci#include <linux/notifier.h> 1562306a36Sopenharmony_ci#include <linux/export.h> 1662306a36Sopenharmony_ci#include <linux/indirect_call_wrapper.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <net/fib_rules.h> 1962306a36Sopenharmony_ci#include <net/inet_dscp.h> 2062306a36Sopenharmony_ci#include <net/ipv6.h> 2162306a36Sopenharmony_ci#include <net/addrconf.h> 2262306a36Sopenharmony_ci#include <net/ip6_route.h> 2362306a36Sopenharmony_ci#include <net/netlink.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistruct fib6_rule { 2662306a36Sopenharmony_ci struct fib_rule common; 2762306a36Sopenharmony_ci struct rt6key src; 2862306a36Sopenharmony_ci struct rt6key dst; 2962306a36Sopenharmony_ci dscp_t dscp; 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic bool fib6_rule_matchall(const struct fib_rule *rule) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci struct fib6_rule *r = container_of(rule, struct fib6_rule, common); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci if (r->dst.plen || r->src.plen || r->dscp) 3762306a36Sopenharmony_ci return false; 3862306a36Sopenharmony_ci return fib_rule_matchall(rule); 3962306a36Sopenharmony_ci} 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cibool fib6_rule_default(const struct fib_rule *rule) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci if (!fib6_rule_matchall(rule) || rule->action != FR_ACT_TO_TBL || 4462306a36Sopenharmony_ci rule->l3mdev) 4562306a36Sopenharmony_ci return false; 4662306a36Sopenharmony_ci if (rule->table != RT6_TABLE_LOCAL && rule->table != RT6_TABLE_MAIN) 4762306a36Sopenharmony_ci return false; 4862306a36Sopenharmony_ci return true; 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(fib6_rule_default); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ciint fib6_rules_dump(struct net *net, struct notifier_block *nb, 5362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci return fib_rules_dump(net, nb, AF_INET6, extack); 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ciunsigned int fib6_rules_seq_read(struct net *net) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci return fib_rules_seq_read(net, AF_INET6); 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci/* called with rcu lock held; no reference taken on fib6_info */ 6462306a36Sopenharmony_ciint fib6_lookup(struct net *net, int oif, struct flowi6 *fl6, 6562306a36Sopenharmony_ci struct fib6_result *res, int flags) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci int err; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (net->ipv6.fib6_has_custom_rules) { 7062306a36Sopenharmony_ci struct fib_lookup_arg arg = { 7162306a36Sopenharmony_ci .lookup_ptr = fib6_table_lookup, 7262306a36Sopenharmony_ci .lookup_data = &oif, 7362306a36Sopenharmony_ci .result = res, 7462306a36Sopenharmony_ci .flags = FIB_LOOKUP_NOREF, 7562306a36Sopenharmony_ci }; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci l3mdev_update_flow(net, flowi6_to_flowi(fl6)); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci err = fib_rules_lookup(net->ipv6.fib6_rules_ops, 8062306a36Sopenharmony_ci flowi6_to_flowi(fl6), flags, &arg); 8162306a36Sopenharmony_ci } else { 8262306a36Sopenharmony_ci err = fib6_table_lookup(net, net->ipv6.fib6_local_tbl, oif, 8362306a36Sopenharmony_ci fl6, res, flags); 8462306a36Sopenharmony_ci if (err || res->f6i == net->ipv6.fib6_null_entry) 8562306a36Sopenharmony_ci err = fib6_table_lookup(net, net->ipv6.fib6_main_tbl, 8662306a36Sopenharmony_ci oif, fl6, res, flags); 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return err; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistruct dst_entry *fib6_rule_lookup(struct net *net, struct flowi6 *fl6, 9362306a36Sopenharmony_ci const struct sk_buff *skb, 9462306a36Sopenharmony_ci int flags, pol_lookup_t lookup) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci if (net->ipv6.fib6_has_custom_rules) { 9762306a36Sopenharmony_ci struct fib6_result res = {}; 9862306a36Sopenharmony_ci struct fib_lookup_arg arg = { 9962306a36Sopenharmony_ci .lookup_ptr = lookup, 10062306a36Sopenharmony_ci .lookup_data = skb, 10162306a36Sopenharmony_ci .result = &res, 10262306a36Sopenharmony_ci .flags = FIB_LOOKUP_NOREF, 10362306a36Sopenharmony_ci }; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci /* update flow if oif or iif point to device enslaved to l3mdev */ 10662306a36Sopenharmony_ci l3mdev_update_flow(net, flowi6_to_flowi(fl6)); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci fib_rules_lookup(net->ipv6.fib6_rules_ops, 10962306a36Sopenharmony_ci flowi6_to_flowi(fl6), flags, &arg); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci if (res.rt6) 11262306a36Sopenharmony_ci return &res.rt6->dst; 11362306a36Sopenharmony_ci } else { 11462306a36Sopenharmony_ci struct rt6_info *rt; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci rt = pol_lookup_func(lookup, 11762306a36Sopenharmony_ci net, net->ipv6.fib6_local_tbl, fl6, skb, flags); 11862306a36Sopenharmony_ci if (rt != net->ipv6.ip6_null_entry && rt->dst.error != -EAGAIN) 11962306a36Sopenharmony_ci return &rt->dst; 12062306a36Sopenharmony_ci ip6_rt_put_flags(rt, flags); 12162306a36Sopenharmony_ci rt = pol_lookup_func(lookup, 12262306a36Sopenharmony_ci net, net->ipv6.fib6_main_tbl, fl6, skb, flags); 12362306a36Sopenharmony_ci if (rt->dst.error != -EAGAIN) 12462306a36Sopenharmony_ci return &rt->dst; 12562306a36Sopenharmony_ci ip6_rt_put_flags(rt, flags); 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (!(flags & RT6_LOOKUP_F_DST_NOREF)) 12962306a36Sopenharmony_ci dst_hold(&net->ipv6.ip6_null_entry->dst); 13062306a36Sopenharmony_ci return &net->ipv6.ip6_null_entry->dst; 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic int fib6_rule_saddr(struct net *net, struct fib_rule *rule, int flags, 13462306a36Sopenharmony_ci struct flowi6 *flp6, const struct net_device *dev) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci struct fib6_rule *r = (struct fib6_rule *)rule; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci /* If we need to find a source address for this traffic, 13962306a36Sopenharmony_ci * we check the result if it meets requirement of the rule. 14062306a36Sopenharmony_ci */ 14162306a36Sopenharmony_ci if ((rule->flags & FIB_RULE_FIND_SADDR) && 14262306a36Sopenharmony_ci r->src.plen && !(flags & RT6_LOOKUP_F_HAS_SADDR)) { 14362306a36Sopenharmony_ci struct in6_addr saddr; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci if (ipv6_dev_get_saddr(net, dev, &flp6->daddr, 14662306a36Sopenharmony_ci rt6_flags2srcprefs(flags), &saddr)) 14762306a36Sopenharmony_ci return -EAGAIN; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci if (!ipv6_prefix_equal(&saddr, &r->src.addr, r->src.plen)) 15062306a36Sopenharmony_ci return -EAGAIN; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci flp6->saddr = saddr; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci return 0; 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic int fib6_rule_action_alt(struct fib_rule *rule, struct flowi *flp, 15962306a36Sopenharmony_ci int flags, struct fib_lookup_arg *arg) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci struct fib6_result *res = arg->result; 16262306a36Sopenharmony_ci struct flowi6 *flp6 = &flp->u.ip6; 16362306a36Sopenharmony_ci struct net *net = rule->fr_net; 16462306a36Sopenharmony_ci struct fib6_table *table; 16562306a36Sopenharmony_ci int err, *oif; 16662306a36Sopenharmony_ci u32 tb_id; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci switch (rule->action) { 16962306a36Sopenharmony_ci case FR_ACT_TO_TBL: 17062306a36Sopenharmony_ci break; 17162306a36Sopenharmony_ci case FR_ACT_UNREACHABLE: 17262306a36Sopenharmony_ci return -ENETUNREACH; 17362306a36Sopenharmony_ci case FR_ACT_PROHIBIT: 17462306a36Sopenharmony_ci return -EACCES; 17562306a36Sopenharmony_ci case FR_ACT_BLACKHOLE: 17662306a36Sopenharmony_ci default: 17762306a36Sopenharmony_ci return -EINVAL; 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci tb_id = fib_rule_get_table(rule, arg); 18162306a36Sopenharmony_ci table = fib6_get_table(net, tb_id); 18262306a36Sopenharmony_ci if (!table) 18362306a36Sopenharmony_ci return -EAGAIN; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci oif = (int *)arg->lookup_data; 18662306a36Sopenharmony_ci err = fib6_table_lookup(net, table, *oif, flp6, res, flags); 18762306a36Sopenharmony_ci if (!err && res->f6i != net->ipv6.fib6_null_entry) 18862306a36Sopenharmony_ci err = fib6_rule_saddr(net, rule, flags, flp6, 18962306a36Sopenharmony_ci res->nh->fib_nh_dev); 19062306a36Sopenharmony_ci else 19162306a36Sopenharmony_ci err = -EAGAIN; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci return err; 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic int __fib6_rule_action(struct fib_rule *rule, struct flowi *flp, 19762306a36Sopenharmony_ci int flags, struct fib_lookup_arg *arg) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci struct fib6_result *res = arg->result; 20062306a36Sopenharmony_ci struct flowi6 *flp6 = &flp->u.ip6; 20162306a36Sopenharmony_ci struct rt6_info *rt = NULL; 20262306a36Sopenharmony_ci struct fib6_table *table; 20362306a36Sopenharmony_ci struct net *net = rule->fr_net; 20462306a36Sopenharmony_ci pol_lookup_t lookup = arg->lookup_ptr; 20562306a36Sopenharmony_ci int err = 0; 20662306a36Sopenharmony_ci u32 tb_id; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci switch (rule->action) { 20962306a36Sopenharmony_ci case FR_ACT_TO_TBL: 21062306a36Sopenharmony_ci break; 21162306a36Sopenharmony_ci case FR_ACT_UNREACHABLE: 21262306a36Sopenharmony_ci err = -ENETUNREACH; 21362306a36Sopenharmony_ci rt = net->ipv6.ip6_null_entry; 21462306a36Sopenharmony_ci goto discard_pkt; 21562306a36Sopenharmony_ci default: 21662306a36Sopenharmony_ci case FR_ACT_BLACKHOLE: 21762306a36Sopenharmony_ci err = -EINVAL; 21862306a36Sopenharmony_ci rt = net->ipv6.ip6_blk_hole_entry; 21962306a36Sopenharmony_ci goto discard_pkt; 22062306a36Sopenharmony_ci case FR_ACT_PROHIBIT: 22162306a36Sopenharmony_ci err = -EACCES; 22262306a36Sopenharmony_ci rt = net->ipv6.ip6_prohibit_entry; 22362306a36Sopenharmony_ci goto discard_pkt; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci tb_id = fib_rule_get_table(rule, arg); 22762306a36Sopenharmony_ci table = fib6_get_table(net, tb_id); 22862306a36Sopenharmony_ci if (!table) { 22962306a36Sopenharmony_ci err = -EAGAIN; 23062306a36Sopenharmony_ci goto out; 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci rt = pol_lookup_func(lookup, 23462306a36Sopenharmony_ci net, table, flp6, arg->lookup_data, flags); 23562306a36Sopenharmony_ci if (rt != net->ipv6.ip6_null_entry) { 23662306a36Sopenharmony_ci err = fib6_rule_saddr(net, rule, flags, flp6, 23762306a36Sopenharmony_ci ip6_dst_idev(&rt->dst)->dev); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci if (err == -EAGAIN) 24062306a36Sopenharmony_ci goto again; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci err = rt->dst.error; 24362306a36Sopenharmony_ci if (err != -EAGAIN) 24462306a36Sopenharmony_ci goto out; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ciagain: 24762306a36Sopenharmony_ci ip6_rt_put_flags(rt, flags); 24862306a36Sopenharmony_ci err = -EAGAIN; 24962306a36Sopenharmony_ci rt = NULL; 25062306a36Sopenharmony_ci goto out; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cidiscard_pkt: 25362306a36Sopenharmony_ci if (!(flags & RT6_LOOKUP_F_DST_NOREF)) 25462306a36Sopenharmony_ci dst_hold(&rt->dst); 25562306a36Sopenharmony_ciout: 25662306a36Sopenharmony_ci res->rt6 = rt; 25762306a36Sopenharmony_ci return err; 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ciINDIRECT_CALLABLE_SCOPE int fib6_rule_action(struct fib_rule *rule, 26162306a36Sopenharmony_ci struct flowi *flp, int flags, 26262306a36Sopenharmony_ci struct fib_lookup_arg *arg) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci if (arg->lookup_ptr == fib6_table_lookup) 26562306a36Sopenharmony_ci return fib6_rule_action_alt(rule, flp, flags, arg); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci return __fib6_rule_action(rule, flp, flags, arg); 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ciINDIRECT_CALLABLE_SCOPE bool fib6_rule_suppress(struct fib_rule *rule, 27162306a36Sopenharmony_ci int flags, 27262306a36Sopenharmony_ci struct fib_lookup_arg *arg) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci struct fib6_result *res = arg->result; 27562306a36Sopenharmony_ci struct rt6_info *rt = res->rt6; 27662306a36Sopenharmony_ci struct net_device *dev = NULL; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci if (!rt) 27962306a36Sopenharmony_ci return false; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci if (rt->rt6i_idev) 28262306a36Sopenharmony_ci dev = rt->rt6i_idev->dev; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci /* do not accept result if the route does 28562306a36Sopenharmony_ci * not meet the required prefix length 28662306a36Sopenharmony_ci */ 28762306a36Sopenharmony_ci if (rt->rt6i_dst.plen <= rule->suppress_prefixlen) 28862306a36Sopenharmony_ci goto suppress_route; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* do not accept result if the route uses a device 29162306a36Sopenharmony_ci * belonging to a forbidden interface group 29262306a36Sopenharmony_ci */ 29362306a36Sopenharmony_ci if (rule->suppress_ifgroup != -1 && dev && dev->group == rule->suppress_ifgroup) 29462306a36Sopenharmony_ci goto suppress_route; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci return false; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_cisuppress_route: 29962306a36Sopenharmony_ci ip6_rt_put_flags(rt, flags); 30062306a36Sopenharmony_ci return true; 30162306a36Sopenharmony_ci} 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ciINDIRECT_CALLABLE_SCOPE int fib6_rule_match(struct fib_rule *rule, 30462306a36Sopenharmony_ci struct flowi *fl, int flags) 30562306a36Sopenharmony_ci{ 30662306a36Sopenharmony_ci struct fib6_rule *r = (struct fib6_rule *) rule; 30762306a36Sopenharmony_ci struct flowi6 *fl6 = &fl->u.ip6; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (r->dst.plen && 31062306a36Sopenharmony_ci !ipv6_prefix_equal(&fl6->daddr, &r->dst.addr, r->dst.plen)) 31162306a36Sopenharmony_ci return 0; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci /* 31462306a36Sopenharmony_ci * If FIB_RULE_FIND_SADDR is set and we do not have a 31562306a36Sopenharmony_ci * source address for the traffic, we defer check for 31662306a36Sopenharmony_ci * source address. 31762306a36Sopenharmony_ci */ 31862306a36Sopenharmony_ci if (r->src.plen) { 31962306a36Sopenharmony_ci if (flags & RT6_LOOKUP_F_HAS_SADDR) { 32062306a36Sopenharmony_ci if (!ipv6_prefix_equal(&fl6->saddr, &r->src.addr, 32162306a36Sopenharmony_ci r->src.plen)) 32262306a36Sopenharmony_ci return 0; 32362306a36Sopenharmony_ci } else if (!(r->common.flags & FIB_RULE_FIND_SADDR)) 32462306a36Sopenharmony_ci return 0; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci if (r->dscp && r->dscp != ip6_dscp(fl6->flowlabel)) 32862306a36Sopenharmony_ci return 0; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci if (rule->ip_proto && (rule->ip_proto != fl6->flowi6_proto)) 33162306a36Sopenharmony_ci return 0; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (fib_rule_port_range_set(&rule->sport_range) && 33462306a36Sopenharmony_ci !fib_rule_port_inrange(&rule->sport_range, fl6->fl6_sport)) 33562306a36Sopenharmony_ci return 0; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci if (fib_rule_port_range_set(&rule->dport_range) && 33862306a36Sopenharmony_ci !fib_rule_port_inrange(&rule->dport_range, fl6->fl6_dport)) 33962306a36Sopenharmony_ci return 0; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci return 1; 34262306a36Sopenharmony_ci} 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_cistatic int fib6_rule_configure(struct fib_rule *rule, struct sk_buff *skb, 34562306a36Sopenharmony_ci struct fib_rule_hdr *frh, 34662306a36Sopenharmony_ci struct nlattr **tb, 34762306a36Sopenharmony_ci struct netlink_ext_ack *extack) 34862306a36Sopenharmony_ci{ 34962306a36Sopenharmony_ci int err = -EINVAL; 35062306a36Sopenharmony_ci struct net *net = sock_net(skb->sk); 35162306a36Sopenharmony_ci struct fib6_rule *rule6 = (struct fib6_rule *) rule; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci if (!inet_validate_dscp(frh->tos)) { 35462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 35562306a36Sopenharmony_ci "Invalid dsfield (tos): ECN bits must be 0"); 35662306a36Sopenharmony_ci goto errout; 35762306a36Sopenharmony_ci } 35862306a36Sopenharmony_ci rule6->dscp = inet_dsfield_to_dscp(frh->tos); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci if (rule->action == FR_ACT_TO_TBL && !rule->l3mdev) { 36162306a36Sopenharmony_ci if (rule->table == RT6_TABLE_UNSPEC) { 36262306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Invalid table"); 36362306a36Sopenharmony_ci goto errout; 36462306a36Sopenharmony_ci } 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci if (fib6_new_table(net, rule->table) == NULL) { 36762306a36Sopenharmony_ci err = -ENOBUFS; 36862306a36Sopenharmony_ci goto errout; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci if (frh->src_len) 37362306a36Sopenharmony_ci rule6->src.addr = nla_get_in6_addr(tb[FRA_SRC]); 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci if (frh->dst_len) 37662306a36Sopenharmony_ci rule6->dst.addr = nla_get_in6_addr(tb[FRA_DST]); 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci rule6->src.plen = frh->src_len; 37962306a36Sopenharmony_ci rule6->dst.plen = frh->dst_len; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci if (fib_rule_requires_fldissect(rule)) 38262306a36Sopenharmony_ci net->ipv6.fib6_rules_require_fldissect++; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci net->ipv6.fib6_has_custom_rules = true; 38562306a36Sopenharmony_ci err = 0; 38662306a36Sopenharmony_cierrout: 38762306a36Sopenharmony_ci return err; 38862306a36Sopenharmony_ci} 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_cistatic int fib6_rule_delete(struct fib_rule *rule) 39162306a36Sopenharmony_ci{ 39262306a36Sopenharmony_ci struct net *net = rule->fr_net; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci if (net->ipv6.fib6_rules_require_fldissect && 39562306a36Sopenharmony_ci fib_rule_requires_fldissect(rule)) 39662306a36Sopenharmony_ci net->ipv6.fib6_rules_require_fldissect--; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci return 0; 39962306a36Sopenharmony_ci} 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_cistatic int fib6_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh, 40262306a36Sopenharmony_ci struct nlattr **tb) 40362306a36Sopenharmony_ci{ 40462306a36Sopenharmony_ci struct fib6_rule *rule6 = (struct fib6_rule *) rule; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci if (frh->src_len && (rule6->src.plen != frh->src_len)) 40762306a36Sopenharmony_ci return 0; 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci if (frh->dst_len && (rule6->dst.plen != frh->dst_len)) 41062306a36Sopenharmony_ci return 0; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci if (frh->tos && inet_dscp_to_dsfield(rule6->dscp) != frh->tos) 41362306a36Sopenharmony_ci return 0; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci if (frh->src_len && 41662306a36Sopenharmony_ci nla_memcmp(tb[FRA_SRC], &rule6->src.addr, sizeof(struct in6_addr))) 41762306a36Sopenharmony_ci return 0; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci if (frh->dst_len && 42062306a36Sopenharmony_ci nla_memcmp(tb[FRA_DST], &rule6->dst.addr, sizeof(struct in6_addr))) 42162306a36Sopenharmony_ci return 0; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci return 1; 42462306a36Sopenharmony_ci} 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_cistatic int fib6_rule_fill(struct fib_rule *rule, struct sk_buff *skb, 42762306a36Sopenharmony_ci struct fib_rule_hdr *frh) 42862306a36Sopenharmony_ci{ 42962306a36Sopenharmony_ci struct fib6_rule *rule6 = (struct fib6_rule *) rule; 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci frh->dst_len = rule6->dst.plen; 43262306a36Sopenharmony_ci frh->src_len = rule6->src.plen; 43362306a36Sopenharmony_ci frh->tos = inet_dscp_to_dsfield(rule6->dscp); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci if ((rule6->dst.plen && 43662306a36Sopenharmony_ci nla_put_in6_addr(skb, FRA_DST, &rule6->dst.addr)) || 43762306a36Sopenharmony_ci (rule6->src.plen && 43862306a36Sopenharmony_ci nla_put_in6_addr(skb, FRA_SRC, &rule6->src.addr))) 43962306a36Sopenharmony_ci goto nla_put_failure; 44062306a36Sopenharmony_ci return 0; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_cinla_put_failure: 44362306a36Sopenharmony_ci return -ENOBUFS; 44462306a36Sopenharmony_ci} 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_cistatic size_t fib6_rule_nlmsg_payload(struct fib_rule *rule) 44762306a36Sopenharmony_ci{ 44862306a36Sopenharmony_ci return nla_total_size(16) /* dst */ 44962306a36Sopenharmony_ci + nla_total_size(16); /* src */ 45062306a36Sopenharmony_ci} 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_cistatic void fib6_rule_flush_cache(struct fib_rules_ops *ops) 45362306a36Sopenharmony_ci{ 45462306a36Sopenharmony_ci rt_genid_bump_ipv6(ops->fro_net); 45562306a36Sopenharmony_ci} 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_cistatic const struct fib_rules_ops __net_initconst fib6_rules_ops_template = { 45862306a36Sopenharmony_ci .family = AF_INET6, 45962306a36Sopenharmony_ci .rule_size = sizeof(struct fib6_rule), 46062306a36Sopenharmony_ci .addr_size = sizeof(struct in6_addr), 46162306a36Sopenharmony_ci .action = fib6_rule_action, 46262306a36Sopenharmony_ci .match = fib6_rule_match, 46362306a36Sopenharmony_ci .suppress = fib6_rule_suppress, 46462306a36Sopenharmony_ci .configure = fib6_rule_configure, 46562306a36Sopenharmony_ci .delete = fib6_rule_delete, 46662306a36Sopenharmony_ci .compare = fib6_rule_compare, 46762306a36Sopenharmony_ci .fill = fib6_rule_fill, 46862306a36Sopenharmony_ci .nlmsg_payload = fib6_rule_nlmsg_payload, 46962306a36Sopenharmony_ci .flush_cache = fib6_rule_flush_cache, 47062306a36Sopenharmony_ci .nlgroup = RTNLGRP_IPV6_RULE, 47162306a36Sopenharmony_ci .owner = THIS_MODULE, 47262306a36Sopenharmony_ci .fro_net = &init_net, 47362306a36Sopenharmony_ci}; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_cistatic int __net_init fib6_rules_net_init(struct net *net) 47662306a36Sopenharmony_ci{ 47762306a36Sopenharmony_ci struct fib_rules_ops *ops; 47862306a36Sopenharmony_ci int err; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci ops = fib_rules_register(&fib6_rules_ops_template, net); 48162306a36Sopenharmony_ci if (IS_ERR(ops)) 48262306a36Sopenharmony_ci return PTR_ERR(ops); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci err = fib_default_rule_add(ops, 0, RT6_TABLE_LOCAL, 0); 48562306a36Sopenharmony_ci if (err) 48662306a36Sopenharmony_ci goto out_fib6_rules_ops; 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci err = fib_default_rule_add(ops, 0x7FFE, RT6_TABLE_MAIN, 0); 48962306a36Sopenharmony_ci if (err) 49062306a36Sopenharmony_ci goto out_fib6_rules_ops; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci net->ipv6.fib6_rules_ops = ops; 49362306a36Sopenharmony_ci net->ipv6.fib6_rules_require_fldissect = 0; 49462306a36Sopenharmony_ciout: 49562306a36Sopenharmony_ci return err; 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ciout_fib6_rules_ops: 49862306a36Sopenharmony_ci fib_rules_unregister(ops); 49962306a36Sopenharmony_ci goto out; 50062306a36Sopenharmony_ci} 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_cistatic void __net_exit fib6_rules_net_exit_batch(struct list_head *net_list) 50362306a36Sopenharmony_ci{ 50462306a36Sopenharmony_ci struct net *net; 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci rtnl_lock(); 50762306a36Sopenharmony_ci list_for_each_entry(net, net_list, exit_list) { 50862306a36Sopenharmony_ci fib_rules_unregister(net->ipv6.fib6_rules_ops); 50962306a36Sopenharmony_ci cond_resched(); 51062306a36Sopenharmony_ci } 51162306a36Sopenharmony_ci rtnl_unlock(); 51262306a36Sopenharmony_ci} 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_cistatic struct pernet_operations fib6_rules_net_ops = { 51562306a36Sopenharmony_ci .init = fib6_rules_net_init, 51662306a36Sopenharmony_ci .exit_batch = fib6_rules_net_exit_batch, 51762306a36Sopenharmony_ci}; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ciint __init fib6_rules_init(void) 52062306a36Sopenharmony_ci{ 52162306a36Sopenharmony_ci return register_pernet_subsys(&fib6_rules_net_ops); 52262306a36Sopenharmony_ci} 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_civoid fib6_rules_cleanup(void) 52662306a36Sopenharmony_ci{ 52762306a36Sopenharmony_ci unregister_pernet_subsys(&fib6_rules_net_ops); 52862306a36Sopenharmony_ci} 529