]> git.proxmox.com Git - mirror_frr.git/blob - lib/nexthop_group.c
Merge pull request #4423 from ton31337/feature/delete_prefix_list_by_sequence_number_7.1
[mirror_frr.git] / lib / nexthop_group.c
1 /*
2 * Nexthop Group structure definition.
3 * Copyright (C) 2018 Cumulus Networks, Inc.
4 * Donald Sharp
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation; either version 2 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; see the file COPYING; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 #include <zebra.h>
21
22 #include <vrf.h>
23 #include <sockunion.h>
24 #include <nexthop.h>
25 #include <nexthop_group.h>
26 #include <vty.h>
27 #include <command.h>
28 #include <jhash.h>
29
30 #ifndef VTYSH_EXTRACT_PL
31 #include "lib/nexthop_group_clippy.c"
32 #endif
33
34 DEFINE_MTYPE_STATIC(LIB, NEXTHOP_GROUP, "Nexthop Group")
35
36 struct nexthop_group_hooks {
37 void (*new)(const char *name);
38 void (*add_nexthop)(const struct nexthop_group_cmd *nhg,
39 const struct nexthop *nhop);
40 void (*del_nexthop)(const struct nexthop_group_cmd *nhg,
41 const struct nexthop *nhop);
42 void (*delete)(const char *name);
43 };
44
45 static struct nexthop_group_hooks nhg_hooks;
46
47 static inline int
48 nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1,
49 const struct nexthop_group_cmd *nhgc2);
50 RB_GENERATE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry,
51 nexthop_group_cmd_compare)
52
53 static struct nhgc_entry_head nhgc_entries;
54
55 static inline int
56 nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1,
57 const struct nexthop_group_cmd *nhgc2)
58 {
59 return strcmp(nhgc1->name, nhgc2->name);
60 }
61
62 uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg)
63 {
64 struct nexthop *nhop;
65 uint8_t num = 0;
66
67 for (ALL_NEXTHOPS_PTR(nhg, nhop))
68 num++;
69
70 return num;
71 }
72
73 uint8_t nexthop_group_active_nexthop_num(const struct nexthop_group *nhg)
74 {
75 struct nexthop *nhop;
76 uint8_t num = 0;
77
78 for (ALL_NEXTHOPS_PTR(nhg, nhop)) {
79 if (CHECK_FLAG(nhop->flags, NEXTHOP_FLAG_ACTIVE))
80 num++;
81 }
82
83 return num;
84 }
85
86 struct nexthop *nexthop_exists(struct nexthop_group *nhg, struct nexthop *nh)
87 {
88 struct nexthop *nexthop;
89
90 for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) {
91 if (nexthop_same(nh, nexthop))
92 return nexthop;
93 }
94
95 return NULL;
96 }
97
98 struct nexthop_group *nexthop_group_new(void)
99 {
100 return XCALLOC(MTYPE_NEXTHOP_GROUP, sizeof(struct nexthop_group));
101 }
102
103 void nexthop_group_delete(struct nexthop_group **nhg)
104 {
105 XFREE(MTYPE_NEXTHOP_GROUP, *nhg);
106 }
107
108 /* Add nexthop to the end of a nexthop list. */
109 void nexthop_add(struct nexthop **target, struct nexthop *nexthop)
110 {
111 struct nexthop *last;
112
113 for (last = *target; last && last->next; last = last->next)
114 ;
115 if (last)
116 last->next = nexthop;
117 else
118 *target = nexthop;
119 nexthop->prev = last;
120 }
121
122 /* Delete nexthop from a nexthop list. */
123 void nexthop_del(struct nexthop_group *nhg, struct nexthop *nh)
124 {
125 struct nexthop *nexthop;
126
127 for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) {
128 if (nexthop_same(nh, nexthop))
129 break;
130 }
131
132 assert(nexthop);
133
134 if (nexthop->prev)
135 nexthop->prev->next = nexthop->next;
136 else
137 nhg->nexthop = nexthop->next;
138
139 if (nexthop->next)
140 nexthop->next->prev = nexthop->prev;
141
142 nh->prev = NULL;
143 nh->next = NULL;
144 }
145
146 void copy_nexthops(struct nexthop **tnh, const struct nexthop *nh,
147 struct nexthop *rparent)
148 {
149 struct nexthop *nexthop;
150 const struct nexthop *nh1;
151
152 for (nh1 = nh; nh1; nh1 = nh1->next) {
153 nexthop = nexthop_new();
154 nexthop->vrf_id = nh1->vrf_id;
155 nexthop->ifindex = nh1->ifindex;
156 nexthop->type = nh1->type;
157 nexthop->flags = nh1->flags;
158 memcpy(&nexthop->gate, &nh1->gate, sizeof(nh1->gate));
159 memcpy(&nexthop->src, &nh1->src, sizeof(nh1->src));
160 memcpy(&nexthop->rmap_src, &nh1->rmap_src,
161 sizeof(nh1->rmap_src));
162 nexthop->rparent = rparent;
163 if (nh1->nh_label)
164 nexthop_add_labels(nexthop, nh1->nh_label_type,
165 nh1->nh_label->num_labels,
166 &nh1->nh_label->label[0]);
167 nexthop_add(tnh, nexthop);
168
169 if (CHECK_FLAG(nh1->flags, NEXTHOP_FLAG_RECURSIVE))
170 copy_nexthops(&nexthop->resolved, nh1->resolved,
171 nexthop);
172 }
173 }
174
175 uint32_t nexthop_group_hash(const struct nexthop_group *nhg)
176 {
177 struct nexthop *nh;
178 uint32_t key = 0;
179
180 /*
181 * We are not interested in hashing over any recursively
182 * resolved nexthops
183 */
184 for (nh = nhg->nexthop; nh; nh = nh->next)
185 key = jhash_1word(nexthop_hash(nh), key);
186
187 return key;
188 }
189
190 static void nhgc_delete_nexthops(struct nexthop_group_cmd *nhgc)
191 {
192 struct nexthop *nexthop;
193
194 nexthop = nhgc->nhg.nexthop;
195 while (nexthop) {
196 struct nexthop *next = nexthop_next(nexthop);
197
198 nexthop_del(&nhgc->nhg, nexthop);
199 if (nhg_hooks.del_nexthop)
200 nhg_hooks.del_nexthop(nhgc, nexthop);
201
202 nexthop_free(nexthop);
203
204 nexthop = next;
205 }
206 }
207
208 struct nexthop_group_cmd *nhgc_find(const char *name)
209 {
210 struct nexthop_group_cmd find;
211
212 strlcpy(find.name, name, sizeof(find.name));
213
214 return RB_FIND(nhgc_entry_head, &nhgc_entries, &find);
215 }
216
217 static int nhgc_cmp_helper(const char *a, const char *b)
218 {
219 if (!a && !b)
220 return 0;
221
222 if (a && !b)
223 return -1;
224
225 if (!a && b)
226 return 1;
227
228 return strcmp(a, b);
229 }
230
231 static int nhgc_addr_cmp_helper(const union sockunion *a, const union sockunion *b)
232 {
233 if (!a && !b)
234 return 0;
235
236 if (a && !b)
237 return -1;
238
239 if (!a && b)
240 return 1;
241
242 return sockunion_cmp(a, b);
243 }
244
245 static int nhgl_cmp(struct nexthop_hold *nh1, struct nexthop_hold *nh2)
246 {
247 int ret;
248
249 ret = nhgc_addr_cmp_helper(nh1->addr, nh2->addr);
250 if (ret)
251 return ret;
252
253 ret = nhgc_cmp_helper(nh1->intf, nh2->intf);
254 if (ret)
255 return ret;
256
257 return nhgc_cmp_helper(nh1->nhvrf_name, nh2->nhvrf_name);
258 }
259
260 static void nhgl_delete(struct nexthop_hold *nh)
261 {
262 XFREE(MTYPE_TMP, nh->intf);
263
264 XFREE(MTYPE_TMP, nh->nhvrf_name);
265
266 if (nh->addr)
267 sockunion_free(nh->addr);
268
269 XFREE(MTYPE_TMP, nh);
270 }
271
272 static struct nexthop_group_cmd *nhgc_get(const char *name)
273 {
274 struct nexthop_group_cmd *nhgc;
275
276 nhgc = nhgc_find(name);
277 if (!nhgc) {
278 nhgc = XCALLOC(MTYPE_TMP, sizeof(*nhgc));
279 strlcpy(nhgc->name, name, sizeof(nhgc->name));
280
281 QOBJ_REG(nhgc, nexthop_group_cmd);
282 RB_INSERT(nhgc_entry_head, &nhgc_entries, nhgc);
283
284 nhgc->nhg_list = list_new();
285 nhgc->nhg_list->cmp = (int (*)(void *, void *))nhgl_cmp;
286 nhgc->nhg_list->del = (void (*)(void *))nhgl_delete;
287
288 if (nhg_hooks.new)
289 nhg_hooks.new(name);
290 }
291
292 return nhgc;
293 }
294
295 static void nhgc_delete(struct nexthop_group_cmd *nhgc)
296 {
297 nhgc_delete_nexthops(nhgc);
298
299 if (nhg_hooks.delete)
300 nhg_hooks.delete(nhgc->name);
301
302 RB_REMOVE(nhgc_entry_head, &nhgc_entries, nhgc);
303
304 list_delete(&nhgc->nhg_list);
305
306 XFREE(MTYPE_TMP, nhgc);
307 }
308
309 DEFINE_QOBJ_TYPE(nexthop_group_cmd)
310
311 DEFUN_NOSH(nexthop_group, nexthop_group_cmd, "nexthop-group NAME",
312 "Enter into the nexthop-group submode\n"
313 "Specify the NAME of the nexthop-group\n")
314 {
315 const char *nhg_name = argv[1]->arg;
316 struct nexthop_group_cmd *nhgc = NULL;
317
318 nhgc = nhgc_get(nhg_name);
319 VTY_PUSH_CONTEXT(NH_GROUP_NODE, nhgc);
320
321 return CMD_SUCCESS;
322 }
323
324 DEFUN_NOSH(no_nexthop_group, no_nexthop_group_cmd, "no nexthop-group NAME",
325 NO_STR
326 "Delete the nexthop-group\n"
327 "Specify the NAME of the nexthop-group\n")
328 {
329 const char *nhg_name = argv[2]->arg;
330 struct nexthop_group_cmd *nhgc = NULL;
331
332 nhgc = nhgc_find(nhg_name);
333 if (nhgc)
334 nhgc_delete(nhgc);
335
336 return CMD_SUCCESS;
337 }
338
339 static void nexthop_group_save_nhop(struct nexthop_group_cmd *nhgc,
340 const char *nhvrf_name,
341 const union sockunion *addr,
342 const char *intf)
343 {
344 struct nexthop_hold *nh;
345
346 nh = XCALLOC(MTYPE_TMP, sizeof(*nh));
347
348 if (nhvrf_name)
349 nh->nhvrf_name = XSTRDUP(MTYPE_TMP, nhvrf_name);
350 if (intf)
351 nh->intf = XSTRDUP(MTYPE_TMP, intf);
352 if (addr)
353 nh->addr = sockunion_dup(addr);
354
355 listnode_add_sort(nhgc->nhg_list, nh);
356 }
357
358 static void nexthop_group_unsave_nhop(struct nexthop_group_cmd *nhgc,
359 const char *nhvrf_name,
360 const union sockunion *addr,
361 const char *intf)
362 {
363 struct nexthop_hold *nh;
364 struct listnode *node;
365
366 for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) {
367 if (nhgc_cmp_helper(nhvrf_name, nh->nhvrf_name) == 0 &&
368 nhgc_addr_cmp_helper(addr, nh->addr) == 0 &&
369 nhgc_cmp_helper(intf, nh->intf) == 0)
370 break;
371 }
372
373 /*
374 * Something has gone seriously wrong, fail gracefully
375 */
376 if (!nh)
377 return;
378
379 list_delete_node(nhgc->nhg_list, node);
380 nhgl_delete(nh);
381 }
382
383 static bool nexthop_group_parse_nexthop(struct nexthop *nhop,
384 const union sockunion *addr,
385 const char *intf, const char *name)
386 {
387 struct vrf *vrf;
388
389 memset(nhop, 0, sizeof(*nhop));
390
391 if (name)
392 vrf = vrf_lookup_by_name(name);
393 else
394 vrf = vrf_lookup_by_id(VRF_DEFAULT);
395
396 if (!vrf)
397 return false;
398
399 nhop->vrf_id = vrf->vrf_id;
400
401 if (intf) {
402 nhop->ifindex = ifname2ifindex(intf, vrf->vrf_id);
403 if (nhop->ifindex == IFINDEX_INTERNAL)
404 return false;
405 }
406
407 if (addr) {
408 if (addr->sa.sa_family == AF_INET) {
409 nhop->gate.ipv4.s_addr = addr->sin.sin_addr.s_addr;
410 if (intf)
411 nhop->type = NEXTHOP_TYPE_IPV4_IFINDEX;
412 else
413 nhop->type = NEXTHOP_TYPE_IPV4;
414 } else {
415 nhop->gate.ipv6 = addr->sin6.sin6_addr;
416 if (intf)
417 nhop->type = NEXTHOP_TYPE_IPV6_IFINDEX;
418 else
419 nhop->type = NEXTHOP_TYPE_IPV6;
420 }
421 } else
422 nhop->type = NEXTHOP_TYPE_IFINDEX;
423
424 return true;
425 }
426
427 DEFPY(ecmp_nexthops, ecmp_nexthops_cmd,
428 "[no] nexthop\
429 <\
430 <A.B.C.D|X:X::X:X>$addr [INTERFACE$intf]\
431 |INTERFACE$intf\
432 >\
433 [nexthop-vrf NAME$name]",
434 NO_STR
435 "Specify one of the nexthops in this ECMP group\n"
436 "v4 Address\n"
437 "v6 Address\n"
438 "Interface to use\n"
439 "Interface to use\n"
440 "If the nexthop is in a different vrf tell us\n"
441 "The nexthop-vrf Name\n")
442 {
443 VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc);
444 struct nexthop nhop;
445 struct nexthop *nh;
446 bool legal;
447
448 legal = nexthop_group_parse_nexthop(&nhop, addr, intf, name);
449
450 if (nhop.type == NEXTHOP_TYPE_IPV6
451 && IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) {
452 vty_out(vty,
453 "Specified a v6 LL with no interface, rejecting\n");
454 return CMD_WARNING_CONFIG_FAILED;
455 }
456
457 nh = nexthop_exists(&nhgc->nhg, &nhop);
458
459 if (no) {
460 nexthop_group_unsave_nhop(nhgc, name, addr, intf);
461 if (nh) {
462 nexthop_del(&nhgc->nhg, nh);
463
464 if (nhg_hooks.del_nexthop)
465 nhg_hooks.del_nexthop(nhgc, nh);
466
467 nexthop_free(nh);
468 }
469 } else if (!nh) {
470 /* must be adding new nexthop since !no and !nexthop_exists */
471 if (legal) {
472 nh = nexthop_new();
473
474 memcpy(nh, &nhop, sizeof(nhop));
475 nexthop_add(&nhgc->nhg.nexthop, nh);
476 }
477
478 nexthop_group_save_nhop(nhgc, name, addr, intf);
479
480 if (legal && nhg_hooks.add_nexthop)
481 nhg_hooks.add_nexthop(nhgc, nh);
482 }
483
484 return CMD_SUCCESS;
485 }
486
487 struct cmd_node nexthop_group_node = {
488 NH_GROUP_NODE,
489 "%s(config-nh-group)# ",
490 1
491 };
492
493 void nexthop_group_write_nexthop(struct vty *vty, struct nexthop *nh)
494 {
495 char buf[100];
496 struct vrf *vrf;
497
498 vty_out(vty, "nexthop ");
499
500 switch (nh->type) {
501 case NEXTHOP_TYPE_IFINDEX:
502 vty_out(vty, "%s", ifindex2ifname(nh->ifindex, nh->vrf_id));
503 break;
504 case NEXTHOP_TYPE_IPV4:
505 vty_out(vty, "%s", inet_ntoa(nh->gate.ipv4));
506 break;
507 case NEXTHOP_TYPE_IPV4_IFINDEX:
508 vty_out(vty, "%s %s", inet_ntoa(nh->gate.ipv4),
509 ifindex2ifname(nh->ifindex, nh->vrf_id));
510 break;
511 case NEXTHOP_TYPE_IPV6:
512 vty_out(vty, "%s",
513 inet_ntop(AF_INET6, &nh->gate.ipv6, buf, sizeof(buf)));
514 break;
515 case NEXTHOP_TYPE_IPV6_IFINDEX:
516 vty_out(vty, "%s %s",
517 inet_ntop(AF_INET6, &nh->gate.ipv6, buf, sizeof(buf)),
518 ifindex2ifname(nh->ifindex, nh->vrf_id));
519 break;
520 case NEXTHOP_TYPE_BLACKHOLE:
521 break;
522 }
523
524 if (nh->vrf_id != VRF_DEFAULT) {
525 vrf = vrf_lookup_by_id(nh->vrf_id);
526 vty_out(vty, " nexthop-vrf %s", vrf->name);
527 }
528 vty_out(vty, "\n");
529 }
530
531 static void nexthop_group_write_nexthop_internal(struct vty *vty,
532 struct nexthop_hold *nh)
533 {
534 char buf[100];
535
536 vty_out(vty, "nexthop");
537
538 if (nh->addr)
539 vty_out(vty, " %s", sockunion2str(nh->addr, buf, sizeof(buf)));
540
541 if (nh->intf)
542 vty_out(vty, " %s", nh->intf);
543
544 if (nh->nhvrf_name)
545 vty_out(vty, " nexthop-vrf %s", nh->nhvrf_name);
546
547 vty_out(vty, "\n");
548 }
549
550 static int nexthop_group_write(struct vty *vty)
551 {
552 struct nexthop_group_cmd *nhgc;
553 struct nexthop_hold *nh;
554
555 RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
556 struct listnode *node;
557
558 vty_out(vty, "nexthop-group %s\n", nhgc->name);
559
560 for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) {
561 vty_out(vty, " ");
562 nexthop_group_write_nexthop_internal(vty, nh);
563 }
564
565 vty_out(vty, "!\n");
566 }
567
568 return 1;
569 }
570
571 void nexthop_group_enable_vrf(struct vrf *vrf)
572 {
573 struct nexthop_group_cmd *nhgc;
574 struct nexthop_hold *nhh;
575
576 RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
577 struct listnode *node;
578
579 for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) {
580 struct nexthop nhop;
581 struct nexthop *nh;
582
583 if (!nexthop_group_parse_nexthop(&nhop, nhh->addr,
584 nhh->intf,
585 nhh->nhvrf_name))
586 continue;
587
588 nh = nexthop_exists(&nhgc->nhg, &nhop);
589
590 if (nh)
591 continue;
592
593 if (nhop.vrf_id != vrf->vrf_id)
594 continue;
595
596 nh = nexthop_new();
597
598 memcpy(nh, &nhop, sizeof(nhop));
599 nexthop_add(&nhgc->nhg.nexthop, nh);
600
601 if (nhg_hooks.add_nexthop)
602 nhg_hooks.add_nexthop(nhgc, nh);
603 }
604 }
605 }
606
607 void nexthop_group_disable_vrf(struct vrf *vrf)
608 {
609 struct nexthop_group_cmd *nhgc;
610 struct nexthop_hold *nhh;
611
612 RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
613 struct listnode *node;
614
615 for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) {
616 struct nexthop nhop;
617 struct nexthop *nh;
618
619 if (!nexthop_group_parse_nexthop(&nhop, nhh->addr,
620 nhh->intf,
621 nhh->nhvrf_name))
622 continue;
623
624 nh = nexthop_exists(&nhgc->nhg, &nhop);
625
626 if (!nh)
627 continue;
628
629 if (nh->vrf_id != vrf->vrf_id)
630 continue;
631
632 nexthop_del(&nhgc->nhg, nh);
633
634 if (nhg_hooks.del_nexthop)
635 nhg_hooks.del_nexthop(nhgc, nh);
636
637 nexthop_free(nh);
638 }
639 }
640 }
641
642 void nexthop_group_interface_state_change(struct interface *ifp,
643 ifindex_t oldifindex)
644 {
645 struct nexthop_group_cmd *nhgc;
646 struct nexthop_hold *nhh;
647
648 RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) {
649 struct listnode *node;
650 struct nexthop *nh;
651
652 if (if_is_up(ifp)) {
653 for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) {
654 struct nexthop nhop;
655
656 if (!nexthop_group_parse_nexthop(
657 &nhop, nhh->addr, nhh->intf,
658 nhh->nhvrf_name))
659 continue;
660
661 switch (nhop.type) {
662 case NEXTHOP_TYPE_IPV4:
663 case NEXTHOP_TYPE_IPV6:
664 case NEXTHOP_TYPE_BLACKHOLE:
665 continue;
666 case NEXTHOP_TYPE_IFINDEX:
667 case NEXTHOP_TYPE_IPV4_IFINDEX:
668 case NEXTHOP_TYPE_IPV6_IFINDEX:
669 break;
670 }
671 nh = nexthop_exists(&nhgc->nhg, &nhop);
672
673 if (nh)
674 continue;
675
676 if (ifp->ifindex != nhop.ifindex)
677 continue;
678
679 nh = nexthop_new();
680
681 memcpy(nh, &nhop, sizeof(nhop));
682 nexthop_add(&nhgc->nhg.nexthop, nh);
683
684 if (nhg_hooks.add_nexthop)
685 nhg_hooks.add_nexthop(nhgc, nh);
686 }
687 } else {
688 struct nexthop *next_nh;
689
690 for (nh = nhgc->nhg.nexthop; nh; nh = next_nh) {
691 next_nh = nh->next;
692 switch (nh->type) {
693 case NEXTHOP_TYPE_IPV4:
694 case NEXTHOP_TYPE_IPV6:
695 case NEXTHOP_TYPE_BLACKHOLE:
696 continue;
697 case NEXTHOP_TYPE_IFINDEX:
698 case NEXTHOP_TYPE_IPV4_IFINDEX:
699 case NEXTHOP_TYPE_IPV6_IFINDEX:
700 break;
701 }
702
703 if (oldifindex != nh->ifindex)
704 continue;
705
706 nexthop_del(&nhgc->nhg, nh);
707
708 if (nhg_hooks.del_nexthop)
709 nhg_hooks.del_nexthop(nhgc, nh);
710
711 nexthop_free(nh);
712 }
713 }
714 }
715 }
716
717 void nexthop_group_init(void (*new)(const char *name),
718 void (*add_nexthop)(const struct nexthop_group_cmd *nhg,
719 const struct nexthop *nhop),
720 void (*del_nexthop)(const struct nexthop_group_cmd *nhg,
721 const struct nexthop *nhop),
722 void (*delete)(const char *name))
723 {
724 RB_INIT(nhgc_entry_head, &nhgc_entries);
725
726 install_node(&nexthop_group_node, nexthop_group_write);
727 install_element(CONFIG_NODE, &nexthop_group_cmd);
728 install_element(CONFIG_NODE, &no_nexthop_group_cmd);
729
730 install_default(NH_GROUP_NODE);
731 install_element(NH_GROUP_NODE, &ecmp_nexthops_cmd);
732
733 memset(&nhg_hooks, 0, sizeof(nhg_hooks));
734
735 if (new)
736 nhg_hooks.new = new;
737 if (add_nexthop)
738 nhg_hooks.add_nexthop = add_nexthop;
739 if (del_nexthop)
740 nhg_hooks.del_nexthop = del_nexthop;
741 if (delete)
742 nhg_hooks.delete = delete;
743 }