]> git.proxmox.com Git - mirror_frr.git/blob - pimd/pim_vxlan.c
Merge pull request #10770 from chiragshah6/evpn_dev3
[mirror_frr.git] / pimd / pim_vxlan.c
1 /* PIM support for VxLAN BUM flooding
2 *
3 * Copyright (C) 2019 Cumulus Networks, Inc.
4 *
5 * This file is part of FRR.
6 *
7 * FRR is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2, or (at your option) any
10 * later version.
11 *
12 * FRR is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 */
21
22 #include <zebra.h>
23
24 #include <hash.h>
25 #include <jhash.h>
26 #include <log.h>
27 #include <prefix.h>
28 #include <vrf.h>
29
30 #include "pimd.h"
31 #include "pim_iface.h"
32 #include "pim_memory.h"
33 #include "pim_oil.h"
34 #include "pim_register.h"
35 #include "pim_str.h"
36 #include "pim_upstream.h"
37 #include "pim_ifchannel.h"
38 #include "pim_nht.h"
39 #include "pim_zebra.h"
40 #include "pim_vxlan.h"
41 #include "pim_mlag.h"
42
43 /* pim-vxlan global info */
44 struct pim_vxlan vxlan_info, *pim_vxlan_p = &vxlan_info;
45
46 static void pim_vxlan_work_timer_setup(bool start);
47 static void pim_vxlan_set_peerlink_rif(struct pim_instance *pim,
48 struct interface *ifp);
49
50 /*************************** vxlan work list **********************************
51 * A work list is maintained for staggered generation of pim null register
52 * messages for vxlan SG entries that are in a reg_join state.
53 *
54 * A max of 500 NULL registers are generated at one shot. If paused reg
55 * generation continues on the next second and so on till all register
56 * messages have been sent out. And the process is restarted every 60s.
57 *
58 * purpose of this null register generation is to setup the SPT and maintain
59 * independent of the presence of overlay BUM traffic.
60 ****************************************************************************/
61 static void pim_vxlan_do_reg_work(void)
62 {
63 struct listnode *listnode;
64 int work_cnt = 0;
65 struct pim_vxlan_sg *vxlan_sg;
66 static int sec_count;
67
68 ++sec_count;
69
70 if (sec_count > PIM_VXLAN_NULL_REG_INTERVAL) {
71 sec_count = 0;
72 listnode = vxlan_info.next_work ?
73 vxlan_info.next_work :
74 vxlan_info.work_list->head;
75 if (PIM_DEBUG_VXLAN && listnode)
76 zlog_debug("vxlan SG work %s",
77 vxlan_info.next_work ? "continues" : "starts");
78 } else {
79 listnode = vxlan_info.next_work;
80 }
81
82 for (; listnode; listnode = listnode->next) {
83 vxlan_sg = (struct pim_vxlan_sg *)listnode->data;
84 if (vxlan_sg->up && (vxlan_sg->up->reg_state == PIM_REG_JOIN)) {
85 if (PIM_DEBUG_VXLAN)
86 zlog_debug("vxlan SG %s periodic NULL register",
87 vxlan_sg->sg_str);
88
89 /*
90 * If we are on the work queue *and* the rpf
91 * has been lost on the vxlan_sg->up let's
92 * make sure that we don't send it.
93 */
94 if (vxlan_sg->up->rpf.source_nexthop.interface) {
95 pim_null_register_send(vxlan_sg->up);
96 ++work_cnt;
97 }
98 }
99
100 if (work_cnt > vxlan_info.max_work_cnt) {
101 vxlan_info.next_work = listnode->next;
102 if (PIM_DEBUG_VXLAN)
103 zlog_debug("vxlan SG %d work items proc and pause",
104 work_cnt);
105 return;
106 }
107 }
108
109 if (work_cnt) {
110 if (PIM_DEBUG_VXLAN)
111 zlog_debug("vxlan SG %d work items proc", work_cnt);
112 }
113 vxlan_info.next_work = NULL;
114 }
115
116 /* Staggered work related info is initialized when the first work comes
117 * along
118 */
119 static void pim_vxlan_init_work(void)
120 {
121 if (vxlan_info.flags & PIM_VXLANF_WORK_INITED)
122 return;
123
124 vxlan_info.max_work_cnt = PIM_VXLAN_WORK_MAX;
125 vxlan_info.flags |= PIM_VXLANF_WORK_INITED;
126 vxlan_info.work_list = list_new();
127 pim_vxlan_work_timer_setup(true/* start */);
128 }
129
130 static void pim_vxlan_add_work(struct pim_vxlan_sg *vxlan_sg)
131 {
132 if (vxlan_sg->flags & PIM_VXLAN_SGF_DEL_IN_PROG) {
133 if (PIM_DEBUG_VXLAN)
134 zlog_debug("vxlan SG %s skip work list; del-in-prog",
135 vxlan_sg->sg_str);
136 return;
137 }
138
139 pim_vxlan_init_work();
140
141 /* already a part of the work list */
142 if (vxlan_sg->work_node)
143 return;
144
145 if (PIM_DEBUG_VXLAN)
146 zlog_debug("vxlan SG %s work list add",
147 vxlan_sg->sg_str);
148 vxlan_sg->work_node = listnode_add(vxlan_info.work_list, vxlan_sg);
149 /* XXX: adjust max_work_cnt if needed */
150 }
151
152 static void pim_vxlan_del_work(struct pim_vxlan_sg *vxlan_sg)
153 {
154 if (!vxlan_sg->work_node)
155 return;
156
157 if (PIM_DEBUG_VXLAN)
158 zlog_debug("vxlan SG %s work list del",
159 vxlan_sg->sg_str);
160
161 if (vxlan_sg->work_node == vxlan_info.next_work)
162 vxlan_info.next_work = vxlan_sg->work_node->next;
163
164 list_delete_node(vxlan_info.work_list, vxlan_sg->work_node);
165 vxlan_sg->work_node = NULL;
166 }
167
168 void pim_vxlan_update_sg_reg_state(struct pim_instance *pim,
169 struct pim_upstream *up, bool reg_join)
170 {
171 struct pim_vxlan_sg *vxlan_sg;
172
173 vxlan_sg = pim_vxlan_sg_find(pim, &up->sg);
174 if (!vxlan_sg)
175 return;
176
177 /* add the vxlan sg entry to a work list for periodic reg joins.
178 * the entry will stay in the list as long as the register state is
179 * PIM_REG_JOIN
180 */
181 if (reg_join)
182 pim_vxlan_add_work(vxlan_sg);
183 else
184 pim_vxlan_del_work(vxlan_sg);
185 }
186
187 static void pim_vxlan_work_timer_cb(struct thread *t)
188 {
189 pim_vxlan_do_reg_work();
190 pim_vxlan_work_timer_setup(true /* start */);
191 }
192
193 /* global 1second timer used for periodic processing */
194 static void pim_vxlan_work_timer_setup(bool start)
195 {
196 THREAD_OFF(vxlan_info.work_timer);
197 if (start)
198 thread_add_timer(router->master, pim_vxlan_work_timer_cb, NULL,
199 PIM_VXLAN_WORK_TIME, &vxlan_info.work_timer);
200 }
201
202 /**************************** vxlan origination mroutes ***********************
203 * For every (local-vtep-ip, bum-mcast-grp) registered by evpn an origination
204 * mroute is setup by pimd. The purpose of this mroute is to forward vxlan
205 * encapsulated BUM (broadcast, unknown-unicast and unknown-multicast packets
206 * over the underlay.)
207 *
208 * Sample mroute (single VTEP):
209 * (27.0.0.7, 239.1.1.100) Iif: lo Oifs: uplink-1
210 *
211 * Sample mroute (anycast VTEP):
212 * (36.0.0.9, 239.1.1.100) Iif: peerlink-3.4094\
213 * Oifs: peerlink-3.4094 uplink-1
214 ***************************************************************************/
215 static void pim_vxlan_orig_mr_up_del(struct pim_vxlan_sg *vxlan_sg)
216 {
217 struct pim_upstream *up = vxlan_sg->up;
218
219 if (!up)
220 return;
221
222 if (PIM_DEBUG_VXLAN)
223 zlog_debug("vxlan SG %s orig mroute-up del",
224 vxlan_sg->sg_str);
225
226 vxlan_sg->up = NULL;
227
228 if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) {
229 /* clear out all the vxlan properties */
230 up->flags &= ~(PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG |
231 PIM_UPSTREAM_FLAG_MASK_STATIC_IIF |
232 PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY |
233 PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG |
234 PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA |
235 PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL);
236
237 /* We bring things to a grinding halt by force expirying
238 * the kat. Doing this will also remove the reference we
239 * created as a "vxlan" source and delete the upstream entry
240 * if there are no other references.
241 */
242 if (PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) {
243 THREAD_OFF(up->t_ka_timer);
244 up = pim_upstream_keep_alive_timer_proc(up);
245 } else {
246 /* this is really unexpected as we force vxlan
247 * origination mroutes active sources but just in
248 * case
249 */
250 up = pim_upstream_del(vxlan_sg->pim, up, __func__);
251 }
252 /* if there are other references register the source
253 * for nht
254 */
255 if (up) {
256 enum pim_rpf_result r;
257
258 r = pim_rpf_update(vxlan_sg->pim, up, NULL, __func__);
259 if (r == PIM_RPF_FAILURE) {
260 if (PIM_DEBUG_VXLAN)
261 zlog_debug(
262 "vxlan SG %s rpf_update failure",
263 vxlan_sg->sg_str);
264 }
265 }
266 }
267 }
268
269 static void pim_vxlan_orig_mr_up_iif_update(struct pim_vxlan_sg *vxlan_sg)
270 {
271 /* update MFC with the new IIF */
272 pim_upstream_fill_static_iif(vxlan_sg->up, vxlan_sg->iif);
273 pim_upstream_mroute_iif_update(vxlan_sg->up->channel_oil, __func__);
274
275 if (PIM_DEBUG_VXLAN)
276 zlog_debug("vxlan SG %s orig mroute-up updated with iif %s",
277 vxlan_sg->sg_str,
278 vxlan_sg->iif?vxlan_sg->iif->name:"-");
279
280 }
281
282 /* For every VxLAN BUM multicast group we setup a SG-up that has the following
283 * "forced properties" -
284 * 1. Directly connected on a DR interface i.e. we must act as an FHR
285 * 2. We prime the pump i.e. no multicast data is needed to register this
286 * source with the FHR. To do that we send periodic null registers if
287 * the SG entry is in a register-join state. We also prevent expiry of
288 * KAT.
289 * 3. As this SG is setup without data there is no need to register encapsulate
290 * data traffic. This encapsulation is explicitly skipped for the following
291 * reasons -
292 * a) Many levels of encapsulation are needed creating MTU disc challenges.
293 * Overlay BUM is encapsulated in a vxlan/UDP/IP header and then
294 * encapsulated again in a pim-register header.
295 * b) On a vxlan-aa setup both switches rx a copy of each BUM packet. if
296 * they both reg encapsulated traffic the RP will accept the duplicates
297 * as there are no RPF checks for this encapsulated data.
298 * a), b) can be workarounded if needed, but there is really no need because
299 * of (2) i.e. the pump is primed without data.
300 */
301 static void pim_vxlan_orig_mr_up_add(struct pim_vxlan_sg *vxlan_sg)
302 {
303 struct pim_upstream *up;
304 struct pim_interface *term_ifp;
305 int flags = 0;
306 struct prefix nht_p;
307 struct pim_instance *pim = vxlan_sg->pim;
308
309 if (vxlan_sg->up) {
310 /* nothing to do */
311 return;
312 }
313
314 if (PIM_DEBUG_VXLAN)
315 zlog_debug("vxlan SG %s orig mroute-up add with iif %s",
316 vxlan_sg->sg_str,
317 vxlan_sg->iif?vxlan_sg->iif->name:"-");
318
319 PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_ORIG(flags);
320 /* pin the IIF to lo or peerlink-subinterface and disable NHT */
321 PIM_UPSTREAM_FLAG_SET_STATIC_IIF(flags);
322 /* Fake traffic by setting SRC_STREAM and starting KAT */
323 /* We intentionally skip updating ref count for SRC_STREAM/FHR.
324 * Setting SRC_VXLAN should have already created a reference
325 * preventing the entry from being deleted
326 */
327 PIM_UPSTREAM_FLAG_SET_FHR(flags);
328 PIM_UPSTREAM_FLAG_SET_SRC_STREAM(flags);
329 /* Force pimreg even if non-DR. This is needed on a MLAG setup for
330 * VxLAN AA
331 */
332 PIM_UPSTREAM_FLAG_SET_FORCE_PIMREG(flags);
333 /* prevent KAT expiry. we want the MDT setup even if there is no BUM
334 * traffic
335 */
336 PIM_UPSTREAM_FLAG_SET_DISABLE_KAT_EXPIRY(flags);
337 /* SPT for vxlan BUM groups is primed and maintained via NULL
338 * registers so there is no need to reg-encapsulate
339 * vxlan-encapsulated overlay data traffic
340 */
341 PIM_UPSTREAM_FLAG_SET_NO_PIMREG_DATA(flags);
342 /* On a MLAG setup we force a copy to the MLAG peer while also
343 * accepting traffic from the peer. To do this we set peerlink-rif as
344 * the IIF and also add it to the OIL
345 */
346 PIM_UPSTREAM_FLAG_SET_ALLOW_IIF_IN_OIL(flags);
347
348 /* XXX: todo: defer pim_upstream add if pim is not enabled on the iif */
349 up = pim_upstream_find(vxlan_sg->pim, &vxlan_sg->sg);
350 if (up) {
351 /* if the iif is set to something other than the vxlan_sg->iif
352 * we must dereg the old nexthop and force to new "static"
353 * iif
354 */
355 if (!PIM_UPSTREAM_FLAG_TEST_STATIC_IIF(up->flags)) {
356 pim_addr_to_prefix(&nht_p, up->upstream_addr);
357 pim_delete_tracked_nexthop(vxlan_sg->pim, &nht_p, up,
358 NULL);
359 }
360 /* We are acting FHR; clear out use_rpt setting if any */
361 pim_upstream_update_use_rpt(up, false /*update_mroute*/);
362 pim_upstream_ref(up, flags, __func__);
363 vxlan_sg->up = up;
364 term_ifp = pim_vxlan_get_term_ifp(pim);
365 /* mute termination device on origination mroutes */
366 if (term_ifp)
367 pim_channel_update_oif_mute(up->channel_oil,
368 term_ifp);
369 pim_vxlan_orig_mr_up_iif_update(vxlan_sg);
370 /* mute pimreg on origination mroutes */
371 if (pim->regiface)
372 pim_channel_update_oif_mute(up->channel_oil,
373 pim->regiface->info);
374 } else {
375 up = pim_upstream_add(vxlan_sg->pim, &vxlan_sg->sg,
376 vxlan_sg->iif, flags, __func__, NULL);
377 vxlan_sg->up = up;
378 }
379
380 if (!up) {
381 if (PIM_DEBUG_VXLAN)
382 zlog_debug("vxlan SG %s orig mroute-up add failed",
383 vxlan_sg->sg_str);
384 return;
385 }
386
387 pim_upstream_keep_alive_timer_start(up, vxlan_sg->pim->keep_alive_time);
388
389 /* register the source with the RP */
390 switch (up->reg_state) {
391
392 case PIM_REG_NOINFO:
393 pim_register_join(up);
394 pim_null_register_send(up);
395 break;
396
397 case PIM_REG_JOIN:
398 /* if the pim upstream entry is already in reg-join state
399 * send null_register right away and add to the register
400 * worklist
401 */
402 pim_null_register_send(up);
403 pim_vxlan_update_sg_reg_state(pim, up, true);
404 break;
405
406 case PIM_REG_JOIN_PENDING:
407 case PIM_REG_PRUNE:
408 break;
409 }
410
411 /* update the inherited OIL */
412 pim_upstream_inherited_olist(vxlan_sg->pim, up);
413 if (!up->channel_oil->installed)
414 pim_upstream_mroute_add(up->channel_oil, __func__);
415 }
416
417 static void pim_vxlan_orig_mr_oif_add(struct pim_vxlan_sg *vxlan_sg)
418 {
419 if (!vxlan_sg->up || !vxlan_sg->orig_oif)
420 return;
421
422 if (PIM_DEBUG_VXLAN)
423 zlog_debug("vxlan SG %s oif %s add",
424 vxlan_sg->sg_str, vxlan_sg->orig_oif->name);
425
426 vxlan_sg->flags |= PIM_VXLAN_SGF_OIF_INSTALLED;
427 pim_channel_add_oif(vxlan_sg->up->channel_oil,
428 vxlan_sg->orig_oif, PIM_OIF_FLAG_PROTO_VXLAN,
429 __func__);
430 }
431
432 static void pim_vxlan_orig_mr_oif_del(struct pim_vxlan_sg *vxlan_sg)
433 {
434 struct interface *orig_oif;
435
436 orig_oif = vxlan_sg->orig_oif;
437 vxlan_sg->orig_oif = NULL;
438
439 if (!(vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED))
440 return;
441
442 if (PIM_DEBUG_VXLAN)
443 zlog_debug("vxlan SG %s oif %s del",
444 vxlan_sg->sg_str, orig_oif->name);
445
446 vxlan_sg->flags &= ~PIM_VXLAN_SGF_OIF_INSTALLED;
447 pim_channel_del_oif(vxlan_sg->up->channel_oil,
448 orig_oif, PIM_OIF_FLAG_PROTO_VXLAN, __func__);
449 }
450
451 static inline struct interface *pim_vxlan_orig_mr_oif_get(
452 struct pim_instance *pim)
453 {
454 return (vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) ?
455 pim->vxlan.peerlink_rif : NULL;
456 }
457
458 /* Single VTEPs: IIF for the vxlan-origination-mroutes is lo or vrf-dev (if
459 * the mroute is in a non-default vrf).
460 * Anycast VTEPs: IIF is the MLAG ISL/peerlink.
461 */
462 static inline struct interface *pim_vxlan_orig_mr_iif_get(
463 struct pim_instance *pim)
464 {
465 return ((vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) &&
466 pim->vxlan.peerlink_rif) ?
467 pim->vxlan.peerlink_rif : pim->vxlan.default_iif;
468 }
469
470 static bool pim_vxlan_orig_mr_add_is_ok(struct pim_vxlan_sg *vxlan_sg)
471 {
472 struct pim_interface *pim_ifp;
473
474 vxlan_sg->iif = pim_vxlan_orig_mr_iif_get(vxlan_sg->pim);
475 if (!vxlan_sg->iif)
476 return false;
477
478 pim_ifp = (struct pim_interface *)vxlan_sg->iif->info;
479 if (!pim_ifp || (pim_ifp->mroute_vif_index < 0))
480 return false;
481
482 return true;
483 }
484
485 static void pim_vxlan_orig_mr_install(struct pim_vxlan_sg *vxlan_sg)
486 {
487 pim_vxlan_orig_mr_up_add(vxlan_sg);
488
489 vxlan_sg->orig_oif = pim_vxlan_orig_mr_oif_get(vxlan_sg->pim);
490 pim_vxlan_orig_mr_oif_add(vxlan_sg);
491 }
492
493 static void pim_vxlan_orig_mr_add(struct pim_vxlan_sg *vxlan_sg)
494 {
495 if (!pim_vxlan_orig_mr_add_is_ok(vxlan_sg))
496 return;
497
498 if (PIM_DEBUG_VXLAN)
499 zlog_debug("vxlan SG %s orig-mr add", vxlan_sg->sg_str);
500
501 pim_vxlan_orig_mr_install(vxlan_sg);
502 }
503
504 static void pim_vxlan_orig_mr_del(struct pim_vxlan_sg *vxlan_sg)
505 {
506 if (PIM_DEBUG_VXLAN)
507 zlog_debug("vxlan SG %s orig-mr del", vxlan_sg->sg_str);
508
509 pim_vxlan_orig_mr_oif_del(vxlan_sg);
510 pim_vxlan_orig_mr_up_del(vxlan_sg);
511 }
512
513 static void pim_vxlan_orig_mr_iif_update(struct hash_bucket *bucket, void *arg)
514 {
515 struct interface *ifp;
516 struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)bucket->data;
517 struct interface *old_iif = vxlan_sg->iif;
518
519 if (!pim_vxlan_is_orig_mroute(vxlan_sg))
520 return;
521
522 ifp = pim_vxlan_orig_mr_iif_get(vxlan_sg->pim);
523 if (PIM_DEBUG_VXLAN)
524 zlog_debug("vxlan SG %s iif changed from %s to %s",
525 vxlan_sg->sg_str,
526 old_iif ? old_iif->name : "-",
527 ifp ? ifp->name : "-");
528
529 if (pim_vxlan_orig_mr_add_is_ok(vxlan_sg)) {
530 if (vxlan_sg->up) {
531 /* upstream exists but iif changed */
532 pim_vxlan_orig_mr_up_iif_update(vxlan_sg);
533 } else {
534 /* install mroute */
535 pim_vxlan_orig_mr_install(vxlan_sg);
536 }
537 } else {
538 pim_vxlan_orig_mr_del(vxlan_sg);
539 }
540 }
541
542 /**************************** vxlan termination mroutes ***********************
543 * For every bum-mcast-grp registered by evpn a *G termination
544 * mroute is setup by pimd. The purpose of this mroute is to pull down vxlan
545 * packets with the bum-mcast-grp dip from the underlay and terminate the
546 * tunnel. This is done by including the vxlan termination device (ipmr-lo) in
547 * its OIL. The vxlan de-capsulated packets are subject to subsequent overlay
548 * bridging.
549 *
550 * Sample mroute:
551 * (0.0.0.0, 239.1.1.100) Iif: uplink-1 Oifs: ipmr-lo, uplink-1
552 *****************************************************************************/
553 struct pim_interface *pim_vxlan_get_term_ifp(struct pim_instance *pim)
554 {
555 return pim->vxlan.term_if ?
556 (struct pim_interface *)pim->vxlan.term_if->info : NULL;
557 }
558
559 static void pim_vxlan_term_mr_oif_add(struct pim_vxlan_sg *vxlan_sg)
560 {
561 if (vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED)
562 return;
563
564 if (PIM_DEBUG_VXLAN)
565 zlog_debug("vxlan SG %s term-oif %s add",
566 vxlan_sg->sg_str, vxlan_sg->term_oif->name);
567
568 if (pim_ifchannel_local_membership_add(vxlan_sg->term_oif,
569 &vxlan_sg->sg, true /*is_vxlan */)) {
570 vxlan_sg->flags |= PIM_VXLAN_SGF_OIF_INSTALLED;
571 /* update the inherited OIL */
572 /* XXX - I don't see the inherited OIL updated when a local
573 * member is added. And that probably needs to be fixed. Till
574 * that happens we do a force update on the inherited OIL
575 * here.
576 */
577 pim_upstream_inherited_olist(vxlan_sg->pim, vxlan_sg->up);
578 } else {
579 zlog_warn("vxlan SG %s term-oif %s add failed",
580 vxlan_sg->sg_str, vxlan_sg->term_oif->name);
581 }
582 }
583
584 static void pim_vxlan_term_mr_oif_del(struct pim_vxlan_sg *vxlan_sg)
585 {
586 if (!(vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED))
587 return;
588
589 if (PIM_DEBUG_VXLAN)
590 zlog_debug("vxlan SG %s oif %s del",
591 vxlan_sg->sg_str, vxlan_sg->term_oif->name);
592
593 vxlan_sg->flags &= ~PIM_VXLAN_SGF_OIF_INSTALLED;
594 pim_ifchannel_local_membership_del(vxlan_sg->term_oif, &vxlan_sg->sg);
595 /* update the inherited OIL */
596 /* XXX - I don't see the inherited OIL updated when a local member
597 * is deleted. And that probably needs to be fixed. Till that happens
598 * we do a force update on the inherited OIL here.
599 */
600 pim_upstream_inherited_olist(vxlan_sg->pim, vxlan_sg->up);
601 }
602
603 static void pim_vxlan_update_sg_entry_mlag(struct pim_instance *pim,
604 struct pim_upstream *up, bool inherit)
605 {
606 bool is_df = true;
607
608 if (inherit && up->parent &&
609 PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(up->parent->flags) &&
610 PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->parent->flags))
611 is_df = false;
612
613 pim_mlag_up_df_role_update(pim, up, is_df, "inherit_xg_df");
614 }
615
616 /* We run MLAG DF election only on mroutes that have the termination
617 * device ipmr-lo in the immediate OIL. This is only (*, G) entries at the
618 * moment. For (S, G) entries that (with ipmr-lo in the inherited OIL) we
619 * inherit the DF role from the (*, G) entry.
620 */
621 void pim_vxlan_inherit_mlag_flags(struct pim_instance *pim,
622 struct pim_upstream *up, bool inherit)
623 {
624 struct listnode *listnode;
625 struct pim_upstream *child;
626
627 for (ALL_LIST_ELEMENTS_RO(up->sources, listnode,
628 child)) {
629 pim_vxlan_update_sg_entry_mlag(pim,
630 child, true /* inherit */);
631 }
632 }
633
634 static void pim_vxlan_term_mr_up_add(struct pim_vxlan_sg *vxlan_sg)
635 {
636 struct pim_upstream *up;
637 int flags = 0;
638
639 if (vxlan_sg->up) {
640 /* nothing to do */
641 return;
642 }
643
644 if (PIM_DEBUG_VXLAN)
645 zlog_debug("vxlan SG %s term mroute-up add",
646 vxlan_sg->sg_str);
647
648 PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_TERM(flags);
649 /* enable MLAG designated-forwarder election on termination mroutes */
650 PIM_UPSTREAM_FLAG_SET_MLAG_VXLAN(flags);
651
652 up = pim_upstream_add(vxlan_sg->pim, &vxlan_sg->sg, NULL /* iif */,
653 flags, __func__, NULL);
654 vxlan_sg->up = up;
655
656 if (!up) {
657 zlog_warn("vxlan SG %s term mroute-up add failed",
658 vxlan_sg->sg_str);
659 return;
660 }
661
662 /* update existing SG entries with the parent's MLAG flag */
663 pim_vxlan_inherit_mlag_flags(vxlan_sg->pim, up, true /*enable*/);
664 }
665
666 static void pim_vxlan_term_mr_up_del(struct pim_vxlan_sg *vxlan_sg)
667 {
668 struct pim_upstream *up = vxlan_sg->up;
669
670 if (!up)
671 return;
672
673 if (PIM_DEBUG_VXLAN)
674 zlog_debug("vxlan SG %s term mroute-up del",
675 vxlan_sg->sg_str);
676 vxlan_sg->up = NULL;
677 if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) {
678 /* update SG entries that are inheriting from this XG entry */
679 pim_vxlan_inherit_mlag_flags(vxlan_sg->pim, up,
680 false /*enable*/);
681 /* clear out all the vxlan related flags */
682 up->flags &= ~(PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM |
683 PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN);
684 pim_mlag_up_local_del(vxlan_sg->pim, up);
685 pim_upstream_del(vxlan_sg->pim, up, __func__);
686 }
687 }
688
689 static void pim_vxlan_term_mr_add(struct pim_vxlan_sg *vxlan_sg)
690 {
691 if (PIM_DEBUG_VXLAN)
692 zlog_debug("vxlan SG %s term mroute add", vxlan_sg->sg_str);
693
694 vxlan_sg->term_oif = vxlan_sg->pim->vxlan.term_if;
695 if (!vxlan_sg->term_oif)
696 /* defer termination mroute till we have a termination device */
697 return;
698
699 pim_vxlan_term_mr_up_add(vxlan_sg);
700 /* set up local membership for the term-oif */
701 pim_vxlan_term_mr_oif_add(vxlan_sg);
702 }
703
704 static void pim_vxlan_term_mr_del(struct pim_vxlan_sg *vxlan_sg)
705 {
706 if (PIM_DEBUG_VXLAN)
707 zlog_debug("vxlan SG %s term mroute del", vxlan_sg->sg_str);
708
709 /* remove local membership associated with the term oif */
710 pim_vxlan_term_mr_oif_del(vxlan_sg);
711 /* remove references to the upstream entry */
712 pim_vxlan_term_mr_up_del(vxlan_sg);
713 }
714
715 /************************** vxlan SG cache management ************************/
716 static unsigned int pim_vxlan_sg_hash_key_make(const void *p)
717 {
718 const struct pim_vxlan_sg *vxlan_sg = p;
719
720 return pim_sgaddr_hash(vxlan_sg->sg, 0);
721 }
722
723 static bool pim_vxlan_sg_hash_eq(const void *p1, const void *p2)
724 {
725 const struct pim_vxlan_sg *sg1 = p1;
726 const struct pim_vxlan_sg *sg2 = p2;
727
728 return !pim_sgaddr_cmp(sg1->sg, sg2->sg);
729 }
730
731 static struct pim_vxlan_sg *pim_vxlan_sg_new(struct pim_instance *pim,
732 pim_sgaddr *sg)
733 {
734 struct pim_vxlan_sg *vxlan_sg;
735
736 vxlan_sg = XCALLOC(MTYPE_PIM_VXLAN_SG, sizeof(*vxlan_sg));
737
738 vxlan_sg->pim = pim;
739 vxlan_sg->sg = *sg;
740 snprintfrr(vxlan_sg->sg_str, sizeof(vxlan_sg->sg_str), "%pSG", sg);
741
742 if (PIM_DEBUG_VXLAN)
743 zlog_debug("vxlan SG %s alloc", vxlan_sg->sg_str);
744
745 vxlan_sg = hash_get(pim->vxlan.sg_hash, vxlan_sg, hash_alloc_intern);
746
747 /* we register with the MLAG daemon in the first VxLAN SG and never
748 * de-register during that life of the pimd
749 */
750 if (pim->vxlan.sg_hash->count == 1) {
751 vxlan_mlag.flags |= PIM_VXLAN_MLAGF_DO_REG;
752 pim_mlag_register();
753 }
754
755 return vxlan_sg;
756 }
757
758 struct pim_vxlan_sg *pim_vxlan_sg_find(struct pim_instance *pim, pim_sgaddr *sg)
759 {
760 struct pim_vxlan_sg lookup;
761
762 lookup.sg = *sg;
763 return hash_lookup(pim->vxlan.sg_hash, &lookup);
764 }
765
766 struct pim_vxlan_sg *pim_vxlan_sg_add(struct pim_instance *pim, pim_sgaddr *sg)
767 {
768 struct pim_vxlan_sg *vxlan_sg;
769
770 vxlan_sg = pim_vxlan_sg_find(pim, sg);
771 if (vxlan_sg)
772 return vxlan_sg;
773
774 vxlan_sg = pim_vxlan_sg_new(pim, sg);
775
776 if (pim_vxlan_is_orig_mroute(vxlan_sg))
777 pim_vxlan_orig_mr_add(vxlan_sg);
778 else
779 pim_vxlan_term_mr_add(vxlan_sg);
780
781 return vxlan_sg;
782 }
783
784 static void pim_vxlan_sg_del_item(struct pim_vxlan_sg *vxlan_sg)
785 {
786 vxlan_sg->flags |= PIM_VXLAN_SGF_DEL_IN_PROG;
787
788 pim_vxlan_del_work(vxlan_sg);
789
790 if (pim_vxlan_is_orig_mroute(vxlan_sg))
791 pim_vxlan_orig_mr_del(vxlan_sg);
792 else
793 pim_vxlan_term_mr_del(vxlan_sg);
794
795 if (PIM_DEBUG_VXLAN)
796 zlog_debug("vxlan SG %s free", vxlan_sg->sg_str);
797
798 XFREE(MTYPE_PIM_VXLAN_SG, vxlan_sg);
799 }
800
801 void pim_vxlan_sg_del(struct pim_instance *pim, pim_sgaddr *sg)
802 {
803 struct pim_vxlan_sg *vxlan_sg;
804
805 vxlan_sg = pim_vxlan_sg_find(pim, sg);
806 if (!vxlan_sg)
807 return;
808
809 hash_release(pim->vxlan.sg_hash, vxlan_sg);
810 pim_vxlan_sg_del_item(vxlan_sg);
811 }
812
813 /******************************* MLAG handling *******************************/
814 bool pim_vxlan_do_mlag_reg(void)
815 {
816 return (vxlan_mlag.flags & PIM_VXLAN_MLAGF_DO_REG);
817 }
818
819 /* The peerlink sub-interface is added as an OIF to the origination-mroute.
820 * This is done to send a copy of the multicast-vxlan encapsulated traffic
821 * to the MLAG peer which may mroute it over the underlay if there are any
822 * interested receivers.
823 */
824 static void pim_vxlan_sg_peerlink_oif_update(struct hash_bucket *bucket,
825 void *arg)
826 {
827 struct interface *new_oif = (struct interface *)arg;
828 struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)bucket->data;
829
830 if (!pim_vxlan_is_orig_mroute(vxlan_sg))
831 return;
832
833 if (vxlan_sg->orig_oif == new_oif)
834 return;
835
836 pim_vxlan_orig_mr_oif_del(vxlan_sg);
837
838 vxlan_sg->orig_oif = new_oif;
839 pim_vxlan_orig_mr_oif_add(vxlan_sg);
840 }
841
842 /* In the case of anycast VTEPs the VTEP-PIP must be used as the
843 * register source.
844 */
845 bool pim_vxlan_get_register_src(struct pim_instance *pim,
846 struct pim_upstream *up, struct in_addr *src_p)
847 {
848 if (!(vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED))
849 return true;
850
851 /* if address is not available suppress the pim-register */
852 if (vxlan_mlag.reg_addr.s_addr == INADDR_ANY)
853 return false;
854
855 *src_p = vxlan_mlag.reg_addr;
856 return true;
857 }
858
859 void pim_vxlan_mlag_update(bool enable, bool peer_state, uint32_t role,
860 struct interface *peerlink_rif,
861 struct in_addr *reg_addr)
862 {
863 struct pim_instance *pim;
864 char addr_buf[INET_ADDRSTRLEN];
865 struct pim_interface *pim_ifp = NULL;
866
867 if (PIM_DEBUG_VXLAN) {
868 inet_ntop(AF_INET, reg_addr,
869 addr_buf, INET_ADDRSTRLEN);
870 zlog_debug("vxlan MLAG update %s state %s role %d rif %s addr %s",
871 enable ? "enable" : "disable",
872 peer_state ? "up" : "down",
873 role,
874 peerlink_rif ? peerlink_rif->name : "-",
875 addr_buf);
876 }
877
878 /* XXX: for now vxlan termination is only possible in the default VRF
879 * when that changes this will need to change to iterate all VRFs
880 */
881 pim = pim_get_pim_instance(VRF_DEFAULT);
882
883 if (enable)
884 vxlan_mlag.flags |= PIM_VXLAN_MLAGF_ENABLED;
885 else
886 vxlan_mlag.flags &= ~PIM_VXLAN_MLAGF_ENABLED;
887
888 if (vxlan_mlag.peerlink_rif != peerlink_rif)
889 vxlan_mlag.peerlink_rif = peerlink_rif;
890
891 vxlan_mlag.reg_addr = *reg_addr;
892 vxlan_mlag.peer_state = peer_state;
893 vxlan_mlag.role = role;
894
895 /* process changes */
896 if (vxlan_mlag.peerlink_rif)
897 pim_ifp = (struct pim_interface *)vxlan_mlag.peerlink_rif->info;
898 if ((vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) &&
899 pim_ifp && (pim_ifp->mroute_vif_index > 0))
900 pim_vxlan_set_peerlink_rif(pim, peerlink_rif);
901 else
902 pim_vxlan_set_peerlink_rif(pim, NULL);
903 }
904
905 /****************************** misc callbacks *******************************/
906 static void pim_vxlan_set_default_iif(struct pim_instance *pim,
907 struct interface *ifp)
908 {
909 struct interface *old_iif;
910
911 if (pim->vxlan.default_iif == ifp)
912 return;
913
914 old_iif = pim->vxlan.default_iif;
915 if (PIM_DEBUG_VXLAN)
916 zlog_debug("%s: vxlan default iif changed from %s to %s",
917 __func__, old_iif ? old_iif->name : "-",
918 ifp ? ifp->name : "-");
919
920 old_iif = pim_vxlan_orig_mr_iif_get(pim);
921 pim->vxlan.default_iif = ifp;
922 ifp = pim_vxlan_orig_mr_iif_get(pim);
923 if (old_iif == ifp)
924 return;
925
926 if (PIM_DEBUG_VXLAN)
927 zlog_debug("%s: vxlan orig iif changed from %s to %s", __func__,
928 old_iif ? old_iif->name : "-",
929 ifp ? ifp->name : "-");
930
931 /* add/del upstream entries for the existing vxlan SG when the
932 * interface becomes available
933 */
934 if (pim->vxlan.sg_hash)
935 hash_iterate(pim->vxlan.sg_hash,
936 pim_vxlan_orig_mr_iif_update, NULL);
937 }
938
939 static void pim_vxlan_up_cost_update(struct pim_instance *pim,
940 struct pim_upstream *up,
941 struct interface *old_peerlink_rif)
942 {
943 if (!PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(up->flags))
944 return;
945
946 if (up->rpf.source_nexthop.interface &&
947 ((up->rpf.source_nexthop.interface ==
948 pim->vxlan.peerlink_rif) ||
949 (up->rpf.source_nexthop.interface ==
950 old_peerlink_rif))) {
951 if (PIM_DEBUG_VXLAN)
952 zlog_debug("RPF cost adjust for %s on peerlink-rif (old: %s, new: %s) change",
953 up->sg_str,
954 old_peerlink_rif ?
955 old_peerlink_rif->name : "-",
956 pim->vxlan.peerlink_rif ?
957 pim->vxlan.peerlink_rif->name : "-");
958 pim_mlag_up_local_add(pim, up);
959 }
960 }
961
962 static void pim_vxlan_term_mr_cost_update(struct hash_bucket *bucket, void *arg)
963 {
964 struct interface *old_peerlink_rif = (struct interface *)arg;
965 struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)bucket->data;
966 struct pim_upstream *up;
967 struct listnode *listnode;
968 struct pim_upstream *child;
969
970 if (pim_vxlan_is_orig_mroute(vxlan_sg))
971 return;
972
973 /* Lookup all XG and SG entries with RPF-interface peerlink_rif */
974 up = vxlan_sg->up;
975 if (!up)
976 return;
977
978 pim_vxlan_up_cost_update(vxlan_sg->pim, up,
979 old_peerlink_rif);
980
981 for (ALL_LIST_ELEMENTS_RO(up->sources, listnode,
982 child))
983 pim_vxlan_up_cost_update(vxlan_sg->pim, child,
984 old_peerlink_rif);
985 }
986
987 static void pim_vxlan_sg_peerlink_rif_update(struct hash_bucket *bucket,
988 void *arg)
989 {
990 pim_vxlan_orig_mr_iif_update(bucket, NULL);
991 pim_vxlan_term_mr_cost_update(bucket, arg);
992 }
993
994 static void pim_vxlan_set_peerlink_rif(struct pim_instance *pim,
995 struct interface *ifp)
996 {
997 struct interface *old_iif;
998 struct interface *new_iif;
999 struct interface *old_oif;
1000 struct interface *new_oif;
1001
1002 if (pim->vxlan.peerlink_rif == ifp)
1003 return;
1004
1005 old_iif = pim->vxlan.peerlink_rif;
1006 if (PIM_DEBUG_VXLAN)
1007 zlog_debug("%s: vxlan peerlink_rif changed from %s to %s",
1008 __func__, old_iif ? old_iif->name : "-",
1009 ifp ? ifp->name : "-");
1010
1011 old_iif = pim_vxlan_orig_mr_iif_get(pim);
1012 old_oif = pim_vxlan_orig_mr_oif_get(pim);
1013 pim->vxlan.peerlink_rif = ifp;
1014
1015 new_iif = pim_vxlan_orig_mr_iif_get(pim);
1016 if (old_iif != new_iif) {
1017 if (PIM_DEBUG_VXLAN)
1018 zlog_debug("%s: vxlan orig iif changed from %s to %s",
1019 __func__, old_iif ? old_iif->name : "-",
1020 new_iif ? new_iif->name : "-");
1021
1022 /* add/del upstream entries for the existing vxlan SG when the
1023 * interface becomes available
1024 */
1025 if (pim->vxlan.sg_hash)
1026 hash_iterate(pim->vxlan.sg_hash,
1027 pim_vxlan_sg_peerlink_rif_update,
1028 old_iif);
1029 }
1030
1031 new_oif = pim_vxlan_orig_mr_oif_get(pim);
1032 if (old_oif != new_oif) {
1033 if (PIM_DEBUG_VXLAN)
1034 zlog_debug("%s: vxlan orig oif changed from %s to %s",
1035 __func__, old_oif ? old_oif->name : "-",
1036 new_oif ? new_oif->name : "-");
1037 if (pim->vxlan.sg_hash)
1038 hash_iterate(pim->vxlan.sg_hash,
1039 pim_vxlan_sg_peerlink_oif_update,
1040 new_oif);
1041 }
1042 }
1043
1044 static void pim_vxlan_term_mr_oif_update(struct hash_bucket *bucket, void *arg)
1045 {
1046 struct interface *ifp = (struct interface *)arg;
1047 struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)bucket->data;
1048
1049 if (pim_vxlan_is_orig_mroute(vxlan_sg))
1050 return;
1051
1052 if (vxlan_sg->term_oif == ifp)
1053 return;
1054
1055 if (PIM_DEBUG_VXLAN)
1056 zlog_debug("vxlan SG %s term oif changed from %s to %s",
1057 vxlan_sg->sg_str,
1058 vxlan_sg->term_oif ? vxlan_sg->term_oif->name : "-",
1059 ifp ? ifp->name : "-");
1060
1061 pim_vxlan_term_mr_del(vxlan_sg);
1062 vxlan_sg->term_oif = ifp;
1063 pim_vxlan_term_mr_add(vxlan_sg);
1064 }
1065
1066 static void pim_vxlan_term_oif_update(struct pim_instance *pim,
1067 struct interface *ifp)
1068 {
1069 if (pim->vxlan.term_if == ifp)
1070 return;
1071
1072 if (PIM_DEBUG_VXLAN)
1073 zlog_debug("vxlan term oif changed from %s to %s",
1074 pim->vxlan.term_if ? pim->vxlan.term_if->name : "-",
1075 ifp ? ifp->name : "-");
1076
1077 pim->vxlan.term_if = ifp;
1078 if (pim->vxlan.sg_hash)
1079 hash_iterate(pim->vxlan.sg_hash,
1080 pim_vxlan_term_mr_oif_update, ifp);
1081 }
1082
1083 void pim_vxlan_add_vif(struct interface *ifp)
1084 {
1085 struct pim_interface *pim_ifp = ifp->info;
1086 struct pim_instance *pim = pim_ifp->pim;
1087
1088 if (pim->vrf->vrf_id != VRF_DEFAULT)
1089 return;
1090
1091 if (if_is_loopback(ifp))
1092 pim_vxlan_set_default_iif(pim, ifp);
1093
1094 if (vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED &&
1095 (ifp == vxlan_mlag.peerlink_rif))
1096 pim_vxlan_set_peerlink_rif(pim, ifp);
1097
1098 if (pim->vxlan.term_if_cfg == ifp)
1099 pim_vxlan_term_oif_update(pim, ifp);
1100 }
1101
1102 void pim_vxlan_del_vif(struct interface *ifp)
1103 {
1104 struct pim_interface *pim_ifp = ifp->info;
1105 struct pim_instance *pim = pim_ifp->pim;
1106
1107 if (pim->vrf->vrf_id != VRF_DEFAULT)
1108 return;
1109
1110 if (pim->vxlan.default_iif == ifp)
1111 pim_vxlan_set_default_iif(pim, NULL);
1112
1113 if (pim->vxlan.peerlink_rif == ifp)
1114 pim_vxlan_set_peerlink_rif(pim, NULL);
1115
1116 if (pim->vxlan.term_if == ifp)
1117 pim_vxlan_term_oif_update(pim, NULL);
1118 }
1119
1120 /* enable pim implicitly on the termination device add */
1121 void pim_vxlan_add_term_dev(struct pim_instance *pim,
1122 struct interface *ifp)
1123 {
1124 struct pim_interface *pim_ifp;
1125
1126 if (pim->vxlan.term_if_cfg == ifp)
1127 return;
1128
1129 if (PIM_DEBUG_VXLAN)
1130 zlog_debug("vxlan term oif cfg changed from %s to %s",
1131 pim->vxlan.term_if_cfg ?
1132 pim->vxlan.term_if_cfg->name : "-",
1133 ifp->name);
1134
1135 pim->vxlan.term_if_cfg = ifp;
1136
1137 /* enable pim on the term ifp */
1138 pim_ifp = (struct pim_interface *)ifp->info;
1139 if (pim_ifp) {
1140 PIM_IF_DO_PIM(pim_ifp->options);
1141 /* ifp is already oper up; activate it as a term dev */
1142 if (pim_ifp->mroute_vif_index >= 0)
1143 pim_vxlan_term_oif_update(pim, ifp);
1144 } else {
1145 /* ensure that pimreg exists before using the newly created
1146 * vxlan termination device
1147 */
1148 pim_if_create_pimreg(pim);
1149 (void)pim_if_new(ifp, false /*igmp*/, true /*pim*/,
1150 false /*pimreg*/, true /*vxlan_term*/);
1151 }
1152 }
1153
1154 /* disable pim implicitly, if needed, on the termination device deletion */
1155 void pim_vxlan_del_term_dev(struct pim_instance *pim)
1156 {
1157 struct interface *ifp = pim->vxlan.term_if_cfg;
1158 struct pim_interface *pim_ifp;
1159
1160 if (PIM_DEBUG_VXLAN)
1161 zlog_debug("vxlan term oif cfg changed from %s to -",
1162 ifp->name);
1163
1164 pim->vxlan.term_if_cfg = NULL;
1165
1166 pim_ifp = (struct pim_interface *)ifp->info;
1167 if (pim_ifp) {
1168 PIM_IF_DONT_PIM(pim_ifp->options);
1169 if (!PIM_IF_TEST_IGMP(pim_ifp->options))
1170 pim_if_delete(ifp);
1171 }
1172 }
1173
1174 void pim_vxlan_init(struct pim_instance *pim)
1175 {
1176 char hash_name[64];
1177
1178 snprintf(hash_name, sizeof(hash_name),
1179 "PIM %s vxlan SG hash", pim->vrf->name);
1180 pim->vxlan.sg_hash = hash_create(pim_vxlan_sg_hash_key_make,
1181 pim_vxlan_sg_hash_eq, hash_name);
1182 }
1183
1184 void pim_vxlan_exit(struct pim_instance *pim)
1185 {
1186 if (pim->vxlan.sg_hash) {
1187 hash_clean(pim->vxlan.sg_hash,
1188 (void (*)(void *))pim_vxlan_sg_del_item);
1189 hash_free(pim->vxlan.sg_hash);
1190 pim->vxlan.sg_hash = NULL;
1191 }
1192 }
1193
1194 void pim_vxlan_terminate(void)
1195 {
1196 pim_vxlan_work_timer_setup(false);
1197 }