]> git.proxmox.com Git - mirror_frr.git/blob - zebra/zebra_rnh.c
Merge pull request #10875 from anlancs/doc-mh-esi-1
[mirror_frr.git] / zebra / zebra_rnh.c
1 /* Zebra next hop tracking code
2 * Copyright (C) 2013 Cumulus Networks, Inc.
3 *
4 * This file is part of GNU Zebra.
5 *
6 * GNU Zebra 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
8 * Free Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * GNU Zebra is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for 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
21 #include <zebra.h>
22
23 #include "prefix.h"
24 #include "table.h"
25 #include "memory.h"
26 #include "command.h"
27 #include "if.h"
28 #include "log.h"
29 #include "sockunion.h"
30 #include "linklist.h"
31 #include "thread.h"
32 #include "workqueue.h"
33 #include "prefix.h"
34 #include "routemap.h"
35 #include "stream.h"
36 #include "nexthop.h"
37 #include "vrf.h"
38
39 #include "zebra/zebra_router.h"
40 #include "zebra/rib.h"
41 #include "zebra/rt.h"
42 #include "zebra/zserv.h"
43 #include "zebra/zebra_ns.h"
44 #include "zebra/zebra_vrf.h"
45 #include "zebra/redistribute.h"
46 #include "zebra/debug.h"
47 #include "zebra/zebra_rnh.h"
48 #include "zebra/zebra_routemap.h"
49 #include "zebra/zebra_srte.h"
50 #include "zebra/interface.h"
51 #include "zebra/zebra_errors.h"
52
53 DEFINE_MTYPE_STATIC(ZEBRA, RNH, "Nexthop tracking object");
54
55 /* UI controls whether to notify about changes that only involve backup
56 * nexthops. Default is to notify all changes.
57 */
58 static bool rnh_hide_backups;
59
60 static void free_state(vrf_id_t vrf_id, struct route_entry *re,
61 struct route_node *rn);
62 static void copy_state(struct rnh *rnh, const struct route_entry *re,
63 struct route_node *rn);
64 static bool compare_state(struct route_entry *r1, struct route_entry *r2);
65 static void print_rnh(struct route_node *rn, struct vty *vty);
66 static int zebra_client_cleanup_rnh(struct zserv *client);
67
68 void zebra_rnh_init(void)
69 {
70 hook_register(zserv_client_close, zebra_client_cleanup_rnh);
71 }
72
73 static inline struct route_table *get_rnh_table(vrf_id_t vrfid, afi_t afi,
74 safi_t safi)
75 {
76 struct zebra_vrf *zvrf;
77 struct route_table *t = NULL;
78
79 zvrf = zebra_vrf_lookup_by_id(vrfid);
80 if (zvrf) {
81 if (safi == SAFI_UNICAST)
82 t = zvrf->rnh_table[afi];
83 else if (safi == SAFI_MULTICAST)
84 t = zvrf->rnh_table_multicast[afi];
85 }
86
87 return t;
88 }
89
90 static void zebra_rnh_remove_from_routing_table(struct rnh *rnh)
91 {
92 struct zebra_vrf *zvrf = zebra_vrf_lookup_by_id(rnh->vrf_id);
93 struct route_table *table = zvrf->table[rnh->afi][rnh->safi];
94 struct route_node *rn;
95 rib_dest_t *dest;
96
97 if (!table)
98 return;
99
100 rn = route_node_match(table, &rnh->resolved_route);
101 if (!rn)
102 return;
103
104 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
105 zlog_debug("%s: %s(%u):%pRN removed from tracking on %pRN",
106 __func__, VRF_LOGNAME(zvrf->vrf), rnh->vrf_id,
107 rnh->node, rn);
108
109 dest = rib_dest_from_rnode(rn);
110 rnh_list_del(&dest->nht, rnh);
111 route_unlock_node(rn);
112 }
113
114 static void zebra_rnh_store_in_routing_table(struct rnh *rnh)
115 {
116 struct zebra_vrf *zvrf = zebra_vrf_lookup_by_id(rnh->vrf_id);
117 struct route_table *table = zvrf->table[rnh->afi][rnh->safi];
118 struct route_node *rn;
119 rib_dest_t *dest;
120
121 rn = route_node_match(table, &rnh->resolved_route);
122 if (!rn)
123 return;
124
125 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
126 zlog_debug("%s: %s(%u):%pRN added for tracking on %pRN",
127 __func__, VRF_LOGNAME(zvrf->vrf), rnh->vrf_id,
128 rnh->node, rn);
129
130 dest = rib_dest_from_rnode(rn);
131 rnh_list_add_tail(&dest->nht, rnh);
132 route_unlock_node(rn);
133 }
134
135 struct rnh *zebra_add_rnh(struct prefix *p, vrf_id_t vrfid, safi_t safi,
136 bool *exists)
137 {
138 struct route_table *table;
139 struct route_node *rn;
140 struct rnh *rnh = NULL;
141 afi_t afi = family2afi(p->family);
142
143 if (IS_ZEBRA_DEBUG_NHT) {
144 struct vrf *vrf = vrf_lookup_by_id(vrfid);
145
146 zlog_debug("%s(%u): Add RNH %pFX for safi: %u",
147 VRF_LOGNAME(vrf), vrfid, p, safi);
148 }
149
150 table = get_rnh_table(vrfid, afi, safi);
151 if (!table) {
152 struct vrf *vrf = vrf_lookup_by_id(vrfid);
153
154 flog_warn(EC_ZEBRA_RNH_NO_TABLE,
155 "%s(%u): Add RNH %pFX - table not found",
156 VRF_LOGNAME(vrf), vrfid, p);
157 *exists = false;
158 return NULL;
159 }
160
161 /* Make it sure prefixlen is applied to the prefix. */
162 apply_mask(p);
163
164 /* Lookup (or add) route node.*/
165 rn = route_node_get(table, p);
166
167 if (!rn->info) {
168 rnh = XCALLOC(MTYPE_RNH, sizeof(struct rnh));
169
170 /*
171 * The resolved route is already 0.0.0.0/0 or
172 * 0::0/0 due to the calloc right above, but
173 * we should set the family so that future
174 * comparisons can just be done
175 */
176 rnh->resolved_route.family = p->family;
177 rnh->client_list = list_new();
178 rnh->vrf_id = vrfid;
179 rnh->seqno = 0;
180 rnh->afi = afi;
181 rnh->safi = safi;
182 rnh->zebra_pseudowire_list = list_new();
183 route_lock_node(rn);
184 rn->info = rnh;
185 rnh->node = rn;
186 *exists = false;
187
188 zebra_rnh_store_in_routing_table(rnh);
189 } else
190 *exists = true;
191
192 route_unlock_node(rn);
193 return (rn->info);
194 }
195
196 struct rnh *zebra_lookup_rnh(struct prefix *p, vrf_id_t vrfid, safi_t safi)
197 {
198 struct route_table *table;
199 struct route_node *rn;
200
201 table = get_rnh_table(vrfid, family2afi(PREFIX_FAMILY(p)), safi);
202 if (!table)
203 return NULL;
204
205 /* Make it sure prefixlen is applied to the prefix. */
206 apply_mask(p);
207
208 /* Lookup route node.*/
209 rn = route_node_lookup(table, p);
210 if (!rn)
211 return NULL;
212
213 route_unlock_node(rn);
214 return (rn->info);
215 }
216
217 void zebra_free_rnh(struct rnh *rnh)
218 {
219 struct zebra_vrf *zvrf;
220 struct route_table *table;
221
222 zebra_rnh_remove_from_routing_table(rnh);
223 rnh->flags |= ZEBRA_NHT_DELETED;
224 list_delete(&rnh->client_list);
225 list_delete(&rnh->zebra_pseudowire_list);
226
227 zvrf = zebra_vrf_lookup_by_id(rnh->vrf_id);
228 table = zvrf->table[family2afi(rnh->resolved_route.family)][rnh->safi];
229
230 if (table) {
231 struct route_node *rern;
232
233 rern = route_node_match(table, &rnh->resolved_route);
234 if (rern) {
235 rib_dest_t *dest;
236
237 route_unlock_node(rern);
238
239 dest = rib_dest_from_rnode(rern);
240 rnh_list_del(&dest->nht, rnh);
241 }
242 }
243 free_state(rnh->vrf_id, rnh->state, rnh->node);
244 XFREE(MTYPE_RNH, rnh);
245 }
246
247 static void zebra_delete_rnh(struct rnh *rnh)
248 {
249 struct route_node *rn;
250
251 if (!list_isempty(rnh->client_list)
252 || !list_isempty(rnh->zebra_pseudowire_list))
253 return;
254
255 if ((rnh->flags & ZEBRA_NHT_DELETED) || !(rn = rnh->node))
256 return;
257
258 if (IS_ZEBRA_DEBUG_NHT) {
259 struct vrf *vrf = vrf_lookup_by_id(rnh->vrf_id);
260
261 zlog_debug("%s(%u): Del RNH %pRN", VRF_LOGNAME(vrf),
262 rnh->vrf_id, rnh->node);
263 }
264
265 zebra_free_rnh(rnh);
266 rn->info = NULL;
267 route_unlock_node(rn);
268 }
269
270 /*
271 * This code will send to the registering client
272 * the looked up rnh.
273 * For a rnh that was created, there is no data
274 * so it will send an empty nexthop group
275 * If rnh exists then we know it has been evaluated
276 * and as such it will have a resolved rnh.
277 */
278 void zebra_add_rnh_client(struct rnh *rnh, struct zserv *client,
279 vrf_id_t vrf_id)
280 {
281 if (IS_ZEBRA_DEBUG_NHT) {
282 struct vrf *vrf = vrf_lookup_by_id(vrf_id);
283
284 zlog_debug("%s(%u): Client %s registers for RNH %pRN",
285 VRF_LOGNAME(vrf), vrf_id,
286 zebra_route_string(client->proto), rnh->node);
287 }
288 if (!listnode_lookup(rnh->client_list, client))
289 listnode_add(rnh->client_list, client);
290
291 /*
292 * We always need to respond with known information,
293 * currently multiple daemons expect this behavior
294 */
295 zebra_send_rnh_update(rnh, client, vrf_id, 0);
296 }
297
298 void zebra_remove_rnh_client(struct rnh *rnh, struct zserv *client)
299 {
300 if (IS_ZEBRA_DEBUG_NHT) {
301 struct vrf *vrf = vrf_lookup_by_id(rnh->vrf_id);
302
303 zlog_debug("Client %s unregisters for RNH %s(%u)%pRN",
304 zebra_route_string(client->proto), VRF_LOGNAME(vrf),
305 vrf->vrf_id, rnh->node);
306 }
307 listnode_delete(rnh->client_list, client);
308 zebra_delete_rnh(rnh);
309 }
310
311 /* XXX move this utility function elsewhere? */
312 static void addr2hostprefix(int af, const union g_addr *addr,
313 struct prefix *prefix)
314 {
315 switch (af) {
316 case AF_INET:
317 prefix->family = AF_INET;
318 prefix->prefixlen = IPV4_MAX_BITLEN;
319 prefix->u.prefix4 = addr->ipv4;
320 break;
321 case AF_INET6:
322 prefix->family = AF_INET6;
323 prefix->prefixlen = IPV6_MAX_BITLEN;
324 prefix->u.prefix6 = addr->ipv6;
325 break;
326 default:
327 memset(prefix, 0, sizeof(*prefix));
328 zlog_warn("%s: unknown address family %d", __func__, af);
329 break;
330 }
331 }
332
333 void zebra_register_rnh_pseudowire(vrf_id_t vrf_id, struct zebra_pw *pw,
334 bool *nht_exists)
335 {
336 struct prefix nh;
337 struct rnh *rnh;
338 bool exists;
339 struct zebra_vrf *zvrf;
340
341 *nht_exists = false;
342
343 zvrf = vrf_info_lookup(vrf_id);
344 if (!zvrf)
345 return;
346
347 addr2hostprefix(pw->af, &pw->nexthop, &nh);
348 rnh = zebra_add_rnh(&nh, vrf_id, SAFI_UNICAST, &exists);
349 if (!rnh)
350 return;
351
352 if (!listnode_lookup(rnh->zebra_pseudowire_list, pw)) {
353 listnode_add(rnh->zebra_pseudowire_list, pw);
354 pw->rnh = rnh;
355 zebra_evaluate_rnh(zvrf, family2afi(pw->af), 1, &nh,
356 SAFI_UNICAST);
357 } else
358 *nht_exists = true;
359 }
360
361 void zebra_deregister_rnh_pseudowire(vrf_id_t vrf_id, struct zebra_pw *pw)
362 {
363 struct rnh *rnh;
364
365 rnh = pw->rnh;
366 if (!rnh)
367 return;
368
369 listnode_delete(rnh->zebra_pseudowire_list, pw);
370 pw->rnh = NULL;
371
372 zebra_delete_rnh(rnh);
373 }
374
375 /* Clear the NEXTHOP_FLAG_RNH_FILTERED flags on all nexthops
376 */
377 static void zebra_rnh_clear_nexthop_rnh_filters(struct route_entry *re)
378 {
379 struct nexthop *nexthop;
380
381 if (re) {
382 for (nexthop = re->nhe->nhg.nexthop; nexthop;
383 nexthop = nexthop->next) {
384 UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_RNH_FILTERED);
385 }
386 }
387 }
388
389 /* Apply the NHT route-map for a client to the route (and nexthops)
390 * resolving a NH.
391 */
392 static int zebra_rnh_apply_nht_rmap(afi_t afi, struct zebra_vrf *zvrf,
393 struct route_node *prn,
394 struct route_entry *re, int proto)
395 {
396 int at_least_one = 0;
397 struct nexthop *nexthop;
398 route_map_result_t ret;
399
400 if (prn && re) {
401 for (nexthop = re->nhe->nhg.nexthop; nexthop;
402 nexthop = nexthop->next) {
403 ret = zebra_nht_route_map_check(
404 afi, proto, &prn->p, zvrf, re, nexthop);
405 if (ret != RMAP_DENYMATCH)
406 at_least_one++; /* at least one valid NH */
407 else {
408 SET_FLAG(nexthop->flags,
409 NEXTHOP_FLAG_RNH_FILTERED);
410 }
411 }
412 }
413 return (at_least_one);
414 }
415
416 /*
417 * Notify clients registered for this nexthop about a change.
418 */
419 static void zebra_rnh_notify_protocol_clients(struct zebra_vrf *zvrf, afi_t afi,
420 struct route_node *nrn,
421 struct rnh *rnh,
422 struct route_node *prn,
423 struct route_entry *re)
424 {
425 struct listnode *node;
426 struct zserv *client;
427 int num_resolving_nh;
428
429 if (IS_ZEBRA_DEBUG_NHT) {
430 if (prn && re) {
431 zlog_debug("%s(%u):%pRN: NH resolved over route %pRN",
432 VRF_LOGNAME(zvrf->vrf), zvrf->vrf->vrf_id,
433 nrn, prn);
434 } else
435 zlog_debug("%s(%u):%pRN: NH has become unresolved",
436 VRF_LOGNAME(zvrf->vrf), zvrf->vrf->vrf_id,
437 nrn);
438 }
439
440 for (ALL_LIST_ELEMENTS_RO(rnh->client_list, node, client)) {
441 if (prn && re) {
442 /* Apply route-map for this client to route resolving
443 * this
444 * nexthop to see if it is filtered or not.
445 */
446 zebra_rnh_clear_nexthop_rnh_filters(re);
447 num_resolving_nh = zebra_rnh_apply_nht_rmap(
448 afi, zvrf, prn, re, client->proto);
449 if (num_resolving_nh)
450 rnh->filtered[client->proto] = 0;
451 else
452 rnh->filtered[client->proto] = 1;
453
454 if (IS_ZEBRA_DEBUG_NHT)
455 zlog_debug(
456 "%s(%u):%pRN: Notifying client %s about NH %s",
457 VRF_LOGNAME(zvrf->vrf),
458 zvrf->vrf->vrf_id, nrn,
459 zebra_route_string(client->proto),
460 num_resolving_nh
461 ? ""
462 : "(filtered by route-map)");
463 } else {
464 rnh->filtered[client->proto] = 0;
465 if (IS_ZEBRA_DEBUG_NHT)
466 zlog_debug(
467 "%s(%u):%pRN: Notifying client %s about NH (unreachable)",
468 VRF_LOGNAME(zvrf->vrf),
469 zvrf->vrf->vrf_id, nrn,
470 zebra_route_string(client->proto));
471 }
472
473 zebra_send_rnh_update(rnh, client, zvrf->vrf->vrf_id, 0);
474 }
475
476 if (re)
477 zebra_rnh_clear_nexthop_rnh_filters(re);
478 }
479
480 /*
481 * Utility to determine whether a candidate nexthop is useable. We make this
482 * check in a couple of places, so this is a single home for the logic we
483 * use.
484 */
485
486 static const int RNH_INVALID_NH_FLAGS = (NEXTHOP_FLAG_RECURSIVE |
487 NEXTHOP_FLAG_DUPLICATE |
488 NEXTHOP_FLAG_RNH_FILTERED);
489
490 bool rnh_nexthop_valid(const struct route_entry *re, const struct nexthop *nh)
491 {
492 return (CHECK_FLAG(re->status, ROUTE_ENTRY_INSTALLED)
493 && CHECK_FLAG(nh->flags, NEXTHOP_FLAG_ACTIVE)
494 && !CHECK_FLAG(nh->flags, RNH_INVALID_NH_FLAGS));
495 }
496
497 /*
498 * Determine whether an re's nexthops are valid for tracking.
499 */
500 static bool rnh_check_re_nexthops(const struct route_entry *re,
501 const struct rnh *rnh)
502 {
503 bool ret = false;
504 const struct nexthop *nexthop = NULL;
505
506 /* Check route's nexthops */
507 for (ALL_NEXTHOPS(re->nhe->nhg, nexthop)) {
508 if (rnh_nexthop_valid(re, nexthop))
509 break;
510 }
511
512 /* Check backup nexthops, if any. */
513 if (nexthop == NULL && re->nhe->backup_info &&
514 re->nhe->backup_info->nhe) {
515 for (ALL_NEXTHOPS(re->nhe->backup_info->nhe->nhg, nexthop)) {
516 if (rnh_nexthop_valid(re, nexthop))
517 break;
518 }
519 }
520
521 if (nexthop == NULL) {
522 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
523 zlog_debug(
524 " Route Entry %s no nexthops",
525 zebra_route_string(re->type));
526
527 goto done;
528 }
529
530 /* Some special checks if registration asked for them. */
531 if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED)) {
532 if ((re->type == ZEBRA_ROUTE_CONNECT)
533 || (re->type == ZEBRA_ROUTE_STATIC))
534 ret = true;
535 if (re->type == ZEBRA_ROUTE_NHRP) {
536
537 for (nexthop = re->nhe->nhg.nexthop;
538 nexthop;
539 nexthop = nexthop->next)
540 if (nexthop->type == NEXTHOP_TYPE_IFINDEX)
541 break;
542 if (nexthop)
543 ret = true;
544 }
545 } else {
546 ret = true;
547 }
548
549 done:
550 return ret;
551 }
552
553 /*
554 * Determine appropriate route (route entry) resolving a tracked
555 * nexthop.
556 */
557 static struct route_entry *
558 zebra_rnh_resolve_nexthop_entry(struct zebra_vrf *zvrf, afi_t afi,
559 struct route_node *nrn, const struct rnh *rnh,
560 struct route_node **prn)
561 {
562 struct route_table *route_table;
563 struct route_node *rn;
564 struct route_entry *re;
565
566 *prn = NULL;
567
568 route_table = zvrf->table[afi][rnh->safi];
569 if (!route_table)
570 return NULL;
571
572 rn = route_node_match(route_table, &nrn->p);
573 if (!rn)
574 return NULL;
575
576 /* Unlock route node - we don't need to lock when walking the tree. */
577 route_unlock_node(rn);
578
579 /* While resolving nexthops, we may need to walk up the tree from the
580 * most-specific match. Do similar logic as in zebra_rib.c
581 */
582 while (rn) {
583 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
584 zlog_debug("%s: %s(%u):%pRN Possible Match to %pRN",
585 __func__, VRF_LOGNAME(zvrf->vrf),
586 rnh->vrf_id, rnh->node, rn);
587
588 /* Do not resolve over default route unless allowed &&
589 * match route to be exact if so specified
590 */
591 if (is_default_prefix(&rn->p)
592 && (!CHECK_FLAG(rnh->flags, ZEBRA_NHT_RESOLVE_VIA_DEFAULT)
593 && !rnh_resolve_via_default(zvrf, rn->p.family))) {
594 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
595 zlog_debug(
596 " Not allowed to resolve through default prefix: rnh->resolve_via_default: %u",
597 CHECK_FLAG(
598 rnh->flags,
599 ZEBRA_NHT_RESOLVE_VIA_DEFAULT));
600 return NULL;
601 }
602
603 /* Identify appropriate route entry. */
604 RNODE_FOREACH_RE (rn, re) {
605 if (CHECK_FLAG(re->status, ROUTE_ENTRY_REMOVED)) {
606 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
607 zlog_debug(
608 " Route Entry %s removed",
609 zebra_route_string(re->type));
610 continue;
611 }
612 if (!CHECK_FLAG(re->flags, ZEBRA_FLAG_SELECTED) &&
613 !CHECK_FLAG(re->flags, ZEBRA_FLAG_FIB_OVERRIDE)) {
614 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
615 zlog_debug(
616 " Route Entry %s !selected",
617 zebra_route_string(re->type));
618 continue;
619 }
620
621 if (CHECK_FLAG(re->status, ROUTE_ENTRY_QUEUED)) {
622 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
623 zlog_debug(
624 " Route Entry %s queued",
625 zebra_route_string(re->type));
626 continue;
627 }
628
629 /* Just being SELECTED isn't quite enough - must
630 * have an installed nexthop to be useful.
631 */
632 if (rnh_check_re_nexthops(re, rnh))
633 break;
634 }
635
636 /* Route entry found, we're done; else, walk up the tree. */
637 if (re) {
638 *prn = rn;
639 return re;
640 } else {
641 /* Resolve the nexthop recursively by finding matching
642 * route with lower prefix length
643 */
644 rn = rn->parent;
645 }
646 }
647
648 return NULL;
649 }
650
651 static void zebra_rnh_process_pseudowires(vrf_id_t vrfid, struct rnh *rnh)
652 {
653 struct zebra_pw *pw;
654 struct listnode *node;
655
656 for (ALL_LIST_ELEMENTS_RO(rnh->zebra_pseudowire_list, node, pw))
657 zebra_pw_update(pw);
658 }
659
660 /*
661 * See if a tracked nexthop entry has undergone any change, and if so,
662 * take appropriate action; this involves notifying any clients and/or
663 * scheduling dependent static routes for processing.
664 */
665 static void zebra_rnh_eval_nexthop_entry(struct zebra_vrf *zvrf, afi_t afi,
666 int force, struct route_node *nrn,
667 struct rnh *rnh,
668 struct route_node *prn,
669 struct route_entry *re)
670 {
671 int state_changed = 0;
672
673 /* If we're resolving over a different route, resolution has changed or
674 * the resolving route has some change (e.g., metric), there is a state
675 * change.
676 */
677 zebra_rnh_remove_from_routing_table(rnh);
678 if (!prefix_same(&rnh->resolved_route, prn ? &prn->p : NULL)) {
679 if (prn)
680 prefix_copy(&rnh->resolved_route, &prn->p);
681 else {
682 /*
683 * Just quickly store the family of the resolved
684 * route so that we can reset it in a second here
685 */
686 int family = rnh->resolved_route.family;
687
688 memset(&rnh->resolved_route, 0, sizeof(struct prefix));
689 rnh->resolved_route.family = family;
690 }
691
692 copy_state(rnh, re, nrn);
693 state_changed = 1;
694 } else if (compare_state(re, rnh->state)) {
695 copy_state(rnh, re, nrn);
696 state_changed = 1;
697 }
698 zebra_rnh_store_in_routing_table(rnh);
699
700 if (state_changed || force) {
701 /* NOTE: Use the "copy" of resolving route stored in 'rnh' i.e.,
702 * rnh->state.
703 */
704 /* Notify registered protocol clients. */
705 zebra_rnh_notify_protocol_clients(zvrf, afi, nrn, rnh, prn,
706 rnh->state);
707
708 /* Process pseudowires attached to this nexthop */
709 zebra_rnh_process_pseudowires(zvrf->vrf->vrf_id, rnh);
710 }
711 }
712
713 /* Evaluate one tracked entry */
714 static void zebra_rnh_evaluate_entry(struct zebra_vrf *zvrf, afi_t afi,
715 int force, struct route_node *nrn)
716 {
717 struct rnh *rnh;
718 struct route_entry *re;
719 struct route_node *prn;
720
721 if (IS_ZEBRA_DEBUG_NHT) {
722 zlog_debug("%s(%u):%pRN: Evaluate RNH, %s",
723 VRF_LOGNAME(zvrf->vrf), zvrf->vrf->vrf_id, nrn,
724 force ? "(force)" : "");
725 }
726
727 rnh = nrn->info;
728
729 /* Identify route entry (RE) resolving this tracked entry. */
730 re = zebra_rnh_resolve_nexthop_entry(zvrf, afi, nrn, rnh, &prn);
731
732 /* If the entry cannot be resolved and that is also the existing state,
733 * there is nothing further to do.
734 */
735 if (!re && rnh->state == NULL && !force)
736 return;
737
738 /* Process based on type of entry. */
739 zebra_rnh_eval_nexthop_entry(zvrf, afi, force, nrn, rnh, prn, re);
740 }
741
742 /*
743 * Clear the ROUTE_ENTRY_NEXTHOPS_CHANGED flag
744 * from the re entries.
745 *
746 * Please note we are doing this *after* we have
747 * notified the world about each nexthop as that
748 * we can have a situation where one re entry
749 * covers multiple nexthops we are interested in.
750 */
751 static void zebra_rnh_clear_nhc_flag(struct zebra_vrf *zvrf, afi_t afi,
752 struct route_node *nrn)
753 {
754 struct rnh *rnh;
755 struct route_entry *re;
756 struct route_node *prn;
757
758 rnh = nrn->info;
759
760 /* Identify route entry (RIB) resolving this tracked entry. */
761 re = zebra_rnh_resolve_nexthop_entry(zvrf, afi, nrn, rnh, &prn);
762
763 if (re)
764 UNSET_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED);
765 }
766
767 /* Evaluate all tracked entries (nexthops or routes for import into BGP)
768 * of a particular VRF and address-family or a specific prefix.
769 */
770 void zebra_evaluate_rnh(struct zebra_vrf *zvrf, afi_t afi, int force,
771 const struct prefix *p, safi_t safi)
772 {
773 struct route_table *rnh_table;
774 struct route_node *nrn;
775
776 rnh_table = get_rnh_table(zvrf->vrf->vrf_id, afi, safi);
777 if (!rnh_table) // unexpected
778 return;
779
780 if (p) {
781 /* Evaluating a specific entry, make sure it exists. */
782 nrn = route_node_lookup(rnh_table, p);
783 if (nrn && nrn->info)
784 zebra_rnh_evaluate_entry(zvrf, afi, force, nrn);
785
786 if (nrn)
787 route_unlock_node(nrn);
788 } else {
789 /* Evaluate entire table. */
790 nrn = route_top(rnh_table);
791 while (nrn) {
792 if (nrn->info)
793 zebra_rnh_evaluate_entry(zvrf, afi, force, nrn);
794 nrn = route_next(nrn); /* this will also unlock nrn */
795 }
796 nrn = route_top(rnh_table);
797 while (nrn) {
798 if (nrn->info)
799 zebra_rnh_clear_nhc_flag(zvrf, afi, nrn);
800 nrn = route_next(nrn); /* this will also unlock nrn */
801 }
802 }
803 }
804
805 void zebra_print_rnh_table(vrf_id_t vrfid, afi_t afi, safi_t safi,
806 struct vty *vty, const struct prefix *p)
807 {
808 struct route_table *table;
809 struct route_node *rn;
810
811 table = get_rnh_table(vrfid, afi, safi);
812 if (!table) {
813 if (IS_ZEBRA_DEBUG_NHT)
814 zlog_debug("print_rnhs: rnh table not found");
815 return;
816 }
817
818 for (rn = route_top(table); rn; rn = route_next(rn)) {
819 if (p && !prefix_match(&rn->p, p))
820 continue;
821
822 if (rn->info)
823 print_rnh(rn, vty);
824 }
825 }
826
827 /**
828 * free_state - free up the re structure associated with the rnh.
829 */
830 static void free_state(vrf_id_t vrf_id, struct route_entry *re,
831 struct route_node *rn)
832 {
833 if (!re)
834 return;
835
836 /* free RE and nexthops */
837 zebra_nhg_free(re->nhe);
838 XFREE(MTYPE_RE, re);
839 }
840
841 static void copy_state(struct rnh *rnh, const struct route_entry *re,
842 struct route_node *rn)
843 {
844 struct route_entry *state;
845
846 if (rnh->state) {
847 free_state(rnh->vrf_id, rnh->state, rn);
848 rnh->state = NULL;
849 }
850
851 if (!re)
852 return;
853
854 state = XCALLOC(MTYPE_RE, sizeof(struct route_entry));
855 state->type = re->type;
856 state->distance = re->distance;
857 state->metric = re->metric;
858 state->vrf_id = re->vrf_id;
859 state->status = re->status;
860
861 state->nhe = zebra_nhe_copy(re->nhe, 0);
862
863 /* Copy the 'fib' nexthops also, if present - we want to capture
864 * the true installed nexthops.
865 */
866 if (re->fib_ng.nexthop)
867 nexthop_group_copy(&state->fib_ng, &re->fib_ng);
868 if (re->fib_backup_ng.nexthop)
869 nexthop_group_copy(&state->fib_backup_ng, &re->fib_backup_ng);
870
871 rnh->state = state;
872 }
873
874 /*
875 * Locate the next primary nexthop, used when comparing current rnh info with
876 * an updated route.
877 */
878 static struct nexthop *next_valid_primary_nh(struct route_entry *re,
879 struct nexthop *nh)
880 {
881 struct nexthop_group *nhg;
882 struct nexthop *bnh;
883 int i, idx;
884 bool default_path = true;
885
886 /* Fib backup ng present: some backups are installed,
887 * and we're configured for special handling if there are backups.
888 */
889 if (rnh_hide_backups && (re->fib_backup_ng.nexthop != NULL))
890 default_path = false;
891
892 /* Default path: no special handling, just using the 'installed'
893 * primary nexthops and the common validity test.
894 */
895 if (default_path) {
896 if (nh == NULL) {
897 nhg = rib_get_fib_nhg(re);
898 nh = nhg->nexthop;
899 } else
900 nh = nexthop_next(nh);
901
902 while (nh) {
903 if (rnh_nexthop_valid(re, nh))
904 break;
905 else
906 nh = nexthop_next(nh);
907 }
908
909 return nh;
910 }
911
912 /* Hide backup activation/switchover events.
913 *
914 * If we've had a switchover, an inactive primary won't be in
915 * the fib list at all - the 'fib' list could even be empty
916 * in the case where no primary is installed. But we want to consider
917 * those primaries "valid" if they have an activated backup nh.
918 *
919 * The logic is something like:
920 * if (!fib_nhg)
921 * // then all primaries are installed
922 * else
923 * for each primary in re nhg
924 * if in fib_nhg
925 * primary is installed
926 * else if a backup is installed
927 * primary counts as installed
928 * else
929 * primary !installed
930 */
931
932 /* Start with the first primary */
933 if (nh == NULL)
934 nh = re->nhe->nhg.nexthop;
935 else
936 nh = nexthop_next(nh);
937
938 while (nh) {
939
940 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
941 zlog_debug("%s: checking primary NH %pNHv",
942 __func__, nh);
943
944 /* If this nexthop is in the fib list, it's installed */
945 nhg = rib_get_fib_nhg(re);
946
947 for (bnh = nhg->nexthop; bnh; bnh = nexthop_next(bnh)) {
948 if (nexthop_cmp(nh, bnh) == 0)
949 break;
950 }
951
952 if (bnh != NULL) {
953 /* Found the match */
954 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
955 zlog_debug("%s: NH in fib list", __func__);
956 break;
957 }
958
959 /* Else if this nexthop's backup is installed, it counts */
960 nhg = rib_get_fib_backup_nhg(re);
961 bnh = nhg->nexthop;
962
963 for (idx = 0; bnh != NULL; idx++) {
964 /* If we find an active backup nh for this
965 * primary, we're done;
966 */
967 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
968 zlog_debug("%s: checking backup %pNHv [%d]",
969 __func__, bnh, idx);
970
971 if (!CHECK_FLAG(bnh->flags, NEXTHOP_FLAG_ACTIVE))
972 continue;
973
974 for (i = 0; i < nh->backup_num; i++) {
975 /* Found a matching activated backup nh */
976 if (nh->backup_idx[i] == idx) {
977 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
978 zlog_debug("%s: backup %d activated",
979 __func__, i);
980
981 goto done;
982 }
983 }
984
985 /* Note that we're not recursing here if the
986 * backups are recursive: the primary's index is
987 * only valid in the top-level backup list.
988 */
989 bnh = bnh->next;
990 }
991
992 /* Try the next primary nexthop */
993 nh = nexthop_next(nh);
994 }
995
996 done:
997
998 return nh;
999 }
1000
1001 /*
1002 * Compare two route_entries' nexthops. Account for backup nexthops
1003 * and for the 'fib' nexthop lists, if present.
1004 */
1005 static bool compare_valid_nexthops(struct route_entry *r1,
1006 struct route_entry *r2)
1007 {
1008 bool matched_p = false;
1009 struct nexthop_group *nhg1, *nhg2;
1010 struct nexthop *nh1, *nh2;
1011
1012 /* Start with the primary nexthops */
1013
1014 nh1 = next_valid_primary_nh(r1, NULL);
1015 nh2 = next_valid_primary_nh(r2, NULL);
1016
1017 while (1) {
1018 /* Find any differences in the nexthop lists */
1019
1020 if (nh1 && nh2) {
1021 /* Any difference is a no-match */
1022 if (nexthop_cmp(nh1, nh2) != 0) {
1023 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
1024 zlog_debug("%s: nh1: %pNHv, nh2: %pNHv differ",
1025 __func__, nh1, nh2);
1026 goto done;
1027 }
1028
1029 } else if (nh1 || nh2) {
1030 /* One list has more valid nexthops than the other */
1031 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
1032 zlog_debug("%s: nh1 %s, nh2 %s", __func__,
1033 nh1 ? "non-NULL" : "NULL",
1034 nh2 ? "non-NULL" : "NULL");
1035 goto done;
1036 } else
1037 break; /* Done with both lists */
1038
1039 nh1 = next_valid_primary_nh(r1, nh1);
1040 nh2 = next_valid_primary_nh(r2, nh2);
1041 }
1042
1043 /* If configured, don't compare installed backup state - we've
1044 * accounted for that with the primaries above.
1045 *
1046 * But we do want to compare the routes' backup info,
1047 * in case the owning route has changed the backups -
1048 * that change we do want to report.
1049 */
1050 if (rnh_hide_backups) {
1051 uint32_t hash1 = 0, hash2 = 0;
1052
1053 if (r1->nhe->backup_info)
1054 hash1 = nexthop_group_hash(
1055 &r1->nhe->backup_info->nhe->nhg);
1056
1057 if (r2->nhe->backup_info)
1058 hash2 = nexthop_group_hash(
1059 &r2->nhe->backup_info->nhe->nhg);
1060
1061 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
1062 zlog_debug("%s: backup hash1 %#x, hash2 %#x",
1063 __func__, hash1, hash2);
1064
1065 if (hash1 != hash2)
1066 goto done;
1067 else
1068 goto finished;
1069 }
1070
1071 /* The test for the backups is slightly different: the only installed
1072 * backups will be in the 'fib' list.
1073 */
1074 nhg1 = rib_get_fib_backup_nhg(r1);
1075 nhg2 = rib_get_fib_backup_nhg(r2);
1076
1077 nh1 = nhg1->nexthop;
1078 nh2 = nhg2->nexthop;
1079
1080 while (1) {
1081 /* Find each backup list's next valid nexthop */
1082 while ((nh1 != NULL) && !rnh_nexthop_valid(r1, nh1))
1083 nh1 = nexthop_next(nh1);
1084
1085 while ((nh2 != NULL) && !rnh_nexthop_valid(r2, nh2))
1086 nh2 = nexthop_next(nh2);
1087
1088 if (nh1 && nh2) {
1089 /* Any difference is a no-match */
1090 if (nexthop_cmp(nh1, nh2) != 0) {
1091 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
1092 zlog_debug("%s: backup nh1: %pNHv, nh2: %pNHv differ",
1093 __func__, nh1, nh2);
1094 goto done;
1095 }
1096
1097 nh1 = nexthop_next(nh1);
1098 nh2 = nexthop_next(nh2);
1099 } else if (nh1 || nh2) {
1100 /* One list has more valid nexthops than the other */
1101 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
1102 zlog_debug("%s: backup nh1 %s, nh2 %s",
1103 __func__,
1104 nh1 ? "non-NULL" : "NULL",
1105 nh2 ? "non-NULL" : "NULL");
1106 goto done;
1107 } else
1108 break; /* Done with both lists */
1109 }
1110
1111 finished:
1112
1113 /* Well, it's a match */
1114 matched_p = true;
1115
1116 done:
1117
1118 if (IS_ZEBRA_DEBUG_NHT_DETAILED)
1119 zlog_debug("%s: %smatched",
1120 __func__, (matched_p ? "" : "NOT "));
1121
1122 return matched_p;
1123 }
1124
1125 /* Returns 'false' if no difference. */
1126 static bool compare_state(struct route_entry *r1,
1127 struct route_entry *r2)
1128 {
1129 if (!r1 && !r2)
1130 return false;
1131
1132 if ((!r1 && r2) || (r1 && !r2))
1133 return true;
1134
1135 if (r1->distance != r2->distance)
1136 return true;
1137
1138 if (r1->metric != r2->metric)
1139 return true;
1140
1141 if (!compare_valid_nexthops(r1, r2))
1142 return true;
1143
1144 return false;
1145 }
1146
1147 int zebra_send_rnh_update(struct rnh *rnh, struct zserv *client,
1148 vrf_id_t vrf_id, uint32_t srte_color)
1149 {
1150 struct stream *s = NULL;
1151 struct route_entry *re;
1152 unsigned long nump;
1153 uint8_t num;
1154 struct nexthop *nh;
1155 struct route_node *rn;
1156 int ret;
1157 uint32_t message = 0;
1158
1159 rn = rnh->node;
1160 re = rnh->state;
1161
1162 /* Get output stream. */
1163 s = stream_new(ZEBRA_MAX_PACKET_SIZ);
1164
1165 zclient_create_header(s, ZEBRA_NEXTHOP_UPDATE, vrf_id);
1166
1167 /* Message flags. */
1168 if (srte_color)
1169 SET_FLAG(message, ZAPI_MESSAGE_SRTE);
1170 stream_putl(s, message);
1171
1172 /*
1173 * Put what we were told to match against
1174 */
1175 stream_putw(s, rnh->safi);
1176 stream_putw(s, rn->p.family);
1177 stream_putc(s, rn->p.prefixlen);
1178 switch (rn->p.family) {
1179 case AF_INET:
1180 stream_put_in_addr(s, &rn->p.u.prefix4);
1181 break;
1182 case AF_INET6:
1183 stream_put(s, &rn->p.u.prefix6, IPV6_MAX_BYTELEN);
1184 break;
1185 default:
1186 flog_err(EC_ZEBRA_RNH_UNKNOWN_FAMILY,
1187 "%s: Unknown family (%d) notification attempted",
1188 __func__, rn->p.family);
1189 goto failure;
1190 }
1191
1192 /*
1193 * What we matched against
1194 */
1195 stream_putw(s, rnh->resolved_route.family);
1196 stream_putc(s, rnh->resolved_route.prefixlen);
1197 switch (rnh->resolved_route.family) {
1198 case AF_INET:
1199 stream_put_in_addr(s, &rnh->resolved_route.u.prefix4);
1200 break;
1201 case AF_INET6:
1202 stream_put(s, &rnh->resolved_route.u.prefix6, IPV6_MAX_BYTELEN);
1203 break;
1204 default:
1205 flog_err(EC_ZEBRA_RNH_UNKNOWN_FAMILY,
1206 "%s: Unknown family (%d) notification attempted",
1207 __func__, rn->p.family);
1208 goto failure;
1209 }
1210
1211 if (srte_color)
1212 stream_putl(s, srte_color);
1213
1214 if (re) {
1215 struct zapi_nexthop znh;
1216 struct nexthop_group *nhg;
1217
1218 stream_putc(s, re->type);
1219 stream_putw(s, re->instance);
1220 stream_putc(s, re->distance);
1221 stream_putl(s, re->metric);
1222 num = 0;
1223 nump = stream_get_endp(s);
1224 stream_putc(s, 0);
1225
1226 nhg = rib_get_fib_nhg(re);
1227 for (ALL_NEXTHOPS_PTR(nhg, nh))
1228 if (rnh_nexthop_valid(re, nh)) {
1229 zapi_nexthop_from_nexthop(&znh, nh);
1230 ret = zapi_nexthop_encode(s, &znh, 0, message);
1231 if (ret < 0)
1232 goto failure;
1233
1234 num++;
1235 }
1236
1237 nhg = rib_get_fib_backup_nhg(re);
1238 if (nhg) {
1239 for (ALL_NEXTHOPS_PTR(nhg, nh))
1240 if (rnh_nexthop_valid(re, nh)) {
1241 zapi_nexthop_from_nexthop(&znh, nh);
1242 ret = zapi_nexthop_encode(
1243 s, &znh, 0 /* flags */,
1244 0 /* message */);
1245 if (ret < 0)
1246 goto failure;
1247
1248 num++;
1249 }
1250 }
1251
1252 stream_putc_at(s, nump, num);
1253 } else {
1254 stream_putc(s, 0); // type
1255 stream_putw(s, 0); // instance
1256 stream_putc(s, 0); // distance
1257 stream_putl(s, 0); // metric
1258 stream_putc(s, 0); // nexthops
1259 }
1260 stream_putw_at(s, 0, stream_get_endp(s));
1261
1262 client->nh_last_upd_time = monotime(NULL);
1263 return zserv_send_message(client, s);
1264
1265 failure:
1266
1267 stream_free(s);
1268 return -1;
1269 }
1270
1271 static void print_nh(struct nexthop *nexthop, struct vty *vty)
1272 {
1273 char buf[BUFSIZ];
1274 struct zebra_ns *zns = zebra_ns_lookup(nexthop->vrf_id);
1275
1276 switch (nexthop->type) {
1277 case NEXTHOP_TYPE_IPV4:
1278 case NEXTHOP_TYPE_IPV4_IFINDEX:
1279 vty_out(vty, " via %pI4", &nexthop->gate.ipv4);
1280 if (nexthop->ifindex)
1281 vty_out(vty, ", %s",
1282 ifindex2ifname_per_ns(zns, nexthop->ifindex));
1283 break;
1284 case NEXTHOP_TYPE_IPV6:
1285 case NEXTHOP_TYPE_IPV6_IFINDEX:
1286 vty_out(vty, " %s",
1287 inet_ntop(AF_INET6, &nexthop->gate.ipv6, buf, BUFSIZ));
1288 if (nexthop->ifindex)
1289 vty_out(vty, ", via %s",
1290 ifindex2ifname_per_ns(zns, nexthop->ifindex));
1291 break;
1292 case NEXTHOP_TYPE_IFINDEX:
1293 vty_out(vty, " is directly connected, %s",
1294 ifindex2ifname_per_ns(zns, nexthop->ifindex));
1295 break;
1296 case NEXTHOP_TYPE_BLACKHOLE:
1297 vty_out(vty, " is directly connected, Null0");
1298 break;
1299 default:
1300 break;
1301 }
1302 vty_out(vty, "\n");
1303 }
1304
1305 static void print_rnh(struct route_node *rn, struct vty *vty)
1306 {
1307 struct rnh *rnh;
1308 struct nexthop *nexthop;
1309 struct listnode *node;
1310 struct zserv *client;
1311 char buf[BUFSIZ];
1312
1313 rnh = rn->info;
1314 vty_out(vty, "%s%s\n",
1315 inet_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ),
1316 CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED) ? "(Connected)"
1317 : "");
1318 if (rnh->state) {
1319 vty_out(vty, " resolved via %s\n",
1320 zebra_route_string(rnh->state->type));
1321 for (nexthop = rnh->state->nhe->nhg.nexthop; nexthop;
1322 nexthop = nexthop->next)
1323 print_nh(nexthop, vty);
1324 } else
1325 vty_out(vty, " unresolved%s\n",
1326 CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED)
1327 ? "(Connected)"
1328 : "");
1329
1330 vty_out(vty, " Client list:");
1331 for (ALL_LIST_ELEMENTS_RO(rnh->client_list, node, client))
1332 vty_out(vty, " %s(fd %d)%s", zebra_route_string(client->proto),
1333 client->sock,
1334 rnh->filtered[client->proto] ? "(filtered)" : "");
1335 if (!list_isempty(rnh->zebra_pseudowire_list))
1336 vty_out(vty, " zebra[pseudowires]");
1337 vty_out(vty, "\n");
1338 }
1339
1340 static int zebra_cleanup_rnh_client(vrf_id_t vrf_id, afi_t afi, safi_t safi,
1341 struct zserv *client)
1342 {
1343 struct route_table *ntable;
1344 struct route_node *nrn;
1345 struct rnh *rnh;
1346
1347 if (IS_ZEBRA_DEBUG_NHT) {
1348 struct vrf *vrf = vrf_lookup_by_id(vrf_id);
1349
1350 zlog_debug("%s(%u): Client %s RNH cleanup for family %s",
1351 VRF_LOGNAME(vrf), vrf_id,
1352 zebra_route_string(client->proto), afi2str(afi));
1353 }
1354
1355 ntable = get_rnh_table(vrf_id, afi, safi);
1356 if (!ntable) {
1357 zlog_debug("cleanup_rnh_client: rnh table not found");
1358 return -1;
1359 }
1360
1361 for (nrn = route_top(ntable); nrn; nrn = route_next(nrn)) {
1362 if (!nrn->info)
1363 continue;
1364
1365 rnh = nrn->info;
1366 zebra_remove_rnh_client(rnh, client);
1367 }
1368 return 1;
1369 }
1370
1371 /* Cleanup registered nexthops (across VRFs) upon client disconnect. */
1372 static int zebra_client_cleanup_rnh(struct zserv *client)
1373 {
1374 struct vrf *vrf;
1375 struct zebra_vrf *zvrf;
1376
1377 RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) {
1378 zvrf = vrf->info;
1379 if (zvrf) {
1380 zebra_cleanup_rnh_client(zvrf_id(zvrf), AFI_IP,
1381 SAFI_UNICAST, client);
1382 zebra_cleanup_rnh_client(zvrf_id(zvrf), AFI_IP,
1383 SAFI_MULTICAST, client);
1384 zebra_cleanup_rnh_client(zvrf_id(zvrf), AFI_IP6,
1385 SAFI_UNICAST, client);
1386 zebra_cleanup_rnh_client(zvrf_id(zvrf), AFI_IP6,
1387 SAFI_MULTICAST, client);
1388 }
1389 }
1390
1391 return 0;
1392 }
1393
1394 int rnh_resolve_via_default(struct zebra_vrf *zvrf, int family)
1395 {
1396 if (((family == AF_INET) && zvrf->zebra_rnh_ip_default_route)
1397 || ((family == AF_INET6) && zvrf->zebra_rnh_ipv6_default_route))
1398 return 1;
1399 else
1400 return 0;
1401 }
1402
1403 /*
1404 * UI control to avoid notifications if backup nexthop status changes
1405 */
1406 void rnh_set_hide_backups(bool hide_p)
1407 {
1408 rnh_hide_backups = hide_p;
1409 }
1410
1411 bool rnh_get_hide_backups(void)
1412 {
1413 return rnh_hide_backups;
1414 }