From 064d4355ca82c924ec928c67150f243f17f56139 Mon Sep 17 00:00:00 2001 From: Chirag Shah Date: Wed, 6 Dec 2017 18:20:48 -0800 Subject: [PATCH] ospf6d: Fix External routes ECMP Handle RFC 2328 16.4 Calculating AS external routes with ECMP For ASBR route, if it is learnt via new LSA and contains different nexthop list. First lookup route in ospf6 route table if it exists, merge nexthop list to existing and call the callback to install into FIB (zebra). Delete created new route as it is identical to existing entry in route table. Ticket:CM-16139 Testing Done: Run two ASBR with 2 ECMP paths from each DUT neighbor receievs 4 ECMP path to a external route. ospf6 installs all 4 ECMP path to FIB/RIB Signed-off-by: Chirag Shah --- ospf6d/ospf6_abr.c | 10 +- ospf6d/ospf6_area.c | 4 +- ospf6d/ospf6_asbr.c | 275 ++++++++++++++++++++++++++++++++++++++++-- ospf6d/ospf6_asbr.h | 2 + ospf6d/ospf6_intra.c | 3 +- ospf6d/ospf6_memory.c | 1 + ospf6d/ospf6_memory.h | 1 + ospf6d/ospf6_route.c | 50 +++++++- ospf6d/ospf6_route.h | 9 ++ ospf6d/ospf6_spf.c | 1 + ospf6d/ospf6_top.c | 14 +++ 11 files changed, 354 insertions(+), 16 deletions(-) diff --git a/ospf6d/ospf6_abr.c b/ospf6d/ospf6_abr.c index d270b9547e..8847611492 100644 --- a/ospf6d/ospf6_abr.c +++ b/ospf6d/ospf6_abr.c @@ -901,6 +901,7 @@ void ospf6_abr_examin_summary(struct ospf6_lsa *lsa, struct ospf6_area *oa) ospf6_route_copy_nexthops(route, abr_entry); + /* (7) If the routes are identical, copy the next hops over to existing route. ospf6's route table implementation will otherwise string both routes, but keep the older one as the best route since the routes @@ -910,6 +911,12 @@ void ospf6_abr_examin_summary(struct ospf6_lsa *lsa, struct ospf6_area *oa) if (old && (ospf6_route_cmp(route, old) == 0)) { ospf6_route_merge_nexthops(old, route); + + if (is_debug) + zlog_debug("%s: Update route: %s nh count %u", + __PRETTY_FUNCTION__, + buf, listcount(route->nh_list)); + /* Update RIB/FIB */ if (table->hook_add) (*table->hook_add)(old); @@ -918,7 +925,8 @@ void ospf6_abr_examin_summary(struct ospf6_lsa *lsa, struct ospf6_area *oa) ospf6_route_delete(route); } else { if (is_debug) - zlog_debug("Install route: %s", buf); + zlog_debug("Install route: %s nh count %u", + buf, listcount(route->nh_list)); /* ospf6_ia_add_nw_route (table, &prefix, route); */ ospf6_route_add(route, table); } diff --git a/ospf6d/ospf6_area.c b/ospf6d/ospf6_area.c index bd5e2bd1d3..252e4a4545 100644 --- a/ospf6d/ospf6_area.c +++ b/ospf6d/ospf6_area.c @@ -117,7 +117,9 @@ static void ospf6_area_lsdb_hook_remove(struct ospf6_lsa *lsa) static void ospf6_area_route_hook_add(struct ospf6_route *route) { - struct ospf6_route *copy = ospf6_route_copy(route); + struct ospf6_route *copy; + + copy = ospf6_route_copy(route); ospf6_route_add(copy, ospf6->route_table); } diff --git a/ospf6d/ospf6_asbr.c b/ospf6d/ospf6_asbr.c index 5fbf2dafa5..745b87b890 100644 --- a/ospf6d/ospf6_asbr.c +++ b/ospf6d/ospf6_asbr.c @@ -173,11 +173,136 @@ static route_tag_t ospf6_as_external_lsa_get_tag(struct ospf6_lsa *lsa) return ntohl(network_order); } +void ospf6_asbr_update_route_ecmp_path(struct ospf6_route *old, + struct ospf6_route *route) +{ + struct ospf6_route *old_route; + struct ospf6_path *ecmp_path, *o_path = NULL; + struct listnode *anode; + struct listnode *nnode, *rnode, *rnext; + struct ospf6_nexthop *nh, *rnh; + char buf[PREFIX2STR_BUFFER]; + bool route_found = false; + + for (old_route = old; old_route; old_route = old_route->next) { + if (ospf6_route_is_same(old_route, route) && + (old_route->path.type == route->path.type) && + (old_route->path.cost == route->path.cost) && + (old_route->path.u.cost_e2 == route->path.u.cost_e2)) { + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + prefix2str(&old_route->prefix, buf, + sizeof(buf)); + zlog_debug("%s: old route %s path cost %u [%u]", + __PRETTY_FUNCTION__, buf, + old_route->path.cost, + ospf6_route_is_same(old_route, + route)); + } + route_found = true; + /* check if this path exists already in + * route->paths list, if so, replace nh_list + * from asbr_entry. + */ + for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, + o_path)) { + if ((o_path->origin.id == route->path.origin.id) + && (o_path->origin.adv_router == + route->path.origin.adv_router)) + break; + } + /* If path is not found in old_route paths's list, + * add a new path to route paths list and merge + * nexthops in route->path->nh_list. + * Otherwise replace existing path's nh_list. + */ + if (o_path == NULL) { + ecmp_path = ospf6_path_dup(&route->path); + + /* Add a nh_list to new ecmp path */ + ospf6_copy_nexthops(ecmp_path->nh_list, + route->nh_list); + /* Merge nexthop to existing route's nh_list */ + ospf6_route_merge_nexthops(old_route, route); + + /* Update RIB/FIB */ + if (ospf6->route_table->hook_add) + (*ospf6->route_table->hook_add) + (old_route); + + /* Add the new path to route's path list */ + listnode_add_sort(old_route->paths, ecmp_path); + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + prefix2str(&route->prefix, buf, + sizeof(buf)); + zlog_debug("%s: route %s another path added with nh %u, Paths %u", + __PRETTY_FUNCTION__, buf, + listcount(ecmp_path->nh_list), + old_route->paths ? + listcount(old_route->paths) + : 0); + } + } else { + for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, + nnode, nh)) { + for (ALL_LIST_ELEMENTS( + old_route->nh_list, + rnode, rnext, rnh)) { + if (!ospf6_nexthop_is_same(rnh, + nh)) + continue; + + listnode_delete( + old_route->nh_list, + rnh); + ospf6_nexthop_delete(rnh); + } + } + list_delete_all_node(o_path->nh_list); + ospf6_copy_nexthops(o_path->nh_list, + route->nh_list); + + /* Merge nexthop to existing route's nh_list */ + ospf6_route_merge_nexthops(old_route, + route); + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + prefix2str(&route->prefix, + buf, sizeof(buf)); + zlog_debug("%s: existing route %s with effective nh count %u", + __PRETTY_FUNCTION__, buf, + old_route->nh_list ? + listcount(old_route->nh_list) + : 0); + } + + /* Update RIB/FIB */ + if (ospf6->route_table->hook_add) + (*ospf6->route_table->hook_add) + (old_route); + + } + /* Delete the new route its info added to existing + * route. + */ + ospf6_route_delete(route); + break; + } + } + + if (!route_found) { + /* Add new route to existing node in ospf6 route table. */ + ospf6_route_add(route, ospf6->route_table); + } +} + void ospf6_asbr_lsa_add(struct ospf6_lsa *lsa) { struct ospf6_as_external_lsa *external; struct prefix asbr_id; - struct ospf6_route *asbr_entry, *route; + struct ospf6_route *asbr_entry, *route, *old; + struct ospf6_path *path; char buf[PREFIX2STR_BUFFER]; external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( @@ -245,12 +370,34 @@ void ospf6_asbr_lsa_add(struct ospf6_lsa *lsa) ospf6_route_copy_nexthops(route, asbr_entry); + path = ospf6_path_dup(&route->path); + ospf6_copy_nexthops(path->nh_list, asbr_entry->nh_list); + listnode_add_sort(route->paths, path); + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&route->prefix, buf, sizeof(buf)); - zlog_debug("AS-External route add: %s", buf); + zlog_debug("%s: AS-External %u route add %s cost %u(%u) nh %u", + __PRETTY_FUNCTION__, + (route->path.type == OSPF6_PATH_TYPE_EXTERNAL1) + ? 1 : 2, buf, route->path.cost, + route->path.u.cost_e2, + listcount(route->nh_list)); + } + + old = ospf6_route_lookup(&route->prefix, ospf6->route_table); + if (!old) { + /* Add the new route to ospf6 instance route table. */ + ospf6_route_add(route, ospf6->route_table); + } else { + /* RFC 2328 16.4 (6) + * ECMP: Keep new equal preference path in current + * route's path list, update zebra with new effective + * list along with addition of ECMP path. + */ + ospf6_asbr_update_route_ecmp_path(old, route); } - ospf6_route_add(route, ospf6->route_table); } void ospf6_asbr_lsa_remove(struct ospf6_lsa *lsa) @@ -291,16 +438,126 @@ void ospf6_asbr_lsa_remove(struct ospf6_lsa *lsa) nroute = ospf6_route_next(route); if (route->type != OSPF6_DEST_TYPE_NETWORK) continue; - if (route->path.origin.type != lsa->header->type) - continue; - if (route->path.origin.id != lsa->header->id) - continue; - if (route->path.origin.adv_router != lsa->header->adv_router) + + /* Route has multiple ECMP paths remove, + * matching path and update effective route's nh list. + */ + if (listcount(route->paths) > 1) { + struct listnode *anode, *anext; + struct listnode *nnode, *rnode, *rnext; + struct ospf6_nexthop *nh, *rnh; + struct ospf6_path *o_path; + bool nh_updated = false; + + /* Iterate all paths of route to find maching with LSA + * remove from route path list. If route->path is same, + * replace from paths list. + */ + for (ALL_LIST_ELEMENTS(route->paths, anode, anext, + o_path)) { + if (o_path->origin.type != lsa->header->type) + continue; + if (o_path->origin.id != lsa->header->id) + continue; + if (o_path->origin.adv_router != + lsa->header->adv_router) + continue; + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + prefix2str(&prefix, buf, sizeof(buf)); + zlog_debug( + "%s: route %s path found with nh %u", + __PRETTY_FUNCTION__, buf, + listcount(o_path->nh_list)); + } + + /* Remove found path's nh_list from + * the route's nh_list. + */ + for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, + nnode, nh)) { + for (ALL_LIST_ELEMENTS(route->nh_list, + rnode, rnext, rnh)) { + if (!ospf6_nexthop_is_same(rnh, + nh)) + continue; + listnode_delete(route->nh_list, + rnh); + ospf6_nexthop_delete(rnh); + } + } + /* Delete the path from route's path list */ + listnode_delete(route->paths, o_path); + ospf6_path_free(o_path); + nh_updated = true; + } + + if (nh_updated) { + /* Iterate all paths and merge nexthop, + * unlesss any of the nexthop similar to + * ones deleted as part of path deletion. + */ + + for (ALL_LIST_ELEMENTS(route->paths, anode, + anext, o_path)) { + ospf6_merge_nexthops(route->nh_list, + o_path->nh_list); + } + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + prefix2str(&route->prefix, buf, + sizeof(buf)); + zlog_debug("%s: AS-External %u route %s update paths %u nh %u" + , __PRETTY_FUNCTION__, + (route->path.type == + OSPF6_PATH_TYPE_EXTERNAL1) + ? 1 : 2, buf, + listcount(route->paths), + listcount(route->nh_list)); + } + + /* Update RIB/FIB w/ effective nh_list */ + if (ospf6->route_table->hook_add) + (*ospf6->route_table->hook_add)(route); + + /* route's path is similar to lsa header, + * replace route's path with route's + * paths list head. + */ + if (route->path.origin.id == lsa->header->id && + route->path.origin.adv_router == + lsa->header->adv_router) { + struct ospf6_path *h_path; + + h_path = (struct ospf6_path *) + listgetdata(listhead(route->paths)); + route->path.origin.type = + h_path->origin.type; + route->path.origin.id = + h_path->origin.id; + route->path.origin.adv_router = + h_path->origin.adv_router; + } + } continue; + } else { + if (route->path.origin.type != lsa->header->type) + continue; + if (route->path.origin.id != lsa->header->id) + continue; + if (route->path.origin.adv_router != + lsa->header->adv_router) + continue; + } if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { prefix2str(&route->prefix, buf, sizeof(buf)); - zlog_debug("AS-External route remove: %s", buf); + zlog_debug("%s: AS-External %u route remove %s cost %u(%u) nh %u", + __PRETTY_FUNCTION__, + route->path.type == OSPF6_PATH_TYPE_EXTERNAL1 + ? 1 : 2, buf, route->path.cost, + route->path.u.cost_e2, + listcount(route->nh_list)); } ospf6_route_remove(route, ospf6->route_table); } diff --git a/ospf6d/ospf6_asbr.h b/ospf6d/ospf6_asbr.h index 73053452e6..7f4665ac2b 100644 --- a/ospf6d/ospf6_asbr.h +++ b/ospf6d/ospf6_asbr.h @@ -93,5 +93,7 @@ extern void ospf6_asbr_send_externals_to_area(struct ospf6_area *); extern int config_write_ospf6_debug_asbr(struct vty *vty); extern void install_element_ospf6_debug_asbr(void); +extern void ospf6_asbr_update_route_ecmp_path(struct ospf6_route *old, + struct ospf6_route *route); #endif /* OSPF6_ASBR_H */ diff --git a/ospf6d/ospf6_intra.c b/ospf6d/ospf6_intra.c index b1d940952c..77653ea33f 100644 --- a/ospf6d/ospf6_intra.c +++ b/ospf6d/ospf6_intra.c @@ -1404,7 +1404,8 @@ void ospf6_intra_prefix_lsa_add(struct ospf6_lsa *lsa) if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { prefix2str(&route->prefix, buf, sizeof(buf)); - zlog_debug(" route %s add", buf); + zlog_debug(" route %s add with nh count %u", buf, + listcount(route->nh_list)); } ospf6_route_add(route, oa->route_table); diff --git a/ospf6d/ospf6_memory.c b/ospf6d/ospf6_memory.c index 56c232d6da..1c3523b43d 100644 --- a/ospf6d/ospf6_memory.c +++ b/ospf6d/ospf6_memory.c @@ -41,4 +41,5 @@ DEFINE_MTYPE(OSPF6D, OSPF6_VERTEX, "OSPF6 vertex") DEFINE_MTYPE(OSPF6D, OSPF6_SPFTREE, "OSPF6 SPF tree") DEFINE_MTYPE(OSPF6D, OSPF6_NEXTHOP, "OSPF6 nexthop") DEFINE_MTYPE(OSPF6D, OSPF6_EXTERNAL_INFO, "OSPF6 ext. info") +DEFINE_MTYPE(OSPF6D, OSPF6_PATH, "OSPF6 Path") DEFINE_MTYPE(OSPF6D, OSPF6_OTHER, "OSPF6 other") diff --git a/ospf6d/ospf6_memory.h b/ospf6d/ospf6_memory.h index fe72ee3669..548af5e321 100644 --- a/ospf6d/ospf6_memory.h +++ b/ospf6d/ospf6_memory.h @@ -40,6 +40,7 @@ DECLARE_MTYPE(OSPF6_VERTEX) DECLARE_MTYPE(OSPF6_SPFTREE) DECLARE_MTYPE(OSPF6_NEXTHOP) DECLARE_MTYPE(OSPF6_EXTERNAL_INFO) +DECLARE_MTYPE(OSPF6_PATH) DECLARE_MTYPE(OSPF6_OTHER) #endif /* _QUAGGA_OSPF6_MEMORY_H */ diff --git a/ospf6d/ospf6_route.c b/ospf6d/ospf6_route.c index 281757222d..735b28a693 100644 --- a/ospf6d/ospf6_route.c +++ b/ospf6d/ospf6_route.c @@ -215,7 +215,7 @@ void ospf6_copy_nexthops(struct list *dst, struct list *src) if (ospf6_nexthop_is_set(nh)) { nh_new = ospf6_nexthop_create(); ospf6_nexthop_copy(nh_new, nh); - listnode_add(dst, nh_new); + listnode_add_sort(dst, nh_new); } } } @@ -231,7 +231,7 @@ void ospf6_merge_nexthops(struct list *dst, struct list *src) if (!ospf6_route_find_nexthop(dst, nh)) { nh_new = ospf6_nexthop_create(); ospf6_nexthop_copy(nh_new, nh); - listnode_add(dst, nh_new); + listnode_add_sort(dst, nh_new); } } } @@ -338,7 +338,7 @@ int ospf6_route_get_first_nh_index(struct ospf6_route *route) return (-1); } -static int ospf6_nexthop_cmp(struct ospf6_nexthop *a, struct ospf6_nexthop *b) +int ospf6_nexthop_cmp(struct ospf6_nexthop *a, struct ospf6_nexthop *b) { if (a->ifindex < b->ifindex) return -1; @@ -351,6 +351,36 @@ static int ospf6_nexthop_cmp(struct ospf6_nexthop *a, struct ospf6_nexthop *b) return 0; } +static int ospf6_path_cmp(struct ospf6_path *a, struct ospf6_path *b) +{ + if (a->origin.adv_router < b->origin.adv_router) + return -1; + else if (a->origin.adv_router > b->origin.adv_router) + return 1; + else + return 0; +} + +void ospf6_path_free(struct ospf6_path *op) +{ + if (op->nh_list) + list_delete_and_null(&op->nh_list); + XFREE(MTYPE_OSPF6_PATH, op); +} + +struct ospf6_path *ospf6_path_dup(struct ospf6_path *path) +{ + struct ospf6_path *new; + + new = XCALLOC(MTYPE_OSPF6_PATH, sizeof(struct ospf6_path)); + memcpy(new, path, sizeof(struct ospf6_path)); + new->nh_list = list_new(); + new->nh_list->cmp = (int (*)(void *, void *))ospf6_nexthop_cmp; + new->nh_list->del = (void (*) (void *))ospf6_nexthop_delete; + + return new; +} + struct ospf6_route *ospf6_route_create(void) { struct ospf6_route *route; @@ -358,6 +388,9 @@ struct ospf6_route *ospf6_route_create(void) route->nh_list = list_new(); route->nh_list->cmp = (int (*)(void *, void *))ospf6_nexthop_cmp; route->nh_list->del = (void (*) (void *))ospf6_nexthop_delete; + route->paths = list_new(); + route->paths->cmp = (int (*)(void *, void *))ospf6_path_cmp; + route->paths->del = (void (*)(void *))ospf6_path_free; return route; } @@ -366,6 +399,8 @@ void ospf6_route_delete(struct ospf6_route *route) if (route) { if (route->nh_list) list_delete_and_null(&route->nh_list); + if (route->paths) + list_delete_and_null(&route->paths); XFREE(MTYPE_OSPF6_ROUTE, route); } } @@ -464,7 +499,13 @@ ospf6_route_lookup_identical(struct ospf6_route *route, for (target = ospf6_route_lookup(&route->prefix, table); target; target = target->next) { - if (ospf6_route_is_identical(target, route)) + if (target->type == route->type && + (memcmp(&target->prefix, &route->prefix, + sizeof(struct prefix)) == 0) && + target->path.type == route->path.type && + target->path.cost == route->path.cost && + target->path.u.cost_e2 == route->path.u.cost_e2 && + ospf6_route_cmp_nexthops(target, route) == 0) return target; } return NULL; @@ -1083,6 +1124,7 @@ void ospf6_route_show_detail(struct vty *vty, struct ospf6_route *route) vty_out(vty, "Metric: %d (%d)\n", route->path.cost, route->path.u.cost_e2); + vty_out(vty, "Paths count: %u\n", route->paths->count); vty_out(vty, "Nexthop count: %u\n", route->nh_list->count); /* Nexthops */ vty_out(vty, "Nexthop:\n"); diff --git a/ospf6d/ospf6_route.h b/ospf6d/ospf6_route.h index 9eacadbdb7..b759828c39 100644 --- a/ospf6d/ospf6_route.h +++ b/ospf6d/ospf6_route.h @@ -96,6 +96,9 @@ struct ospf6_path { u_int32_t cost_config; } u; u_int32_t tag; + + /* nh list for this path */ + struct list *nh_list; }; #define OSPF6_PATH_TYPE_NONE 0 @@ -149,6 +152,9 @@ struct ospf6_route { /* path */ struct ospf6_path path; + /* List of Paths. */ + struct list *paths; + /* nexthop */ struct list *nh_list; }; @@ -256,6 +262,7 @@ extern void ospf6_linkstate_prefix2str(struct prefix *prefix, char *buf, int size); extern struct ospf6_nexthop *ospf6_nexthop_create(void); +extern int ospf6_nexthop_cmp(struct ospf6_nexthop *a, struct ospf6_nexthop *b); extern void ospf6_nexthop_delete(struct ospf6_nexthop *nh); extern void ospf6_clear_nexthops(struct list *nh_list); extern int ospf6_num_nexthops(struct list *nh_list); @@ -331,5 +338,7 @@ extern int config_write_ospf6_debug_route(struct vty *vty); extern void install_element_ospf6_debug_route(void); extern void ospf6_route_init(void); extern void ospf6_clean(void); +extern void ospf6_path_free(struct ospf6_path *op); +extern struct ospf6_path *ospf6_path_dup(struct ospf6_path *path); #endif /* OSPF6_ROUTE_H */ diff --git a/ospf6d/ospf6_spf.c b/ospf6d/ospf6_spf.c index 340d90159f..6e6d8a7f00 100644 --- a/ospf6d/ospf6_spf.c +++ b/ospf6d/ospf6_spf.c @@ -145,6 +145,7 @@ static struct ospf6_vertex *ospf6_vertex_create(struct ospf6_lsa *lsa) v->options[2] = *(u_char *)(OSPF6_LSA_HEADER_END(lsa->header) + 3); v->nh_list = list_new(); + v->nh_list->cmp = (int (*)(void *, void *))ospf6_nexthop_cmp; v->nh_list->del = (void (*) (void *))ospf6_nexthop_delete; v->parent = NULL; diff --git a/ospf6d/ospf6_top.c b/ospf6d/ospf6_top.c index e0844765d3..5d1144335b 100644 --- a/ospf6d/ospf6_top.c +++ b/ospf6d/ospf6_top.c @@ -95,6 +95,13 @@ static void ospf6_top_route_hook_remove(struct ospf6_route *route) static void ospf6_top_brouter_hook_add(struct ospf6_route *route) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + char buf[PREFIX2STR_BUFFER]; + + prefix2str(&route->prefix, buf, sizeof(buf)); + zlog_debug("%s: brouter %s add with nh count %u", + __PRETTY_FUNCTION__, buf, listcount(route->nh_list)); + } ospf6_abr_examin_brouter(ADV_ROUTER_IN_PREFIX(&route->prefix)); ospf6_asbr_lsentry_add(route); ospf6_abr_originate_summary(route); @@ -102,6 +109,13 @@ static void ospf6_top_brouter_hook_add(struct ospf6_route *route) static void ospf6_top_brouter_hook_remove(struct ospf6_route *route) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + char buf[PREFIX2STR_BUFFER]; + + prefix2str(&route->prefix, buf, sizeof(buf)); + zlog_debug("%s: brouter %s del with nh count %u", + __PRETTY_FUNCTION__, buf, listcount(route->nh_list)); + } route->flag |= OSPF6_ROUTE_REMOVE; ospf6_abr_examin_brouter(ADV_ROUTER_IN_PREFIX(&route->prefix)); ospf6_asbr_lsentry_remove(route); -- 2.39.5