1ab84be7eSDavid Ahern // SPDX-License-Identifier: GPL-2.0 2ab84be7eSDavid Ahern /* Generic nexthop implementation 3ab84be7eSDavid Ahern * 4ab84be7eSDavid Ahern * Copyright (c) 2017-19 Cumulus Networks 5ab84be7eSDavid Ahern * Copyright (c) 2017-19 David Ahern <dsa@cumulusnetworks.com> 6ab84be7eSDavid Ahern */ 7ab84be7eSDavid Ahern 8ab84be7eSDavid Ahern #include <linux/nexthop.h> 9ab84be7eSDavid Ahern #include <linux/rtnetlink.h> 10ab84be7eSDavid Ahern #include <linux/slab.h> 11430a0491SDavid Ahern #include <net/arp.h> 1253010f99SDavid Ahern #include <net/ipv6_stubs.h> 13b513bd03SDavid Ahern #include <net/lwtunnel.h> 14430a0491SDavid Ahern #include <net/ndisc.h> 15ab84be7eSDavid Ahern #include <net/nexthop.h> 16597cfe4fSDavid Ahern #include <net/route.h> 17ab84be7eSDavid Ahern #include <net/sock.h> 18ab84be7eSDavid Ahern 19430a0491SDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh, 20430a0491SDavid Ahern struct nl_info *nlinfo); 21430a0491SDavid Ahern 22597cfe4fSDavid Ahern #define NH_DEV_HASHBITS 8 23597cfe4fSDavid Ahern #define NH_DEV_HASHSIZE (1U << NH_DEV_HASHBITS) 24597cfe4fSDavid Ahern 25ab84be7eSDavid Ahern static const struct nla_policy rtm_nh_policy[NHA_MAX + 1] = { 26ab84be7eSDavid Ahern [NHA_ID] = { .type = NLA_U32 }, 27ab84be7eSDavid Ahern [NHA_GROUP] = { .type = NLA_BINARY }, 28ab84be7eSDavid Ahern [NHA_GROUP_TYPE] = { .type = NLA_U16 }, 29ab84be7eSDavid Ahern [NHA_BLACKHOLE] = { .type = NLA_FLAG }, 30ab84be7eSDavid Ahern [NHA_OIF] = { .type = NLA_U32 }, 31ab84be7eSDavid Ahern [NHA_GATEWAY] = { .type = NLA_BINARY }, 32ab84be7eSDavid Ahern [NHA_ENCAP_TYPE] = { .type = NLA_U16 }, 33ab84be7eSDavid Ahern [NHA_ENCAP] = { .type = NLA_NESTED }, 34ab84be7eSDavid Ahern [NHA_GROUPS] = { .type = NLA_FLAG }, 35ab84be7eSDavid Ahern [NHA_MASTER] = { .type = NLA_U32 }, 3638428d68SRoopa Prabhu [NHA_FDB] = { .type = NLA_FLAG }, 37ab84be7eSDavid Ahern }; 38ab84be7eSDavid Ahern 398590ceedSRoopa Prabhu static int call_nexthop_notifiers(struct net *net, 40d8e79f1dSNathan Chancellor enum nexthop_event_type event_type, 418590ceedSRoopa Prabhu struct nexthop *nh) 428590ceedSRoopa Prabhu { 438590ceedSRoopa Prabhu int err; 448590ceedSRoopa Prabhu 45*80690ec6SIdo Schimmel err = blocking_notifier_call_chain(&net->nexthop.notifier_chain, 468590ceedSRoopa Prabhu event_type, nh); 478590ceedSRoopa Prabhu return notifier_to_errno(err); 488590ceedSRoopa Prabhu } 498590ceedSRoopa Prabhu 50597cfe4fSDavid Ahern static unsigned int nh_dev_hashfn(unsigned int val) 51597cfe4fSDavid Ahern { 52597cfe4fSDavid Ahern unsigned int mask = NH_DEV_HASHSIZE - 1; 53597cfe4fSDavid Ahern 54597cfe4fSDavid Ahern return (val ^ 55597cfe4fSDavid Ahern (val >> NH_DEV_HASHBITS) ^ 56597cfe4fSDavid Ahern (val >> (NH_DEV_HASHBITS * 2))) & mask; 57597cfe4fSDavid Ahern } 58597cfe4fSDavid Ahern 59597cfe4fSDavid Ahern static void nexthop_devhash_add(struct net *net, struct nh_info *nhi) 60597cfe4fSDavid Ahern { 61597cfe4fSDavid Ahern struct net_device *dev = nhi->fib_nhc.nhc_dev; 62597cfe4fSDavid Ahern struct hlist_head *head; 63597cfe4fSDavid Ahern unsigned int hash; 64597cfe4fSDavid Ahern 65597cfe4fSDavid Ahern WARN_ON(!dev); 66597cfe4fSDavid Ahern 67597cfe4fSDavid Ahern hash = nh_dev_hashfn(dev->ifindex); 68597cfe4fSDavid Ahern head = &net->nexthop.devhash[hash]; 69597cfe4fSDavid Ahern hlist_add_head(&nhi->dev_hash, head); 70597cfe4fSDavid Ahern } 71597cfe4fSDavid Ahern 72430a0491SDavid Ahern static void nexthop_free_mpath(struct nexthop *nh) 73ab84be7eSDavid Ahern { 74430a0491SDavid Ahern struct nh_group *nhg; 75430a0491SDavid Ahern int i; 76430a0491SDavid Ahern 77430a0491SDavid Ahern nhg = rcu_dereference_raw(nh->nh_grp); 7890f33bffSNikolay Aleksandrov for (i = 0; i < nhg->num_nh; ++i) { 7990f33bffSNikolay Aleksandrov struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 80430a0491SDavid Ahern 8190f33bffSNikolay Aleksandrov WARN_ON(!list_empty(&nhge->nh_list)); 8290f33bffSNikolay Aleksandrov nexthop_put(nhge->nh); 8390f33bffSNikolay Aleksandrov } 8490f33bffSNikolay Aleksandrov 8590f33bffSNikolay Aleksandrov WARN_ON(nhg->spare == nhg); 8690f33bffSNikolay Aleksandrov 8790f33bffSNikolay Aleksandrov kfree(nhg->spare); 88430a0491SDavid Ahern kfree(nhg); 89430a0491SDavid Ahern } 90430a0491SDavid Ahern 91430a0491SDavid Ahern static void nexthop_free_single(struct nexthop *nh) 92430a0491SDavid Ahern { 93ab84be7eSDavid Ahern struct nh_info *nhi; 94ab84be7eSDavid Ahern 95ab84be7eSDavid Ahern nhi = rcu_dereference_raw(nh->nh_info); 96597cfe4fSDavid Ahern switch (nhi->family) { 97597cfe4fSDavid Ahern case AF_INET: 98597cfe4fSDavid Ahern fib_nh_release(nh->net, &nhi->fib_nh); 99597cfe4fSDavid Ahern break; 10053010f99SDavid Ahern case AF_INET6: 10153010f99SDavid Ahern ipv6_stub->fib6_nh_release(&nhi->fib6_nh); 10253010f99SDavid Ahern break; 103597cfe4fSDavid Ahern } 104ab84be7eSDavid Ahern kfree(nhi); 105430a0491SDavid Ahern } 106430a0491SDavid Ahern 107430a0491SDavid Ahern void nexthop_free_rcu(struct rcu_head *head) 108430a0491SDavid Ahern { 109430a0491SDavid Ahern struct nexthop *nh = container_of(head, struct nexthop, rcu); 110430a0491SDavid Ahern 111430a0491SDavid Ahern if (nh->is_group) 112430a0491SDavid Ahern nexthop_free_mpath(nh); 113430a0491SDavid Ahern else 114430a0491SDavid Ahern nexthop_free_single(nh); 115ab84be7eSDavid Ahern 116ab84be7eSDavid Ahern kfree(nh); 117ab84be7eSDavid Ahern } 118ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_free_rcu); 119ab84be7eSDavid Ahern 120ab84be7eSDavid Ahern static struct nexthop *nexthop_alloc(void) 121ab84be7eSDavid Ahern { 122ab84be7eSDavid Ahern struct nexthop *nh; 123ab84be7eSDavid Ahern 124ab84be7eSDavid Ahern nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL); 125430a0491SDavid Ahern if (nh) { 1264c7e8084SDavid Ahern INIT_LIST_HEAD(&nh->fi_list); 127f88d8ea6SDavid Ahern INIT_LIST_HEAD(&nh->f6i_list); 128430a0491SDavid Ahern INIT_LIST_HEAD(&nh->grp_list); 12938428d68SRoopa Prabhu INIT_LIST_HEAD(&nh->fdb_list); 130430a0491SDavid Ahern } 131ab84be7eSDavid Ahern return nh; 132ab84be7eSDavid Ahern } 133ab84be7eSDavid Ahern 134430a0491SDavid Ahern static struct nh_group *nexthop_grp_alloc(u16 num_nh) 135430a0491SDavid Ahern { 136430a0491SDavid Ahern struct nh_group *nhg; 137430a0491SDavid Ahern 138d7d49dc7SIdo Schimmel nhg = kzalloc(struct_size(nhg, nh_entries, num_nh), GFP_KERNEL); 139430a0491SDavid Ahern if (nhg) 140430a0491SDavid Ahern nhg->num_nh = num_nh; 141430a0491SDavid Ahern 142430a0491SDavid Ahern return nhg; 143430a0491SDavid Ahern } 144430a0491SDavid Ahern 145ab84be7eSDavid Ahern static void nh_base_seq_inc(struct net *net) 146ab84be7eSDavid Ahern { 147ab84be7eSDavid Ahern while (++net->nexthop.seq == 0) 148ab84be7eSDavid Ahern ; 149ab84be7eSDavid Ahern } 150ab84be7eSDavid Ahern 151ab84be7eSDavid Ahern /* no reference taken; rcu lock or rtnl must be held */ 152ab84be7eSDavid Ahern struct nexthop *nexthop_find_by_id(struct net *net, u32 id) 153ab84be7eSDavid Ahern { 154ab84be7eSDavid Ahern struct rb_node **pp, *parent = NULL, *next; 155ab84be7eSDavid Ahern 156ab84be7eSDavid Ahern pp = &net->nexthop.rb_root.rb_node; 157ab84be7eSDavid Ahern while (1) { 158ab84be7eSDavid Ahern struct nexthop *nh; 159ab84be7eSDavid Ahern 160ab84be7eSDavid Ahern next = rcu_dereference_raw(*pp); 161ab84be7eSDavid Ahern if (!next) 162ab84be7eSDavid Ahern break; 163ab84be7eSDavid Ahern parent = next; 164ab84be7eSDavid Ahern 165ab84be7eSDavid Ahern nh = rb_entry(parent, struct nexthop, rb_node); 166ab84be7eSDavid Ahern if (id < nh->id) 167ab84be7eSDavid Ahern pp = &next->rb_left; 168ab84be7eSDavid Ahern else if (id > nh->id) 169ab84be7eSDavid Ahern pp = &next->rb_right; 170ab84be7eSDavid Ahern else 171ab84be7eSDavid Ahern return nh; 172ab84be7eSDavid Ahern } 173ab84be7eSDavid Ahern return NULL; 174ab84be7eSDavid Ahern } 175ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_find_by_id); 176ab84be7eSDavid Ahern 177ab84be7eSDavid Ahern /* used for auto id allocation; called with rtnl held */ 178ab84be7eSDavid Ahern static u32 nh_find_unused_id(struct net *net) 179ab84be7eSDavid Ahern { 180ab84be7eSDavid Ahern u32 id_start = net->nexthop.last_id_allocated; 181ab84be7eSDavid Ahern 182ab84be7eSDavid Ahern while (1) { 183ab84be7eSDavid Ahern net->nexthop.last_id_allocated++; 184ab84be7eSDavid Ahern if (net->nexthop.last_id_allocated == id_start) 185ab84be7eSDavid Ahern break; 186ab84be7eSDavid Ahern 187ab84be7eSDavid Ahern if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated)) 188ab84be7eSDavid Ahern return net->nexthop.last_id_allocated; 189ab84be7eSDavid Ahern } 190ab84be7eSDavid Ahern return 0; 191ab84be7eSDavid Ahern } 192ab84be7eSDavid Ahern 193430a0491SDavid Ahern static int nla_put_nh_group(struct sk_buff *skb, struct nh_group *nhg) 194430a0491SDavid Ahern { 195430a0491SDavid Ahern struct nexthop_grp *p; 196430a0491SDavid Ahern size_t len = nhg->num_nh * sizeof(*p); 197430a0491SDavid Ahern struct nlattr *nla; 198430a0491SDavid Ahern u16 group_type = 0; 199430a0491SDavid Ahern int i; 200430a0491SDavid Ahern 201430a0491SDavid Ahern if (nhg->mpath) 202430a0491SDavid Ahern group_type = NEXTHOP_GRP_TYPE_MPATH; 203430a0491SDavid Ahern 204430a0491SDavid Ahern if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type)) 205430a0491SDavid Ahern goto nla_put_failure; 206430a0491SDavid Ahern 207430a0491SDavid Ahern nla = nla_reserve(skb, NHA_GROUP, len); 208430a0491SDavid Ahern if (!nla) 209430a0491SDavid Ahern goto nla_put_failure; 210430a0491SDavid Ahern 211430a0491SDavid Ahern p = nla_data(nla); 212430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 213430a0491SDavid Ahern p->id = nhg->nh_entries[i].nh->id; 214430a0491SDavid Ahern p->weight = nhg->nh_entries[i].weight - 1; 215430a0491SDavid Ahern p += 1; 216430a0491SDavid Ahern } 217430a0491SDavid Ahern 218430a0491SDavid Ahern return 0; 219430a0491SDavid Ahern 220430a0491SDavid Ahern nla_put_failure: 221430a0491SDavid Ahern return -EMSGSIZE; 222430a0491SDavid Ahern } 223430a0491SDavid Ahern 224ab84be7eSDavid Ahern static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh, 225ab84be7eSDavid Ahern int event, u32 portid, u32 seq, unsigned int nlflags) 226ab84be7eSDavid Ahern { 22753010f99SDavid Ahern struct fib6_nh *fib6_nh; 228597cfe4fSDavid Ahern struct fib_nh *fib_nh; 229ab84be7eSDavid Ahern struct nlmsghdr *nlh; 230ab84be7eSDavid Ahern struct nh_info *nhi; 231ab84be7eSDavid Ahern struct nhmsg *nhm; 232ab84be7eSDavid Ahern 233ab84be7eSDavid Ahern nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags); 234ab84be7eSDavid Ahern if (!nlh) 235ab84be7eSDavid Ahern return -EMSGSIZE; 236ab84be7eSDavid Ahern 237ab84be7eSDavid Ahern nhm = nlmsg_data(nlh); 238ab84be7eSDavid Ahern nhm->nh_family = AF_UNSPEC; 239ab84be7eSDavid Ahern nhm->nh_flags = nh->nh_flags; 240ab84be7eSDavid Ahern nhm->nh_protocol = nh->protocol; 241ab84be7eSDavid Ahern nhm->nh_scope = 0; 242ab84be7eSDavid Ahern nhm->resvd = 0; 243ab84be7eSDavid Ahern 244ab84be7eSDavid Ahern if (nla_put_u32(skb, NHA_ID, nh->id)) 245ab84be7eSDavid Ahern goto nla_put_failure; 246ab84be7eSDavid Ahern 247430a0491SDavid Ahern if (nh->is_group) { 248430a0491SDavid Ahern struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 249430a0491SDavid Ahern 250ce9ac056SDavid Ahern if (nhg->fdb_nh && nla_put_flag(skb, NHA_FDB)) 251ce9ac056SDavid Ahern goto nla_put_failure; 252430a0491SDavid Ahern if (nla_put_nh_group(skb, nhg)) 253430a0491SDavid Ahern goto nla_put_failure; 254430a0491SDavid Ahern goto out; 255430a0491SDavid Ahern } 256430a0491SDavid Ahern 257ab84be7eSDavid Ahern nhi = rtnl_dereference(nh->nh_info); 258ab84be7eSDavid Ahern nhm->nh_family = nhi->family; 259ab84be7eSDavid Ahern if (nhi->reject_nh) { 260ab84be7eSDavid Ahern if (nla_put_flag(skb, NHA_BLACKHOLE)) 261ab84be7eSDavid Ahern goto nla_put_failure; 262ab84be7eSDavid Ahern goto out; 263ce9ac056SDavid Ahern } else if (nhi->fdb_nh) { 264ce9ac056SDavid Ahern if (nla_put_flag(skb, NHA_FDB)) 265ce9ac056SDavid Ahern goto nla_put_failure; 266ce9ac056SDavid Ahern } else { 267597cfe4fSDavid Ahern const struct net_device *dev; 268597cfe4fSDavid Ahern 269597cfe4fSDavid Ahern dev = nhi->fib_nhc.nhc_dev; 270597cfe4fSDavid Ahern if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex)) 271597cfe4fSDavid Ahern goto nla_put_failure; 272597cfe4fSDavid Ahern } 273597cfe4fSDavid Ahern 274597cfe4fSDavid Ahern nhm->nh_scope = nhi->fib_nhc.nhc_scope; 275597cfe4fSDavid Ahern switch (nhi->family) { 276597cfe4fSDavid Ahern case AF_INET: 277597cfe4fSDavid Ahern fib_nh = &nhi->fib_nh; 278597cfe4fSDavid Ahern if (fib_nh->fib_nh_gw_family && 27933d80996SIdo Schimmel nla_put_be32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4)) 280597cfe4fSDavid Ahern goto nla_put_failure; 281597cfe4fSDavid Ahern break; 28253010f99SDavid Ahern 28353010f99SDavid Ahern case AF_INET6: 28453010f99SDavid Ahern fib6_nh = &nhi->fib6_nh; 28553010f99SDavid Ahern if (fib6_nh->fib_nh_gw_family && 28653010f99SDavid Ahern nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6)) 28753010f99SDavid Ahern goto nla_put_failure; 28853010f99SDavid Ahern break; 289ab84be7eSDavid Ahern } 290ab84be7eSDavid Ahern 291b513bd03SDavid Ahern if (nhi->fib_nhc.nhc_lwtstate && 292b513bd03SDavid Ahern lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate, 293b513bd03SDavid Ahern NHA_ENCAP, NHA_ENCAP_TYPE) < 0) 294b513bd03SDavid Ahern goto nla_put_failure; 295b513bd03SDavid Ahern 296ab84be7eSDavid Ahern out: 297ab84be7eSDavid Ahern nlmsg_end(skb, nlh); 298ab84be7eSDavid Ahern return 0; 299ab84be7eSDavid Ahern 300ab84be7eSDavid Ahern nla_put_failure: 301d69100b8SStephen Worley nlmsg_cancel(skb, nlh); 302ab84be7eSDavid Ahern return -EMSGSIZE; 303ab84be7eSDavid Ahern } 304ab84be7eSDavid Ahern 305430a0491SDavid Ahern static size_t nh_nlmsg_size_grp(struct nexthop *nh) 306430a0491SDavid Ahern { 307430a0491SDavid Ahern struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 308430a0491SDavid Ahern size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh; 309430a0491SDavid Ahern 310430a0491SDavid Ahern return nla_total_size(sz) + 311430a0491SDavid Ahern nla_total_size(2); /* NHA_GROUP_TYPE */ 312430a0491SDavid Ahern } 313430a0491SDavid Ahern 314430a0491SDavid Ahern static size_t nh_nlmsg_size_single(struct nexthop *nh) 315ab84be7eSDavid Ahern { 316597cfe4fSDavid Ahern struct nh_info *nhi = rtnl_dereference(nh->nh_info); 317430a0491SDavid Ahern size_t sz; 318ab84be7eSDavid Ahern 319ab84be7eSDavid Ahern /* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE 320ab84be7eSDavid Ahern * are mutually exclusive 321ab84be7eSDavid Ahern */ 322430a0491SDavid Ahern sz = nla_total_size(4); /* NHA_OIF */ 323ab84be7eSDavid Ahern 324597cfe4fSDavid Ahern switch (nhi->family) { 325597cfe4fSDavid Ahern case AF_INET: 326597cfe4fSDavid Ahern if (nhi->fib_nh.fib_nh_gw_family) 327597cfe4fSDavid Ahern sz += nla_total_size(4); /* NHA_GATEWAY */ 328597cfe4fSDavid Ahern break; 32953010f99SDavid Ahern 33053010f99SDavid Ahern case AF_INET6: 33153010f99SDavid Ahern /* NHA_GATEWAY */ 33253010f99SDavid Ahern if (nhi->fib6_nh.fib_nh_gw_family) 33353010f99SDavid Ahern sz += nla_total_size(sizeof(const struct in6_addr)); 33453010f99SDavid Ahern break; 335597cfe4fSDavid Ahern } 336597cfe4fSDavid Ahern 337b513bd03SDavid Ahern if (nhi->fib_nhc.nhc_lwtstate) { 338b513bd03SDavid Ahern sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate); 339b513bd03SDavid Ahern sz += nla_total_size(2); /* NHA_ENCAP_TYPE */ 340b513bd03SDavid Ahern } 341b513bd03SDavid Ahern 342ab84be7eSDavid Ahern return sz; 343ab84be7eSDavid Ahern } 344ab84be7eSDavid Ahern 345430a0491SDavid Ahern static size_t nh_nlmsg_size(struct nexthop *nh) 346430a0491SDavid Ahern { 347f9e95555SStephen Worley size_t sz = NLMSG_ALIGN(sizeof(struct nhmsg)); 348f9e95555SStephen Worley 349f9e95555SStephen Worley sz += nla_total_size(4); /* NHA_ID */ 350430a0491SDavid Ahern 351430a0491SDavid Ahern if (nh->is_group) 352430a0491SDavid Ahern sz += nh_nlmsg_size_grp(nh); 353430a0491SDavid Ahern else 354430a0491SDavid Ahern sz += nh_nlmsg_size_single(nh); 355430a0491SDavid Ahern 356430a0491SDavid Ahern return sz; 357430a0491SDavid Ahern } 358430a0491SDavid Ahern 359ab84be7eSDavid Ahern static void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info) 360ab84be7eSDavid Ahern { 361ab84be7eSDavid Ahern unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0; 362ab84be7eSDavid Ahern u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0; 363ab84be7eSDavid Ahern struct sk_buff *skb; 364ab84be7eSDavid Ahern int err = -ENOBUFS; 365ab84be7eSDavid Ahern 366ab84be7eSDavid Ahern skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any()); 367ab84be7eSDavid Ahern if (!skb) 368ab84be7eSDavid Ahern goto errout; 369ab84be7eSDavid Ahern 370ab84be7eSDavid Ahern err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags); 371ab84be7eSDavid Ahern if (err < 0) { 372ab84be7eSDavid Ahern /* -EMSGSIZE implies BUG in nh_nlmsg_size() */ 373ab84be7eSDavid Ahern WARN_ON(err == -EMSGSIZE); 374ab84be7eSDavid Ahern kfree_skb(skb); 375ab84be7eSDavid Ahern goto errout; 376ab84be7eSDavid Ahern } 377ab84be7eSDavid Ahern 378ab84be7eSDavid Ahern rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP, 379ab84be7eSDavid Ahern info->nlh, gfp_any()); 380ab84be7eSDavid Ahern return; 381ab84be7eSDavid Ahern errout: 382ab84be7eSDavid Ahern if (err < 0) 383ab84be7eSDavid Ahern rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err); 384ab84be7eSDavid Ahern } 385ab84be7eSDavid Ahern 386430a0491SDavid Ahern static bool valid_group_nh(struct nexthop *nh, unsigned int npaths, 387ce9ac056SDavid Ahern bool *is_fdb, struct netlink_ext_ack *extack) 388597cfe4fSDavid Ahern { 389430a0491SDavid Ahern if (nh->is_group) { 390430a0491SDavid Ahern struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 391430a0491SDavid Ahern 392430a0491SDavid Ahern /* nested multipath (group within a group) is not 393430a0491SDavid Ahern * supported 394430a0491SDavid Ahern */ 395430a0491SDavid Ahern if (nhg->mpath) { 396430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 397430a0491SDavid Ahern "Multipath group can not be a nexthop within a group"); 398430a0491SDavid Ahern return false; 399430a0491SDavid Ahern } 400ce9ac056SDavid Ahern *is_fdb = nhg->fdb_nh; 401430a0491SDavid Ahern } else { 402430a0491SDavid Ahern struct nh_info *nhi = rtnl_dereference(nh->nh_info); 403430a0491SDavid Ahern 404430a0491SDavid Ahern if (nhi->reject_nh && npaths > 1) { 405430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 406430a0491SDavid Ahern "Blackhole nexthop can not be used in a group with more than 1 path"); 407430a0491SDavid Ahern return false; 408430a0491SDavid Ahern } 409ce9ac056SDavid Ahern *is_fdb = nhi->fdb_nh; 410430a0491SDavid Ahern } 411430a0491SDavid Ahern 412430a0491SDavid Ahern return true; 413430a0491SDavid Ahern } 414430a0491SDavid Ahern 41538428d68SRoopa Prabhu static int nh_check_attr_fdb_group(struct nexthop *nh, u8 *nh_family, 41638428d68SRoopa Prabhu struct netlink_ext_ack *extack) 41738428d68SRoopa Prabhu { 41838428d68SRoopa Prabhu struct nh_info *nhi; 41938428d68SRoopa Prabhu 420ce9ac056SDavid Ahern nhi = rtnl_dereference(nh->nh_info); 421ce9ac056SDavid Ahern 422ce9ac056SDavid Ahern if (!nhi->fdb_nh) { 42338428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "FDB nexthop group can only have fdb nexthops"); 42438428d68SRoopa Prabhu return -EINVAL; 42538428d68SRoopa Prabhu } 42638428d68SRoopa Prabhu 42738428d68SRoopa Prabhu if (*nh_family == AF_UNSPEC) { 42838428d68SRoopa Prabhu *nh_family = nhi->family; 42938428d68SRoopa Prabhu } else if (*nh_family != nhi->family) { 43038428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "FDB nexthop group cannot have mixed family nexthops"); 43138428d68SRoopa Prabhu return -EINVAL; 43238428d68SRoopa Prabhu } 43338428d68SRoopa Prabhu 43438428d68SRoopa Prabhu return 0; 43538428d68SRoopa Prabhu } 43638428d68SRoopa Prabhu 437430a0491SDavid Ahern static int nh_check_attr_group(struct net *net, struct nlattr *tb[], 438430a0491SDavid Ahern struct netlink_ext_ack *extack) 439430a0491SDavid Ahern { 440430a0491SDavid Ahern unsigned int len = nla_len(tb[NHA_GROUP]); 44138428d68SRoopa Prabhu u8 nh_family = AF_UNSPEC; 442430a0491SDavid Ahern struct nexthop_grp *nhg; 443430a0491SDavid Ahern unsigned int i, j; 44438428d68SRoopa Prabhu u8 nhg_fdb = 0; 445430a0491SDavid Ahern 446eeaac363SNikolay Aleksandrov if (!len || len & (sizeof(struct nexthop_grp) - 1)) { 447430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 448430a0491SDavid Ahern "Invalid length for nexthop group attribute"); 449430a0491SDavid Ahern return -EINVAL; 450430a0491SDavid Ahern } 451430a0491SDavid Ahern 452430a0491SDavid Ahern /* convert len to number of nexthop ids */ 453430a0491SDavid Ahern len /= sizeof(*nhg); 454430a0491SDavid Ahern 455430a0491SDavid Ahern nhg = nla_data(tb[NHA_GROUP]); 456430a0491SDavid Ahern for (i = 0; i < len; ++i) { 457430a0491SDavid Ahern if (nhg[i].resvd1 || nhg[i].resvd2) { 458430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Reserved fields in nexthop_grp must be 0"); 459430a0491SDavid Ahern return -EINVAL; 460430a0491SDavid Ahern } 461430a0491SDavid Ahern if (nhg[i].weight > 254) { 462430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid value for weight"); 463430a0491SDavid Ahern return -EINVAL; 464430a0491SDavid Ahern } 465430a0491SDavid Ahern for (j = i + 1; j < len; ++j) { 466430a0491SDavid Ahern if (nhg[i].id == nhg[j].id) { 467430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group"); 468430a0491SDavid Ahern return -EINVAL; 469430a0491SDavid Ahern } 470430a0491SDavid Ahern } 471430a0491SDavid Ahern } 472430a0491SDavid Ahern 47338428d68SRoopa Prabhu if (tb[NHA_FDB]) 47438428d68SRoopa Prabhu nhg_fdb = 1; 475430a0491SDavid Ahern nhg = nla_data(tb[NHA_GROUP]); 476430a0491SDavid Ahern for (i = 0; i < len; ++i) { 477430a0491SDavid Ahern struct nexthop *nh; 478ce9ac056SDavid Ahern bool is_fdb_nh; 479430a0491SDavid Ahern 480430a0491SDavid Ahern nh = nexthop_find_by_id(net, nhg[i].id); 481430a0491SDavid Ahern if (!nh) { 482430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid nexthop id"); 483430a0491SDavid Ahern return -EINVAL; 484430a0491SDavid Ahern } 485ce9ac056SDavid Ahern if (!valid_group_nh(nh, len, &is_fdb_nh, extack)) 486430a0491SDavid Ahern return -EINVAL; 48738428d68SRoopa Prabhu 48838428d68SRoopa Prabhu if (nhg_fdb && nh_check_attr_fdb_group(nh, &nh_family, extack)) 48938428d68SRoopa Prabhu return -EINVAL; 49038428d68SRoopa Prabhu 491ce9ac056SDavid Ahern if (!nhg_fdb && is_fdb_nh) { 49238428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Non FDB nexthop group cannot have fdb nexthops"); 49338428d68SRoopa Prabhu return -EINVAL; 49438428d68SRoopa Prabhu } 495430a0491SDavid Ahern } 49684be69b8SDavid Ahern for (i = NHA_GROUP_TYPE + 1; i < __NHA_MAX; ++i) { 497430a0491SDavid Ahern if (!tb[i]) 498430a0491SDavid Ahern continue; 49938428d68SRoopa Prabhu if (tb[NHA_FDB]) 50038428d68SRoopa Prabhu continue; 501430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 502430a0491SDavid Ahern "No other attributes can be set in nexthop groups"); 503430a0491SDavid Ahern return -EINVAL; 504430a0491SDavid Ahern } 505430a0491SDavid Ahern 506430a0491SDavid Ahern return 0; 507430a0491SDavid Ahern } 508430a0491SDavid Ahern 509430a0491SDavid Ahern static bool ipv6_good_nh(const struct fib6_nh *nh) 510430a0491SDavid Ahern { 511430a0491SDavid Ahern int state = NUD_REACHABLE; 512430a0491SDavid Ahern struct neighbour *n; 513430a0491SDavid Ahern 514430a0491SDavid Ahern rcu_read_lock_bh(); 515430a0491SDavid Ahern 516430a0491SDavid Ahern n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6); 517430a0491SDavid Ahern if (n) 518430a0491SDavid Ahern state = n->nud_state; 519430a0491SDavid Ahern 520430a0491SDavid Ahern rcu_read_unlock_bh(); 521430a0491SDavid Ahern 522430a0491SDavid Ahern return !!(state & NUD_VALID); 523430a0491SDavid Ahern } 524430a0491SDavid Ahern 525430a0491SDavid Ahern static bool ipv4_good_nh(const struct fib_nh *nh) 526430a0491SDavid Ahern { 527430a0491SDavid Ahern int state = NUD_REACHABLE; 528430a0491SDavid Ahern struct neighbour *n; 529430a0491SDavid Ahern 530430a0491SDavid Ahern rcu_read_lock_bh(); 531430a0491SDavid Ahern 532430a0491SDavid Ahern n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev, 533430a0491SDavid Ahern (__force u32)nh->fib_nh_gw4); 534430a0491SDavid Ahern if (n) 535430a0491SDavid Ahern state = n->nud_state; 536430a0491SDavid Ahern 537430a0491SDavid Ahern rcu_read_unlock_bh(); 538430a0491SDavid Ahern 539430a0491SDavid Ahern return !!(state & NUD_VALID); 540430a0491SDavid Ahern } 541430a0491SDavid Ahern 542430a0491SDavid Ahern struct nexthop *nexthop_select_path(struct nexthop *nh, int hash) 543430a0491SDavid Ahern { 544430a0491SDavid Ahern struct nexthop *rc = NULL; 545430a0491SDavid Ahern struct nh_group *nhg; 546430a0491SDavid Ahern int i; 547430a0491SDavid Ahern 548430a0491SDavid Ahern if (!nh->is_group) 549430a0491SDavid Ahern return nh; 550430a0491SDavid Ahern 551430a0491SDavid Ahern nhg = rcu_dereference(nh->nh_grp); 552430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 553430a0491SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 554430a0491SDavid Ahern struct nh_info *nhi; 555430a0491SDavid Ahern 556430a0491SDavid Ahern if (hash > atomic_read(&nhge->upper_bound)) 557430a0491SDavid Ahern continue; 558430a0491SDavid Ahern 559ce9ac056SDavid Ahern nhi = rcu_dereference(nhge->nh->nh_info); 560ce9ac056SDavid Ahern if (nhi->fdb_nh) 56138428d68SRoopa Prabhu return nhge->nh; 56238428d68SRoopa Prabhu 563430a0491SDavid Ahern /* nexthops always check if it is good and does 564430a0491SDavid Ahern * not rely on a sysctl for this behavior 565430a0491SDavid Ahern */ 566430a0491SDavid Ahern switch (nhi->family) { 567430a0491SDavid Ahern case AF_INET: 568430a0491SDavid Ahern if (ipv4_good_nh(&nhi->fib_nh)) 569430a0491SDavid Ahern return nhge->nh; 570430a0491SDavid Ahern break; 571430a0491SDavid Ahern case AF_INET6: 572430a0491SDavid Ahern if (ipv6_good_nh(&nhi->fib6_nh)) 573430a0491SDavid Ahern return nhge->nh; 574430a0491SDavid Ahern break; 575430a0491SDavid Ahern } 576430a0491SDavid Ahern 577430a0491SDavid Ahern if (!rc) 578430a0491SDavid Ahern rc = nhge->nh; 579430a0491SDavid Ahern } 580430a0491SDavid Ahern 581430a0491SDavid Ahern return rc; 582430a0491SDavid Ahern } 583430a0491SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_select_path); 584430a0491SDavid Ahern 585f88c9aa1SDavid Ahern int nexthop_for_each_fib6_nh(struct nexthop *nh, 586f88c9aa1SDavid Ahern int (*cb)(struct fib6_nh *nh, void *arg), 587f88c9aa1SDavid Ahern void *arg) 588f88c9aa1SDavid Ahern { 589f88c9aa1SDavid Ahern struct nh_info *nhi; 590f88c9aa1SDavid Ahern int err; 591f88c9aa1SDavid Ahern 592f88c9aa1SDavid Ahern if (nh->is_group) { 593f88c9aa1SDavid Ahern struct nh_group *nhg; 594f88c9aa1SDavid Ahern int i; 595f88c9aa1SDavid Ahern 596f88c9aa1SDavid Ahern nhg = rcu_dereference_rtnl(nh->nh_grp); 597f88c9aa1SDavid Ahern for (i = 0; i < nhg->num_nh; i++) { 598f88c9aa1SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 599f88c9aa1SDavid Ahern 600f88c9aa1SDavid Ahern nhi = rcu_dereference_rtnl(nhge->nh->nh_info); 601f88c9aa1SDavid Ahern err = cb(&nhi->fib6_nh, arg); 602f88c9aa1SDavid Ahern if (err) 603f88c9aa1SDavid Ahern return err; 604f88c9aa1SDavid Ahern } 605f88c9aa1SDavid Ahern } else { 606f88c9aa1SDavid Ahern nhi = rcu_dereference_rtnl(nh->nh_info); 607f88c9aa1SDavid Ahern err = cb(&nhi->fib6_nh, arg); 608f88c9aa1SDavid Ahern if (err) 609f88c9aa1SDavid Ahern return err; 610f88c9aa1SDavid Ahern } 611f88c9aa1SDavid Ahern 612f88c9aa1SDavid Ahern return 0; 613f88c9aa1SDavid Ahern } 614f88c9aa1SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh); 615f88c9aa1SDavid Ahern 6167bf4796dSDavid Ahern static int check_src_addr(const struct in6_addr *saddr, 6177bf4796dSDavid Ahern struct netlink_ext_ack *extack) 6187bf4796dSDavid Ahern { 6197bf4796dSDavid Ahern if (!ipv6_addr_any(saddr)) { 6207bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects"); 6217bf4796dSDavid Ahern return -EINVAL; 6227bf4796dSDavid Ahern } 6237bf4796dSDavid Ahern return 0; 6247bf4796dSDavid Ahern } 6257bf4796dSDavid Ahern 626f88d8ea6SDavid Ahern int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg, 627f88d8ea6SDavid Ahern struct netlink_ext_ack *extack) 628f88d8ea6SDavid Ahern { 629f88d8ea6SDavid Ahern struct nh_info *nhi; 630ce9ac056SDavid Ahern bool is_fdb_nh; 63138428d68SRoopa Prabhu 632f88d8ea6SDavid Ahern /* fib6_src is unique to a fib6_info and limits the ability to cache 633f88d8ea6SDavid Ahern * routes in fib6_nh within a nexthop that is potentially shared 634f88d8ea6SDavid Ahern * across multiple fib entries. If the config wants to use source 635f88d8ea6SDavid Ahern * routing it can not use nexthop objects. mlxsw also does not allow 636f88d8ea6SDavid Ahern * fib6_src on routes. 637f88d8ea6SDavid Ahern */ 6387bf4796dSDavid Ahern if (cfg && check_src_addr(&cfg->fc_src, extack) < 0) 639f88d8ea6SDavid Ahern return -EINVAL; 640f88d8ea6SDavid Ahern 641f88d8ea6SDavid Ahern if (nh->is_group) { 642f88d8ea6SDavid Ahern struct nh_group *nhg; 643f88d8ea6SDavid Ahern 644f88d8ea6SDavid Ahern nhg = rtnl_dereference(nh->nh_grp); 645f88d8ea6SDavid Ahern if (nhg->has_v4) 646f88d8ea6SDavid Ahern goto no_v4_nh; 647ce9ac056SDavid Ahern is_fdb_nh = nhg->fdb_nh; 648f88d8ea6SDavid Ahern } else { 649f88d8ea6SDavid Ahern nhi = rtnl_dereference(nh->nh_info); 650f88d8ea6SDavid Ahern if (nhi->family == AF_INET) 651f88d8ea6SDavid Ahern goto no_v4_nh; 652ce9ac056SDavid Ahern is_fdb_nh = nhi->fdb_nh; 653ce9ac056SDavid Ahern } 654ce9ac056SDavid Ahern 655ce9ac056SDavid Ahern if (is_fdb_nh) { 656ce9ac056SDavid Ahern NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop"); 657ce9ac056SDavid Ahern return -EINVAL; 658f88d8ea6SDavid Ahern } 659f88d8ea6SDavid Ahern 660f88d8ea6SDavid Ahern return 0; 661f88d8ea6SDavid Ahern no_v4_nh: 662f88d8ea6SDavid Ahern NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop"); 663f88d8ea6SDavid Ahern return -EINVAL; 664f88d8ea6SDavid Ahern } 665f88d8ea6SDavid Ahern EXPORT_SYMBOL_GPL(fib6_check_nexthop); 666f88d8ea6SDavid Ahern 6677bf4796dSDavid Ahern /* if existing nexthop has ipv6 routes linked to it, need 6687bf4796dSDavid Ahern * to verify this new spec works with ipv6 6697bf4796dSDavid Ahern */ 6707bf4796dSDavid Ahern static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new, 6717bf4796dSDavid Ahern struct netlink_ext_ack *extack) 6727bf4796dSDavid Ahern { 6737bf4796dSDavid Ahern struct fib6_info *f6i; 6747bf4796dSDavid Ahern 6757bf4796dSDavid Ahern if (list_empty(&old->f6i_list)) 6767bf4796dSDavid Ahern return 0; 6777bf4796dSDavid Ahern 6787bf4796dSDavid Ahern list_for_each_entry(f6i, &old->f6i_list, nh_list) { 6797bf4796dSDavid Ahern if (check_src_addr(&f6i->fib6_src.addr, extack) < 0) 6807bf4796dSDavid Ahern return -EINVAL; 6817bf4796dSDavid Ahern } 6827bf4796dSDavid Ahern 6837bf4796dSDavid Ahern return fib6_check_nexthop(new, NULL, extack); 6847bf4796dSDavid Ahern } 6857bf4796dSDavid Ahern 686ce9ac056SDavid Ahern static int nexthop_check_scope(struct nh_info *nhi, u8 scope, 6874c7e8084SDavid Ahern struct netlink_ext_ack *extack) 6884c7e8084SDavid Ahern { 6894c7e8084SDavid Ahern if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) { 6904c7e8084SDavid Ahern NL_SET_ERR_MSG(extack, 6914c7e8084SDavid Ahern "Route with host scope can not have a gateway"); 6924c7e8084SDavid Ahern return -EINVAL; 6934c7e8084SDavid Ahern } 6944c7e8084SDavid Ahern 6954c7e8084SDavid Ahern if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) { 6964c7e8084SDavid Ahern NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop"); 6974c7e8084SDavid Ahern return -EINVAL; 6984c7e8084SDavid Ahern } 6994c7e8084SDavid Ahern 7004c7e8084SDavid Ahern return 0; 7014c7e8084SDavid Ahern } 7024c7e8084SDavid Ahern 7034c7e8084SDavid Ahern /* Invoked by fib add code to verify nexthop by id is ok with 7044c7e8084SDavid Ahern * config for prefix; parts of fib_check_nh not done when nexthop 7054c7e8084SDavid Ahern * object is used. 7064c7e8084SDavid Ahern */ 7074c7e8084SDavid Ahern int fib_check_nexthop(struct nexthop *nh, u8 scope, 7084c7e8084SDavid Ahern struct netlink_ext_ack *extack) 7094c7e8084SDavid Ahern { 710ce9ac056SDavid Ahern struct nh_info *nhi; 7114c7e8084SDavid Ahern int err = 0; 7124c7e8084SDavid Ahern 713ce9ac056SDavid Ahern if (nh->is_group) { 714ce9ac056SDavid Ahern struct nh_group *nhg; 715ce9ac056SDavid Ahern 716ce9ac056SDavid Ahern nhg = rtnl_dereference(nh->nh_grp); 717ce9ac056SDavid Ahern if (nhg->fdb_nh) { 71838428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop"); 71938428d68SRoopa Prabhu err = -EINVAL; 72038428d68SRoopa Prabhu goto out; 72138428d68SRoopa Prabhu } 72238428d68SRoopa Prabhu 7234c7e8084SDavid Ahern if (scope == RT_SCOPE_HOST) { 7244c7e8084SDavid Ahern NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops"); 7254c7e8084SDavid Ahern err = -EINVAL; 7264c7e8084SDavid Ahern goto out; 7274c7e8084SDavid Ahern } 7284c7e8084SDavid Ahern 7294c7e8084SDavid Ahern /* all nexthops in a group have the same scope */ 730ce9ac056SDavid Ahern nhi = rtnl_dereference(nhg->nh_entries[0].nh->nh_info); 731ce9ac056SDavid Ahern err = nexthop_check_scope(nhi, scope, extack); 7324c7e8084SDavid Ahern } else { 733ce9ac056SDavid Ahern nhi = rtnl_dereference(nh->nh_info); 734ce9ac056SDavid Ahern if (nhi->fdb_nh) { 735ce9ac056SDavid Ahern NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop"); 736ce9ac056SDavid Ahern err = -EINVAL; 737ce9ac056SDavid Ahern goto out; 7384c7e8084SDavid Ahern } 739ce9ac056SDavid Ahern err = nexthop_check_scope(nhi, scope, extack); 740ce9ac056SDavid Ahern } 741ce9ac056SDavid Ahern 7424c7e8084SDavid Ahern out: 7434c7e8084SDavid Ahern return err; 7444c7e8084SDavid Ahern } 7454c7e8084SDavid Ahern 7467bf4796dSDavid Ahern static int fib_check_nh_list(struct nexthop *old, struct nexthop *new, 7477bf4796dSDavid Ahern struct netlink_ext_ack *extack) 7487bf4796dSDavid Ahern { 7497bf4796dSDavid Ahern struct fib_info *fi; 7507bf4796dSDavid Ahern 7517bf4796dSDavid Ahern list_for_each_entry(fi, &old->fi_list, nh_list) { 7527bf4796dSDavid Ahern int err; 7537bf4796dSDavid Ahern 7547bf4796dSDavid Ahern err = fib_check_nexthop(new, fi->fib_scope, extack); 7557bf4796dSDavid Ahern if (err) 7567bf4796dSDavid Ahern return err; 7577bf4796dSDavid Ahern } 7587bf4796dSDavid Ahern return 0; 7597bf4796dSDavid Ahern } 7607bf4796dSDavid Ahern 761430a0491SDavid Ahern static void nh_group_rebalance(struct nh_group *nhg) 762430a0491SDavid Ahern { 763430a0491SDavid Ahern int total = 0; 764430a0491SDavid Ahern int w = 0; 765430a0491SDavid Ahern int i; 766430a0491SDavid Ahern 767430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) 768430a0491SDavid Ahern total += nhg->nh_entries[i].weight; 769430a0491SDavid Ahern 770430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 771430a0491SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 772430a0491SDavid Ahern int upper_bound; 773430a0491SDavid Ahern 774430a0491SDavid Ahern w += nhge->weight; 775430a0491SDavid Ahern upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1; 776430a0491SDavid Ahern atomic_set(&nhge->upper_bound, upper_bound); 777430a0491SDavid Ahern } 778430a0491SDavid Ahern } 779430a0491SDavid Ahern 780ac21753aSDavid Ahern static void remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge, 781430a0491SDavid Ahern struct nl_info *nlinfo) 782430a0491SDavid Ahern { 78390f33bffSNikolay Aleksandrov struct nh_grp_entry *nhges, *new_nhges; 784ac21753aSDavid Ahern struct nexthop *nhp = nhge->nh_parent; 785430a0491SDavid Ahern struct nexthop *nh = nhge->nh; 78690f33bffSNikolay Aleksandrov struct nh_group *nhg, *newg; 78790f33bffSNikolay Aleksandrov int i, j; 788430a0491SDavid Ahern 789430a0491SDavid Ahern WARN_ON(!nh); 790430a0491SDavid Ahern 791ac21753aSDavid Ahern nhg = rtnl_dereference(nhp->nh_grp); 79290f33bffSNikolay Aleksandrov newg = nhg->spare; 793430a0491SDavid Ahern 79490f33bffSNikolay Aleksandrov /* last entry, keep it visible and remove the parent */ 79590f33bffSNikolay Aleksandrov if (nhg->num_nh == 1) { 79690f33bffSNikolay Aleksandrov remove_nexthop(net, nhp, nlinfo); 797430a0491SDavid Ahern return; 79890f33bffSNikolay Aleksandrov } 799430a0491SDavid Ahern 800863b2558SIdo Schimmel newg->has_v4 = false; 80190f33bffSNikolay Aleksandrov newg->mpath = nhg->mpath; 802ce9ac056SDavid Ahern newg->fdb_nh = nhg->fdb_nh; 80390f33bffSNikolay Aleksandrov newg->num_nh = nhg->num_nh; 804430a0491SDavid Ahern 80590f33bffSNikolay Aleksandrov /* copy old entries to new except the one getting removed */ 80690f33bffSNikolay Aleksandrov nhges = nhg->nh_entries; 80790f33bffSNikolay Aleksandrov new_nhges = newg->nh_entries; 80890f33bffSNikolay Aleksandrov for (i = 0, j = 0; i < nhg->num_nh; ++i) { 809863b2558SIdo Schimmel struct nh_info *nhi; 810863b2558SIdo Schimmel 81190f33bffSNikolay Aleksandrov /* current nexthop getting removed */ 81290f33bffSNikolay Aleksandrov if (nhg->nh_entries[i].nh == nh) { 81390f33bffSNikolay Aleksandrov newg->num_nh--; 81490f33bffSNikolay Aleksandrov continue; 81590f33bffSNikolay Aleksandrov } 816430a0491SDavid Ahern 817863b2558SIdo Schimmel nhi = rtnl_dereference(nhges[i].nh->nh_info); 818863b2558SIdo Schimmel if (nhi->family == AF_INET) 819863b2558SIdo Schimmel newg->has_v4 = true; 820863b2558SIdo Schimmel 82190f33bffSNikolay Aleksandrov list_del(&nhges[i].nh_list); 82290f33bffSNikolay Aleksandrov new_nhges[j].nh_parent = nhges[i].nh_parent; 82390f33bffSNikolay Aleksandrov new_nhges[j].nh = nhges[i].nh; 82490f33bffSNikolay Aleksandrov new_nhges[j].weight = nhges[i].weight; 82590f33bffSNikolay Aleksandrov list_add(&new_nhges[j].nh_list, &new_nhges[j].nh->grp_list); 82690f33bffSNikolay Aleksandrov j++; 82790f33bffSNikolay Aleksandrov } 82890f33bffSNikolay Aleksandrov 82990f33bffSNikolay Aleksandrov nh_group_rebalance(newg); 83090f33bffSNikolay Aleksandrov rcu_assign_pointer(nhp->nh_grp, newg); 83190f33bffSNikolay Aleksandrov 83290f33bffSNikolay Aleksandrov list_del(&nhge->nh_list); 83390f33bffSNikolay Aleksandrov nexthop_put(nhge->nh); 834430a0491SDavid Ahern 835430a0491SDavid Ahern if (nlinfo) 836ac21753aSDavid Ahern nexthop_notify(RTM_NEWNEXTHOP, nhp, nlinfo); 837430a0491SDavid Ahern } 838430a0491SDavid Ahern 839430a0491SDavid Ahern static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh, 840430a0491SDavid Ahern struct nl_info *nlinfo) 841430a0491SDavid Ahern { 842430a0491SDavid Ahern struct nh_grp_entry *nhge, *tmp; 843430a0491SDavid Ahern 844ac21753aSDavid Ahern list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list) 845ac21753aSDavid Ahern remove_nh_grp_entry(net, nhge, nlinfo); 846430a0491SDavid Ahern 84790f33bffSNikolay Aleksandrov /* make sure all see the newly published array before releasing rtnl */ 84890f33bffSNikolay Aleksandrov synchronize_rcu(); 849430a0491SDavid Ahern } 850430a0491SDavid Ahern 851430a0491SDavid Ahern static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo) 852430a0491SDavid Ahern { 853430a0491SDavid Ahern struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp); 854430a0491SDavid Ahern int i, num_nh = nhg->num_nh; 855430a0491SDavid Ahern 856430a0491SDavid Ahern for (i = 0; i < num_nh; ++i) { 857430a0491SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 858430a0491SDavid Ahern 859430a0491SDavid Ahern if (WARN_ON(!nhge->nh)) 860430a0491SDavid Ahern continue; 861430a0491SDavid Ahern 86290f33bffSNikolay Aleksandrov list_del_init(&nhge->nh_list); 863430a0491SDavid Ahern } 864430a0491SDavid Ahern } 865430a0491SDavid Ahern 8667bf4796dSDavid Ahern /* not called for nexthop replace */ 8674c7e8084SDavid Ahern static void __remove_nexthop_fib(struct net *net, struct nexthop *nh) 8684c7e8084SDavid Ahern { 869f88d8ea6SDavid Ahern struct fib6_info *f6i, *tmp; 8704c7e8084SDavid Ahern bool do_flush = false; 8714c7e8084SDavid Ahern struct fib_info *fi; 8724c7e8084SDavid Ahern 8738590ceedSRoopa Prabhu call_nexthop_notifiers(net, NEXTHOP_EVENT_DEL, nh); 8748590ceedSRoopa Prabhu 8754c7e8084SDavid Ahern list_for_each_entry(fi, &nh->fi_list, nh_list) { 8764c7e8084SDavid Ahern fi->fib_flags |= RTNH_F_DEAD; 8774c7e8084SDavid Ahern do_flush = true; 8784c7e8084SDavid Ahern } 8794c7e8084SDavid Ahern if (do_flush) 8804c7e8084SDavid Ahern fib_flush(net); 881f88d8ea6SDavid Ahern 882f88d8ea6SDavid Ahern /* ip6_del_rt removes the entry from this list hence the _safe */ 883f88d8ea6SDavid Ahern list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) { 884f88d8ea6SDavid Ahern /* __ip6_del_rt does a release, so do a hold here */ 885f88d8ea6SDavid Ahern fib6_info_hold(f6i); 8864f80116dSRoopa Prabhu ipv6_stub->ip6_del_rt(net, f6i, 8874f80116dSRoopa Prabhu !net->ipv4.sysctl_nexthop_compat_mode); 888f88d8ea6SDavid Ahern } 8894c7e8084SDavid Ahern } 8904c7e8084SDavid Ahern 891430a0491SDavid Ahern static void __remove_nexthop(struct net *net, struct nexthop *nh, 892430a0491SDavid Ahern struct nl_info *nlinfo) 893430a0491SDavid Ahern { 8944c7e8084SDavid Ahern __remove_nexthop_fib(net, nh); 8954c7e8084SDavid Ahern 896430a0491SDavid Ahern if (nh->is_group) { 897430a0491SDavid Ahern remove_nexthop_group(nh, nlinfo); 898430a0491SDavid Ahern } else { 899597cfe4fSDavid Ahern struct nh_info *nhi; 900597cfe4fSDavid Ahern 901597cfe4fSDavid Ahern nhi = rtnl_dereference(nh->nh_info); 902597cfe4fSDavid Ahern if (nhi->fib_nhc.nhc_dev) 903597cfe4fSDavid Ahern hlist_del(&nhi->dev_hash); 904430a0491SDavid Ahern 905430a0491SDavid Ahern remove_nexthop_from_groups(net, nh, nlinfo); 906430a0491SDavid Ahern } 907597cfe4fSDavid Ahern } 908597cfe4fSDavid Ahern 909ab84be7eSDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh, 910430a0491SDavid Ahern struct nl_info *nlinfo) 911ab84be7eSDavid Ahern { 912ab84be7eSDavid Ahern /* remove from the tree */ 913ab84be7eSDavid Ahern rb_erase(&nh->rb_node, &net->nexthop.rb_root); 914ab84be7eSDavid Ahern 915ab84be7eSDavid Ahern if (nlinfo) 916ab84be7eSDavid Ahern nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo); 917ab84be7eSDavid Ahern 918430a0491SDavid Ahern __remove_nexthop(net, nh, nlinfo); 919ab84be7eSDavid Ahern nh_base_seq_inc(net); 920ab84be7eSDavid Ahern 921ab84be7eSDavid Ahern nexthop_put(nh); 922ab84be7eSDavid Ahern } 923ab84be7eSDavid Ahern 9247bf4796dSDavid Ahern /* if any FIB entries reference this nexthop, any dst entries 9257bf4796dSDavid Ahern * need to be regenerated 9267bf4796dSDavid Ahern */ 9277bf4796dSDavid Ahern static void nh_rt_cache_flush(struct net *net, struct nexthop *nh) 9287bf4796dSDavid Ahern { 9297bf4796dSDavid Ahern struct fib6_info *f6i; 9307bf4796dSDavid Ahern 9317bf4796dSDavid Ahern if (!list_empty(&nh->fi_list)) 9327bf4796dSDavid Ahern rt_cache_flush(net); 9337bf4796dSDavid Ahern 9347bf4796dSDavid Ahern list_for_each_entry(f6i, &nh->f6i_list, nh_list) 9357bf4796dSDavid Ahern ipv6_stub->fib6_update_sernum(net, f6i); 9367bf4796dSDavid Ahern } 9377bf4796dSDavid Ahern 9387bf4796dSDavid Ahern static int replace_nexthop_grp(struct net *net, struct nexthop *old, 9397bf4796dSDavid Ahern struct nexthop *new, 9407bf4796dSDavid Ahern struct netlink_ext_ack *extack) 9417bf4796dSDavid Ahern { 9427bf4796dSDavid Ahern struct nh_group *oldg, *newg; 9437bf4796dSDavid Ahern int i; 9447bf4796dSDavid Ahern 9457bf4796dSDavid Ahern if (!new->is_group) { 9467bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop."); 9477bf4796dSDavid Ahern return -EINVAL; 9487bf4796dSDavid Ahern } 9497bf4796dSDavid Ahern 9507bf4796dSDavid Ahern oldg = rtnl_dereference(old->nh_grp); 9517bf4796dSDavid Ahern newg = rtnl_dereference(new->nh_grp); 9527bf4796dSDavid Ahern 9537bf4796dSDavid Ahern /* update parents - used by nexthop code for cleanup */ 9547bf4796dSDavid Ahern for (i = 0; i < newg->num_nh; i++) 9557bf4796dSDavid Ahern newg->nh_entries[i].nh_parent = old; 9567bf4796dSDavid Ahern 9577bf4796dSDavid Ahern rcu_assign_pointer(old->nh_grp, newg); 9587bf4796dSDavid Ahern 9597bf4796dSDavid Ahern for (i = 0; i < oldg->num_nh; i++) 9607bf4796dSDavid Ahern oldg->nh_entries[i].nh_parent = new; 9617bf4796dSDavid Ahern 9627bf4796dSDavid Ahern rcu_assign_pointer(new->nh_grp, oldg); 9637bf4796dSDavid Ahern 9647bf4796dSDavid Ahern return 0; 9657bf4796dSDavid Ahern } 9667bf4796dSDavid Ahern 967885a3b15SIdo Schimmel static void nh_group_v4_update(struct nh_group *nhg) 968885a3b15SIdo Schimmel { 969885a3b15SIdo Schimmel struct nh_grp_entry *nhges; 970885a3b15SIdo Schimmel bool has_v4 = false; 971885a3b15SIdo Schimmel int i; 972885a3b15SIdo Schimmel 973885a3b15SIdo Schimmel nhges = nhg->nh_entries; 974885a3b15SIdo Schimmel for (i = 0; i < nhg->num_nh; i++) { 975885a3b15SIdo Schimmel struct nh_info *nhi; 976885a3b15SIdo Schimmel 977885a3b15SIdo Schimmel nhi = rtnl_dereference(nhges[i].nh->nh_info); 978885a3b15SIdo Schimmel if (nhi->family == AF_INET) 979885a3b15SIdo Schimmel has_v4 = true; 980885a3b15SIdo Schimmel } 981885a3b15SIdo Schimmel nhg->has_v4 = has_v4; 982885a3b15SIdo Schimmel } 983885a3b15SIdo Schimmel 9847bf4796dSDavid Ahern static int replace_nexthop_single(struct net *net, struct nexthop *old, 9857bf4796dSDavid Ahern struct nexthop *new, 9867bf4796dSDavid Ahern struct netlink_ext_ack *extack) 9877bf4796dSDavid Ahern { 9887bf4796dSDavid Ahern struct nh_info *oldi, *newi; 9897bf4796dSDavid Ahern 9907bf4796dSDavid Ahern if (new->is_group) { 9917bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group."); 9927bf4796dSDavid Ahern return -EINVAL; 9937bf4796dSDavid Ahern } 9947bf4796dSDavid Ahern 9957bf4796dSDavid Ahern oldi = rtnl_dereference(old->nh_info); 9967bf4796dSDavid Ahern newi = rtnl_dereference(new->nh_info); 9977bf4796dSDavid Ahern 9987bf4796dSDavid Ahern newi->nh_parent = old; 9997bf4796dSDavid Ahern oldi->nh_parent = new; 10007bf4796dSDavid Ahern 10017bf4796dSDavid Ahern old->protocol = new->protocol; 10027bf4796dSDavid Ahern old->nh_flags = new->nh_flags; 10037bf4796dSDavid Ahern 10047bf4796dSDavid Ahern rcu_assign_pointer(old->nh_info, newi); 10057bf4796dSDavid Ahern rcu_assign_pointer(new->nh_info, oldi); 10067bf4796dSDavid Ahern 1007885a3b15SIdo Schimmel /* When replacing an IPv4 nexthop with an IPv6 nexthop, potentially 1008885a3b15SIdo Schimmel * update IPv4 indication in all the groups using the nexthop. 1009885a3b15SIdo Schimmel */ 1010885a3b15SIdo Schimmel if (oldi->family == AF_INET && newi->family == AF_INET6) { 1011885a3b15SIdo Schimmel struct nh_grp_entry *nhge; 1012885a3b15SIdo Schimmel 1013885a3b15SIdo Schimmel list_for_each_entry(nhge, &old->grp_list, nh_list) { 1014885a3b15SIdo Schimmel struct nexthop *nhp = nhge->nh_parent; 1015885a3b15SIdo Schimmel struct nh_group *nhg; 1016885a3b15SIdo Schimmel 1017885a3b15SIdo Schimmel nhg = rtnl_dereference(nhp->nh_grp); 1018885a3b15SIdo Schimmel nh_group_v4_update(nhg); 1019885a3b15SIdo Schimmel } 1020885a3b15SIdo Schimmel } 1021885a3b15SIdo Schimmel 10227bf4796dSDavid Ahern return 0; 10237bf4796dSDavid Ahern } 10247bf4796dSDavid Ahern 10257bf4796dSDavid Ahern static void __nexthop_replace_notify(struct net *net, struct nexthop *nh, 10267bf4796dSDavid Ahern struct nl_info *info) 10277bf4796dSDavid Ahern { 10287bf4796dSDavid Ahern struct fib6_info *f6i; 10297bf4796dSDavid Ahern 10307bf4796dSDavid Ahern if (!list_empty(&nh->fi_list)) { 10317bf4796dSDavid Ahern struct fib_info *fi; 10327bf4796dSDavid Ahern 10337bf4796dSDavid Ahern /* expectation is a few fib_info per nexthop and then 10347bf4796dSDavid Ahern * a lot of routes per fib_info. So mark the fib_info 10357bf4796dSDavid Ahern * and then walk the fib tables once 10367bf4796dSDavid Ahern */ 10377bf4796dSDavid Ahern list_for_each_entry(fi, &nh->fi_list, nh_list) 10387bf4796dSDavid Ahern fi->nh_updated = true; 10397bf4796dSDavid Ahern 10407bf4796dSDavid Ahern fib_info_notify_update(net, info); 10417bf4796dSDavid Ahern 10427bf4796dSDavid Ahern list_for_each_entry(fi, &nh->fi_list, nh_list) 10437bf4796dSDavid Ahern fi->nh_updated = false; 10447bf4796dSDavid Ahern } 10457bf4796dSDavid Ahern 10467bf4796dSDavid Ahern list_for_each_entry(f6i, &nh->f6i_list, nh_list) 10477bf4796dSDavid Ahern ipv6_stub->fib6_rt_update(net, f6i, info); 10487bf4796dSDavid Ahern } 10497bf4796dSDavid Ahern 10507bf4796dSDavid Ahern /* send RTM_NEWROUTE with REPLACE flag set for all FIB entries 10517bf4796dSDavid Ahern * linked to this nexthop and for all groups that the nexthop 10527bf4796dSDavid Ahern * is a member of 10537bf4796dSDavid Ahern */ 10547bf4796dSDavid Ahern static void nexthop_replace_notify(struct net *net, struct nexthop *nh, 10557bf4796dSDavid Ahern struct nl_info *info) 10567bf4796dSDavid Ahern { 10577bf4796dSDavid Ahern struct nh_grp_entry *nhge; 10587bf4796dSDavid Ahern 10597bf4796dSDavid Ahern __nexthop_replace_notify(net, nh, info); 10607bf4796dSDavid Ahern 10617bf4796dSDavid Ahern list_for_each_entry(nhge, &nh->grp_list, nh_list) 10627bf4796dSDavid Ahern __nexthop_replace_notify(net, nhge->nh_parent, info); 10637bf4796dSDavid Ahern } 10647bf4796dSDavid Ahern 1065ab84be7eSDavid Ahern static int replace_nexthop(struct net *net, struct nexthop *old, 1066ab84be7eSDavid Ahern struct nexthop *new, struct netlink_ext_ack *extack) 1067ab84be7eSDavid Ahern { 10687bf4796dSDavid Ahern bool new_is_reject = false; 10697bf4796dSDavid Ahern struct nh_grp_entry *nhge; 10707bf4796dSDavid Ahern int err; 10717bf4796dSDavid Ahern 10727bf4796dSDavid Ahern /* check that existing FIB entries are ok with the 10737bf4796dSDavid Ahern * new nexthop definition 10747bf4796dSDavid Ahern */ 10757bf4796dSDavid Ahern err = fib_check_nh_list(old, new, extack); 10767bf4796dSDavid Ahern if (err) 10777bf4796dSDavid Ahern return err; 10787bf4796dSDavid Ahern 10797bf4796dSDavid Ahern err = fib6_check_nh_list(old, new, extack); 10807bf4796dSDavid Ahern if (err) 10817bf4796dSDavid Ahern return err; 10827bf4796dSDavid Ahern 10837bf4796dSDavid Ahern if (!new->is_group) { 10847bf4796dSDavid Ahern struct nh_info *nhi = rtnl_dereference(new->nh_info); 10857bf4796dSDavid Ahern 10867bf4796dSDavid Ahern new_is_reject = nhi->reject_nh; 10877bf4796dSDavid Ahern } 10887bf4796dSDavid Ahern 10897bf4796dSDavid Ahern list_for_each_entry(nhge, &old->grp_list, nh_list) { 10907bf4796dSDavid Ahern /* if new nexthop is a blackhole, any groups using this 10917bf4796dSDavid Ahern * nexthop cannot have more than 1 path 10927bf4796dSDavid Ahern */ 10937bf4796dSDavid Ahern if (new_is_reject && 10947bf4796dSDavid Ahern nexthop_num_path(nhge->nh_parent) > 1) { 10957bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path"); 10967bf4796dSDavid Ahern return -EINVAL; 10977bf4796dSDavid Ahern } 10987bf4796dSDavid Ahern 10997bf4796dSDavid Ahern err = fib_check_nh_list(nhge->nh_parent, new, extack); 11007bf4796dSDavid Ahern if (err) 11017bf4796dSDavid Ahern return err; 11027bf4796dSDavid Ahern 11037bf4796dSDavid Ahern err = fib6_check_nh_list(nhge->nh_parent, new, extack); 11047bf4796dSDavid Ahern if (err) 11057bf4796dSDavid Ahern return err; 11067bf4796dSDavid Ahern } 11077bf4796dSDavid Ahern 11087bf4796dSDavid Ahern if (old->is_group) 11097bf4796dSDavid Ahern err = replace_nexthop_grp(net, old, new, extack); 11107bf4796dSDavid Ahern else 11117bf4796dSDavid Ahern err = replace_nexthop_single(net, old, new, extack); 11127bf4796dSDavid Ahern 11137bf4796dSDavid Ahern if (!err) { 11147bf4796dSDavid Ahern nh_rt_cache_flush(net, old); 11157bf4796dSDavid Ahern 11167bf4796dSDavid Ahern __remove_nexthop(net, new, NULL); 11177bf4796dSDavid Ahern nexthop_put(new); 11187bf4796dSDavid Ahern } 11197bf4796dSDavid Ahern 11207bf4796dSDavid Ahern return err; 1121ab84be7eSDavid Ahern } 1122ab84be7eSDavid Ahern 1123ab84be7eSDavid Ahern /* called with rtnl_lock held */ 1124ab84be7eSDavid Ahern static int insert_nexthop(struct net *net, struct nexthop *new_nh, 1125ab84be7eSDavid Ahern struct nh_config *cfg, struct netlink_ext_ack *extack) 1126ab84be7eSDavid Ahern { 1127ab84be7eSDavid Ahern struct rb_node **pp, *parent = NULL, *next; 1128ab84be7eSDavid Ahern struct rb_root *root = &net->nexthop.rb_root; 1129ab84be7eSDavid Ahern bool replace = !!(cfg->nlflags & NLM_F_REPLACE); 1130ab84be7eSDavid Ahern bool create = !!(cfg->nlflags & NLM_F_CREATE); 1131ab84be7eSDavid Ahern u32 new_id = new_nh->id; 11327bf4796dSDavid Ahern int replace_notify = 0; 1133ab84be7eSDavid Ahern int rc = -EEXIST; 1134ab84be7eSDavid Ahern 1135ab84be7eSDavid Ahern pp = &root->rb_node; 1136ab84be7eSDavid Ahern while (1) { 1137ab84be7eSDavid Ahern struct nexthop *nh; 1138ab84be7eSDavid Ahern 1139233c6378SIdo Schimmel next = *pp; 1140ab84be7eSDavid Ahern if (!next) 1141ab84be7eSDavid Ahern break; 1142ab84be7eSDavid Ahern 1143ab84be7eSDavid Ahern parent = next; 1144ab84be7eSDavid Ahern 1145ab84be7eSDavid Ahern nh = rb_entry(parent, struct nexthop, rb_node); 1146ab84be7eSDavid Ahern if (new_id < nh->id) { 1147ab84be7eSDavid Ahern pp = &next->rb_left; 1148ab84be7eSDavid Ahern } else if (new_id > nh->id) { 1149ab84be7eSDavid Ahern pp = &next->rb_right; 1150ab84be7eSDavid Ahern } else if (replace) { 1151ab84be7eSDavid Ahern rc = replace_nexthop(net, nh, new_nh, extack); 11527bf4796dSDavid Ahern if (!rc) { 1153ab84be7eSDavid Ahern new_nh = nh; /* send notification with old nh */ 11547bf4796dSDavid Ahern replace_notify = 1; 11557bf4796dSDavid Ahern } 1156ab84be7eSDavid Ahern goto out; 1157ab84be7eSDavid Ahern } else { 1158ab84be7eSDavid Ahern /* id already exists and not a replace */ 1159ab84be7eSDavid Ahern goto out; 1160ab84be7eSDavid Ahern } 1161ab84be7eSDavid Ahern } 1162ab84be7eSDavid Ahern 1163ab84be7eSDavid Ahern if (replace && !create) { 1164ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Replace specified without create and no entry exists"); 1165ab84be7eSDavid Ahern rc = -ENOENT; 1166ab84be7eSDavid Ahern goto out; 1167ab84be7eSDavid Ahern } 1168ab84be7eSDavid Ahern 1169ab84be7eSDavid Ahern rb_link_node_rcu(&new_nh->rb_node, parent, pp); 1170ab84be7eSDavid Ahern rb_insert_color(&new_nh->rb_node, root); 1171ab84be7eSDavid Ahern rc = 0; 1172ab84be7eSDavid Ahern out: 1173ab84be7eSDavid Ahern if (!rc) { 1174ab84be7eSDavid Ahern nh_base_seq_inc(net); 1175ab84be7eSDavid Ahern nexthop_notify(RTM_NEWNEXTHOP, new_nh, &cfg->nlinfo); 11764f80116dSRoopa Prabhu if (replace_notify && net->ipv4.sysctl_nexthop_compat_mode) 11777bf4796dSDavid Ahern nexthop_replace_notify(net, new_nh, &cfg->nlinfo); 1178ab84be7eSDavid Ahern } 1179ab84be7eSDavid Ahern 1180ab84be7eSDavid Ahern return rc; 1181ab84be7eSDavid Ahern } 1182ab84be7eSDavid Ahern 1183597cfe4fSDavid Ahern /* rtnl */ 1184597cfe4fSDavid Ahern /* remove all nexthops tied to a device being deleted */ 1185597cfe4fSDavid Ahern static void nexthop_flush_dev(struct net_device *dev) 1186597cfe4fSDavid Ahern { 1187597cfe4fSDavid Ahern unsigned int hash = nh_dev_hashfn(dev->ifindex); 1188597cfe4fSDavid Ahern struct net *net = dev_net(dev); 1189597cfe4fSDavid Ahern struct hlist_head *head = &net->nexthop.devhash[hash]; 1190597cfe4fSDavid Ahern struct hlist_node *n; 1191597cfe4fSDavid Ahern struct nh_info *nhi; 1192597cfe4fSDavid Ahern 1193597cfe4fSDavid Ahern hlist_for_each_entry_safe(nhi, n, head, dev_hash) { 1194597cfe4fSDavid Ahern if (nhi->fib_nhc.nhc_dev != dev) 1195597cfe4fSDavid Ahern continue; 1196597cfe4fSDavid Ahern 1197430a0491SDavid Ahern remove_nexthop(net, nhi->nh_parent, NULL); 1198597cfe4fSDavid Ahern } 1199597cfe4fSDavid Ahern } 1200597cfe4fSDavid Ahern 1201ab84be7eSDavid Ahern /* rtnl; called when net namespace is deleted */ 1202ab84be7eSDavid Ahern static void flush_all_nexthops(struct net *net) 1203ab84be7eSDavid Ahern { 1204ab84be7eSDavid Ahern struct rb_root *root = &net->nexthop.rb_root; 1205ab84be7eSDavid Ahern struct rb_node *node; 1206ab84be7eSDavid Ahern struct nexthop *nh; 1207ab84be7eSDavid Ahern 1208ab84be7eSDavid Ahern while ((node = rb_first(root))) { 1209ab84be7eSDavid Ahern nh = rb_entry(node, struct nexthop, rb_node); 1210430a0491SDavid Ahern remove_nexthop(net, nh, NULL); 1211ab84be7eSDavid Ahern cond_resched(); 1212ab84be7eSDavid Ahern } 1213ab84be7eSDavid Ahern } 1214ab84be7eSDavid Ahern 1215430a0491SDavid Ahern static struct nexthop *nexthop_create_group(struct net *net, 1216430a0491SDavid Ahern struct nh_config *cfg) 1217430a0491SDavid Ahern { 1218430a0491SDavid Ahern struct nlattr *grps_attr = cfg->nh_grp; 1219430a0491SDavid Ahern struct nexthop_grp *entry = nla_data(grps_attr); 122090f33bffSNikolay Aleksandrov u16 num_nh = nla_len(grps_attr) / sizeof(*entry); 1221430a0491SDavid Ahern struct nh_group *nhg; 1222430a0491SDavid Ahern struct nexthop *nh; 1223430a0491SDavid Ahern int i; 1224430a0491SDavid Ahern 1225eeaac363SNikolay Aleksandrov if (WARN_ON(!num_nh)) 1226eeaac363SNikolay Aleksandrov return ERR_PTR(-EINVAL); 1227eeaac363SNikolay Aleksandrov 1228430a0491SDavid Ahern nh = nexthop_alloc(); 1229430a0491SDavid Ahern if (!nh) 1230430a0491SDavid Ahern return ERR_PTR(-ENOMEM); 1231430a0491SDavid Ahern 1232430a0491SDavid Ahern nh->is_group = 1; 1233430a0491SDavid Ahern 123490f33bffSNikolay Aleksandrov nhg = nexthop_grp_alloc(num_nh); 1235430a0491SDavid Ahern if (!nhg) { 1236430a0491SDavid Ahern kfree(nh); 1237430a0491SDavid Ahern return ERR_PTR(-ENOMEM); 1238430a0491SDavid Ahern } 1239430a0491SDavid Ahern 124090f33bffSNikolay Aleksandrov /* spare group used for removals */ 124190f33bffSNikolay Aleksandrov nhg->spare = nexthop_grp_alloc(num_nh); 1242dafe2078SPatrick Eigensatz if (!nhg->spare) { 124390f33bffSNikolay Aleksandrov kfree(nhg); 124490f33bffSNikolay Aleksandrov kfree(nh); 1245dafe2078SPatrick Eigensatz return ERR_PTR(-ENOMEM); 124690f33bffSNikolay Aleksandrov } 124790f33bffSNikolay Aleksandrov nhg->spare->spare = nhg; 124890f33bffSNikolay Aleksandrov 1249430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 1250430a0491SDavid Ahern struct nexthop *nhe; 1251430a0491SDavid Ahern struct nh_info *nhi; 1252430a0491SDavid Ahern 1253430a0491SDavid Ahern nhe = nexthop_find_by_id(net, entry[i].id); 1254430a0491SDavid Ahern if (!nexthop_get(nhe)) 1255430a0491SDavid Ahern goto out_no_nh; 1256430a0491SDavid Ahern 1257430a0491SDavid Ahern nhi = rtnl_dereference(nhe->nh_info); 1258430a0491SDavid Ahern if (nhi->family == AF_INET) 1259430a0491SDavid Ahern nhg->has_v4 = true; 1260430a0491SDavid Ahern 1261430a0491SDavid Ahern nhg->nh_entries[i].nh = nhe; 1262430a0491SDavid Ahern nhg->nh_entries[i].weight = entry[i].weight + 1; 1263430a0491SDavid Ahern list_add(&nhg->nh_entries[i].nh_list, &nhe->grp_list); 1264430a0491SDavid Ahern nhg->nh_entries[i].nh_parent = nh; 1265430a0491SDavid Ahern } 1266430a0491SDavid Ahern 1267430a0491SDavid Ahern if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_MPATH) { 1268430a0491SDavid Ahern nhg->mpath = 1; 1269430a0491SDavid Ahern nh_group_rebalance(nhg); 1270430a0491SDavid Ahern } 1271430a0491SDavid Ahern 127238428d68SRoopa Prabhu if (cfg->nh_fdb) 1273ce9ac056SDavid Ahern nhg->fdb_nh = 1; 127438428d68SRoopa Prabhu 1275430a0491SDavid Ahern rcu_assign_pointer(nh->nh_grp, nhg); 1276430a0491SDavid Ahern 1277430a0491SDavid Ahern return nh; 1278430a0491SDavid Ahern 1279430a0491SDavid Ahern out_no_nh: 1280430a0491SDavid Ahern for (; i >= 0; --i) 1281430a0491SDavid Ahern nexthop_put(nhg->nh_entries[i].nh); 1282430a0491SDavid Ahern 128390f33bffSNikolay Aleksandrov kfree(nhg->spare); 1284430a0491SDavid Ahern kfree(nhg); 1285430a0491SDavid Ahern kfree(nh); 1286430a0491SDavid Ahern 1287430a0491SDavid Ahern return ERR_PTR(-ENOENT); 1288430a0491SDavid Ahern } 1289430a0491SDavid Ahern 1290597cfe4fSDavid Ahern static int nh_create_ipv4(struct net *net, struct nexthop *nh, 1291597cfe4fSDavid Ahern struct nh_info *nhi, struct nh_config *cfg, 1292597cfe4fSDavid Ahern struct netlink_ext_ack *extack) 1293597cfe4fSDavid Ahern { 1294597cfe4fSDavid Ahern struct fib_nh *fib_nh = &nhi->fib_nh; 1295597cfe4fSDavid Ahern struct fib_config fib_cfg = { 1296597cfe4fSDavid Ahern .fc_oif = cfg->nh_ifindex, 1297597cfe4fSDavid Ahern .fc_gw4 = cfg->gw.ipv4, 1298597cfe4fSDavid Ahern .fc_gw_family = cfg->gw.ipv4 ? AF_INET : 0, 1299597cfe4fSDavid Ahern .fc_flags = cfg->nh_flags, 1300b513bd03SDavid Ahern .fc_encap = cfg->nh_encap, 1301b513bd03SDavid Ahern .fc_encap_type = cfg->nh_encap_type, 1302597cfe4fSDavid Ahern }; 130338428d68SRoopa Prabhu u32 tb_id = (cfg->dev ? l3mdev_fib_table(cfg->dev) : RT_TABLE_MAIN); 1304c76c9925SColin Ian King int err; 1305597cfe4fSDavid Ahern 1306597cfe4fSDavid Ahern err = fib_nh_init(net, fib_nh, &fib_cfg, 1, extack); 1307597cfe4fSDavid Ahern if (err) { 1308597cfe4fSDavid Ahern fib_nh_release(net, fib_nh); 1309597cfe4fSDavid Ahern goto out; 1310597cfe4fSDavid Ahern } 1311597cfe4fSDavid Ahern 1312ce9ac056SDavid Ahern if (nhi->fdb_nh) 131338428d68SRoopa Prabhu goto out; 131438428d68SRoopa Prabhu 1315597cfe4fSDavid Ahern /* sets nh_dev if successful */ 1316597cfe4fSDavid Ahern err = fib_check_nh(net, fib_nh, tb_id, 0, extack); 1317597cfe4fSDavid Ahern if (!err) { 1318597cfe4fSDavid Ahern nh->nh_flags = fib_nh->fib_nh_flags; 1319dcb1ecb5SDavid Ahern fib_info_update_nhc_saddr(net, &fib_nh->nh_common, 1320dcb1ecb5SDavid Ahern fib_nh->fib_nh_scope); 1321597cfe4fSDavid Ahern } else { 1322597cfe4fSDavid Ahern fib_nh_release(net, fib_nh); 1323597cfe4fSDavid Ahern } 1324597cfe4fSDavid Ahern out: 1325597cfe4fSDavid Ahern return err; 1326597cfe4fSDavid Ahern } 1327597cfe4fSDavid Ahern 132853010f99SDavid Ahern static int nh_create_ipv6(struct net *net, struct nexthop *nh, 132953010f99SDavid Ahern struct nh_info *nhi, struct nh_config *cfg, 133053010f99SDavid Ahern struct netlink_ext_ack *extack) 133153010f99SDavid Ahern { 133253010f99SDavid Ahern struct fib6_nh *fib6_nh = &nhi->fib6_nh; 133353010f99SDavid Ahern struct fib6_config fib6_cfg = { 133453010f99SDavid Ahern .fc_table = l3mdev_fib_table(cfg->dev), 133553010f99SDavid Ahern .fc_ifindex = cfg->nh_ifindex, 133653010f99SDavid Ahern .fc_gateway = cfg->gw.ipv6, 133753010f99SDavid Ahern .fc_flags = cfg->nh_flags, 1338b513bd03SDavid Ahern .fc_encap = cfg->nh_encap, 1339b513bd03SDavid Ahern .fc_encap_type = cfg->nh_encap_type, 134038428d68SRoopa Prabhu .fc_is_fdb = cfg->nh_fdb, 134153010f99SDavid Ahern }; 13426f43e525SColin Ian King int err; 134353010f99SDavid Ahern 134453010f99SDavid Ahern if (!ipv6_addr_any(&cfg->gw.ipv6)) 134553010f99SDavid Ahern fib6_cfg.fc_flags |= RTF_GATEWAY; 134653010f99SDavid Ahern 134753010f99SDavid Ahern /* sets nh_dev if successful */ 134853010f99SDavid Ahern err = ipv6_stub->fib6_nh_init(net, fib6_nh, &fib6_cfg, GFP_KERNEL, 134953010f99SDavid Ahern extack); 135053010f99SDavid Ahern if (err) 135153010f99SDavid Ahern ipv6_stub->fib6_nh_release(fib6_nh); 135253010f99SDavid Ahern else 135353010f99SDavid Ahern nh->nh_flags = fib6_nh->fib_nh_flags; 135453010f99SDavid Ahern 135553010f99SDavid Ahern return err; 135653010f99SDavid Ahern } 135753010f99SDavid Ahern 1358ab84be7eSDavid Ahern static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg, 1359ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 1360ab84be7eSDavid Ahern { 1361ab84be7eSDavid Ahern struct nh_info *nhi; 1362ab84be7eSDavid Ahern struct nexthop *nh; 1363ab84be7eSDavid Ahern int err = 0; 1364ab84be7eSDavid Ahern 1365ab84be7eSDavid Ahern nh = nexthop_alloc(); 1366ab84be7eSDavid Ahern if (!nh) 1367ab84be7eSDavid Ahern return ERR_PTR(-ENOMEM); 1368ab84be7eSDavid Ahern 1369ab84be7eSDavid Ahern nhi = kzalloc(sizeof(*nhi), GFP_KERNEL); 1370ab84be7eSDavid Ahern if (!nhi) { 1371ab84be7eSDavid Ahern kfree(nh); 1372ab84be7eSDavid Ahern return ERR_PTR(-ENOMEM); 1373ab84be7eSDavid Ahern } 1374ab84be7eSDavid Ahern 1375ab84be7eSDavid Ahern nh->nh_flags = cfg->nh_flags; 1376ab84be7eSDavid Ahern nh->net = net; 1377ab84be7eSDavid Ahern 1378ab84be7eSDavid Ahern nhi->nh_parent = nh; 1379ab84be7eSDavid Ahern nhi->family = cfg->nh_family; 1380ab84be7eSDavid Ahern nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK; 1381ab84be7eSDavid Ahern 138238428d68SRoopa Prabhu if (cfg->nh_fdb) 1383ce9ac056SDavid Ahern nhi->fdb_nh = 1; 138438428d68SRoopa Prabhu 1385ab84be7eSDavid Ahern if (cfg->nh_blackhole) { 1386ab84be7eSDavid Ahern nhi->reject_nh = 1; 1387ab84be7eSDavid Ahern cfg->nh_ifindex = net->loopback_dev->ifindex; 1388ab84be7eSDavid Ahern } 1389ab84be7eSDavid Ahern 1390597cfe4fSDavid Ahern switch (cfg->nh_family) { 1391597cfe4fSDavid Ahern case AF_INET: 1392597cfe4fSDavid Ahern err = nh_create_ipv4(net, nh, nhi, cfg, extack); 1393597cfe4fSDavid Ahern break; 139453010f99SDavid Ahern case AF_INET6: 139553010f99SDavid Ahern err = nh_create_ipv6(net, nh, nhi, cfg, extack); 139653010f99SDavid Ahern break; 1397597cfe4fSDavid Ahern } 1398597cfe4fSDavid Ahern 1399ab84be7eSDavid Ahern if (err) { 1400ab84be7eSDavid Ahern kfree(nhi); 1401ab84be7eSDavid Ahern kfree(nh); 1402ab84be7eSDavid Ahern return ERR_PTR(err); 1403ab84be7eSDavid Ahern } 1404ab84be7eSDavid Ahern 1405597cfe4fSDavid Ahern /* add the entry to the device based hash */ 1406ce9ac056SDavid Ahern if (!nhi->fdb_nh) 1407597cfe4fSDavid Ahern nexthop_devhash_add(net, nhi); 1408597cfe4fSDavid Ahern 1409ab84be7eSDavid Ahern rcu_assign_pointer(nh->nh_info, nhi); 1410ab84be7eSDavid Ahern 1411ab84be7eSDavid Ahern return nh; 1412ab84be7eSDavid Ahern } 1413ab84be7eSDavid Ahern 1414ab84be7eSDavid Ahern /* called with rtnl lock held */ 1415ab84be7eSDavid Ahern static struct nexthop *nexthop_add(struct net *net, struct nh_config *cfg, 1416ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 1417ab84be7eSDavid Ahern { 1418ab84be7eSDavid Ahern struct nexthop *nh; 1419ab84be7eSDavid Ahern int err; 1420ab84be7eSDavid Ahern 1421ab84be7eSDavid Ahern if (cfg->nlflags & NLM_F_REPLACE && !cfg->nh_id) { 1422ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Replace requires nexthop id"); 1423ab84be7eSDavid Ahern return ERR_PTR(-EINVAL); 1424ab84be7eSDavid Ahern } 1425ab84be7eSDavid Ahern 1426ab84be7eSDavid Ahern if (!cfg->nh_id) { 1427ab84be7eSDavid Ahern cfg->nh_id = nh_find_unused_id(net); 1428ab84be7eSDavid Ahern if (!cfg->nh_id) { 1429ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "No unused id"); 1430ab84be7eSDavid Ahern return ERR_PTR(-EINVAL); 1431ab84be7eSDavid Ahern } 1432ab84be7eSDavid Ahern } 1433ab84be7eSDavid Ahern 1434430a0491SDavid Ahern if (cfg->nh_grp) 1435430a0491SDavid Ahern nh = nexthop_create_group(net, cfg); 1436430a0491SDavid Ahern else 1437ab84be7eSDavid Ahern nh = nexthop_create(net, cfg, extack); 1438430a0491SDavid Ahern 1439ab84be7eSDavid Ahern if (IS_ERR(nh)) 1440ab84be7eSDavid Ahern return nh; 1441ab84be7eSDavid Ahern 1442ab84be7eSDavid Ahern refcount_set(&nh->refcnt, 1); 1443ab84be7eSDavid Ahern nh->id = cfg->nh_id; 1444ab84be7eSDavid Ahern nh->protocol = cfg->nh_protocol; 1445ab84be7eSDavid Ahern nh->net = net; 1446ab84be7eSDavid Ahern 1447ab84be7eSDavid Ahern err = insert_nexthop(net, nh, cfg, extack); 1448ab84be7eSDavid Ahern if (err) { 1449430a0491SDavid Ahern __remove_nexthop(net, nh, NULL); 1450ab84be7eSDavid Ahern nexthop_put(nh); 1451ab84be7eSDavid Ahern nh = ERR_PTR(err); 1452ab84be7eSDavid Ahern } 1453ab84be7eSDavid Ahern 1454ab84be7eSDavid Ahern return nh; 1455ab84be7eSDavid Ahern } 1456ab84be7eSDavid Ahern 1457ab84be7eSDavid Ahern static int rtm_to_nh_config(struct net *net, struct sk_buff *skb, 1458ab84be7eSDavid Ahern struct nlmsghdr *nlh, struct nh_config *cfg, 1459ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 1460ab84be7eSDavid Ahern { 1461ab84be7eSDavid Ahern struct nhmsg *nhm = nlmsg_data(nlh); 1462ab84be7eSDavid Ahern struct nlattr *tb[NHA_MAX + 1]; 1463ab84be7eSDavid Ahern int err; 1464ab84be7eSDavid Ahern 1465ab84be7eSDavid Ahern err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy, 1466ab84be7eSDavid Ahern extack); 1467ab84be7eSDavid Ahern if (err < 0) 1468ab84be7eSDavid Ahern return err; 1469ab84be7eSDavid Ahern 1470ab84be7eSDavid Ahern err = -EINVAL; 1471ab84be7eSDavid Ahern if (nhm->resvd || nhm->nh_scope) { 1472ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid values in ancillary header"); 1473ab84be7eSDavid Ahern goto out; 1474ab84be7eSDavid Ahern } 1475ab84be7eSDavid Ahern if (nhm->nh_flags & ~NEXTHOP_VALID_USER_FLAGS) { 1476ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid nexthop flags in ancillary header"); 1477ab84be7eSDavid Ahern goto out; 1478ab84be7eSDavid Ahern } 1479ab84be7eSDavid Ahern 1480ab84be7eSDavid Ahern switch (nhm->nh_family) { 1481597cfe4fSDavid Ahern case AF_INET: 148253010f99SDavid Ahern case AF_INET6: 1483597cfe4fSDavid Ahern break; 1484430a0491SDavid Ahern case AF_UNSPEC: 1485430a0491SDavid Ahern if (tb[NHA_GROUP]) 1486430a0491SDavid Ahern break; 1487a8eceea8SJoe Perches fallthrough; 1488ab84be7eSDavid Ahern default: 1489ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid address family"); 1490ab84be7eSDavid Ahern goto out; 1491ab84be7eSDavid Ahern } 1492ab84be7eSDavid Ahern 1493ab84be7eSDavid Ahern if (tb[NHA_GROUPS] || tb[NHA_MASTER]) { 1494ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid attributes in request"); 1495ab84be7eSDavid Ahern goto out; 1496ab84be7eSDavid Ahern } 1497ab84be7eSDavid Ahern 1498ab84be7eSDavid Ahern memset(cfg, 0, sizeof(*cfg)); 1499ab84be7eSDavid Ahern cfg->nlflags = nlh->nlmsg_flags; 1500ab84be7eSDavid Ahern cfg->nlinfo.portid = NETLINK_CB(skb).portid; 1501ab84be7eSDavid Ahern cfg->nlinfo.nlh = nlh; 1502ab84be7eSDavid Ahern cfg->nlinfo.nl_net = net; 1503ab84be7eSDavid Ahern 1504ab84be7eSDavid Ahern cfg->nh_family = nhm->nh_family; 1505ab84be7eSDavid Ahern cfg->nh_protocol = nhm->nh_protocol; 1506ab84be7eSDavid Ahern cfg->nh_flags = nhm->nh_flags; 1507ab84be7eSDavid Ahern 1508ab84be7eSDavid Ahern if (tb[NHA_ID]) 1509ab84be7eSDavid Ahern cfg->nh_id = nla_get_u32(tb[NHA_ID]); 1510ab84be7eSDavid Ahern 151138428d68SRoopa Prabhu if (tb[NHA_FDB]) { 151238428d68SRoopa Prabhu if (tb[NHA_OIF] || tb[NHA_BLACKHOLE] || 151338428d68SRoopa Prabhu tb[NHA_ENCAP] || tb[NHA_ENCAP_TYPE]) { 151438428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Fdb attribute can not be used with encap, oif or blackhole"); 151538428d68SRoopa Prabhu goto out; 151638428d68SRoopa Prabhu } 151738428d68SRoopa Prabhu if (nhm->nh_flags) { 151838428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Unsupported nexthop flags in ancillary header"); 151938428d68SRoopa Prabhu goto out; 152038428d68SRoopa Prabhu } 152138428d68SRoopa Prabhu cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]); 152238428d68SRoopa Prabhu } 152338428d68SRoopa Prabhu 1524430a0491SDavid Ahern if (tb[NHA_GROUP]) { 1525430a0491SDavid Ahern if (nhm->nh_family != AF_UNSPEC) { 1526430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid family for group"); 1527430a0491SDavid Ahern goto out; 1528430a0491SDavid Ahern } 1529430a0491SDavid Ahern cfg->nh_grp = tb[NHA_GROUP]; 1530430a0491SDavid Ahern 1531430a0491SDavid Ahern cfg->nh_grp_type = NEXTHOP_GRP_TYPE_MPATH; 1532430a0491SDavid Ahern if (tb[NHA_GROUP_TYPE]) 1533430a0491SDavid Ahern cfg->nh_grp_type = nla_get_u16(tb[NHA_GROUP_TYPE]); 1534430a0491SDavid Ahern 1535430a0491SDavid Ahern if (cfg->nh_grp_type > NEXTHOP_GRP_TYPE_MAX) { 1536430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid group type"); 1537430a0491SDavid Ahern goto out; 1538430a0491SDavid Ahern } 1539430a0491SDavid Ahern err = nh_check_attr_group(net, tb, extack); 1540430a0491SDavid Ahern 1541430a0491SDavid Ahern /* no other attributes should be set */ 1542430a0491SDavid Ahern goto out; 1543430a0491SDavid Ahern } 1544430a0491SDavid Ahern 1545ab84be7eSDavid Ahern if (tb[NHA_BLACKHOLE]) { 1546b513bd03SDavid Ahern if (tb[NHA_GATEWAY] || tb[NHA_OIF] || 154738428d68SRoopa Prabhu tb[NHA_ENCAP] || tb[NHA_ENCAP_TYPE] || tb[NHA_FDB]) { 154838428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Blackhole attribute can not be used with gateway, oif, encap or fdb"); 1549ab84be7eSDavid Ahern goto out; 1550ab84be7eSDavid Ahern } 1551ab84be7eSDavid Ahern 1552ab84be7eSDavid Ahern cfg->nh_blackhole = 1; 1553ab84be7eSDavid Ahern err = 0; 1554ab84be7eSDavid Ahern goto out; 1555ab84be7eSDavid Ahern } 1556ab84be7eSDavid Ahern 155738428d68SRoopa Prabhu if (!cfg->nh_fdb && !tb[NHA_OIF]) { 155838428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Device attribute required for non-blackhole and non-fdb nexthops"); 1559ab84be7eSDavid Ahern goto out; 1560ab84be7eSDavid Ahern } 1561ab84be7eSDavid Ahern 156238428d68SRoopa Prabhu if (!cfg->nh_fdb && tb[NHA_OIF]) { 1563ab84be7eSDavid Ahern cfg->nh_ifindex = nla_get_u32(tb[NHA_OIF]); 1564ab84be7eSDavid Ahern if (cfg->nh_ifindex) 1565ab84be7eSDavid Ahern cfg->dev = __dev_get_by_index(net, cfg->nh_ifindex); 1566ab84be7eSDavid Ahern 1567ab84be7eSDavid Ahern if (!cfg->dev) { 1568ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid device index"); 1569ab84be7eSDavid Ahern goto out; 1570ab84be7eSDavid Ahern } else if (!(cfg->dev->flags & IFF_UP)) { 1571ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop device is not up"); 1572ab84be7eSDavid Ahern err = -ENETDOWN; 1573ab84be7eSDavid Ahern goto out; 1574ab84be7eSDavid Ahern } else if (!netif_carrier_ok(cfg->dev)) { 1575ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Carrier for nexthop device is down"); 1576ab84be7eSDavid Ahern err = -ENETDOWN; 1577ab84be7eSDavid Ahern goto out; 1578ab84be7eSDavid Ahern } 157938428d68SRoopa Prabhu } 1580ab84be7eSDavid Ahern 1581597cfe4fSDavid Ahern err = -EINVAL; 1582597cfe4fSDavid Ahern if (tb[NHA_GATEWAY]) { 1583597cfe4fSDavid Ahern struct nlattr *gwa = tb[NHA_GATEWAY]; 1584597cfe4fSDavid Ahern 1585597cfe4fSDavid Ahern switch (cfg->nh_family) { 1586597cfe4fSDavid Ahern case AF_INET: 1587597cfe4fSDavid Ahern if (nla_len(gwa) != sizeof(u32)) { 1588597cfe4fSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid gateway"); 1589597cfe4fSDavid Ahern goto out; 1590597cfe4fSDavid Ahern } 1591597cfe4fSDavid Ahern cfg->gw.ipv4 = nla_get_be32(gwa); 1592597cfe4fSDavid Ahern break; 159353010f99SDavid Ahern case AF_INET6: 159453010f99SDavid Ahern if (nla_len(gwa) != sizeof(struct in6_addr)) { 159553010f99SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid gateway"); 159653010f99SDavid Ahern goto out; 159753010f99SDavid Ahern } 159853010f99SDavid Ahern cfg->gw.ipv6 = nla_get_in6_addr(gwa); 159953010f99SDavid Ahern break; 1600597cfe4fSDavid Ahern default: 1601597cfe4fSDavid Ahern NL_SET_ERR_MSG(extack, 1602597cfe4fSDavid Ahern "Unknown address family for gateway"); 1603597cfe4fSDavid Ahern goto out; 1604597cfe4fSDavid Ahern } 1605597cfe4fSDavid Ahern } else { 1606597cfe4fSDavid Ahern /* device only nexthop (no gateway) */ 1607597cfe4fSDavid Ahern if (cfg->nh_flags & RTNH_F_ONLINK) { 1608597cfe4fSDavid Ahern NL_SET_ERR_MSG(extack, 1609597cfe4fSDavid Ahern "ONLINK flag can not be set for nexthop without a gateway"); 1610597cfe4fSDavid Ahern goto out; 1611597cfe4fSDavid Ahern } 1612597cfe4fSDavid Ahern } 1613597cfe4fSDavid Ahern 1614b513bd03SDavid Ahern if (tb[NHA_ENCAP]) { 1615b513bd03SDavid Ahern cfg->nh_encap = tb[NHA_ENCAP]; 1616b513bd03SDavid Ahern 1617b513bd03SDavid Ahern if (!tb[NHA_ENCAP_TYPE]) { 1618b513bd03SDavid Ahern NL_SET_ERR_MSG(extack, "LWT encapsulation type is missing"); 1619b513bd03SDavid Ahern goto out; 1620b513bd03SDavid Ahern } 1621b513bd03SDavid Ahern 1622b513bd03SDavid Ahern cfg->nh_encap_type = nla_get_u16(tb[NHA_ENCAP_TYPE]); 1623b513bd03SDavid Ahern err = lwtunnel_valid_encap_type(cfg->nh_encap_type, extack); 1624b513bd03SDavid Ahern if (err < 0) 1625b513bd03SDavid Ahern goto out; 1626b513bd03SDavid Ahern 1627b513bd03SDavid Ahern } else if (tb[NHA_ENCAP_TYPE]) { 1628b513bd03SDavid Ahern NL_SET_ERR_MSG(extack, "LWT encapsulation attribute is missing"); 1629b513bd03SDavid Ahern goto out; 1630b513bd03SDavid Ahern } 1631b513bd03SDavid Ahern 1632b513bd03SDavid Ahern 1633ab84be7eSDavid Ahern err = 0; 1634ab84be7eSDavid Ahern out: 1635ab84be7eSDavid Ahern return err; 1636ab84be7eSDavid Ahern } 1637ab84be7eSDavid Ahern 1638ab84be7eSDavid Ahern /* rtnl */ 1639ab84be7eSDavid Ahern static int rtm_new_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh, 1640ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 1641ab84be7eSDavid Ahern { 1642ab84be7eSDavid Ahern struct net *net = sock_net(skb->sk); 1643ab84be7eSDavid Ahern struct nh_config cfg; 1644ab84be7eSDavid Ahern struct nexthop *nh; 1645ab84be7eSDavid Ahern int err; 1646ab84be7eSDavid Ahern 1647ab84be7eSDavid Ahern err = rtm_to_nh_config(net, skb, nlh, &cfg, extack); 1648ab84be7eSDavid Ahern if (!err) { 1649ab84be7eSDavid Ahern nh = nexthop_add(net, &cfg, extack); 1650ab84be7eSDavid Ahern if (IS_ERR(nh)) 1651ab84be7eSDavid Ahern err = PTR_ERR(nh); 1652ab84be7eSDavid Ahern } 1653ab84be7eSDavid Ahern 1654ab84be7eSDavid Ahern return err; 1655ab84be7eSDavid Ahern } 1656ab84be7eSDavid Ahern 1657ab84be7eSDavid Ahern static int nh_valid_get_del_req(struct nlmsghdr *nlh, u32 *id, 1658ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 1659ab84be7eSDavid Ahern { 1660ab84be7eSDavid Ahern struct nhmsg *nhm = nlmsg_data(nlh); 1661ab84be7eSDavid Ahern struct nlattr *tb[NHA_MAX + 1]; 1662ab84be7eSDavid Ahern int err, i; 1663ab84be7eSDavid Ahern 1664ab84be7eSDavid Ahern err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy, 1665ab84be7eSDavid Ahern extack); 1666ab84be7eSDavid Ahern if (err < 0) 1667ab84be7eSDavid Ahern return err; 1668ab84be7eSDavid Ahern 1669ab84be7eSDavid Ahern err = -EINVAL; 1670ab84be7eSDavid Ahern for (i = 0; i < __NHA_MAX; ++i) { 1671ab84be7eSDavid Ahern if (!tb[i]) 1672ab84be7eSDavid Ahern continue; 1673ab84be7eSDavid Ahern 1674ab84be7eSDavid Ahern switch (i) { 1675ab84be7eSDavid Ahern case NHA_ID: 1676ab84be7eSDavid Ahern break; 1677ab84be7eSDavid Ahern default: 1678ab84be7eSDavid Ahern NL_SET_ERR_MSG_ATTR(extack, tb[i], 1679ab84be7eSDavid Ahern "Unexpected attribute in request"); 1680ab84be7eSDavid Ahern goto out; 1681ab84be7eSDavid Ahern } 1682ab84be7eSDavid Ahern } 1683ab84be7eSDavid Ahern if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) { 1684ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid values in header"); 1685ab84be7eSDavid Ahern goto out; 1686ab84be7eSDavid Ahern } 1687ab84be7eSDavid Ahern 1688ab84be7eSDavid Ahern if (!tb[NHA_ID]) { 1689ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop id is missing"); 1690ab84be7eSDavid Ahern goto out; 1691ab84be7eSDavid Ahern } 1692ab84be7eSDavid Ahern 1693ab84be7eSDavid Ahern *id = nla_get_u32(tb[NHA_ID]); 1694ab84be7eSDavid Ahern if (!(*id)) 1695ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid nexthop id"); 1696ab84be7eSDavid Ahern else 1697ab84be7eSDavid Ahern err = 0; 1698ab84be7eSDavid Ahern out: 1699ab84be7eSDavid Ahern return err; 1700ab84be7eSDavid Ahern } 1701ab84be7eSDavid Ahern 1702ab84be7eSDavid Ahern /* rtnl */ 1703ab84be7eSDavid Ahern static int rtm_del_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh, 1704ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 1705ab84be7eSDavid Ahern { 1706ab84be7eSDavid Ahern struct net *net = sock_net(skb->sk); 1707ab84be7eSDavid Ahern struct nl_info nlinfo = { 1708ab84be7eSDavid Ahern .nlh = nlh, 1709ab84be7eSDavid Ahern .nl_net = net, 1710ab84be7eSDavid Ahern .portid = NETLINK_CB(skb).portid, 1711ab84be7eSDavid Ahern }; 1712ab84be7eSDavid Ahern struct nexthop *nh; 1713ab84be7eSDavid Ahern int err; 1714ab84be7eSDavid Ahern u32 id; 1715ab84be7eSDavid Ahern 1716ab84be7eSDavid Ahern err = nh_valid_get_del_req(nlh, &id, extack); 1717ab84be7eSDavid Ahern if (err) 1718ab84be7eSDavid Ahern return err; 1719ab84be7eSDavid Ahern 1720ab84be7eSDavid Ahern nh = nexthop_find_by_id(net, id); 1721ab84be7eSDavid Ahern if (!nh) 1722ab84be7eSDavid Ahern return -ENOENT; 1723ab84be7eSDavid Ahern 1724430a0491SDavid Ahern remove_nexthop(net, nh, &nlinfo); 1725ab84be7eSDavid Ahern 1726ab84be7eSDavid Ahern return 0; 1727ab84be7eSDavid Ahern } 1728ab84be7eSDavid Ahern 1729ab84be7eSDavid Ahern /* rtnl */ 1730ab84be7eSDavid Ahern static int rtm_get_nexthop(struct sk_buff *in_skb, struct nlmsghdr *nlh, 1731ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 1732ab84be7eSDavid Ahern { 1733ab84be7eSDavid Ahern struct net *net = sock_net(in_skb->sk); 1734ab84be7eSDavid Ahern struct sk_buff *skb = NULL; 1735ab84be7eSDavid Ahern struct nexthop *nh; 1736ab84be7eSDavid Ahern int err; 1737ab84be7eSDavid Ahern u32 id; 1738ab84be7eSDavid Ahern 1739ab84be7eSDavid Ahern err = nh_valid_get_del_req(nlh, &id, extack); 1740ab84be7eSDavid Ahern if (err) 1741ab84be7eSDavid Ahern return err; 1742ab84be7eSDavid Ahern 1743ab84be7eSDavid Ahern err = -ENOBUFS; 1744ab84be7eSDavid Ahern skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); 1745ab84be7eSDavid Ahern if (!skb) 1746ab84be7eSDavid Ahern goto out; 1747ab84be7eSDavid Ahern 1748ab84be7eSDavid Ahern err = -ENOENT; 1749ab84be7eSDavid Ahern nh = nexthop_find_by_id(net, id); 1750ab84be7eSDavid Ahern if (!nh) 1751ab84be7eSDavid Ahern goto errout_free; 1752ab84be7eSDavid Ahern 1753ab84be7eSDavid Ahern err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, NETLINK_CB(in_skb).portid, 1754ab84be7eSDavid Ahern nlh->nlmsg_seq, 0); 1755ab84be7eSDavid Ahern if (err < 0) { 1756ab84be7eSDavid Ahern WARN_ON(err == -EMSGSIZE); 1757ab84be7eSDavid Ahern goto errout_free; 1758ab84be7eSDavid Ahern } 1759ab84be7eSDavid Ahern 1760ab84be7eSDavid Ahern err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid); 1761ab84be7eSDavid Ahern out: 1762ab84be7eSDavid Ahern return err; 1763ab84be7eSDavid Ahern errout_free: 1764ab84be7eSDavid Ahern kfree_skb(skb); 1765ab84be7eSDavid Ahern goto out; 1766ab84be7eSDavid Ahern } 1767ab84be7eSDavid Ahern 1768430a0491SDavid Ahern static bool nh_dump_filtered(struct nexthop *nh, int dev_idx, int master_idx, 1769430a0491SDavid Ahern bool group_filter, u8 family) 1770ab84be7eSDavid Ahern { 1771ab84be7eSDavid Ahern const struct net_device *dev; 1772ab84be7eSDavid Ahern const struct nh_info *nhi; 1773ab84be7eSDavid Ahern 1774430a0491SDavid Ahern if (group_filter && !nh->is_group) 1775430a0491SDavid Ahern return true; 1776430a0491SDavid Ahern 1777ab84be7eSDavid Ahern if (!dev_idx && !master_idx && !family) 1778ab84be7eSDavid Ahern return false; 1779ab84be7eSDavid Ahern 1780430a0491SDavid Ahern if (nh->is_group) 1781430a0491SDavid Ahern return true; 1782430a0491SDavid Ahern 1783ab84be7eSDavid Ahern nhi = rtnl_dereference(nh->nh_info); 1784ab84be7eSDavid Ahern if (family && nhi->family != family) 1785ab84be7eSDavid Ahern return true; 1786ab84be7eSDavid Ahern 1787ab84be7eSDavid Ahern dev = nhi->fib_nhc.nhc_dev; 1788ab84be7eSDavid Ahern if (dev_idx && (!dev || dev->ifindex != dev_idx)) 1789ab84be7eSDavid Ahern return true; 1790ab84be7eSDavid Ahern 1791ab84be7eSDavid Ahern if (master_idx) { 1792ab84be7eSDavid Ahern struct net_device *master; 1793ab84be7eSDavid Ahern 1794ab84be7eSDavid Ahern if (!dev) 1795ab84be7eSDavid Ahern return true; 1796ab84be7eSDavid Ahern 1797ab84be7eSDavid Ahern master = netdev_master_upper_dev_get((struct net_device *)dev); 1798ab84be7eSDavid Ahern if (!master || master->ifindex != master_idx) 1799ab84be7eSDavid Ahern return true; 1800ab84be7eSDavid Ahern } 1801ab84be7eSDavid Ahern 1802ab84be7eSDavid Ahern return false; 1803ab84be7eSDavid Ahern } 1804ab84be7eSDavid Ahern 1805430a0491SDavid Ahern static int nh_valid_dump_req(const struct nlmsghdr *nlh, int *dev_idx, 1806430a0491SDavid Ahern int *master_idx, bool *group_filter, 180738428d68SRoopa Prabhu bool *fdb_filter, struct netlink_callback *cb) 1808ab84be7eSDavid Ahern { 1809ab84be7eSDavid Ahern struct netlink_ext_ack *extack = cb->extack; 1810ab84be7eSDavid Ahern struct nlattr *tb[NHA_MAX + 1]; 1811ab84be7eSDavid Ahern struct nhmsg *nhm; 1812ab84be7eSDavid Ahern int err, i; 1813ab84be7eSDavid Ahern u32 idx; 1814ab84be7eSDavid Ahern 1815ab84be7eSDavid Ahern err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy, 1816ab84be7eSDavid Ahern NULL); 1817ab84be7eSDavid Ahern if (err < 0) 1818ab84be7eSDavid Ahern return err; 1819ab84be7eSDavid Ahern 1820ab84be7eSDavid Ahern for (i = 0; i <= NHA_MAX; ++i) { 1821ab84be7eSDavid Ahern if (!tb[i]) 1822ab84be7eSDavid Ahern continue; 1823ab84be7eSDavid Ahern 1824ab84be7eSDavid Ahern switch (i) { 1825ab84be7eSDavid Ahern case NHA_OIF: 1826ab84be7eSDavid Ahern idx = nla_get_u32(tb[i]); 1827ab84be7eSDavid Ahern if (idx > INT_MAX) { 1828ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid device index"); 1829ab84be7eSDavid Ahern return -EINVAL; 1830ab84be7eSDavid Ahern } 1831ab84be7eSDavid Ahern *dev_idx = idx; 1832ab84be7eSDavid Ahern break; 1833ab84be7eSDavid Ahern case NHA_MASTER: 1834ab84be7eSDavid Ahern idx = nla_get_u32(tb[i]); 1835ab84be7eSDavid Ahern if (idx > INT_MAX) { 1836ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid master device index"); 1837ab84be7eSDavid Ahern return -EINVAL; 1838ab84be7eSDavid Ahern } 1839ab84be7eSDavid Ahern *master_idx = idx; 1840ab84be7eSDavid Ahern break; 1841430a0491SDavid Ahern case NHA_GROUPS: 1842430a0491SDavid Ahern *group_filter = true; 1843430a0491SDavid Ahern break; 184438428d68SRoopa Prabhu case NHA_FDB: 184538428d68SRoopa Prabhu *fdb_filter = true; 184638428d68SRoopa Prabhu break; 1847ab84be7eSDavid Ahern default: 1848ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request"); 1849ab84be7eSDavid Ahern return -EINVAL; 1850ab84be7eSDavid Ahern } 1851ab84be7eSDavid Ahern } 1852ab84be7eSDavid Ahern 1853ab84be7eSDavid Ahern nhm = nlmsg_data(nlh); 1854ab84be7eSDavid Ahern if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) { 1855ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid values in header for nexthop dump request"); 1856ab84be7eSDavid Ahern return -EINVAL; 1857ab84be7eSDavid Ahern } 1858ab84be7eSDavid Ahern 1859ab84be7eSDavid Ahern return 0; 1860ab84be7eSDavid Ahern } 1861ab84be7eSDavid Ahern 1862ab84be7eSDavid Ahern /* rtnl */ 1863ab84be7eSDavid Ahern static int rtm_dump_nexthop(struct sk_buff *skb, struct netlink_callback *cb) 1864ab84be7eSDavid Ahern { 186538428d68SRoopa Prabhu bool group_filter = false, fdb_filter = false; 1866ab84be7eSDavid Ahern struct nhmsg *nhm = nlmsg_data(cb->nlh); 1867ab84be7eSDavid Ahern int dev_filter_idx = 0, master_idx = 0; 1868ab84be7eSDavid Ahern struct net *net = sock_net(skb->sk); 1869ab84be7eSDavid Ahern struct rb_root *root = &net->nexthop.rb_root; 1870ab84be7eSDavid Ahern struct rb_node *node; 1871ab84be7eSDavid Ahern int idx = 0, s_idx; 1872ab84be7eSDavid Ahern int err; 1873ab84be7eSDavid Ahern 1874430a0491SDavid Ahern err = nh_valid_dump_req(cb->nlh, &dev_filter_idx, &master_idx, 187538428d68SRoopa Prabhu &group_filter, &fdb_filter, cb); 1876ab84be7eSDavid Ahern if (err < 0) 1877ab84be7eSDavid Ahern return err; 1878ab84be7eSDavid Ahern 1879ab84be7eSDavid Ahern s_idx = cb->args[0]; 1880ab84be7eSDavid Ahern for (node = rb_first(root); node; node = rb_next(node)) { 1881ab84be7eSDavid Ahern struct nexthop *nh; 1882ab84be7eSDavid Ahern 1883ab84be7eSDavid Ahern if (idx < s_idx) 1884ab84be7eSDavid Ahern goto cont; 1885ab84be7eSDavid Ahern 1886ab84be7eSDavid Ahern nh = rb_entry(node, struct nexthop, rb_node); 1887ab84be7eSDavid Ahern if (nh_dump_filtered(nh, dev_filter_idx, master_idx, 1888430a0491SDavid Ahern group_filter, nhm->nh_family)) 1889ab84be7eSDavid Ahern goto cont; 1890ab84be7eSDavid Ahern 1891ab84be7eSDavid Ahern err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, 1892ab84be7eSDavid Ahern NETLINK_CB(cb->skb).portid, 1893ab84be7eSDavid Ahern cb->nlh->nlmsg_seq, NLM_F_MULTI); 1894ab84be7eSDavid Ahern if (err < 0) { 1895ab84be7eSDavid Ahern if (likely(skb->len)) 1896ab84be7eSDavid Ahern goto out; 1897ab84be7eSDavid Ahern 1898ab84be7eSDavid Ahern goto out_err; 1899ab84be7eSDavid Ahern } 1900ab84be7eSDavid Ahern cont: 1901ab84be7eSDavid Ahern idx++; 1902ab84be7eSDavid Ahern } 1903ab84be7eSDavid Ahern 1904ab84be7eSDavid Ahern out: 1905ab84be7eSDavid Ahern err = skb->len; 1906ab84be7eSDavid Ahern out_err: 1907ab84be7eSDavid Ahern cb->args[0] = idx; 1908ab84be7eSDavid Ahern cb->seq = net->nexthop.seq; 1909ab84be7eSDavid Ahern nl_dump_check_consistent(cb, nlmsg_hdr(skb)); 1910ab84be7eSDavid Ahern 1911ab84be7eSDavid Ahern return err; 1912ab84be7eSDavid Ahern } 1913ab84be7eSDavid Ahern 1914597cfe4fSDavid Ahern static void nexthop_sync_mtu(struct net_device *dev, u32 orig_mtu) 1915597cfe4fSDavid Ahern { 1916597cfe4fSDavid Ahern unsigned int hash = nh_dev_hashfn(dev->ifindex); 1917597cfe4fSDavid Ahern struct net *net = dev_net(dev); 1918597cfe4fSDavid Ahern struct hlist_head *head = &net->nexthop.devhash[hash]; 1919597cfe4fSDavid Ahern struct hlist_node *n; 1920597cfe4fSDavid Ahern struct nh_info *nhi; 1921597cfe4fSDavid Ahern 1922597cfe4fSDavid Ahern hlist_for_each_entry_safe(nhi, n, head, dev_hash) { 1923597cfe4fSDavid Ahern if (nhi->fib_nhc.nhc_dev == dev) { 1924597cfe4fSDavid Ahern if (nhi->family == AF_INET) 1925597cfe4fSDavid Ahern fib_nhc_update_mtu(&nhi->fib_nhc, dev->mtu, 1926597cfe4fSDavid Ahern orig_mtu); 1927597cfe4fSDavid Ahern } 1928597cfe4fSDavid Ahern } 1929597cfe4fSDavid Ahern } 1930597cfe4fSDavid Ahern 1931597cfe4fSDavid Ahern /* rtnl */ 1932597cfe4fSDavid Ahern static int nh_netdev_event(struct notifier_block *this, 1933597cfe4fSDavid Ahern unsigned long event, void *ptr) 1934597cfe4fSDavid Ahern { 1935597cfe4fSDavid Ahern struct net_device *dev = netdev_notifier_info_to_dev(ptr); 1936597cfe4fSDavid Ahern struct netdev_notifier_info_ext *info_ext; 1937597cfe4fSDavid Ahern 1938597cfe4fSDavid Ahern switch (event) { 1939597cfe4fSDavid Ahern case NETDEV_DOWN: 1940597cfe4fSDavid Ahern case NETDEV_UNREGISTER: 1941597cfe4fSDavid Ahern nexthop_flush_dev(dev); 1942597cfe4fSDavid Ahern break; 1943597cfe4fSDavid Ahern case NETDEV_CHANGE: 1944597cfe4fSDavid Ahern if (!(dev_get_flags(dev) & (IFF_RUNNING | IFF_LOWER_UP))) 1945597cfe4fSDavid Ahern nexthop_flush_dev(dev); 1946597cfe4fSDavid Ahern break; 1947597cfe4fSDavid Ahern case NETDEV_CHANGEMTU: 1948597cfe4fSDavid Ahern info_ext = ptr; 1949597cfe4fSDavid Ahern nexthop_sync_mtu(dev, info_ext->ext.mtu); 1950597cfe4fSDavid Ahern rt_cache_flush(dev_net(dev)); 1951597cfe4fSDavid Ahern break; 1952597cfe4fSDavid Ahern } 1953597cfe4fSDavid Ahern return NOTIFY_DONE; 1954597cfe4fSDavid Ahern } 1955597cfe4fSDavid Ahern 1956597cfe4fSDavid Ahern static struct notifier_block nh_netdev_notifier = { 1957597cfe4fSDavid Ahern .notifier_call = nh_netdev_event, 1958597cfe4fSDavid Ahern }; 1959597cfe4fSDavid Ahern 19608590ceedSRoopa Prabhu int register_nexthop_notifier(struct net *net, struct notifier_block *nb) 19618590ceedSRoopa Prabhu { 1962*80690ec6SIdo Schimmel return blocking_notifier_chain_register(&net->nexthop.notifier_chain, 1963*80690ec6SIdo Schimmel nb); 19648590ceedSRoopa Prabhu } 19658590ceedSRoopa Prabhu EXPORT_SYMBOL(register_nexthop_notifier); 19668590ceedSRoopa Prabhu 19678590ceedSRoopa Prabhu int unregister_nexthop_notifier(struct net *net, struct notifier_block *nb) 19688590ceedSRoopa Prabhu { 1969*80690ec6SIdo Schimmel return blocking_notifier_chain_unregister(&net->nexthop.notifier_chain, 19708590ceedSRoopa Prabhu nb); 19718590ceedSRoopa Prabhu } 19728590ceedSRoopa Prabhu EXPORT_SYMBOL(unregister_nexthop_notifier); 19738590ceedSRoopa Prabhu 1974ab84be7eSDavid Ahern static void __net_exit nexthop_net_exit(struct net *net) 1975ab84be7eSDavid Ahern { 1976ab84be7eSDavid Ahern rtnl_lock(); 1977ab84be7eSDavid Ahern flush_all_nexthops(net); 1978ab84be7eSDavid Ahern rtnl_unlock(); 1979597cfe4fSDavid Ahern kfree(net->nexthop.devhash); 1980ab84be7eSDavid Ahern } 1981ab84be7eSDavid Ahern 1982ab84be7eSDavid Ahern static int __net_init nexthop_net_init(struct net *net) 1983ab84be7eSDavid Ahern { 1984597cfe4fSDavid Ahern size_t sz = sizeof(struct hlist_head) * NH_DEV_HASHSIZE; 1985597cfe4fSDavid Ahern 1986ab84be7eSDavid Ahern net->nexthop.rb_root = RB_ROOT; 1987597cfe4fSDavid Ahern net->nexthop.devhash = kzalloc(sz, GFP_KERNEL); 1988597cfe4fSDavid Ahern if (!net->nexthop.devhash) 1989597cfe4fSDavid Ahern return -ENOMEM; 1990*80690ec6SIdo Schimmel BLOCKING_INIT_NOTIFIER_HEAD(&net->nexthop.notifier_chain); 1991ab84be7eSDavid Ahern 1992ab84be7eSDavid Ahern return 0; 1993ab84be7eSDavid Ahern } 1994ab84be7eSDavid Ahern 1995ab84be7eSDavid Ahern static struct pernet_operations nexthop_net_ops = { 1996ab84be7eSDavid Ahern .init = nexthop_net_init, 1997ab84be7eSDavid Ahern .exit = nexthop_net_exit, 1998ab84be7eSDavid Ahern }; 1999ab84be7eSDavid Ahern 2000ab84be7eSDavid Ahern static int __init nexthop_init(void) 2001ab84be7eSDavid Ahern { 2002ab84be7eSDavid Ahern register_pernet_subsys(&nexthop_net_ops); 2003ab84be7eSDavid Ahern 2004597cfe4fSDavid Ahern register_netdevice_notifier(&nh_netdev_notifier); 2005597cfe4fSDavid Ahern 2006ab84be7eSDavid Ahern rtnl_register(PF_UNSPEC, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0); 2007ab84be7eSDavid Ahern rtnl_register(PF_UNSPEC, RTM_DELNEXTHOP, rtm_del_nexthop, NULL, 0); 2008ab84be7eSDavid Ahern rtnl_register(PF_UNSPEC, RTM_GETNEXTHOP, rtm_get_nexthop, 2009ab84be7eSDavid Ahern rtm_dump_nexthop, 0); 2010ab84be7eSDavid Ahern 2011ab84be7eSDavid Ahern rtnl_register(PF_INET, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0); 2012ab84be7eSDavid Ahern rtnl_register(PF_INET, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0); 2013ab84be7eSDavid Ahern 2014ab84be7eSDavid Ahern rtnl_register(PF_INET6, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0); 2015ab84be7eSDavid Ahern rtnl_register(PF_INET6, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0); 2016ab84be7eSDavid Ahern 2017ab84be7eSDavid Ahern return 0; 2018ab84be7eSDavid Ahern } 2019ab84be7eSDavid Ahern subsys_initcall(nexthop_init); 2020