]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/netlink/route/nexthop.c
netlink: fix interface dump.
[FreeBSD/FreeBSD.git] / sys / netlink / route / nexthop.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 #include "opt_inet.h"
31 #include "opt_inet6.h"
32 #include "opt_route.h"
33 #include <sys/types.h>
34 #include <sys/ck.h>
35 #include <sys/epoch.h>
36 #include <sys/kernel.h>
37 #include <sys/malloc.h>
38 #include <sys/rmlock.h>
39 #include <sys/socket.h>
40
41 #include <net/if.h>
42 #include <net/route.h>
43 #include <net/route/nhop.h>
44 #include <net/route/nhop_utils.h>
45
46 #include <net/route/route_ctl.h>
47 #include <net/route/route_var.h>
48 #include <netinet6/scope6_var.h>
49 #include <netlink/netlink.h>
50 #include <netlink/netlink_ctl.h>
51 #include <netlink/netlink_route.h>
52 #include <netlink/route/route_var.h>
53
54 #define DEBUG_MOD_NAME  nl_nhop
55 #define DEBUG_MAX_LEVEL LOG_DEBUG3
56 #include <netlink/netlink_debug.h>
57 _DECLARE_DEBUG(LOG_DEBUG3);
58
59 /*
60  * This file contains the logic to maintain kernel nexthops and
61  *  nexhop groups based om the data provided by the user.
62  *
63  * Kernel stores (nearly) all of the routing data in the nexthops,
64  *  including the prefix-specific flags (NHF_HOST and NHF_DEFAULT).
65  *
66  * Netlink API provides higher-level abstraction for the user. Each
67  *  user-created nexthop may map to multiple kernel nexthops.
68  *
69  * The following variations require separate kernel nexthop to be
70  *  created:
71  *  * prefix flags (NHF_HOST, NHF_DEFAULT)
72  *  * using IPv6 gateway for IPv4 routes
73  *  * different fibnum
74  *
75  * These kernel nexthops have the lifetime bound to the lifetime of
76  *  the user_nhop object. They are not collected until user requests
77  *  to delete the created user_nhop.
78  *
79  */
80 struct user_nhop {
81         uint32_t                        un_idx; /* Userland-provided index */
82         uint32_t                        un_fibfam; /* fibnum+af(as highest byte) */
83         uint8_t                         un_protocol; /* protocol that install the record */
84         struct nhop_object              *un_nhop; /* "production" nexthop */
85         struct nhop_object              *un_nhop_src; /* nexthop to copy from */
86         struct weightened_nhop          *un_nhgrp_src; /* nexthops for nhg */
87         uint32_t                        un_nhgrp_count; /* number of nexthops */
88         struct user_nhop                *un_next; /* next item in hash chain */
89         struct user_nhop                *un_nextchild; /* master -> children */
90         struct epoch_context            un_epoch_ctx;   /* epoch ctl helper */
91 };
92
93 /* produce hash value for an object */
94 #define unhop_hash_obj(_obj)    (hash_unhop(_obj))
95 /* compare two objects */
96 #define unhop_cmp(_one, _two)   (cmp_unhop(_one, _two))
97 /* next object accessor */
98 #define unhop_next(_obj)        (_obj)->un_next
99
100 CHT_SLIST_DEFINE(unhop, struct user_nhop);
101
102 struct unhop_ctl {
103         struct unhop_head       un_head;
104         struct rmlock           un_lock;
105 };
106 #define UN_LOCK_INIT(_ctl)      rm_init(&(_ctl)->un_lock, "unhop_ctl")
107 #define UN_TRACKER              struct rm_priotracker un_tracker
108 #define UN_RLOCK(_ctl)          rm_rlock(&((_ctl)->un_lock), &un_tracker)
109 #define UN_RUNLOCK(_ctl)        rm_runlock(&((_ctl)->un_lock), &un_tracker)
110
111 #define UN_WLOCK(_ctl)          rm_wlock(&(_ctl)->un_lock);
112 #define UN_WUNLOCK(_ctl)        rm_wunlock(&(_ctl)->un_lock);
113
114 VNET_DEFINE_STATIC(struct unhop_ctl *, un_ctl) = NULL;
115 #define V_un_ctl        VNET(un_ctl)
116
117 static void consider_resize(struct unhop_ctl *ctl, uint32_t new_size);
118 static int cmp_unhop(const struct user_nhop *a, const struct user_nhop *b);
119 static unsigned int hash_unhop(const struct user_nhop *obj);
120
121 static void destroy_unhop(struct user_nhop *unhop);
122 static struct nhop_object *clone_unhop(const struct user_nhop *unhop,
123     uint32_t fibnum, int family, int nh_flags);
124
125 static int
126 cmp_unhop(const struct user_nhop *a, const struct user_nhop *b)
127 {
128         return (a->un_idx == b->un_idx && a->un_fibfam == b->un_fibfam);
129 }
130
131 /*
132  * Hash callback: calculate hash of an object
133  */
134 static unsigned int
135 hash_unhop(const struct user_nhop *obj)
136 {
137         return (obj->un_idx ^ obj->un_fibfam);
138 }
139
140 #define UNHOP_IS_MASTER(_unhop) ((_unhop)->un_fibfam == 0)
141
142 /*
143  * Factory interface for creating matching kernel nexthops/nexthop groups
144  *
145  * @uidx: userland nexhop index used to create the nexthop
146  * @fibnum: fibnum nexthop will be used in
147  * @family: upper family nexthop will be used in
148  * @nh_flags: desired nexthop prefix flags
149  * @perror: pointer to store error to
150  *
151  * Returns referenced nexthop linked to @fibnum/@family rib on success.
152  */
153 struct nhop_object *
154 nl_find_nhop(uint32_t fibnum, int family, uint32_t uidx,
155     int nh_flags, int *perror)
156 {
157         struct unhop_ctl *ctl = atomic_load_ptr(&V_un_ctl);
158         UN_TRACKER;
159
160         if (__predict_false(ctl == NULL))
161                 return (NULL);
162
163         struct user_nhop key= {
164                 .un_idx = uidx,
165                 .un_fibfam = fibnum  | ((uint32_t)family) << 24,
166         };
167         struct user_nhop *unhop;
168
169         nh_flags = nh_flags & (NHF_HOST | NHF_DEFAULT);
170
171         if (__predict_false(family == 0))
172                 return (NULL);
173
174         UN_RLOCK(ctl);
175         CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
176         if (unhop != NULL) {
177                 struct nhop_object *nh = unhop->un_nhop;
178                 UN_RLOCK(ctl);
179                 *perror = 0;
180                 nhop_ref_any(nh);
181                 return (nh);
182         }
183
184         /*
185          * Exact nexthop not found. Search for template nexthop to clone from.
186          */
187         key.un_fibfam = 0;
188         CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
189         if (unhop == NULL) {
190                 UN_RUNLOCK(ctl);
191                 *perror = ESRCH;
192                 return (NULL);
193         }
194
195         UN_RUNLOCK(ctl);
196
197         /* Create entry to insert first */
198         struct user_nhop *un_new, *un_tmp;
199         un_new = malloc(sizeof(struct user_nhop), M_NETLINK, M_NOWAIT | M_ZERO);
200         if (un_new == NULL) {
201                 *perror = ENOMEM;
202                 return (NULL);
203         }
204         un_new->un_idx = uidx;
205         un_new->un_fibfam = fibnum  | ((uint32_t)family) << 24;
206
207         /* Relying on epoch to protect unhop here */
208         un_new->un_nhop = clone_unhop(unhop, fibnum, family, nh_flags);
209         if (un_new->un_nhop == NULL) {
210                 free(un_new, M_NETLINK);
211                 *perror = ENOMEM;
212                 return (NULL);
213         }
214
215         /* Insert back and report */
216         UN_WLOCK(ctl);
217
218         /* First, find template record once again */
219         CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
220         if (unhop == NULL) {
221                 /* Someone deleted the nexthop during the call */
222                 UN_WUNLOCK(ctl);
223                 *perror = ESRCH;
224                 destroy_unhop(un_new);
225                 return (NULL);
226         }
227
228         /* Second, check the direct match */
229         CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, un_new, un_tmp);
230         struct nhop_object *nh;
231         if (un_tmp != NULL) {
232                 /* Another thread already created the desired nextop, use it */
233                 nh = un_tmp->un_nhop;
234         } else {
235                 /* Finally, insert the new nexthop and link it to the primary */
236                 nh = un_new->un_nhop;
237                 CHT_SLIST_INSERT_HEAD(&ctl->un_head, unhop, un_new);
238                 un_new->un_nextchild = unhop->un_nextchild;
239                 unhop->un_nextchild = un_new;
240                 un_new = NULL;
241                 NL_LOG(LOG_DEBUG2, "linked cloned nexthop %p", nh);
242         }
243
244         UN_WUNLOCK(ctl);
245
246         if (un_new != NULL)
247                 destroy_unhop(un_new);
248
249         *perror = 0;
250         nhop_ref_any(nh);
251         return (nh);
252 }
253
254 static struct user_nhop *
255 nl_find_base_unhop(struct unhop_ctl *ctl, uint32_t uidx)
256 {
257         struct user_nhop key= { .un_idx = uidx };
258         struct user_nhop *unhop = NULL;
259         UN_TRACKER;
260
261         UN_RLOCK(ctl);
262         CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
263         UN_RUNLOCK(ctl);
264
265         return (unhop);
266 }
267
268 #define MAX_STACK_NHOPS 4
269 static struct nhop_object *
270 clone_unhop(const struct user_nhop *unhop, uint32_t fibnum, int family, int nh_flags)
271 {
272 #ifdef ROUTE_MPATH
273         const struct weightened_nhop *wn;
274         struct weightened_nhop *wn_new, wn_base[MAX_STACK_NHOPS];
275         uint32_t num_nhops;
276 #endif
277         struct nhop_object *nh = NULL;
278         int error;
279
280         if (unhop->un_nhop_src != NULL) {
281                 IF_DEBUG_LEVEL(LOG_DEBUG2) {
282                         char nhbuf[NHOP_PRINT_BUFSIZE];
283                         nhop_print_buf_any(unhop->un_nhop_src, nhbuf, sizeof(nhbuf));
284                         FIB_NH_LOG(LOG_DEBUG2, unhop->un_nhop_src,
285                             "cloning nhop %s -> %u.%u flags 0x%X", nhbuf, fibnum,
286                             family, nh_flags);
287                 }
288                 struct nhop_object *nh;
289                 nh = nhop_alloc(fibnum, AF_UNSPEC);
290                 if (nh == NULL)
291                         return (NULL);
292                 nhop_copy(nh, unhop->un_nhop_src);
293                 /* Check that nexthop gateway is compatible with the new family */
294                 if (!nhop_set_upper_family(nh, family)) {
295                         nhop_free(nh);
296                         return (NULL);
297                 }
298                 nhop_set_uidx(nh, unhop->un_idx);
299                 nhop_set_pxtype_flag(nh, nh_flags);
300                 return (nhop_get_nhop(nh, &error));
301         }
302 #ifdef ROUTE_MPATH
303         wn = unhop->un_nhgrp_src;
304         num_nhops = unhop->un_nhgrp_count;
305
306         if (num_nhops > MAX_STACK_NHOPS) {
307                 wn_new = malloc(num_nhops * sizeof(struct weightened_nhop), M_TEMP, M_NOWAIT);
308                 if (wn_new == NULL)
309                         return (NULL);
310         } else
311                 wn_new = wn_base;
312
313         for (int i = 0; i < num_nhops; i++) {
314                 uint32_t uidx = nhop_get_uidx(wn[i].nh);
315                 MPASS(uidx != 0);
316                 wn_new[i].nh = nl_find_nhop(fibnum, family, uidx, nh_flags, &error);
317                 if (error != 0)
318                         break;
319                 wn_new[i].weight = wn[i].weight;
320         }
321
322         if (error == 0) {
323                 struct rib_head *rh = nhop_get_rh(wn_new[0].nh);
324                 struct nhgrp_object *nhg;
325
326                 error = nhgrp_get_group(rh, wn_new, num_nhops, unhop->un_idx, &nhg);
327                 nh = (struct nhop_object *)nhg;
328         }
329
330         if (wn_new != wn_base)
331                 free(wn_new, M_TEMP);
332 #endif
333         return (nh);
334 }
335
336 static void
337 destroy_unhop(struct user_nhop *unhop)
338 {
339         if (unhop->un_nhop != NULL)
340                 nhop_free_any(unhop->un_nhop);
341         if (unhop->un_nhop_src != NULL)
342                 nhop_free_any(unhop->un_nhop_src);
343         free(unhop, M_NETLINK);
344 }
345
346 static void
347 destroy_unhop_epoch(epoch_context_t ctx)
348 {
349         struct user_nhop *unhop;
350
351         unhop = __containerof(ctx, struct user_nhop, un_epoch_ctx);
352
353         destroy_unhop(unhop);
354 }
355
356 static uint32_t
357 find_spare_uidx(struct unhop_ctl *ctl)
358 {
359         struct user_nhop *unhop, key = {};
360         uint32_t uidx = 0;
361         UN_TRACKER;
362
363         UN_RLOCK(ctl);
364         /* This should return spare uid with 75% of 65k used in ~99/100 cases */
365         for (int i = 0; i < 16; i++) {
366                 key.un_idx = (arc4random() % 65536) + 65536 * 4;
367                 CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
368                 if (unhop == NULL) {
369                         uidx = key.un_idx;
370                         break;
371                 }
372         }
373         UN_RUNLOCK(ctl);
374
375         return (uidx);
376 }
377
378
379 /*
380  * Actual netlink code
381  */
382 struct netlink_walkargs {
383         struct nl_writer *nw;
384         struct nlmsghdr hdr;
385         struct nlpcb *so;
386         int family;
387         int error;
388         int count;
389         int dumped;
390 };
391 #define ENOMEM_IF_NULL(_v)      if ((_v) == NULL) goto enomem
392
393 static bool
394 dump_nhgrp(const struct user_nhop *unhop, struct nlmsghdr *hdr,
395     struct nl_writer *nw)
396 {
397
398         if (!nlmsg_reply(nw, hdr, sizeof(struct nhmsg)))
399                 goto enomem;
400
401         struct nhmsg *nhm = nlmsg_reserve_object(nw, struct nhmsg);
402         nhm->nh_family = AF_UNSPEC;
403         nhm->nh_scope = 0;
404         nhm->nh_protocol = unhop->un_protocol;
405         nhm->nh_flags = 0;
406
407         nlattr_add_u32(nw, NHA_ID, unhop->un_idx);
408         nlattr_add_u16(nw, NHA_GROUP_TYPE, NEXTHOP_GRP_TYPE_MPATH);
409
410         struct weightened_nhop *wn = unhop->un_nhgrp_src;
411         uint32_t num_nhops = unhop->un_nhgrp_count;
412         /* TODO: a better API? */
413         int nla_len = sizeof(struct nlattr);
414         nla_len += NETLINK_ALIGN(num_nhops * sizeof(struct nexthop_grp));
415         struct nlattr *nla = nlmsg_reserve_data(nw, nla_len, struct nlattr);
416         if (nla == NULL)
417                 goto enomem;
418         nla->nla_type = NHA_GROUP;
419         nla->nla_len = nla_len;
420         for (int i = 0; i < num_nhops; i++) {
421                 struct nexthop_grp *grp = &((struct nexthop_grp *)(nla + 1))[i];
422                 grp->id = nhop_get_uidx(wn[i].nh);
423                 grp->weight = wn[i].weight;
424                 grp->resvd1 = 0;
425                 grp->resvd2 = 0;
426         }
427
428         if (nlmsg_end(nw))
429                 return (true);
430 enomem:
431         NL_LOG(LOG_DEBUG, "error: unable to allocate attribute memory");
432         nlmsg_abort(nw);
433         return (false);
434 }
435
436 static bool
437 dump_nhop(const struct user_nhop *unhop, struct nlmsghdr *hdr,
438     struct nl_writer *nw)
439 {
440         struct nhop_object *nh = unhop->un_nhop_src;
441
442         if (!nlmsg_reply(nw, hdr, sizeof(struct nhmsg)))
443                 goto enomem;
444
445         struct nhmsg *nhm = nlmsg_reserve_object(nw, struct nhmsg);
446         ENOMEM_IF_NULL(nhm);
447         nhm->nh_family = nhop_get_neigh_family(nh);
448         nhm->nh_scope = 0; // XXX: what's that?
449         nhm->nh_protocol = unhop->un_protocol;
450         nhm->nh_flags = 0;
451
452         nlattr_add_u32(nw, NHA_ID, unhop->un_idx);
453         if (nh->nh_flags & NHF_BLACKHOLE) {
454                 nlattr_add_flag(nw, NHA_BLACKHOLE);
455                 goto done;
456         }
457         nlattr_add_u32(nw, NHA_OIF, nh->nh_ifp->if_index);
458
459         switch (nh->gw_sa.sa_family) {
460 #ifdef INET
461         case AF_INET:
462                 nlattr_add(nw, NHA_GATEWAY, 4, &nh->gw4_sa.sin_addr);
463                 break;
464 #endif
465 #ifdef INET6
466         case AF_INET6:
467                 {
468                         struct in6_addr addr = nh->gw6_sa.sin6_addr;
469                         in6_clearscope(&addr);
470                         nlattr_add(nw, NHA_GATEWAY, 16, &addr);
471                         break;
472                 }
473 #endif
474         }
475
476 done:
477         if (nlmsg_end(nw))
478                 return (true);
479 enomem:
480         nlmsg_abort(nw);
481         return (false);
482 }
483
484 static void
485 dump_unhop(const struct user_nhop *unhop, struct nlmsghdr *hdr,
486     struct nl_writer *nw)
487 {
488         if (unhop->un_nhop_src != NULL)
489                 dump_nhop(unhop, hdr, nw);
490         else
491                 dump_nhgrp(unhop, hdr, nw);
492 }
493
494 static int
495 delete_unhop(struct unhop_ctl *ctl, struct nlmsghdr *hdr, uint32_t uidx)
496 {
497         struct user_nhop *unhop_ret, *unhop_base, *unhop_chain;
498
499         struct user_nhop key = { .un_idx = uidx };
500
501         UN_WLOCK(ctl);
502
503         CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop_base);
504
505         if (unhop_base != NULL) {
506                 CHT_SLIST_REMOVE(&ctl->un_head, unhop, unhop_base, unhop_ret);
507                 IF_DEBUG_LEVEL(LOG_DEBUG2) {
508                         char nhbuf[NHOP_PRINT_BUFSIZE];
509                         nhop_print_buf_any(unhop_base->un_nhop, nhbuf, sizeof(nhbuf));
510                         FIB_NH_LOG(LOG_DEBUG3, unhop_base->un_nhop,
511                             "removed base nhop %u: %s", uidx, nhbuf);
512                 }
513                 /* Unlink all child nexhops as well, keeping the chain intact */
514                 unhop_chain = unhop_base->un_nextchild;
515                 while (unhop_chain != NULL) {
516                         CHT_SLIST_REMOVE(&ctl->un_head, unhop, unhop_chain,
517                             unhop_ret);
518                         MPASS(unhop_chain == unhop_ret);
519                         IF_DEBUG_LEVEL(LOG_DEBUG3) {
520                                 char nhbuf[NHOP_PRINT_BUFSIZE];
521                                 nhop_print_buf_any(unhop_chain->un_nhop,
522                                     nhbuf, sizeof(nhbuf));
523                                 FIB_NH_LOG(LOG_DEBUG3, unhop_chain->un_nhop,
524                                     "removed child nhop %u: %s", uidx, nhbuf);
525                         }
526                         unhop_chain = unhop_chain->un_nextchild;
527                 }
528         }
529
530         UN_WUNLOCK(ctl);
531
532         if (unhop_base == NULL) {
533                 NL_LOG(LOG_DEBUG, "unable to find unhop %u", uidx);
534                 return (ENOENT);
535         }
536
537         /* Report nexthop deletion */
538         struct netlink_walkargs wa = {
539                 .hdr.nlmsg_pid = hdr->nlmsg_pid,
540                 .hdr.nlmsg_seq = hdr->nlmsg_seq,
541                 .hdr.nlmsg_flags = hdr->nlmsg_flags,
542                 .hdr.nlmsg_type = NL_RTM_DELNEXTHOP,
543         };
544
545         struct nl_writer nw = {};
546         if (!nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, RTNLGRP_NEXTHOP)) {
547                 NL_LOG(LOG_DEBUG, "error allocating message writer");
548                 return (ENOMEM);
549         }
550
551         dump_unhop(unhop_base, &wa.hdr, &nw);
552         nlmsg_flush(&nw);
553
554         while (unhop_base != NULL) {
555                 unhop_chain = unhop_base->un_nextchild;
556                 NET_EPOCH_CALL(destroy_unhop_epoch, &unhop_base->un_epoch_ctx);
557                 unhop_base = unhop_chain;
558         }
559
560         return (0);
561 }
562
563 static void
564 consider_resize(struct unhop_ctl *ctl, uint32_t new_size)
565 {
566         void *new_ptr = NULL;
567         size_t alloc_size;
568
569         if (new_size == 0)
570                 return;
571
572         if (new_size != 0) {
573                 alloc_size = CHT_SLIST_GET_RESIZE_SIZE(new_size);
574                 new_ptr = malloc(alloc_size, M_NETLINK, M_NOWAIT | M_ZERO);
575                 if (new_ptr == NULL)
576                         return;
577         }
578
579         NL_LOG(LOG_DEBUG, "resizing hash: %u -> %u", ctl->un_head.hash_size, new_size);
580         UN_WLOCK(ctl);
581         if (new_ptr != NULL) {
582                 CHT_SLIST_RESIZE(&ctl->un_head, unhop, new_ptr, new_size);
583         }
584         UN_WUNLOCK(ctl);
585
586
587         if (new_ptr != NULL)
588                 free(new_ptr, M_NETLINK);
589 }
590
591 static bool __noinline
592 vnet_init_unhops(void)
593 {
594         uint32_t num_buckets = 16;
595         size_t alloc_size = CHT_SLIST_GET_RESIZE_SIZE(num_buckets);
596
597         struct unhop_ctl *ctl = malloc(sizeof(struct unhop_ctl), M_NETLINK,
598             M_NOWAIT | M_ZERO);
599         if (ctl == NULL)
600                 return (false);
601
602         void *ptr = malloc(alloc_size, M_NETLINK, M_NOWAIT | M_ZERO);
603         if (ptr == NULL) {
604                 free(ctl, M_NETLINK);
605                 return (false);
606         }
607         CHT_SLIST_INIT(&ctl->un_head, ptr, num_buckets);
608         UN_LOCK_INIT(ctl);
609
610         if (!atomic_cmpset_ptr((uintptr_t *)&V_un_ctl, (uintptr_t)NULL, (uintptr_t)ctl)) {
611                 free(ptr, M_NETLINK);
612                 free(ctl, M_NETLINK);
613         }
614
615         if (atomic_load_ptr(&V_un_ctl) == NULL)
616                 return (false);
617
618         NL_LOG(LOG_NOTICE, "UNHOPS init done");
619
620         return (true);
621 }
622
623 static void
624 vnet_destroy_unhops(const void *unused __unused)
625 {
626         struct unhop_ctl *ctl = atomic_load_ptr(&V_un_ctl);
627         struct user_nhop *unhop, *tmp;
628
629         if (ctl == NULL)
630                 return;
631         V_un_ctl = NULL;
632
633         /* Wait till all unhop users finish their reads */
634         NET_EPOCH_WAIT();
635
636         UN_WLOCK(ctl);
637         CHT_SLIST_FOREACH_SAFE(&ctl->un_head, unhop, unhop, tmp) {
638                 destroy_unhop(unhop);
639         } CHT_SLIST_FOREACH_SAFE_END;
640         UN_WUNLOCK(ctl);
641
642         free(ctl->un_head.ptr, M_NETLINK);
643         free(ctl, M_NETLINK);
644 }
645 VNET_SYSUNINIT(vnet_destroy_unhops, SI_SUB_PROTO_IF, SI_ORDER_ANY,
646     vnet_destroy_unhops, NULL);
647
648 static int
649 nlattr_get_nhg(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target)
650 {
651         int error = 0;
652
653         /* Verify attribute correctness */
654         struct nexthop_grp *grp = NLA_DATA(nla);
655         int data_len = NLA_DATA_LEN(nla);
656
657         int count = data_len / sizeof(*grp);
658         if (count == 0 || (count * sizeof(*grp) != data_len)) {
659                 NL_LOG(LOG_DEBUG, "Invalid length for RTA_GROUP: %d", data_len);
660                 return (EINVAL);
661         }
662
663         *((struct nlattr **)target) = nla;
664         return (error);
665 }
666
667 struct nl_parsed_nhop {
668         uint32_t        nha_id;
669         uint8_t         nha_blackhole;
670         uint8_t         nha_groups;
671         struct ifnet    *nha_oif;
672         struct sockaddr *nha_gw;
673         struct nlattr   *nha_group;
674         uint8_t         nh_family;
675         uint8_t         nh_protocol;
676 };
677
678 #define _IN(_field)     offsetof(struct nhmsg, _field)
679 #define _OUT(_field)    offsetof(struct nl_parsed_nhop, _field)
680 static const struct nlfield_parser nlf_p_nh[] = {
681         { .off_in = _IN(nh_family), .off_out = _OUT(nh_family), .cb = nlf_get_u8 },
682         { .off_in = _IN(nh_protocol), .off_out = _OUT(nh_protocol), .cb = nlf_get_u8 },
683 };
684
685 static const struct nlattr_parser nla_p_nh[] = {
686         { .type = NHA_ID, .off = _OUT(nha_id), .cb = nlattr_get_uint32 },
687         { .type = NHA_GROUP, .off = _OUT(nha_group), .cb = nlattr_get_nhg },
688         { .type = NHA_BLACKHOLE, .off = _OUT(nha_blackhole), .cb = nlattr_get_flag },
689         { .type = NHA_OIF, .off = _OUT(nha_oif), .cb = nlattr_get_ifp },
690         { .type = NHA_GATEWAY, .off = _OUT(nha_gw), .cb = nlattr_get_ip },
691         { .type = NHA_GROUPS, .off = _OUT(nha_groups), .cb = nlattr_get_flag },
692 };
693 #undef _IN
694 #undef _OUT
695 NL_DECLARE_PARSER(nhmsg_parser, struct nhmsg, nlf_p_nh, nla_p_nh);
696
697 static bool
698 eligible_nhg(const struct nhop_object *nh)
699 {
700         return (nh->nh_flags & NHF_GATEWAY);
701 }
702
703 static int
704 newnhg(struct unhop_ctl *ctl, struct nl_parsed_nhop *attrs, struct user_nhop *unhop)
705 {
706         struct nexthop_grp *grp = NLA_DATA(attrs->nha_group);
707         int count = NLA_DATA_LEN(attrs->nha_group) / sizeof(*grp);
708         struct weightened_nhop *wn;
709
710         wn = malloc(sizeof(*wn) * count, M_NETLINK, M_NOWAIT | M_ZERO);
711         if (wn == NULL)
712                 return (ENOMEM);
713
714         for (int i = 0; i < count; i++) {
715                 struct user_nhop *unhop;
716                 unhop = nl_find_base_unhop(ctl, grp[i].id);
717                 if (unhop == NULL) {
718                         NL_LOG(LOG_DEBUG, "unable to find uidx %u", grp[i].id);
719                         free(wn, M_NETLINK);
720                         return (ESRCH);
721                 } else if (unhop->un_nhop_src == NULL) {
722                         NL_LOG(LOG_DEBUG, "uidx %u is a group, nested group unsupported",
723                             grp[i].id);
724                         free(wn, M_NETLINK);
725                         return (ENOTSUP);
726                 } else if (!eligible_nhg(unhop->un_nhop_src)) {
727                         NL_LOG(LOG_DEBUG, "uidx %u nhop is not mpath-eligible",
728                             grp[i].id);
729                         free(wn, M_NETLINK);
730                         return (ENOTSUP);
731                 }
732                 /*
733                  * TODO: consider more rigid eligibility checks:
734                  * restrict nexthops with the same gateway
735                  */
736                 wn[i].nh = unhop->un_nhop_src;
737                 wn[i].weight = grp[i].weight;
738         }
739         unhop->un_nhgrp_src = wn;
740         unhop->un_nhgrp_count = count;
741         return (0);
742 }
743
744 static int
745 newnhop(struct nl_parsed_nhop *attrs, struct user_nhop *unhop)
746 {
747         struct ifaddr *ifa = NULL;
748         struct nhop_object *nh;
749         int error;
750
751         if (!attrs->nha_blackhole) {
752                 if (attrs->nha_gw == NULL) {
753                         NL_LOG(LOG_DEBUG, "missing NHA_GATEWAY");
754                         return (EINVAL);
755                 }
756                 if (attrs->nha_oif == NULL) {
757                         NL_LOG(LOG_DEBUG, "missing NHA_OIF");
758                         return (EINVAL);
759                 }
760                 if (ifa == NULL)
761                         ifa = ifaof_ifpforaddr(attrs->nha_gw, attrs->nha_oif);
762                 if (ifa == NULL) {
763                         NL_LOG(LOG_DEBUG, "Unable to determine default source IP");
764                         return (EINVAL);
765                 }
766         }
767
768         int family = attrs->nha_gw != NULL ? attrs->nha_gw->sa_family : attrs->nh_family;
769
770         nh = nhop_alloc(RT_DEFAULT_FIB, family);
771         if (nh == NULL) {
772                 NL_LOG(LOG_DEBUG, "Unable to allocate nexthop");
773                 return (ENOMEM);
774         }
775         nhop_set_uidx(nh, attrs->nha_id);
776
777         if (attrs->nha_blackhole)
778                 nhop_set_blackhole(nh, NHF_BLACKHOLE);
779         else {
780                 nhop_set_gw(nh, attrs->nha_gw, true);
781                 nhop_set_transmit_ifp(nh, attrs->nha_oif);
782                 nhop_set_src(nh, ifa);
783         }
784
785         error = nhop_get_unlinked(nh);
786         if (error != 0) {
787                 NL_LOG(LOG_DEBUG, "unable to finalize nexthop");
788                 return (error);
789         }
790
791         IF_DEBUG_LEVEL(LOG_DEBUG2) {
792                 char nhbuf[NHOP_PRINT_BUFSIZE];
793                 nhop_print_buf(nh, nhbuf, sizeof(nhbuf));
794                 NL_LOG(LOG_DEBUG2, "Adding unhop %u: %s", attrs->nha_id, nhbuf);
795         }
796
797         unhop->un_nhop_src = nh;
798         return (0);
799 }
800
801 static int
802 rtnl_handle_newnhop(struct nlmsghdr *hdr, struct nlpcb *nlp,
803     struct nl_pstate *npt)
804 {
805         struct user_nhop *unhop;
806         int error;
807
808         if ((__predict_false(V_un_ctl == NULL)) && (!vnet_init_unhops()))
809                 return (ENOMEM);
810         struct unhop_ctl *ctl = V_un_ctl;
811
812         struct nl_parsed_nhop attrs = {};
813         error = nl_parse_nlmsg(hdr, &nhmsg_parser, npt, &attrs);
814         if (error != 0)
815                 return (error);
816
817         /*
818          * Get valid nha_id. Treat nha_id == 0 (auto-assignment) as a second-class
819          *  citizen.
820          */
821         if (attrs.nha_id == 0) {
822                 attrs.nha_id = find_spare_uidx(ctl);
823                 if (attrs.nha_id == 0) {
824                         NL_LOG(LOG_DEBUG, "Unable to get spare uidx");
825                         return (ENOSPC);
826                 }
827         }
828
829         NL_LOG(LOG_DEBUG, "IFINDEX %d", attrs.nha_oif ? attrs.nha_oif->if_index : 0);
830
831         unhop = malloc(sizeof(struct user_nhop), M_NETLINK, M_NOWAIT | M_ZERO);
832         if (unhop == NULL) {
833                 NL_LOG(LOG_DEBUG, "Unable to allocate user_nhop");
834                 return (ENOMEM);
835         }
836         unhop->un_idx = attrs.nha_id;
837         unhop->un_protocol = attrs.nh_protocol;
838
839         if (attrs.nha_group)
840                 error = newnhg(ctl, &attrs, unhop);
841         else
842                 error = newnhop(&attrs, unhop);
843
844         if (error != 0) {
845                 free(unhop, M_NETLINK);
846                 return (error);
847         }
848
849         UN_WLOCK(ctl);
850         /* Check if uidx already exists */
851         struct user_nhop *tmp = NULL;
852         CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, unhop, tmp);
853         if (tmp != NULL) {
854                 UN_WUNLOCK(ctl);
855                 NL_LOG(LOG_DEBUG, "nhop idx %u already exists", attrs.nha_id);
856                 destroy_unhop(unhop);
857                 return (EEXIST);
858         }
859         CHT_SLIST_INSERT_HEAD(&ctl->un_head, unhop, unhop);
860         uint32_t num_buckets_new = CHT_SLIST_GET_RESIZE_BUCKETS(&ctl->un_head);
861         UN_WUNLOCK(ctl);
862
863         /* Report addition of the next nexhop */
864         struct netlink_walkargs wa = {
865                 .hdr.nlmsg_pid = hdr->nlmsg_pid,
866                 .hdr.nlmsg_seq = hdr->nlmsg_seq,
867                 .hdr.nlmsg_flags = hdr->nlmsg_flags,
868                 .hdr.nlmsg_type = NL_RTM_NEWNEXTHOP,
869         };
870
871         struct nl_writer nw = {};
872         if (!nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, RTNLGRP_NEXTHOP)) {
873                 NL_LOG(LOG_DEBUG, "error allocating message writer");
874                 return (ENOMEM);
875         }
876
877         dump_unhop(unhop, &wa.hdr, &nw);
878         nlmsg_flush(&nw);
879
880         consider_resize(ctl, num_buckets_new);
881
882         return (0);
883 }
884
885 static int
886 rtnl_handle_delnhop(struct nlmsghdr *hdr, struct nlpcb *nlp,
887     struct nl_pstate *npt)
888 {
889         struct unhop_ctl *ctl = atomic_load_ptr(&V_un_ctl);
890         int error;
891
892         if (__predict_false(ctl == NULL))
893                 return (ESRCH);
894
895         struct nl_parsed_nhop attrs = {};
896         error = nl_parse_nlmsg(hdr, &nhmsg_parser, npt, &attrs);
897         if (error != 0)
898                 return (error);
899
900         if (attrs.nha_id == 0) {
901                 NL_LOG(LOG_DEBUG, "NHA_ID not set");
902                 return (EINVAL);
903         }
904
905         error = delete_unhop(ctl, hdr, attrs.nha_id);
906
907         return (error);
908 }
909
910 static bool
911 match_unhop(const struct nl_parsed_nhop *attrs, struct user_nhop *unhop)
912 {
913         if (attrs->nha_id != 0 && unhop->un_idx != attrs->nha_id)
914                 return (false);
915         if (attrs->nha_groups != 0 && unhop->un_nhgrp_src == NULL)
916                 return (false);
917         if (attrs->nha_oif != NULL &&
918             (unhop->un_nhop_src == NULL || unhop->un_nhop_src->nh_ifp != attrs->nha_oif))
919                 return (false);
920
921         return (true);
922 }
923
924 static int
925 rtnl_handle_getnhop(struct nlmsghdr *hdr, struct nlpcb *nlp,
926     struct nl_pstate *npt)
927 {
928         struct unhop_ctl *ctl = atomic_load_ptr(&V_un_ctl);
929         struct user_nhop *unhop;
930         UN_TRACKER;
931         int error;
932
933         if (__predict_false(ctl == NULL))
934                 return (ESRCH);
935
936         struct nl_parsed_nhop attrs = {};
937         error = nl_parse_nlmsg(hdr, &nhmsg_parser, npt, &attrs);
938         if (error != 0)
939                 return (error);
940
941         struct netlink_walkargs wa = {
942                 .nw = npt->nw,
943                 .hdr.nlmsg_pid = hdr->nlmsg_pid,
944                 .hdr.nlmsg_seq = hdr->nlmsg_seq,
945                 .hdr.nlmsg_flags = hdr->nlmsg_flags,
946                 .hdr.nlmsg_type = NL_RTM_NEWNEXTHOP,
947         };
948
949         if (attrs.nha_id != 0) {
950                 NL_LOG(LOG_DEBUG2, "searching for uidx %u", attrs.nha_id);
951                 struct user_nhop key= { .un_idx = attrs.nha_id };
952                 UN_RLOCK(ctl);
953                 CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
954                 UN_RUNLOCK(ctl);
955
956                 if (unhop == NULL)
957                         return (ESRCH);
958                 dump_unhop(unhop, &wa.hdr, wa.nw);
959                 return (0);
960         }
961
962         UN_RLOCK(ctl);
963         wa.hdr.nlmsg_flags |= NLM_F_MULTI;
964         CHT_SLIST_FOREACH(&ctl->un_head, unhop, unhop) {
965                 if (UNHOP_IS_MASTER(unhop) && match_unhop(&attrs, unhop))
966                         dump_unhop(unhop, &wa.hdr, wa.nw);
967         } CHT_SLIST_FOREACH_END;
968         UN_RUNLOCK(ctl);
969
970         if (wa.error == 0) {
971                 if (!nlmsg_end_dump(wa.nw, wa.error, &wa.hdr))
972                         return (ENOMEM);
973         }
974         return (0);
975 }
976
977 static const struct rtnl_cmd_handler cmd_handlers[] = {
978         {
979                 .cmd = NL_RTM_NEWNEXTHOP,
980                 .name = "RTM_NEWNEXTHOP",
981                 .cb = &rtnl_handle_newnhop,
982                 .priv = PRIV_NET_ROUTE,
983         },
984         {
985                 .cmd = NL_RTM_DELNEXTHOP,
986                 .name = "RTM_DELNEXTHOP",
987                 .cb = &rtnl_handle_delnhop,
988                 .priv = PRIV_NET_ROUTE,
989         },
990         {
991                 .cmd = NL_RTM_GETNEXTHOP,
992                 .name = "RTM_GETNEXTHOP",
993                 .cb = &rtnl_handle_getnhop,
994         }
995 };
996
997 static const struct nlhdr_parser *all_parsers[] = { &nhmsg_parser };
998
999 void
1000 rtnl_nexthops_init(void)
1001 {
1002         NL_VERIFY_PARSERS(all_parsers);
1003         rtnl_register_messages(cmd_handlers, NL_ARRAY_LEN(cmd_handlers));
1004 }