/src/frr/bgpd/rfapi/rfapi_ap.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * |
4 | | * Copyright 2009-2016, LabN Consulting, L.L.C. |
5 | | * |
6 | | */ |
7 | | |
8 | | #include "lib/zebra.h" |
9 | | #include "lib/prefix.h" |
10 | | #include "lib/agg_table.h" |
11 | | #include "lib/vty.h" |
12 | | #include "lib/memory.h" |
13 | | #include "lib/routemap.h" |
14 | | #include "lib/log.h" |
15 | | #include "lib/linklist.h" |
16 | | #include "lib/command.h" |
17 | | #include "lib/stream.h" |
18 | | |
19 | | #include "bgpd/bgpd.h" |
20 | | #include "bgpd/bgp_ecommunity.h" |
21 | | #include "bgpd/bgp_attr.h" |
22 | | |
23 | | #include "bgpd/rfapi/bgp_rfapi_cfg.h" |
24 | | #include "bgpd/rfapi/rfapi.h" |
25 | | #include "bgpd/rfapi/rfapi_backend.h" |
26 | | |
27 | | #include "bgpd/bgp_route.h" |
28 | | #include "bgpd/bgp_mplsvpn.h" |
29 | | #include "bgpd/bgp_aspath.h" |
30 | | #include "bgpd/bgp_advertise.h" |
31 | | |
32 | | #include "bgpd/rfapi/rfapi_import.h" |
33 | | #include "bgpd/rfapi/rfapi_private.h" |
34 | | #include "bgpd/rfapi/rfapi_monitor.h" |
35 | | #include "bgpd/rfapi/rfapi_vty.h" |
36 | | #include "bgpd/rfapi/vnc_export_bgp.h" |
37 | | #include "bgpd/rfapi/vnc_export_bgp_p.h" |
38 | | #include "bgpd/rfapi/vnc_zebra.h" |
39 | | #include "bgpd/rfapi/vnc_import_bgp.h" |
40 | | #include "bgpd/rfapi/rfapi_rib.h" |
41 | | |
42 | | #include "bgpd/rfapi/rfapi_ap.h" |
43 | | #include "bgpd/rfapi/vnc_debug.h" |
44 | | |
45 | | /* |
46 | | * Per-NVE Advertised prefixes |
47 | | * |
48 | | * We maintain a list of prefixes advertised by each NVE. |
49 | | * There are two indices: by prefix and by lifetime. |
50 | | * |
51 | | * BY-PREFIX skiplist |
52 | | * |
53 | | * key: ptr to struct prefix (when storing, point to prefix that |
54 | | * is part of rfapi_adb). |
55 | | * |
56 | | * value: ptr to struct rfapi_adb |
57 | | * |
58 | | * BY-LIFETIME skiplist |
59 | | * |
60 | | * key: ptr to struct rfapi_adb |
61 | | * value: ptr to struct rfapi_adb |
62 | | * |
63 | | */ |
64 | | |
65 | | /* |
66 | | * Skiplist sort function that sorts first according to lifetime |
67 | | * and then according to adb pointer value. The adb pointer |
68 | | * is used to spread out the sort for adbs with the same lifetime |
69 | | * and thereby make the skip list operations more efficient. |
70 | | */ |
71 | | static int sl_adb_lifetime_cmp(const void *adb1, const void *adb2) |
72 | 0 | { |
73 | 0 | const struct rfapi_adb *a1 = adb1; |
74 | 0 | const struct rfapi_adb *a2 = adb2; |
75 | |
|
76 | 0 | if (a1->lifetime < a2->lifetime) |
77 | 0 | return -1; |
78 | 0 | if (a1->lifetime > a2->lifetime) |
79 | 0 | return 1; |
80 | | |
81 | 0 | if (a1 < a2) |
82 | 0 | return -1; |
83 | 0 | if (a1 > a2) |
84 | 0 | return 1; |
85 | | |
86 | 0 | return 0; |
87 | 0 | } |
88 | | |
89 | | void rfapiApInit(struct rfapi_advertised_prefixes *ap) |
90 | 0 | { |
91 | 0 | ap->ipN_by_prefix = skiplist_new(0, rfapi_rib_key_cmp, NULL); |
92 | 0 | ap->ip0_by_ether = skiplist_new(0, rfapi_rib_key_cmp, NULL); |
93 | 0 | ap->by_lifetime = skiplist_new(0, sl_adb_lifetime_cmp, NULL); |
94 | 0 | } |
95 | | |
96 | | void rfapiApRelease(struct rfapi_advertised_prefixes *ap) |
97 | 0 | { |
98 | 0 | struct rfapi_adb *adb; |
99 | | |
100 | | /* Free ADBs and lifetime items */ |
101 | 0 | while (0 == skiplist_first(ap->by_lifetime, NULL, (void **)&adb)) { |
102 | 0 | rfapiAdbFree(adb); |
103 | 0 | skiplist_delete_first(ap->by_lifetime); |
104 | 0 | } |
105 | |
|
106 | 0 | while (0 == skiplist_delete_first(ap->ipN_by_prefix)) |
107 | 0 | ; |
108 | 0 | while (0 == skiplist_delete_first(ap->ip0_by_ether)) |
109 | 0 | ; |
110 | | |
111 | | /* Free lists */ |
112 | 0 | skiplist_free(ap->ipN_by_prefix); |
113 | 0 | skiplist_free(ap->ip0_by_ether); |
114 | 0 | skiplist_free(ap->by_lifetime); |
115 | |
|
116 | 0 | ap->ipN_by_prefix = NULL; |
117 | 0 | ap->ip0_by_ether = NULL; |
118 | 0 | ap->by_lifetime = NULL; |
119 | 0 | } |
120 | | |
121 | | int rfapiApCount(struct rfapi_descriptor *rfd) |
122 | 0 | { |
123 | 0 | if (!rfd->advertised.by_lifetime) |
124 | 0 | return 0; |
125 | | |
126 | 0 | return skiplist_count(rfd->advertised.by_lifetime); |
127 | 0 | } |
128 | | |
129 | | int rfapiApCountAll(struct bgp *bgp) |
130 | 0 | { |
131 | 0 | struct rfapi *h; |
132 | 0 | struct listnode *node; |
133 | 0 | struct rfapi_descriptor *rfd; |
134 | 0 | int total = 0; |
135 | |
|
136 | 0 | h = bgp->rfapi; |
137 | 0 | if (h) { |
138 | 0 | for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { |
139 | 0 | total += rfapiApCount(rfd); |
140 | 0 | } |
141 | 0 | } |
142 | 0 | return total; |
143 | 0 | } |
144 | | |
145 | | |
146 | | void rfapiApReadvertiseAll(struct bgp *bgp, struct rfapi_descriptor *rfd) |
147 | 0 | { |
148 | 0 | struct rfapi_adb *adb; |
149 | 0 | void *cursor = NULL; |
150 | 0 | int rc; |
151 | |
|
152 | 0 | for (rc = skiplist_next(rfd->advertised.by_lifetime, NULL, |
153 | 0 | (void **)&adb, &cursor); |
154 | 0 | rc == 0; rc = skiplist_next(rfd->advertised.by_lifetime, NULL, |
155 | 0 | (void **)&adb, &cursor)) { |
156 | |
|
157 | 0 | struct prefix_rd prd; |
158 | 0 | uint32_t local_pref = rfp_cost_to_localpref(adb->cost); |
159 | |
|
160 | 0 | prd = rfd->rd; |
161 | 0 | prd.family = AF_UNSPEC; |
162 | 0 | prd.prefixlen = 64; |
163 | | |
164 | | /* |
165 | | * TBD this is not quite right. When pfx_ip is 0/32 or 0/128, |
166 | | * we need to substitute the VN address as the prefix |
167 | | */ |
168 | 0 | add_vnc_route(rfd, bgp, SAFI_MPLS_VPN, &adb->u.s.prefix_ip, |
169 | 0 | &prd, /* RD to use (0 for ENCAP) */ |
170 | 0 | &rfd->vn_addr, /* nexthop */ |
171 | 0 | &local_pref, &adb->lifetime, NULL, |
172 | 0 | NULL, /* struct rfapi_un_option */ |
173 | 0 | NULL, /* struct rfapi_vn_option */ |
174 | 0 | rfd->rt_export_list, NULL, /* med */ |
175 | 0 | NULL, ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, 0); |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | | void rfapiApWithdrawAll(struct bgp *bgp, struct rfapi_descriptor *rfd) |
180 | 0 | { |
181 | 0 | struct rfapi_adb *adb; |
182 | 0 | void *cursor; |
183 | 0 | int rc; |
184 | | |
185 | |
|
186 | 0 | cursor = NULL; |
187 | 0 | for (rc = skiplist_next(rfd->advertised.by_lifetime, NULL, |
188 | 0 | (void **)&adb, &cursor); |
189 | 0 | rc == 0; rc = skiplist_next(rfd->advertised.by_lifetime, NULL, |
190 | 0 | (void **)&adb, &cursor)) { |
191 | |
|
192 | 0 | struct prefix pfx_vn_buf; |
193 | 0 | struct prefix *pfx_ip; |
194 | |
|
195 | 0 | if (!(RFAPI_0_PREFIX(&adb->u.s.prefix_ip) |
196 | 0 | && RFAPI_HOST_PREFIX(&adb->u.s.prefix_ip))) { |
197 | |
|
198 | 0 | pfx_ip = &adb->u.s.prefix_ip; |
199 | |
|
200 | 0 | } else { |
201 | |
|
202 | 0 | pfx_ip = NULL; |
203 | | |
204 | | /* |
205 | | * 0/32 or 0/128 => mac advertisement |
206 | | */ |
207 | 0 | if (rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn_buf)) { |
208 | | /* |
209 | | * Bad: it means we can't delete the route |
210 | | */ |
211 | 0 | vnc_zlog_debug_verbose( |
212 | 0 | "%s: BAD: handle has bad vn_addr: skipping", |
213 | 0 | __func__); |
214 | 0 | continue; |
215 | 0 | } |
216 | 0 | } |
217 | | |
218 | 0 | del_vnc_route(rfd, rfd->peer, bgp, SAFI_MPLS_VPN, |
219 | 0 | pfx_ip ? pfx_ip : &pfx_vn_buf, |
220 | 0 | &adb->u.s.prd, /* RD to use (0 for ENCAP) */ |
221 | 0 | ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, NULL, 0); |
222 | 0 | } |
223 | 0 | } |
224 | | |
225 | | /* |
226 | | * returns nonzero if tunnel readvertisement is needed, 0 otherwise |
227 | | */ |
228 | | static int rfapiApAdjustLifetimeStats( |
229 | | struct rfapi_descriptor *rfd, |
230 | | uint32_t *old_lifetime, /* set if removing/replacing */ |
231 | | uint32_t *new_lifetime) /* set if replacing/adding */ |
232 | 0 | { |
233 | 0 | int advertise = 0; |
234 | 0 | int find_max = 0; |
235 | 0 | int find_min = 0; |
236 | |
|
237 | 0 | vnc_zlog_debug_verbose("%s: rfd=%p, pOldLife=%p, pNewLife=%p", __func__, |
238 | 0 | rfd, old_lifetime, new_lifetime); |
239 | 0 | if (old_lifetime) |
240 | 0 | vnc_zlog_debug_verbose("%s: OldLife=%d", __func__, |
241 | 0 | *old_lifetime); |
242 | 0 | if (new_lifetime) |
243 | 0 | vnc_zlog_debug_verbose("%s: NewLife=%d", __func__, |
244 | 0 | *new_lifetime); |
245 | |
|
246 | 0 | if (new_lifetime) { |
247 | | /* |
248 | | * Adding new lifetime |
249 | | */ |
250 | 0 | if (old_lifetime) { |
251 | | /* |
252 | | * replacing existing lifetime |
253 | | */ |
254 | | |
255 | | |
256 | | /* old and new are same */ |
257 | 0 | if (*old_lifetime == *new_lifetime) |
258 | 0 | return 0; |
259 | | |
260 | 0 | if (*old_lifetime == rfd->min_prefix_lifetime) { |
261 | 0 | find_min = 1; |
262 | 0 | } |
263 | 0 | if (*old_lifetime == rfd->max_prefix_lifetime) { |
264 | 0 | find_max = 1; |
265 | 0 | } |
266 | | |
267 | | /* no need to search if new value is at or equals |
268 | | * min|max */ |
269 | 0 | if (*new_lifetime <= rfd->min_prefix_lifetime) { |
270 | 0 | rfd->min_prefix_lifetime = *new_lifetime; |
271 | 0 | find_min = 0; |
272 | 0 | } |
273 | 0 | if (*new_lifetime >= rfd->max_prefix_lifetime) { |
274 | 0 | rfd->max_prefix_lifetime = *new_lifetime; |
275 | 0 | advertise = 1; |
276 | 0 | find_max = 0; |
277 | 0 | } |
278 | |
|
279 | 0 | } else { |
280 | | /* |
281 | | * Just adding new lifetime |
282 | | */ |
283 | 0 | if (*new_lifetime < rfd->min_prefix_lifetime) { |
284 | 0 | rfd->min_prefix_lifetime = *new_lifetime; |
285 | 0 | } |
286 | 0 | if (*new_lifetime > rfd->max_prefix_lifetime) { |
287 | 0 | advertise = 1; |
288 | 0 | rfd->max_prefix_lifetime = *new_lifetime; |
289 | 0 | } |
290 | 0 | } |
291 | 0 | } else { |
292 | | /* |
293 | | * Deleting |
294 | | */ |
295 | | |
296 | | /* |
297 | | * See if the max prefix lifetime for this NVE has decreased. |
298 | | * The easy optimization: track min & max; walk the table only |
299 | | * if they are different. |
300 | | * The general optimization: index the advertised_prefixes |
301 | | * table by lifetime. |
302 | | * |
303 | | * Note: for a given nve_descriptor, only one of the |
304 | | * advertised_prefixes[] tables will be used: viz., the |
305 | | * address family that matches the VN address. |
306 | | * |
307 | | */ |
308 | 0 | if (rfd->max_prefix_lifetime == rfd->min_prefix_lifetime) { |
309 | | |
310 | | /* |
311 | | * Common case: all lifetimes are the same. Only |
312 | | * thing we need to do here is check if there are |
313 | | * no exported routes left. In that case, reinitialize |
314 | | * the max and min values. |
315 | | */ |
316 | 0 | if (!rfapiApCount(rfd)) { |
317 | 0 | rfd->max_prefix_lifetime = 0; |
318 | 0 | rfd->min_prefix_lifetime = UINT32_MAX; |
319 | 0 | } |
320 | | |
321 | |
|
322 | 0 | } else { |
323 | 0 | if (old_lifetime) { |
324 | 0 | if (*old_lifetime == rfd->min_prefix_lifetime) { |
325 | 0 | find_min = 1; |
326 | 0 | } |
327 | 0 | if (*old_lifetime == rfd->max_prefix_lifetime) { |
328 | 0 | find_max = 1; |
329 | 0 | } |
330 | 0 | } |
331 | 0 | } |
332 | 0 | } |
333 | | |
334 | 0 | if (find_min || find_max) { |
335 | 0 | uint32_t min = UINT32_MAX; |
336 | 0 | uint32_t max = 0; |
337 | |
|
338 | 0 | struct rfapi_adb *adb_min; |
339 | 0 | struct rfapi_adb *adb_max; |
340 | |
|
341 | 0 | if (!skiplist_first(rfd->advertised.by_lifetime, |
342 | 0 | (void **)&adb_min, NULL) |
343 | 0 | && !skiplist_last(rfd->advertised.by_lifetime, |
344 | 0 | (void **)&adb_max, NULL)) { |
345 | | |
346 | | /* |
347 | | * This should always work |
348 | | */ |
349 | 0 | min = adb_min->lifetime; |
350 | 0 | max = adb_max->lifetime; |
351 | |
|
352 | 0 | } else { |
353 | |
|
354 | 0 | void *cursor; |
355 | 0 | struct rfapi_rib_key rk; |
356 | 0 | struct rfapi_adb *adb; |
357 | 0 | int rc; |
358 | |
|
359 | 0 | vnc_zlog_debug_verbose( |
360 | 0 | "%s: walking to find new min/max", __func__); |
361 | |
|
362 | 0 | cursor = NULL; |
363 | 0 | for (rc = skiplist_next(rfd->advertised.ipN_by_prefix, |
364 | 0 | (void **)&rk, (void **)&adb, |
365 | 0 | &cursor); |
366 | 0 | !rc; |
367 | 0 | rc = skiplist_next(rfd->advertised.ipN_by_prefix, |
368 | 0 | (void **)&rk, (void **)&adb, |
369 | 0 | &cursor)) { |
370 | |
|
371 | 0 | uint32_t lt = adb->lifetime; |
372 | |
|
373 | 0 | if (lt > max) |
374 | 0 | max = lt; |
375 | 0 | if (lt < min) |
376 | 0 | min = lt; |
377 | 0 | } |
378 | 0 | cursor = NULL; |
379 | 0 | for (rc = skiplist_next(rfd->advertised.ip0_by_ether, |
380 | 0 | (void **)&rk, (void **)&adb, |
381 | 0 | &cursor); |
382 | 0 | !rc; |
383 | 0 | rc = skiplist_next(rfd->advertised.ip0_by_ether, |
384 | 0 | (void **)&rk, (void **)&adb, |
385 | 0 | &cursor)) { |
386 | |
|
387 | 0 | uint32_t lt = adb->lifetime; |
388 | |
|
389 | 0 | if (lt > max) |
390 | 0 | max = lt; |
391 | 0 | if (lt < min) |
392 | 0 | min = lt; |
393 | 0 | } |
394 | 0 | } |
395 | | |
396 | | /* |
397 | | * trigger tunnel route update |
398 | | * but only if we found a VPN route and it had |
399 | | * a lifetime greater than 0 |
400 | | */ |
401 | 0 | if (max && rfd->max_prefix_lifetime != max) |
402 | 0 | advertise = 1; |
403 | 0 | rfd->max_prefix_lifetime = max; |
404 | 0 | rfd->min_prefix_lifetime = min; |
405 | 0 | } |
406 | |
|
407 | 0 | vnc_zlog_debug_verbose("%s: returning advertise=%d, min=%d, max=%d", |
408 | 0 | __func__, advertise, rfd->min_prefix_lifetime, |
409 | 0 | rfd->max_prefix_lifetime); |
410 | |
|
411 | 0 | return (advertise != 0); |
412 | 0 | } |
413 | | |
414 | | /* |
415 | | * Return Value |
416 | | * |
417 | | * 0 No need to advertise tunnel route |
418 | | * non-0 advertise tunnel route |
419 | | */ |
420 | | int rfapiApAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, |
421 | | struct prefix *pfx_ip, struct prefix *pfx_eth, |
422 | | struct prefix_rd *prd, uint32_t lifetime, uint8_t cost, |
423 | | struct rfapi_l2address_option *l2o) /* other options TBD */ |
424 | 0 | { |
425 | 0 | int rc; |
426 | 0 | struct rfapi_adb *adb; |
427 | 0 | uint32_t old_lifetime = 0; |
428 | 0 | int use_ip0 = 0; |
429 | 0 | struct rfapi_rib_key rk; |
430 | |
|
431 | 0 | rfapi_rib_key_init(pfx_ip, prd, pfx_eth, &rk); |
432 | 0 | if (RFAPI_0_PREFIX(pfx_ip) && RFAPI_HOST_PREFIX(pfx_ip)) { |
433 | 0 | use_ip0 = 1; |
434 | 0 | assert(pfx_eth); |
435 | 0 | rc = skiplist_search(rfd->advertised.ip0_by_ether, &rk, |
436 | 0 | (void **)&adb); |
437 | |
|
438 | 0 | } else { |
439 | | |
440 | | /* find prefix in advertised prefixes list */ |
441 | 0 | rc = skiplist_search(rfd->advertised.ipN_by_prefix, &rk, |
442 | 0 | (void **)&adb); |
443 | 0 | } |
444 | | |
445 | |
|
446 | 0 | if (rc) { |
447 | | /* Not found */ |
448 | 0 | adb = XCALLOC(MTYPE_RFAPI_ADB, sizeof(struct rfapi_adb)); |
449 | 0 | adb->lifetime = lifetime; |
450 | 0 | adb->u.key = rk; |
451 | |
|
452 | 0 | if (use_ip0) { |
453 | 0 | assert(pfx_eth); |
454 | 0 | skiplist_insert(rfd->advertised.ip0_by_ether, |
455 | 0 | &adb->u.key, adb); |
456 | 0 | } else { |
457 | 0 | skiplist_insert(rfd->advertised.ipN_by_prefix, |
458 | 0 | &adb->u.key, adb); |
459 | 0 | } |
460 | |
|
461 | 0 | skiplist_insert(rfd->advertised.by_lifetime, adb, adb); |
462 | 0 | } else { |
463 | 0 | old_lifetime = adb->lifetime; |
464 | 0 | if (old_lifetime != lifetime) { |
465 | 0 | assert(!skiplist_delete(rfd->advertised.by_lifetime, |
466 | 0 | adb, NULL)); |
467 | 0 | adb->lifetime = lifetime; |
468 | 0 | assert(!skiplist_insert(rfd->advertised.by_lifetime, |
469 | 0 | adb, adb)); |
470 | 0 | } |
471 | 0 | } |
472 | 0 | adb->cost = cost; |
473 | 0 | if (l2o) |
474 | 0 | adb->l2o = *l2o; |
475 | 0 | else |
476 | 0 | memset(&adb->l2o, 0, sizeof(struct rfapi_l2address_option)); |
477 | |
|
478 | 0 | if (rfapiApAdjustLifetimeStats(rfd, (rc ? NULL : &old_lifetime), |
479 | 0 | &lifetime)) |
480 | 0 | return 1; |
481 | | |
482 | 0 | return 0; |
483 | 0 | } |
484 | | |
485 | | /* |
486 | | * After this function returns successfully, caller should call |
487 | | * rfapiAdjustLifetimeStats() and possibly rfapiTunnelRouteAnnounce() |
488 | | */ |
489 | | int rfapiApDelete(struct bgp *bgp, struct rfapi_descriptor *rfd, |
490 | | struct prefix *pfx_ip, struct prefix *pfx_eth, |
491 | | struct prefix_rd *prd, int *advertise_tunnel) /* out */ |
492 | 0 | { |
493 | 0 | int rc; |
494 | 0 | struct rfapi_adb *adb; |
495 | 0 | uint32_t old_lifetime; |
496 | 0 | int use_ip0 = 0; |
497 | 0 | struct rfapi_rib_key rk; |
498 | |
|
499 | 0 | if (advertise_tunnel) |
500 | 0 | *advertise_tunnel = 0; |
501 | |
|
502 | 0 | rfapi_rib_key_init(pfx_ip, prd, pfx_eth, &rk); |
503 | | /* find prefix in advertised prefixes list */ |
504 | 0 | if (RFAPI_0_PREFIX(pfx_ip) && RFAPI_HOST_PREFIX(pfx_ip)) { |
505 | 0 | use_ip0 = 1; |
506 | 0 | assert(pfx_eth); |
507 | |
|
508 | 0 | rc = skiplist_search(rfd->advertised.ip0_by_ether, &rk, |
509 | 0 | (void **)&adb); |
510 | |
|
511 | 0 | } else { |
512 | | |
513 | | /* find prefix in advertised prefixes list */ |
514 | 0 | rc = skiplist_search(rfd->advertised.ipN_by_prefix, &rk, |
515 | 0 | (void **)&adb); |
516 | 0 | } |
517 | |
|
518 | 0 | if (rc) { |
519 | 0 | return ENOENT; |
520 | 0 | } |
521 | | |
522 | 0 | old_lifetime = adb->lifetime; |
523 | |
|
524 | 0 | if (use_ip0) { |
525 | 0 | rc = skiplist_delete(rfd->advertised.ip0_by_ether, &rk, NULL); |
526 | 0 | } else { |
527 | 0 | rc = skiplist_delete(rfd->advertised.ipN_by_prefix, &rk, NULL); |
528 | 0 | } |
529 | 0 | assert(!rc); |
530 | |
|
531 | 0 | rc = skiplist_delete(rfd->advertised.by_lifetime, adb, NULL); |
532 | 0 | assert(!rc); |
533 | |
|
534 | 0 | rfapiAdbFree(adb); |
535 | |
|
536 | 0 | if (rfapiApAdjustLifetimeStats(rfd, &old_lifetime, NULL)) { |
537 | 0 | if (advertise_tunnel) |
538 | 0 | *advertise_tunnel = 1; |
539 | 0 | } |
540 | |
|
541 | 0 | return 0; |
542 | 0 | } |