/src/frr/lib/nexthop_group.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * Nexthop Group structure definition. |
4 | | * Copyright (C) 2018 Cumulus Networks, Inc. |
5 | | * Donald Sharp |
6 | | */ |
7 | | #include <zebra.h> |
8 | | |
9 | | #include <vrf.h> |
10 | | #include <sockunion.h> |
11 | | #include <nexthop.h> |
12 | | #include <nexthop_group.h> |
13 | | #include <nexthop_group_private.h> |
14 | | #include <vty.h> |
15 | | #include <command.h> |
16 | | #include <jhash.h> |
17 | | |
18 | | #include "lib/nexthop_group_clippy.c" |
19 | | |
20 | 6 | DEFINE_MTYPE_STATIC(LIB, NEXTHOP_GROUP, "Nexthop Group"); |
21 | 6 | |
22 | 6 | /* |
23 | 6 | * Internal struct used to hold nhg config strings |
24 | 6 | */ |
25 | 6 | struct nexthop_hold { |
26 | 6 | char *nhvrf_name; |
27 | 6 | union sockunion *addr; |
28 | 6 | char *intf; |
29 | 6 | bool onlink; |
30 | 6 | char *labels; |
31 | 6 | vni_t vni; |
32 | 6 | uint32_t weight; |
33 | 6 | char *backup_str; |
34 | 6 | }; |
35 | 6 | |
36 | 6 | struct nexthop_group_hooks { |
37 | 6 | void (*new)(const char *name); |
38 | 6 | void (*modify)(const struct nexthop_group_cmd *nhgc); |
39 | 6 | void (*add_nexthop)(const struct nexthop_group_cmd *nhg, |
40 | 6 | const struct nexthop *nhop); |
41 | 6 | void (*del_nexthop)(const struct nexthop_group_cmd *nhg, |
42 | 6 | const struct nexthop *nhop); |
43 | 6 | void (*delete)(const char *name); |
44 | 6 | }; |
45 | 6 | |
46 | 6 | static struct nexthop_group_hooks nhg_hooks; |
47 | 6 | |
48 | 6 | static inline int |
49 | 6 | nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1, |
50 | 6 | const struct nexthop_group_cmd *nhgc2); |
51 | 6 | RB_GENERATE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry, |
52 | 6 | nexthop_group_cmd_compare) |
53 | 6 | |
54 | 6 | static struct nhgc_entry_head nhgc_entries; |
55 | 6 | |
56 | 6 | static inline int |
57 | 6 | nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1, |
58 | 6 | const struct nexthop_group_cmd *nhgc2) |
59 | 6 | { |
60 | 0 | return strcmp(nhgc1->name, nhgc2->name); |
61 | 0 | } |
62 | | |
63 | | static struct nexthop *nexthop_group_tail(const struct nexthop_group *nhg) |
64 | 0 | { |
65 | 0 | struct nexthop *nexthop = nhg->nexthop; |
66 | |
|
67 | 0 | while (nexthop && nexthop->next) |
68 | 0 | nexthop = nexthop->next; |
69 | |
|
70 | 0 | return nexthop; |
71 | 0 | } |
72 | | |
73 | | uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg) |
74 | 0 | { |
75 | 0 | struct nexthop *nhop; |
76 | 0 | uint8_t num = 0; |
77 | |
|
78 | 0 | for (ALL_NEXTHOPS_PTR(nhg, nhop)) |
79 | 0 | num++; |
80 | |
|
81 | 0 | return num; |
82 | 0 | } |
83 | | |
84 | | uint8_t nexthop_group_nexthop_num_no_recurse(const struct nexthop_group *nhg) |
85 | 0 | { |
86 | 0 | struct nexthop *nhop; |
87 | 0 | uint8_t num = 0; |
88 | |
|
89 | 0 | for (nhop = nhg->nexthop; nhop; nhop = nhop->next) |
90 | 0 | num++; |
91 | |
|
92 | 0 | return num; |
93 | 0 | } |
94 | | |
95 | | uint8_t nexthop_group_active_nexthop_num(const struct nexthop_group *nhg) |
96 | 0 | { |
97 | 0 | struct nexthop *nhop; |
98 | 0 | uint8_t num = 0; |
99 | |
|
100 | 0 | for (ALL_NEXTHOPS_PTR(nhg, nhop)) { |
101 | 0 | if (CHECK_FLAG(nhop->flags, NEXTHOP_FLAG_ACTIVE)) |
102 | 0 | num++; |
103 | 0 | } |
104 | |
|
105 | 0 | return num; |
106 | 0 | } |
107 | | |
108 | | uint8_t |
109 | | nexthop_group_active_nexthop_num_no_recurse(const struct nexthop_group *nhg) |
110 | 0 | { |
111 | 0 | struct nexthop *nhop; |
112 | 0 | uint8_t num = 0; |
113 | |
|
114 | 0 | for (nhop = nhg->nexthop; nhop; nhop = nhop->next) { |
115 | 0 | if (CHECK_FLAG(nhop->flags, NEXTHOP_FLAG_ACTIVE)) |
116 | 0 | num++; |
117 | 0 | } |
118 | |
|
119 | 0 | return num; |
120 | 0 | } |
121 | | |
122 | | bool nexthop_group_has_label(const struct nexthop_group *nhg) |
123 | 0 | { |
124 | 0 | struct nexthop *nhop; |
125 | |
|
126 | 0 | for (ALL_NEXTHOPS_PTR(nhg, nhop)) { |
127 | 0 | if (nhop->nh_label) |
128 | 0 | return true; |
129 | 0 | } |
130 | | |
131 | 0 | return false; |
132 | 0 | } |
133 | | |
134 | | struct nexthop *nexthop_exists(const struct nexthop_group *nhg, |
135 | | const struct nexthop *nh) |
136 | 0 | { |
137 | 0 | struct nexthop *nexthop; |
138 | |
|
139 | 0 | for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) { |
140 | 0 | if (nexthop_same(nh, nexthop)) |
141 | 0 | return nexthop; |
142 | 0 | } |
143 | | |
144 | 0 | return NULL; |
145 | 0 | } |
146 | | |
147 | | /* |
148 | | * Helper that locates a nexthop in an nhg config list. Note that |
149 | | * this uses a specific matching / equality rule that's different from |
150 | | * the complete match performed by 'nexthop_same()'. |
151 | | */ |
152 | | static struct nexthop *nhg_nh_find(const struct nexthop_group *nhg, |
153 | | const struct nexthop *nh) |
154 | 0 | { |
155 | 0 | struct nexthop *nexthop; |
156 | 0 | int ret; |
157 | | |
158 | | /* We compare: vrf, gateway, and interface */ |
159 | |
|
160 | 0 | for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) { |
161 | | |
162 | | /* Compare vrf and type */ |
163 | 0 | if (nexthop->vrf_id != nh->vrf_id) |
164 | 0 | continue; |
165 | 0 | if (nexthop->type != nh->type) |
166 | 0 | continue; |
167 | | |
168 | | /* Compare gateway */ |
169 | 0 | switch (nexthop->type) { |
170 | 0 | case NEXTHOP_TYPE_IPV4: |
171 | 0 | case NEXTHOP_TYPE_IPV6: |
172 | 0 | ret = nexthop_g_addr_cmp(nexthop->type, |
173 | 0 | &nexthop->gate, &nh->gate); |
174 | 0 | if (ret != 0) |
175 | 0 | continue; |
176 | 0 | break; |
177 | 0 | case NEXTHOP_TYPE_IPV4_IFINDEX: |
178 | 0 | case NEXTHOP_TYPE_IPV6_IFINDEX: |
179 | 0 | ret = nexthop_g_addr_cmp(nexthop->type, |
180 | 0 | &nexthop->gate, &nh->gate); |
181 | 0 | if (ret != 0) |
182 | 0 | continue; |
183 | | /* Intentional Fall-Through */ |
184 | 0 | case NEXTHOP_TYPE_IFINDEX: |
185 | 0 | if (nexthop->ifindex != nh->ifindex) |
186 | 0 | continue; |
187 | 0 | break; |
188 | 0 | case NEXTHOP_TYPE_BLACKHOLE: |
189 | 0 | if (nexthop->bh_type != nh->bh_type) |
190 | 0 | continue; |
191 | 0 | break; |
192 | 0 | } |
193 | | |
194 | 0 | return nexthop; |
195 | 0 | } |
196 | | |
197 | 0 | return NULL; |
198 | 0 | } |
199 | | |
200 | | static bool |
201 | | nexthop_group_equal_common(const struct nexthop_group *nhg1, |
202 | | const struct nexthop_group *nhg2, |
203 | | uint8_t (*nexthop_group_nexthop_num_func)( |
204 | | const struct nexthop_group *nhg)) |
205 | 0 | { |
206 | 0 | if (nhg1 && !nhg2) |
207 | 0 | return false; |
208 | | |
209 | 0 | if (!nhg1 && nhg2) |
210 | 0 | return false; |
211 | | |
212 | 0 | if (nhg1 == nhg2) |
213 | 0 | return true; |
214 | | |
215 | 0 | if (nexthop_group_nexthop_num_func(nhg1) |
216 | 0 | != nexthop_group_nexthop_num_func(nhg2)) |
217 | 0 | return false; |
218 | | |
219 | 0 | return true; |
220 | 0 | } |
221 | | |
222 | | /* This assumes ordered */ |
223 | | bool nexthop_group_equal_no_recurse(const struct nexthop_group *nhg1, |
224 | | const struct nexthop_group *nhg2) |
225 | 0 | { |
226 | 0 | struct nexthop *nh1 = NULL; |
227 | 0 | struct nexthop *nh2 = NULL; |
228 | |
|
229 | 0 | if (!nexthop_group_equal_common(nhg1, nhg2, |
230 | 0 | &nexthop_group_nexthop_num_no_recurse)) |
231 | 0 | return false; |
232 | | |
233 | 0 | for (nh1 = nhg1->nexthop, nh2 = nhg2->nexthop; nh1 || nh2; |
234 | 0 | nh1 = nh1->next, nh2 = nh2->next) { |
235 | 0 | if (nh1 && !nh2) |
236 | 0 | return false; |
237 | 0 | if (!nh1 && nh2) |
238 | 0 | return false; |
239 | 0 | if (!nexthop_same(nh1, nh2)) |
240 | 0 | return false; |
241 | 0 | } |
242 | | |
243 | 0 | return true; |
244 | 0 | } |
245 | | |
246 | | /* This assumes ordered */ |
247 | | bool nexthop_group_equal(const struct nexthop_group *nhg1, |
248 | | const struct nexthop_group *nhg2) |
249 | 0 | { |
250 | 0 | struct nexthop *nh1 = NULL; |
251 | 0 | struct nexthop *nh2 = NULL; |
252 | |
|
253 | 0 | if (!nexthop_group_equal_common(nhg1, nhg2, &nexthop_group_nexthop_num)) |
254 | 0 | return false; |
255 | | |
256 | 0 | for (nh1 = nhg1->nexthop, nh2 = nhg2->nexthop; nh1 || nh2; |
257 | 0 | nh1 = nexthop_next(nh1), nh2 = nexthop_next(nh2)) { |
258 | 0 | if (nh1 && !nh2) |
259 | 0 | return false; |
260 | 0 | if (!nh1 && nh2) |
261 | 0 | return false; |
262 | 0 | if (!nexthop_same(nh1, nh2)) |
263 | 0 | return false; |
264 | 0 | } |
265 | | |
266 | 0 | return true; |
267 | 0 | } |
268 | | struct nexthop_group *nexthop_group_new(void) |
269 | 0 | { |
270 | 0 | return XCALLOC(MTYPE_NEXTHOP_GROUP, sizeof(struct nexthop_group)); |
271 | 0 | } |
272 | | |
273 | | void nexthop_group_copy(struct nexthop_group *to, |
274 | | const struct nexthop_group *from) |
275 | 0 | { |
276 | 0 | to->nhgr = from->nhgr; |
277 | | /* Copy everything, including recursive info */ |
278 | 0 | copy_nexthops(&to->nexthop, from->nexthop, NULL); |
279 | 0 | } |
280 | | |
281 | | void nexthop_group_delete(struct nexthop_group **nhg) |
282 | 0 | { |
283 | | /* OK to call with NULL group */ |
284 | 0 | if ((*nhg) == NULL) |
285 | 0 | return; |
286 | | |
287 | 0 | if ((*nhg)->nexthop) |
288 | 0 | nexthops_free((*nhg)->nexthop); |
289 | |
|
290 | 0 | XFREE(MTYPE_NEXTHOP_GROUP, *nhg); |
291 | 0 | } |
292 | | |
293 | | /* Add nexthop to the end of a nexthop list. */ |
294 | | void _nexthop_add(struct nexthop **target, struct nexthop *nexthop) |
295 | 0 | { |
296 | 0 | struct nexthop *last; |
297 | |
|
298 | 0 | for (last = *target; last && last->next; last = last->next) |
299 | 0 | ; |
300 | 0 | if (last) |
301 | 0 | last->next = nexthop; |
302 | 0 | else |
303 | 0 | *target = nexthop; |
304 | 0 | nexthop->prev = last; |
305 | 0 | } |
306 | | |
307 | | /* Add nexthop to sorted list of nexthops */ |
308 | | static void _nexthop_add_sorted(struct nexthop **head, |
309 | | struct nexthop *nexthop) |
310 | 0 | { |
311 | 0 | struct nexthop *position, *prev; |
312 | |
|
313 | 0 | assert(!nexthop->next); |
314 | |
|
315 | 0 | for (position = *head, prev = NULL; position; |
316 | 0 | prev = position, position = position->next) { |
317 | 0 | if (nexthop_cmp(position, nexthop) > 0) { |
318 | 0 | nexthop->next = position; |
319 | 0 | nexthop->prev = prev; |
320 | |
|
321 | 0 | if (nexthop->prev) |
322 | 0 | nexthop->prev->next = nexthop; |
323 | 0 | else |
324 | 0 | *head = nexthop; |
325 | |
|
326 | 0 | position->prev = nexthop; |
327 | 0 | return; |
328 | 0 | } |
329 | 0 | } |
330 | | |
331 | 0 | nexthop->prev = prev; |
332 | 0 | if (prev) |
333 | 0 | prev->next = nexthop; |
334 | 0 | else |
335 | 0 | *head = nexthop; |
336 | 0 | } |
337 | | |
338 | | void nexthop_group_add_sorted(struct nexthop_group *nhg, |
339 | | struct nexthop *nexthop) |
340 | 0 | { |
341 | 0 | struct nexthop *tail; |
342 | |
|
343 | 0 | assert(!nexthop->next); |
344 | | |
345 | | /* Try to just append to the end first; |
346 | | * trust the list is already sorted |
347 | | */ |
348 | 0 | tail = nexthop_group_tail(nhg); |
349 | |
|
350 | 0 | if (tail && (nexthop_cmp(tail, nexthop) < 0)) { |
351 | 0 | tail->next = nexthop; |
352 | 0 | nexthop->prev = tail; |
353 | |
|
354 | 0 | return; |
355 | 0 | } |
356 | | |
357 | 0 | _nexthop_add_sorted(&nhg->nexthop, nexthop); |
358 | 0 | } |
359 | | |
360 | | /* Delete nexthop from a nexthop list. */ |
361 | | void _nexthop_del(struct nexthop_group *nhg, struct nexthop *nh) |
362 | 0 | { |
363 | 0 | struct nexthop *nexthop; |
364 | |
|
365 | 0 | for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) { |
366 | 0 | if (nexthop_same(nh, nexthop)) |
367 | 0 | break; |
368 | 0 | } |
369 | |
|
370 | 0 | assert(nexthop); |
371 | |
|
372 | 0 | if (nexthop->prev) |
373 | 0 | nexthop->prev->next = nexthop->next; |
374 | 0 | else |
375 | 0 | nhg->nexthop = nexthop->next; |
376 | |
|
377 | 0 | if (nexthop->next) |
378 | 0 | nexthop->next->prev = nexthop->prev; |
379 | |
|
380 | 0 | nh->prev = NULL; |
381 | 0 | nh->next = NULL; |
382 | 0 | } |
383 | | |
384 | | /* Unlink a nexthop from the list it's on, unconditionally */ |
385 | | static void nexthop_unlink(struct nexthop_group *nhg, struct nexthop *nexthop) |
386 | 0 | { |
387 | |
|
388 | 0 | if (nexthop->prev) |
389 | 0 | nexthop->prev->next = nexthop->next; |
390 | 0 | else { |
391 | 0 | assert(nhg->nexthop == nexthop); |
392 | 0 | assert(nexthop->prev == NULL); |
393 | 0 | nhg->nexthop = nexthop->next; |
394 | 0 | } |
395 | |
|
396 | 0 | if (nexthop->next) |
397 | 0 | nexthop->next->prev = nexthop->prev; |
398 | |
|
399 | 0 | nexthop->prev = NULL; |
400 | 0 | nexthop->next = NULL; |
401 | 0 | } |
402 | | |
403 | | /* |
404 | | * Copy a list of nexthops in 'nh' to an nhg, enforcing canonical sort order |
405 | | */ |
406 | | void nexthop_group_copy_nh_sorted(struct nexthop_group *nhg, |
407 | | const struct nexthop *nh) |
408 | 0 | { |
409 | 0 | struct nexthop *nexthop, *tail; |
410 | 0 | const struct nexthop *nh1; |
411 | | |
412 | | /* We'll try to append to the end of the new list; |
413 | | * if the original list in nh is already sorted, this eliminates |
414 | | * lots of comparison operations. |
415 | | */ |
416 | 0 | tail = nexthop_group_tail(nhg); |
417 | |
|
418 | 0 | for (nh1 = nh; nh1; nh1 = nh1->next) { |
419 | 0 | nexthop = nexthop_dup(nh1, NULL); |
420 | |
|
421 | 0 | if (tail && (nexthop_cmp(tail, nexthop) < 0)) { |
422 | 0 | tail->next = nexthop; |
423 | 0 | nexthop->prev = tail; |
424 | |
|
425 | 0 | tail = nexthop; |
426 | 0 | continue; |
427 | 0 | } |
428 | | |
429 | 0 | _nexthop_add_sorted(&nhg->nexthop, nexthop); |
430 | |
|
431 | 0 | if (tail == NULL) |
432 | 0 | tail = nexthop; |
433 | 0 | } |
434 | 0 | } |
435 | | |
436 | | /* Copy a list of nexthops, no effort made to sort or order them. */ |
437 | | void copy_nexthops(struct nexthop **tnh, const struct nexthop *nh, |
438 | | struct nexthop *rparent) |
439 | 0 | { |
440 | 0 | struct nexthop *nexthop; |
441 | 0 | const struct nexthop *nh1; |
442 | |
|
443 | 0 | for (nh1 = nh; nh1; nh1 = nh1->next) { |
444 | 0 | nexthop = nexthop_dup(nh1, rparent); |
445 | 0 | _nexthop_add(tnh, nexthop); |
446 | 0 | } |
447 | 0 | } |
448 | | |
449 | | uint32_t nexthop_group_hash_no_recurse(const struct nexthop_group *nhg) |
450 | 0 | { |
451 | 0 | struct nexthop *nh; |
452 | 0 | uint32_t key = 0; |
453 | | |
454 | | /* |
455 | | * We are not interested in hashing over any recursively |
456 | | * resolved nexthops |
457 | | */ |
458 | 0 | for (nh = nhg->nexthop; nh; nh = nh->next) |
459 | 0 | key = jhash_1word(nexthop_hash(nh), key); |
460 | |
|
461 | 0 | return key; |
462 | 0 | } |
463 | | |
464 | | uint32_t nexthop_group_hash(const struct nexthop_group *nhg) |
465 | 0 | { |
466 | 0 | struct nexthop *nh; |
467 | 0 | uint32_t key = 0; |
468 | |
|
469 | 0 | for (ALL_NEXTHOPS_PTR(nhg, nh)) |
470 | 0 | key = jhash_1word(nexthop_hash(nh), key); |
471 | |
|
472 | 0 | return key; |
473 | 0 | } |
474 | | |
475 | | void nexthop_group_mark_duplicates(struct nexthop_group *nhg) |
476 | 0 | { |
477 | 0 | struct nexthop *nexthop, *prev; |
478 | |
|
479 | 0 | for (ALL_NEXTHOPS_PTR(nhg, nexthop)) { |
480 | 0 | UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE); |
481 | 0 | for (ALL_NEXTHOPS_PTR(nhg, prev)) { |
482 | 0 | if (prev == nexthop) |
483 | 0 | break; |
484 | 0 | if (nexthop_same(nexthop, prev)) { |
485 | 0 | SET_FLAG(nexthop->flags, |
486 | 0 | NEXTHOP_FLAG_DUPLICATE); |
487 | 0 | break; |
488 | 0 | } |
489 | 0 | } |
490 | 0 | } |
491 | 0 | } |
492 | | |
493 | | static void nhgc_delete_nexthops(struct nexthop_group_cmd *nhgc) |
494 | 0 | { |
495 | 0 | struct nexthop *nexthop; |
496 | |
|
497 | 0 | nexthop = nhgc->nhg.nexthop; |
498 | 0 | while (nexthop) { |
499 | 0 | struct nexthop *next = nexthop_next(nexthop); |
500 | |
|
501 | 0 | _nexthop_del(&nhgc->nhg, nexthop); |
502 | 0 | if (nhg_hooks.del_nexthop) |
503 | 0 | nhg_hooks.del_nexthop(nhgc, nexthop); |
504 | |
|
505 | 0 | nexthop_free(nexthop); |
506 | |
|
507 | 0 | nexthop = next; |
508 | 0 | } |
509 | 0 | } |
510 | | |
511 | | struct nexthop_group_cmd *nhgc_find(const char *name) |
512 | 0 | { |
513 | 0 | struct nexthop_group_cmd find; |
514 | |
|
515 | 0 | strlcpy(find.name, name, sizeof(find.name)); |
516 | |
|
517 | 0 | return RB_FIND(nhgc_entry_head, &nhgc_entries, &find); |
518 | 0 | } |
519 | | |
520 | | static int nhgc_cmp_helper(const char *a, const char *b) |
521 | 0 | { |
522 | 0 | if (!a && !b) |
523 | 0 | return 0; |
524 | | |
525 | 0 | if (a && !b) |
526 | 0 | return -1; |
527 | | |
528 | 0 | if (!a && b) |
529 | 0 | return 1; |
530 | | |
531 | 0 | return strcmp(a, b); |
532 | 0 | } |
533 | | |
534 | | static int nhgc_addr_cmp_helper(const union sockunion *a, const union sockunion *b) |
535 | 0 | { |
536 | 0 | if (!a && !b) |
537 | 0 | return 0; |
538 | | |
539 | 0 | if (a && !b) |
540 | 0 | return -1; |
541 | | |
542 | 0 | if (!a && b) |
543 | 0 | return 1; |
544 | | |
545 | 0 | return sockunion_cmp(a, b); |
546 | 0 | } |
547 | | |
548 | | static int nhgl_cmp(struct nexthop_hold *nh1, struct nexthop_hold *nh2) |
549 | 0 | { |
550 | 0 | int ret; |
551 | |
|
552 | 0 | ret = nhgc_addr_cmp_helper(nh1->addr, nh2->addr); |
553 | 0 | if (ret) |
554 | 0 | return ret; |
555 | | |
556 | 0 | ret = nhgc_cmp_helper(nh1->intf, nh2->intf); |
557 | 0 | if (ret) |
558 | 0 | return ret; |
559 | | |
560 | 0 | ret = nhgc_cmp_helper(nh1->nhvrf_name, nh2->nhvrf_name); |
561 | 0 | if (ret) |
562 | 0 | return ret; |
563 | | |
564 | 0 | ret = ((int)nh2->onlink) - ((int)nh1->onlink); |
565 | 0 | if (ret) |
566 | 0 | return ret; |
567 | | |
568 | 0 | return nhgc_cmp_helper(nh1->labels, nh2->labels); |
569 | 0 | } |
570 | | |
571 | | static void nhgl_delete(struct nexthop_hold *nh) |
572 | 0 | { |
573 | 0 | XFREE(MTYPE_TMP, nh->intf); |
574 | |
|
575 | 0 | XFREE(MTYPE_TMP, nh->nhvrf_name); |
576 | |
|
577 | 0 | if (nh->addr) |
578 | 0 | sockunion_free(nh->addr); |
579 | |
|
580 | 0 | XFREE(MTYPE_TMP, nh->labels); |
581 | |
|
582 | 0 | XFREE(MTYPE_TMP, nh); |
583 | 0 | } |
584 | | |
585 | | static struct nexthop_group_cmd *nhgc_get(const char *name) |
586 | 0 | { |
587 | 0 | struct nexthop_group_cmd *nhgc; |
588 | |
|
589 | 0 | nhgc = nhgc_find(name); |
590 | 0 | if (!nhgc) { |
591 | 0 | nhgc = XCALLOC(MTYPE_TMP, sizeof(*nhgc)); |
592 | 0 | strlcpy(nhgc->name, name, sizeof(nhgc->name)); |
593 | |
|
594 | 0 | QOBJ_REG(nhgc, nexthop_group_cmd); |
595 | 0 | RB_INSERT(nhgc_entry_head, &nhgc_entries, nhgc); |
596 | |
|
597 | 0 | nhgc->nhg_list = list_new(); |
598 | 0 | nhgc->nhg_list->cmp = (int (*)(void *, void *))nhgl_cmp; |
599 | 0 | nhgc->nhg_list->del = (void (*)(void *))nhgl_delete; |
600 | |
|
601 | 0 | if (nhg_hooks.new) |
602 | 0 | nhg_hooks.new(name); |
603 | 0 | } |
604 | |
|
605 | 0 | return nhgc; |
606 | 0 | } |
607 | | |
608 | | static void nhgc_delete(struct nexthop_group_cmd *nhgc) |
609 | 0 | { |
610 | 0 | nhgc_delete_nexthops(nhgc); |
611 | |
|
612 | 0 | if (nhg_hooks.delete) |
613 | 0 | nhg_hooks.delete(nhgc->name); |
614 | |
|
615 | 0 | RB_REMOVE(nhgc_entry_head, &nhgc_entries, nhgc); |
616 | |
|
617 | 0 | list_delete(&nhgc->nhg_list); |
618 | |
|
619 | 0 | QOBJ_UNREG(nhgc); |
620 | 0 | XFREE(MTYPE_TMP, nhgc); |
621 | 0 | } |
622 | | |
623 | | DEFINE_QOBJ_TYPE(nexthop_group_cmd); |
624 | | |
625 | | DEFUN_NOSH(nexthop_group, nexthop_group_cmd, "nexthop-group NHGNAME", |
626 | | "Enter into the nexthop-group submode\n" |
627 | | "Specify the NAME of the nexthop-group\n") |
628 | 0 | { |
629 | 0 | const char *nhg_name = argv[1]->arg; |
630 | 0 | struct nexthop_group_cmd *nhgc = NULL; |
631 | |
|
632 | 0 | nhgc = nhgc_get(nhg_name); |
633 | 0 | VTY_PUSH_CONTEXT(NH_GROUP_NODE, nhgc); |
634 | |
|
635 | 0 | return CMD_SUCCESS; |
636 | 0 | } |
637 | | |
638 | | DEFUN_NOSH(no_nexthop_group, no_nexthop_group_cmd, "no nexthop-group NHGNAME", |
639 | | NO_STR |
640 | | "Delete the nexthop-group\n" |
641 | | "Specify the NAME of the nexthop-group\n") |
642 | 0 | { |
643 | 0 | const char *nhg_name = argv[2]->arg; |
644 | 0 | struct nexthop_group_cmd *nhgc = NULL; |
645 | |
|
646 | 0 | nhgc = nhgc_find(nhg_name); |
647 | 0 | if (nhgc) |
648 | 0 | nhgc_delete(nhgc); |
649 | |
|
650 | 0 | return CMD_SUCCESS; |
651 | 0 | } |
652 | | |
653 | | DEFPY(nexthop_group_backup, nexthop_group_backup_cmd, |
654 | | "backup-group WORD$name", |
655 | | "Specify a group name containing backup nexthops\n" |
656 | | "The name of the backup group\n") |
657 | 0 | { |
658 | 0 | VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); |
659 | |
|
660 | 0 | strlcpy(nhgc->backup_list_name, name, sizeof(nhgc->backup_list_name)); |
661 | |
|
662 | 0 | return CMD_SUCCESS; |
663 | 0 | } |
664 | | |
665 | | DEFPY(no_nexthop_group_backup, no_nexthop_group_backup_cmd, |
666 | | "no backup-group [WORD$name]", |
667 | | NO_STR |
668 | | "Clear group name containing backup nexthops\n" |
669 | | "The name of the backup group\n") |
670 | 0 | { |
671 | 0 | VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); |
672 | |
|
673 | 0 | nhgc->backup_list_name[0] = 0; |
674 | |
|
675 | 0 | return CMD_SUCCESS; |
676 | 0 | } |
677 | | |
678 | | DEFPY(nexthop_group_resilience, |
679 | | nexthop_group_resilience_cmd, |
680 | | "resilient buckets (1-256) idle-timer (1-4294967295) unbalanced-timer (1-4294967295)", |
681 | | "A resilient Nexthop Group\n" |
682 | | "Buckets in the Hash for this Group\n" |
683 | | "Number of buckets\n" |
684 | | "The Idle timer for this Resilient Nexthop Group in seconds\n" |
685 | | "Number of seconds of Idle time\n" |
686 | | "The length of time that the Nexthop Group can be unbalanced\n" |
687 | | "Number of seconds of Unbalanced time\n") |
688 | 0 | { |
689 | 0 | VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); |
690 | |
|
691 | 0 | nhgc->nhg.nhgr.buckets = buckets; |
692 | 0 | nhgc->nhg.nhgr.idle_timer = idle_timer; |
693 | 0 | nhgc->nhg.nhgr.unbalanced_timer = unbalanced_timer; |
694 | |
|
695 | 0 | if (nhg_hooks.modify) |
696 | 0 | nhg_hooks.modify(nhgc); |
697 | |
|
698 | 0 | return CMD_SUCCESS; |
699 | 0 | } |
700 | | |
701 | | DEFPY(no_nexthop_group_resilience, |
702 | | no_nexthop_group_resilience_cmd, |
703 | | "no resilient [buckets (1-256) idle-timer (1-4294967295) unbalanced-timer (1-4294967295)]", |
704 | | NO_STR |
705 | | "A resilient Nexthop Group\n" |
706 | | "Buckets in the Hash for this Group\n" |
707 | | "Number of buckets\n" |
708 | | "The Idle timer for this Resilient Nexthop Group in seconds\n" |
709 | | "Number of seconds of Idle time\n" |
710 | | "The length of time that the Nexthop Group can be unbalanced\n" |
711 | | "Number of seconds of Unbalanced time\n") |
712 | 0 | { |
713 | 0 | VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); |
714 | |
|
715 | 0 | nhgc->nhg.nhgr.buckets = 0; |
716 | 0 | nhgc->nhg.nhgr.idle_timer = 0; |
717 | 0 | nhgc->nhg.nhgr.unbalanced_timer = 0; |
718 | |
|
719 | 0 | return CMD_SUCCESS; |
720 | 0 | } |
721 | | |
722 | | static void nexthop_group_save_nhop(struct nexthop_group_cmd *nhgc, |
723 | | const char *nhvrf_name, |
724 | | const union sockunion *addr, |
725 | | const char *intf, bool onlink, |
726 | | const char *labels, const uint32_t weight, |
727 | | const char *backup_str) |
728 | 0 | { |
729 | 0 | struct nexthop_hold *nh; |
730 | |
|
731 | 0 | nh = XCALLOC(MTYPE_TMP, sizeof(*nh)); |
732 | |
|
733 | 0 | if (nhvrf_name) |
734 | 0 | nh->nhvrf_name = XSTRDUP(MTYPE_TMP, nhvrf_name); |
735 | 0 | if (intf) |
736 | 0 | nh->intf = XSTRDUP(MTYPE_TMP, intf); |
737 | 0 | if (addr) |
738 | 0 | nh->addr = sockunion_dup(addr); |
739 | 0 | if (labels) |
740 | 0 | nh->labels = XSTRDUP(MTYPE_TMP, labels); |
741 | |
|
742 | 0 | nh->onlink = onlink; |
743 | |
|
744 | 0 | nh->weight = weight; |
745 | |
|
746 | 0 | if (backup_str) |
747 | 0 | nh->backup_str = XSTRDUP(MTYPE_TMP, backup_str); |
748 | |
|
749 | 0 | listnode_add_sort(nhgc->nhg_list, nh); |
750 | 0 | } |
751 | | |
752 | | /* |
753 | | * Remove config info about a nexthop from group 'nhgc'. Note that we |
754 | | * use only a subset of the available attributes here to determine |
755 | | * a 'match'. |
756 | | * Note that this doesn't change the list of nexthops, only the config |
757 | | * information. |
758 | | */ |
759 | | static void nexthop_group_unsave_nhop(struct nexthop_group_cmd *nhgc, |
760 | | const char *nhvrf_name, |
761 | | const union sockunion *addr, |
762 | | const char *intf) |
763 | 0 | { |
764 | 0 | struct nexthop_hold *nh; |
765 | 0 | struct listnode *node; |
766 | |
|
767 | 0 | for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) { |
768 | 0 | if (nhgc_cmp_helper(nhvrf_name, nh->nhvrf_name) == 0 |
769 | 0 | && nhgc_addr_cmp_helper(addr, nh->addr) == 0 |
770 | 0 | && nhgc_cmp_helper(intf, nh->intf) == 0) |
771 | 0 | break; |
772 | 0 | } |
773 | | |
774 | | /* |
775 | | * Something has gone seriously wrong, fail gracefully |
776 | | */ |
777 | 0 | if (!nh) |
778 | 0 | return; |
779 | | |
780 | 0 | list_delete_node(nhgc->nhg_list, node); |
781 | 0 | nhgl_delete(nh); |
782 | 0 | } |
783 | | |
784 | | /* |
785 | | * Parse the config strings we support for a single nexthop. This gets used |
786 | | * in a couple of different ways, and we distinguish between transient |
787 | | * failures - such as a still-unprocessed interface - and fatal errors |
788 | | * from label-string parsing. |
789 | | */ |
790 | | static bool nexthop_group_parse_nexthop(struct nexthop *nhop, |
791 | | const union sockunion *addr, |
792 | | const char *intf, bool onlink, |
793 | | const char *name, const char *labels, |
794 | | vni_t vni, int *lbl_ret, |
795 | | uint32_t weight, const char *backup_str) |
796 | 0 | { |
797 | 0 | int ret = 0; |
798 | 0 | struct vrf *vrf; |
799 | 0 | int num; |
800 | 0 | uint8_t labelnum = 0; |
801 | |
|
802 | 0 | memset(nhop, 0, sizeof(*nhop)); |
803 | |
|
804 | 0 | if (name) |
805 | 0 | vrf = vrf_lookup_by_name(name); |
806 | 0 | else |
807 | 0 | vrf = vrf_lookup_by_id(VRF_DEFAULT); |
808 | |
|
809 | 0 | if (!vrf) |
810 | 0 | return false; |
811 | | |
812 | 0 | nhop->vrf_id = vrf->vrf_id; |
813 | |
|
814 | 0 | if (intf) { |
815 | 0 | nhop->ifindex = ifname2ifindex(intf, vrf->vrf_id); |
816 | 0 | if (nhop->ifindex == IFINDEX_INTERNAL) |
817 | 0 | return false; |
818 | 0 | } |
819 | | |
820 | 0 | if (onlink) |
821 | 0 | SET_FLAG(nhop->flags, NEXTHOP_FLAG_ONLINK); |
822 | |
|
823 | 0 | if (addr) { |
824 | 0 | if (addr->sa.sa_family == AF_INET) { |
825 | 0 | nhop->gate.ipv4.s_addr = addr->sin.sin_addr.s_addr; |
826 | 0 | if (intf) |
827 | 0 | nhop->type = NEXTHOP_TYPE_IPV4_IFINDEX; |
828 | 0 | else |
829 | 0 | nhop->type = NEXTHOP_TYPE_IPV4; |
830 | 0 | } else { |
831 | 0 | nhop->gate.ipv6 = addr->sin6.sin6_addr; |
832 | 0 | if (intf) |
833 | 0 | nhop->type = NEXTHOP_TYPE_IPV6_IFINDEX; |
834 | 0 | else |
835 | 0 | nhop->type = NEXTHOP_TYPE_IPV6; |
836 | 0 | } |
837 | 0 | } else |
838 | 0 | nhop->type = NEXTHOP_TYPE_IFINDEX; |
839 | |
|
840 | 0 | if (labels) { |
841 | 0 | mpls_label_t larray[MPLS_MAX_LABELS]; |
842 | |
|
843 | 0 | ret = mpls_str2label(labels, &labelnum, larray); |
844 | | |
845 | | /* Return label parse result */ |
846 | 0 | if (lbl_ret) |
847 | 0 | *lbl_ret = ret; |
848 | |
|
849 | 0 | if (ret < 0) |
850 | 0 | return false; |
851 | 0 | else if (labelnum > 0) |
852 | 0 | nexthop_add_labels(nhop, ZEBRA_LSP_NONE, labelnum, |
853 | 0 | larray); |
854 | 0 | } else if (vni) { |
855 | 0 | mpls_label_t label = MPLS_INVALID_LABEL; |
856 | |
|
857 | 0 | vni2label(vni, &label); |
858 | 0 | nexthop_add_labels(nhop, ZEBRA_LSP_EVPN, 1, &label); |
859 | 0 | } |
860 | | |
861 | 0 | nhop->weight = weight; |
862 | |
|
863 | 0 | if (backup_str) { |
864 | | /* Parse backup indexes */ |
865 | 0 | ret = nexthop_str2backups(backup_str, |
866 | 0 | &num, nhop->backup_idx); |
867 | 0 | if (ret == 0) { |
868 | 0 | SET_FLAG(nhop->flags, NEXTHOP_FLAG_HAS_BACKUP); |
869 | 0 | nhop->backup_num = num; |
870 | 0 | } else |
871 | 0 | return false; |
872 | 0 | } |
873 | | |
874 | 0 | return true; |
875 | 0 | } |
876 | | |
877 | | /* |
878 | | * Wrapper to parse the strings in a 'nexthop_hold' |
879 | | */ |
880 | | static bool nexthop_group_parse_nhh(struct nexthop *nhop, |
881 | | const struct nexthop_hold *nhh) |
882 | 0 | { |
883 | 0 | return (nexthop_group_parse_nexthop( |
884 | 0 | nhop, nhh->addr, nhh->intf, nhh->onlink, nhh->nhvrf_name, |
885 | 0 | nhh->labels, nhh->vni, NULL, nhh->weight, nhh->backup_str)); |
886 | 0 | } |
887 | | |
888 | | DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, |
889 | | "[no] nexthop\ |
890 | | <\ |
891 | | <A.B.C.D|X:X::X:X>$addr [INTERFACE$intf [onlink$onlink]]\ |
892 | | |INTERFACE$intf\ |
893 | | >\ |
894 | | [{ \ |
895 | | nexthop-vrf NAME$vrf_name \ |
896 | | |label WORD \ |
897 | | |vni (1-16777215) \ |
898 | | |weight (1-255) \ |
899 | | |backup-idx WORD \ |
900 | | }]", |
901 | | NO_STR |
902 | | "Specify one of the nexthops in this ECMP group\n" |
903 | | "v4 Address\n" |
904 | | "v6 Address\n" |
905 | | "Interface to use\n" |
906 | | "Treat nexthop as directly attached to the interface\n" |
907 | | "Interface to use\n" |
908 | | "If the nexthop is in a different vrf tell us\n" |
909 | | "The nexthop-vrf Name\n" |
910 | | "Specify label(s) for this nexthop\n" |
911 | | "One or more labels in the range (16-1048575) separated by '/'\n" |
912 | | "Specify VNI(s) for this nexthop\n" |
913 | | "VNI in the range (1-16777215)\n" |
914 | | "Weight to be used by the nexthop for purposes of ECMP\n" |
915 | | "Weight value to be used\n" |
916 | | "Specify backup nexthop indexes in another group\n" |
917 | | "One or more indexes in the range (0-254) separated by ','\n") |
918 | 0 | { |
919 | 0 | VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); |
920 | 0 | struct nexthop nhop; |
921 | 0 | struct nexthop *nh; |
922 | 0 | int lbl_ret = 0; |
923 | 0 | bool legal; |
924 | 0 | int num; |
925 | 0 | uint8_t backups[NEXTHOP_MAX_BACKUPS]; |
926 | 0 | bool yes = !no; |
927 | | |
928 | | /* Pre-parse backup string to validate */ |
929 | 0 | if (backup_idx) { |
930 | 0 | lbl_ret = nexthop_str2backups(backup_idx, &num, backups); |
931 | 0 | if (lbl_ret < 0) { |
932 | 0 | vty_out(vty, "%% Invalid backups\n"); |
933 | 0 | return CMD_WARNING_CONFIG_FAILED; |
934 | 0 | } |
935 | 0 | } |
936 | | |
937 | 0 | legal = nexthop_group_parse_nexthop(&nhop, addr, intf, !!onlink, |
938 | 0 | vrf_name, label, vni, &lbl_ret, |
939 | 0 | weight, backup_idx); |
940 | |
|
941 | 0 | if (nhop.type == NEXTHOP_TYPE_IPV6 |
942 | 0 | && IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) { |
943 | 0 | vty_out(vty, |
944 | 0 | "Specified a v6 LL with no interface, rejecting\n"); |
945 | 0 | return CMD_WARNING_CONFIG_FAILED; |
946 | 0 | } |
947 | | |
948 | | /* Handle label-string errors */ |
949 | 0 | if (!legal && lbl_ret < 0) { |
950 | 0 | switch (lbl_ret) { |
951 | 0 | case -1: |
952 | 0 | vty_out(vty, "%% Malformed label(s)\n"); |
953 | 0 | break; |
954 | 0 | case -2: |
955 | 0 | vty_out(vty, |
956 | 0 | "%% Cannot use reserved label(s) (%d-%d)\n", |
957 | 0 | MPLS_LABEL_RESERVED_MIN, |
958 | 0 | MPLS_LABEL_RESERVED_MAX); |
959 | 0 | break; |
960 | 0 | case -3: |
961 | 0 | vty_out(vty, |
962 | 0 | "%% Too many labels. Enter %d or fewer\n", |
963 | 0 | MPLS_MAX_LABELS); |
964 | 0 | break; |
965 | 0 | } |
966 | 0 | return CMD_WARNING_CONFIG_FAILED; |
967 | 0 | } |
968 | | |
969 | | /* Look for an existing nexthop in the config. Note that the test |
970 | | * here tests only some attributes - it's not a complete comparison. |
971 | | * Note that we've got two kinds of objects to manage: 'nexthop_hold' |
972 | | * that represent config that may or may not be valid (yet), and |
973 | | * actual nexthops that have been validated and parsed. |
974 | | */ |
975 | 0 | nh = nhg_nh_find(&nhgc->nhg, &nhop); |
976 | | |
977 | | /* Always attempt to remove old config info. */ |
978 | 0 | nexthop_group_unsave_nhop(nhgc, vrf_name, addr, intf); |
979 | | |
980 | | /* Remove any existing nexthop, for delete and replace cases. */ |
981 | 0 | if (nh) { |
982 | 0 | nexthop_unlink(&nhgc->nhg, nh); |
983 | |
|
984 | 0 | if (nhg_hooks.del_nexthop) |
985 | 0 | nhg_hooks.del_nexthop(nhgc, nh); |
986 | |
|
987 | 0 | nexthop_free(nh); |
988 | 0 | } |
989 | 0 | if (yes) { |
990 | | /* Add/replace case: capture nexthop if valid, and capture |
991 | | * config info always. |
992 | | */ |
993 | 0 | if (legal) { |
994 | 0 | nh = nexthop_new(); |
995 | |
|
996 | 0 | memcpy(nh, &nhop, sizeof(nhop)); |
997 | 0 | _nexthop_add(&nhgc->nhg.nexthop, nh); |
998 | 0 | } |
999 | | |
1000 | | /* Save config always */ |
1001 | 0 | nexthop_group_save_nhop(nhgc, vrf_name, addr, intf, !!onlink, |
1002 | 0 | label, weight, backup_idx); |
1003 | |
|
1004 | 0 | if (legal && nhg_hooks.add_nexthop) |
1005 | 0 | nhg_hooks.add_nexthop(nhgc, nh); |
1006 | 0 | } |
1007 | |
|
1008 | 0 | return CMD_SUCCESS; |
1009 | 0 | } |
1010 | | |
1011 | | static int nexthop_group_write(struct vty *vty); |
1012 | | static struct cmd_node nexthop_group_node = { |
1013 | | .name = "nexthop-group", |
1014 | | .node = NH_GROUP_NODE, |
1015 | | .parent_node = CONFIG_NODE, |
1016 | | .prompt = "%s(config-nh-group)# ", |
1017 | | .config_write = nexthop_group_write, |
1018 | | }; |
1019 | | |
1020 | | void nexthop_group_write_nexthop_simple(struct vty *vty, |
1021 | | const struct nexthop *nh, |
1022 | | char *altifname) |
1023 | 0 | { |
1024 | 0 | char *ifname; |
1025 | |
|
1026 | 0 | vty_out(vty, "nexthop "); |
1027 | |
|
1028 | 0 | if (altifname) |
1029 | 0 | ifname = altifname; |
1030 | 0 | else |
1031 | 0 | ifname = (char *)ifindex2ifname(nh->ifindex, nh->vrf_id); |
1032 | |
|
1033 | 0 | switch (nh->type) { |
1034 | 0 | case NEXTHOP_TYPE_IFINDEX: |
1035 | 0 | vty_out(vty, "%s", ifname); |
1036 | 0 | break; |
1037 | 0 | case NEXTHOP_TYPE_IPV4: |
1038 | 0 | vty_out(vty, "%pI4", &nh->gate.ipv4); |
1039 | 0 | break; |
1040 | 0 | case NEXTHOP_TYPE_IPV4_IFINDEX: |
1041 | 0 | vty_out(vty, "%pI4 %s", &nh->gate.ipv4, ifname); |
1042 | 0 | break; |
1043 | 0 | case NEXTHOP_TYPE_IPV6: |
1044 | 0 | vty_out(vty, "%pI6", &nh->gate.ipv6); |
1045 | 0 | break; |
1046 | 0 | case NEXTHOP_TYPE_IPV6_IFINDEX: |
1047 | 0 | vty_out(vty, "%pI6 %s", &nh->gate.ipv6, ifname); |
1048 | 0 | break; |
1049 | 0 | case NEXTHOP_TYPE_BLACKHOLE: |
1050 | 0 | break; |
1051 | 0 | } |
1052 | 0 | } |
1053 | | |
1054 | | void nexthop_group_write_nexthop(struct vty *vty, const struct nexthop *nh) |
1055 | 0 | { |
1056 | 0 | struct vrf *vrf; |
1057 | 0 | int i; |
1058 | |
|
1059 | 0 | nexthop_group_write_nexthop_simple(vty, nh, NULL); |
1060 | |
|
1061 | 0 | if (nh->vrf_id != VRF_DEFAULT) { |
1062 | 0 | vrf = vrf_lookup_by_id(nh->vrf_id); |
1063 | 0 | vty_out(vty, " nexthop-vrf %s", VRF_LOGNAME(vrf)); |
1064 | 0 | } |
1065 | |
|
1066 | 0 | if (nh->nh_label && nh->nh_label->num_labels > 0) { |
1067 | 0 | char buf[200]; |
1068 | |
|
1069 | 0 | mpls_label2str(nh->nh_label->num_labels, nh->nh_label->label, |
1070 | 0 | buf, sizeof(buf), nh->nh_label_type, 0); |
1071 | 0 | vty_out(vty, " label %s", buf); |
1072 | 0 | } |
1073 | |
|
1074 | 0 | if (nh->weight) |
1075 | 0 | vty_out(vty, " weight %u", nh->weight); |
1076 | |
|
1077 | 0 | if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) { |
1078 | 0 | vty_out(vty, " backup-idx %d", nh->backup_idx[0]); |
1079 | |
|
1080 | 0 | for (i = 1; i < nh->backup_num; i++) |
1081 | 0 | vty_out(vty, ",%d", nh->backup_idx[i]); |
1082 | 0 | } |
1083 | |
|
1084 | 0 | vty_out(vty, "\n"); |
1085 | 0 | } |
1086 | | |
1087 | | void nexthop_group_json_nexthop(json_object *j, const struct nexthop *nh) |
1088 | 0 | { |
1089 | 0 | struct vrf *vrf; |
1090 | 0 | json_object *json_backups = NULL; |
1091 | 0 | int i; |
1092 | |
|
1093 | 0 | switch (nh->type) { |
1094 | 0 | case NEXTHOP_TYPE_IFINDEX: |
1095 | 0 | json_object_string_add(j, "nexthop", |
1096 | 0 | ifindex2ifname(nh->ifindex, nh->vrf_id)); |
1097 | 0 | break; |
1098 | 0 | case NEXTHOP_TYPE_IPV4: |
1099 | 0 | json_object_string_addf(j, "nexthop", "%pI4", &nh->gate.ipv4); |
1100 | 0 | break; |
1101 | 0 | case NEXTHOP_TYPE_IPV4_IFINDEX: |
1102 | 0 | json_object_string_addf(j, "nexthop", "%pI4", &nh->gate.ipv4); |
1103 | 0 | json_object_string_add(j, "vrfId", |
1104 | 0 | ifindex2ifname(nh->ifindex, nh->vrf_id)); |
1105 | 0 | break; |
1106 | 0 | case NEXTHOP_TYPE_IPV6: |
1107 | 0 | json_object_string_addf(j, "nexthop", "%pI6", &nh->gate.ipv6); |
1108 | 0 | break; |
1109 | 0 | case NEXTHOP_TYPE_IPV6_IFINDEX: |
1110 | 0 | json_object_string_addf(j, "nexthop", "%pI6", &nh->gate.ipv6); |
1111 | 0 | json_object_string_add(j, "vrfId", |
1112 | 0 | ifindex2ifname(nh->ifindex, nh->vrf_id)); |
1113 | 0 | break; |
1114 | 0 | case NEXTHOP_TYPE_BLACKHOLE: |
1115 | 0 | break; |
1116 | 0 | } |
1117 | | |
1118 | 0 | if (nh->vrf_id != VRF_DEFAULT) { |
1119 | 0 | vrf = vrf_lookup_by_id(nh->vrf_id); |
1120 | 0 | json_object_string_add(j, "targetVrf", vrf->name); |
1121 | 0 | } |
1122 | |
|
1123 | 0 | if (nh->nh_label && nh->nh_label->num_labels > 0) { |
1124 | 0 | char buf[200]; |
1125 | |
|
1126 | 0 | mpls_label2str(nh->nh_label->num_labels, nh->nh_label->label, |
1127 | 0 | buf, sizeof(buf), nh->nh_label_type, 0); |
1128 | 0 | json_object_string_add(j, "label", buf); |
1129 | 0 | } |
1130 | |
|
1131 | 0 | if (nh->weight) |
1132 | 0 | json_object_int_add(j, "weight", nh->weight); |
1133 | |
|
1134 | 0 | if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) { |
1135 | 0 | json_backups = json_object_new_array(); |
1136 | 0 | for (i = 0; i < nh->backup_num; i++) |
1137 | 0 | json_object_array_add( |
1138 | 0 | json_backups, |
1139 | 0 | json_object_new_int(nh->backup_idx[i])); |
1140 | |
|
1141 | 0 | json_object_object_add(j, "backupIdx", json_backups); |
1142 | 0 | } |
1143 | 0 | } |
1144 | | |
1145 | | static void nexthop_group_write_nexthop_internal(struct vty *vty, |
1146 | | const struct nexthop_hold *nh) |
1147 | 0 | { |
1148 | 0 | vty_out(vty, "nexthop"); |
1149 | |
|
1150 | 0 | if (nh->addr) |
1151 | 0 | vty_out(vty, " %pSU", nh->addr); |
1152 | |
|
1153 | 0 | if (nh->intf) |
1154 | 0 | vty_out(vty, " %s", nh->intf); |
1155 | |
|
1156 | 0 | if (nh->onlink) |
1157 | 0 | vty_out(vty, " onlink"); |
1158 | |
|
1159 | 0 | if (nh->nhvrf_name) |
1160 | 0 | vty_out(vty, " nexthop-vrf %s", nh->nhvrf_name); |
1161 | |
|
1162 | 0 | if (nh->labels) |
1163 | 0 | vty_out(vty, " label %s", nh->labels); |
1164 | |
|
1165 | 0 | if (nh->vni) |
1166 | 0 | vty_out(vty, " vni %u", nh->vni); |
1167 | |
|
1168 | 0 | if (nh->weight) |
1169 | 0 | vty_out(vty, " weight %u", nh->weight); |
1170 | |
|
1171 | 0 | if (nh->backup_str) |
1172 | 0 | vty_out(vty, " backup-idx %s", nh->backup_str); |
1173 | |
|
1174 | 0 | vty_out(vty, "\n"); |
1175 | 0 | } |
1176 | | |
1177 | | static int nexthop_group_write(struct vty *vty) |
1178 | 0 | { |
1179 | 0 | struct nexthop_group_cmd *nhgc; |
1180 | 0 | struct nexthop_hold *nh; |
1181 | |
|
1182 | 0 | RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { |
1183 | 0 | struct listnode *node; |
1184 | |
|
1185 | 0 | vty_out(vty, "nexthop-group %s\n", nhgc->name); |
1186 | |
|
1187 | 0 | if (nhgc->nhg.nhgr.buckets) |
1188 | 0 | vty_out(vty, |
1189 | 0 | " resilient buckets %u idle-timer %u unbalanced-timer %u\n", |
1190 | 0 | nhgc->nhg.nhgr.buckets, |
1191 | 0 | nhgc->nhg.nhgr.idle_timer, |
1192 | 0 | nhgc->nhg.nhgr.unbalanced_timer); |
1193 | |
|
1194 | 0 | if (nhgc->backup_list_name[0]) |
1195 | 0 | vty_out(vty, " backup-group %s\n", |
1196 | 0 | nhgc->backup_list_name); |
1197 | |
|
1198 | 0 | for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) { |
1199 | 0 | vty_out(vty, " "); |
1200 | 0 | nexthop_group_write_nexthop_internal(vty, nh); |
1201 | 0 | } |
1202 | |
|
1203 | 0 | vty_out(vty, "exit\n"); |
1204 | 0 | vty_out(vty, "!\n"); |
1205 | 0 | } |
1206 | |
|
1207 | 0 | return 1; |
1208 | 0 | } |
1209 | | |
1210 | | void nexthop_group_enable_vrf(struct vrf *vrf) |
1211 | 1 | { |
1212 | 1 | struct nexthop_group_cmd *nhgc; |
1213 | 1 | struct nexthop_hold *nhh; |
1214 | | |
1215 | 1 | RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { |
1216 | 0 | struct listnode *node; |
1217 | |
|
1218 | 0 | for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) { |
1219 | 0 | struct nexthop nhop; |
1220 | 0 | struct nexthop *nh; |
1221 | |
|
1222 | 0 | if (!nexthop_group_parse_nhh(&nhop, nhh)) |
1223 | 0 | continue; |
1224 | | |
1225 | 0 | nh = nexthop_exists(&nhgc->nhg, &nhop); |
1226 | |
|
1227 | 0 | if (nh) |
1228 | 0 | continue; |
1229 | | |
1230 | 0 | if (nhop.vrf_id != vrf->vrf_id) |
1231 | 0 | continue; |
1232 | | |
1233 | 0 | nh = nexthop_new(); |
1234 | |
|
1235 | 0 | memcpy(nh, &nhop, sizeof(nhop)); |
1236 | 0 | _nexthop_add(&nhgc->nhg.nexthop, nh); |
1237 | |
|
1238 | 0 | if (nhg_hooks.add_nexthop) |
1239 | 0 | nhg_hooks.add_nexthop(nhgc, nh); |
1240 | 0 | } |
1241 | 0 | } |
1242 | 1 | } |
1243 | | |
1244 | | void nexthop_group_disable_vrf(struct vrf *vrf) |
1245 | 0 | { |
1246 | 0 | struct nexthop_group_cmd *nhgc; |
1247 | 0 | struct nexthop_hold *nhh; |
1248 | |
|
1249 | 0 | RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { |
1250 | 0 | struct listnode *node; |
1251 | |
|
1252 | 0 | for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) { |
1253 | 0 | struct nexthop nhop; |
1254 | 0 | struct nexthop *nh; |
1255 | |
|
1256 | 0 | if (!nexthop_group_parse_nhh(&nhop, nhh)) |
1257 | 0 | continue; |
1258 | | |
1259 | 0 | nh = nexthop_exists(&nhgc->nhg, &nhop); |
1260 | |
|
1261 | 0 | if (!nh) |
1262 | 0 | continue; |
1263 | | |
1264 | 0 | if (nh->vrf_id != vrf->vrf_id) |
1265 | 0 | continue; |
1266 | | |
1267 | 0 | _nexthop_del(&nhgc->nhg, nh); |
1268 | |
|
1269 | 0 | if (nhg_hooks.del_nexthop) |
1270 | 0 | nhg_hooks.del_nexthop(nhgc, nh); |
1271 | |
|
1272 | 0 | nexthop_free(nh); |
1273 | 0 | } |
1274 | 0 | } |
1275 | 0 | } |
1276 | | |
1277 | | void nexthop_group_interface_state_change(struct interface *ifp, |
1278 | | ifindex_t oldifindex) |
1279 | 0 | { |
1280 | 0 | struct nexthop_group_cmd *nhgc; |
1281 | 0 | struct nexthop_hold *nhh; |
1282 | |
|
1283 | 0 | RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { |
1284 | 0 | struct listnode *node; |
1285 | 0 | struct nexthop *nh; |
1286 | |
|
1287 | 0 | if (if_is_up(ifp)) { |
1288 | 0 | for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) { |
1289 | 0 | struct nexthop nhop; |
1290 | |
|
1291 | 0 | if (!nexthop_group_parse_nhh(&nhop, nhh)) |
1292 | 0 | continue; |
1293 | | |
1294 | 0 | switch (nhop.type) { |
1295 | 0 | case NEXTHOP_TYPE_IPV4: |
1296 | 0 | case NEXTHOP_TYPE_IPV6: |
1297 | 0 | case NEXTHOP_TYPE_BLACKHOLE: |
1298 | 0 | continue; |
1299 | 0 | case NEXTHOP_TYPE_IFINDEX: |
1300 | 0 | case NEXTHOP_TYPE_IPV4_IFINDEX: |
1301 | 0 | case NEXTHOP_TYPE_IPV6_IFINDEX: |
1302 | 0 | break; |
1303 | 0 | } |
1304 | 0 | nh = nexthop_exists(&nhgc->nhg, &nhop); |
1305 | |
|
1306 | 0 | if (nh) |
1307 | 0 | continue; |
1308 | | |
1309 | 0 | if (ifp->ifindex != nhop.ifindex) |
1310 | 0 | continue; |
1311 | | |
1312 | 0 | nh = nexthop_new(); |
1313 | |
|
1314 | 0 | memcpy(nh, &nhop, sizeof(nhop)); |
1315 | 0 | _nexthop_add(&nhgc->nhg.nexthop, nh); |
1316 | |
|
1317 | 0 | if (nhg_hooks.add_nexthop) |
1318 | 0 | nhg_hooks.add_nexthop(nhgc, nh); |
1319 | 0 | } |
1320 | 0 | } else { |
1321 | 0 | struct nexthop *next_nh; |
1322 | |
|
1323 | 0 | for (nh = nhgc->nhg.nexthop; nh; nh = next_nh) { |
1324 | 0 | next_nh = nh->next; |
1325 | 0 | switch (nh->type) { |
1326 | 0 | case NEXTHOP_TYPE_IPV4: |
1327 | 0 | case NEXTHOP_TYPE_IPV6: |
1328 | 0 | case NEXTHOP_TYPE_BLACKHOLE: |
1329 | 0 | continue; |
1330 | 0 | case NEXTHOP_TYPE_IFINDEX: |
1331 | 0 | case NEXTHOP_TYPE_IPV4_IFINDEX: |
1332 | 0 | case NEXTHOP_TYPE_IPV6_IFINDEX: |
1333 | 0 | break; |
1334 | 0 | } |
1335 | | |
1336 | 0 | if (oldifindex != nh->ifindex) |
1337 | 0 | continue; |
1338 | | |
1339 | 0 | _nexthop_del(&nhgc->nhg, nh); |
1340 | |
|
1341 | 0 | if (nhg_hooks.del_nexthop) |
1342 | 0 | nhg_hooks.del_nexthop(nhgc, nh); |
1343 | |
|
1344 | 0 | nexthop_free(nh); |
1345 | 0 | } |
1346 | 0 | } |
1347 | 0 | } |
1348 | 0 | } |
1349 | | |
1350 | | static void nhg_name_autocomplete(vector comps, struct cmd_token *token) |
1351 | 0 | { |
1352 | 0 | struct nexthop_group_cmd *nhgc; |
1353 | |
|
1354 | 0 | RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { |
1355 | 0 | vector_set(comps, XSTRDUP(MTYPE_COMPLETION, nhgc->name)); |
1356 | 0 | } |
1357 | 0 | } |
1358 | | |
1359 | | static const struct cmd_variable_handler nhg_name_handlers[] = { |
1360 | | {.tokenname = "NHGNAME", .completions = nhg_name_autocomplete}, |
1361 | | {.completions = NULL}}; |
1362 | | |
1363 | | void nexthop_group_init(void (*new)(const char *name), |
1364 | | void (*modify)(const struct nexthop_group_cmd *nhgc), |
1365 | | void (*add_nexthop)(const struct nexthop_group_cmd *nhg, |
1366 | | const struct nexthop *nhop), |
1367 | | void (*del_nexthop)(const struct nexthop_group_cmd *nhg, |
1368 | | const struct nexthop *nhop), |
1369 | | void (*delete)(const char *name)) |
1370 | 0 | { |
1371 | 0 | RB_INIT(nhgc_entry_head, &nhgc_entries); |
1372 | |
|
1373 | 0 | cmd_variable_handler_register(nhg_name_handlers); |
1374 | |
|
1375 | 0 | install_node(&nexthop_group_node); |
1376 | 0 | install_element(CONFIG_NODE, &nexthop_group_cmd); |
1377 | 0 | install_element(CONFIG_NODE, &no_nexthop_group_cmd); |
1378 | |
|
1379 | 0 | install_default(NH_GROUP_NODE); |
1380 | 0 | install_element(NH_GROUP_NODE, &nexthop_group_backup_cmd); |
1381 | 0 | install_element(NH_GROUP_NODE, &no_nexthop_group_backup_cmd); |
1382 | 0 | install_element(NH_GROUP_NODE, &ecmp_nexthops_cmd); |
1383 | |
|
1384 | 0 | install_element(NH_GROUP_NODE, &nexthop_group_resilience_cmd); |
1385 | 0 | install_element(NH_GROUP_NODE, &no_nexthop_group_resilience_cmd); |
1386 | |
|
1387 | 0 | memset(&nhg_hooks, 0, sizeof(nhg_hooks)); |
1388 | |
|
1389 | 0 | if (new) |
1390 | 0 | nhg_hooks.new = new; |
1391 | 0 | if (modify) |
1392 | 0 | nhg_hooks.modify = modify; |
1393 | 0 | if (add_nexthop) |
1394 | 0 | nhg_hooks.add_nexthop = add_nexthop; |
1395 | 0 | if (del_nexthop) |
1396 | 0 | nhg_hooks.del_nexthop = del_nexthop; |
1397 | 0 | if (delete) |
1398 | 0 | nhg_hooks.delete = delete; |
1399 | 0 | } |