From e2bbf175a2184bd76f6c54ccf8456babeb1a46fc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 9 Apr 2024 15:16:35 +0200 Subject: Adding upstream version 9.1. Signed-off-by: Daniel Baumann --- ospf6d/ospf6_asbr.c | 3723 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3723 insertions(+) create mode 100644 ospf6d/ospf6_asbr.c (limited to 'ospf6d/ospf6_asbr.c') diff --git a/ospf6d/ospf6_asbr.c b/ospf6d/ospf6_asbr.c new file mode 100644 index 0000000..99c3e16 --- /dev/null +++ b/ospf6d/ospf6_asbr.c @@ -0,0 +1,3723 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "memory.h" +#include "prefix.h" +#include "command.h" +#include "vty.h" +#include "routemap.h" +#include "table.h" +#include "plist.h" +#include "frrevent.h" +#include "linklist.h" +#include "lib/northbound_cli.h" + +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_route.h" +#include "ospf6_zebra.h" +#include "ospf6_message.h" +#include "ospf6_spf.h" + +#include "ospf6_top.h" +#include "ospf6d.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" +#include "ospf6_asbr.h" +#include "ospf6_abr.h" +#include "ospf6_intra.h" +#include "ospf6_flood.h" +#include "ospf6_nssa.h" +#include "ospf6d.h" +#include "ospf6_spf.h" +#include "ospf6_nssa.h" +#include "ospf6_gr.h" +#include "lib/json.h" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_EXTERNAL_INFO, "OSPF6 ext. info"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_DIST_ARGS, "OSPF6 Distribute arguments"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_REDISTRIBUTE, "OSPF6 Redistribute arguments"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_EXTERNAL_RT_AGGR, "OSPF6 ASBR Summarisation"); + +static void ospf6_asbr_redistribute_set(struct ospf6 *ospf6, int type); +static void ospf6_asbr_redistribute_unset(struct ospf6 *ospf6, + struct ospf6_redist *red, int type); + +#include "ospf6d/ospf6_asbr_clippy.c" + +unsigned char conf_debug_ospf6_asbr = 0; + +#define ZROUTE_NAME(x) zebra_route_string(x) + +/* Originate Type-5 and Type-7 LSA */ +static struct ospf6_lsa *ospf6_originate_type5_type7_lsas( + struct ospf6_route *route, + struct ospf6 *ospf6) +{ + struct ospf6_lsa *lsa; + struct listnode *lnode; + struct ospf6_area *oa = NULL; + + lsa = ospf6_as_external_lsa_originate(route, ospf6); + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, lnode, oa)) { + if (IS_AREA_NSSA(oa)) + ospf6_nssa_lsa_originate(route, oa, true); + } + + return lsa; +} + +/* AS External LSA origination */ +struct ospf6_lsa *ospf6_as_external_lsa_originate(struct ospf6_route *route, + struct ospf6 *ospf6) +{ + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa_header *lsa_header; + struct ospf6_lsa *lsa; + struct ospf6_external_info *info = route->route_option; + + struct ospf6_as_external_lsa *as_external_lsa; + caddr_t p; + + if (ospf6->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Graceful Restart in progress, don't originate LSA"); + return NULL; + } + + if (IS_OSPF6_DEBUG_ASBR || IS_OSPF6_DEBUG_ORIGINATE(AS_EXTERNAL)) + zlog_debug("Originate AS-External-LSA for %pFX", + &route->prefix); + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + as_external_lsa = (struct ospf6_as_external_lsa + *)((caddr_t)lsa_header + + sizeof(struct ospf6_lsa_header)); + p = (caddr_t)((caddr_t)as_external_lsa + + sizeof(struct ospf6_as_external_lsa)); + + /* Fill AS-External-LSA */ + /* Metric type */ + if (route->path.metric_type == 2) + SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E); + else + UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E); + + /* forwarding address */ + if (!IN6_IS_ADDR_UNSPECIFIED(&info->forwarding)) + SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F); + else + UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F); + + /* external route tag */ + if (info->tag) + SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T); + else + UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T); + + /* Set metric */ + OSPF6_ASBR_METRIC_SET(as_external_lsa, route->path.cost); + + /* prefixlen */ + as_external_lsa->prefix.prefix_length = route->prefix.prefixlen; + + /* PrefixOptions */ + as_external_lsa->prefix.prefix_options = route->prefix_options; + + /* don't use refer LS-type */ + as_external_lsa->prefix.prefix_refer_lstype = htons(0); + + /* set Prefix */ + memcpy(p, &route->prefix.u.prefix6, + OSPF6_PREFIX_SPACE(route->prefix.prefixlen)); + ospf6_prefix_apply_mask(&as_external_lsa->prefix); + p += OSPF6_PREFIX_SPACE(route->prefix.prefixlen); + + /* Forwarding address */ + if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F)) { + memcpy(p, &info->forwarding, sizeof(struct in6_addr)); + p += sizeof(struct in6_addr); + } + + /* External Route Tag */ + if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T)) { + route_tag_t network_order = htonl(info->tag); + + memcpy(p, &network_order, sizeof(network_order)); + p += sizeof(network_order); + } + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + lsa_header->id = route->path.origin.id; + lsa_header->adv_router = ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, ospf6->lsdb); + lsa_header->length = htons((caddr_t)p - (caddr_t)lsa_header); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_process(lsa, ospf6); + + return lsa; +} + +void ospf6_orig_as_external_lsa(struct event *thread) +{ + struct ospf6_interface *oi; + struct ospf6_lsa *lsa; + uint32_t type, adv_router; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + + if (oi->state == OSPF6_INTERFACE_DOWN) + return; + if (IS_AREA_NSSA(oi->area) || IS_AREA_STUB(oi->area)) + return; + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + adv_router = oi->area->ospf6->router_id; + for (ALL_LSDB_TYPED_ADVRTR(oi->area->ospf6->lsdb, type, adv_router, + lsa)) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug( + "%s: Send update of AS-External LSA %s seq 0x%x", + __func__, lsa->name, + ntohl(lsa->header->seqnum)); + + ospf6_flood_interface(NULL, lsa, oi); + } +} + +static route_tag_t ospf6_as_external_lsa_get_tag(struct ospf6_lsa *lsa) +{ + struct ospf6_as_external_lsa *external; + ptrdiff_t tag_offset; + route_tag_t network_order; + + if (!lsa) + return 0; + + external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( + lsa->header); + + if (!CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)) + return 0; + + tag_offset = sizeof(*external) + + OSPF6_PREFIX_SPACE(external->prefix.prefix_length); + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) + tag_offset += sizeof(struct in6_addr); + + memcpy(&network_order, (caddr_t)external + tag_offset, + sizeof(network_order)); + return ntohl(network_order); +} + +void ospf6_asbr_update_route_ecmp_path(struct ospf6_route *old, + struct ospf6_route *route, + struct ospf6 *ospf6) +{ + struct ospf6_route *old_route, *next_route; + struct ospf6_path *ecmp_path, *o_path = NULL; + struct listnode *anode, *anext; + struct listnode *nnode, *rnode, *rnext; + struct ospf6_nexthop *nh, *rnh; + bool route_found = false; + + /* check for old entry match with new route origin, + * delete old entry. + */ + for (old_route = old; old_route; old_route = next_route) { + bool route_updated = false; + + next_route = old_route->next; + + /* The route linked-list is grouped in batches of prefix. + * If the new prefix is not the same as the one of interest + * then we have walked over the end of the batch and so we + * should break rather than continuing unnecessarily. + */ + if (!ospf6_route_is_same(old_route, route)) + break; + if (old_route->path.type != route->path.type) + continue; + + /* Current and New route has same origin, + * delete old entry. + */ + for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, + o_path)) { + /* Check old route path and route has same + * origin. + */ + if (o_path->area_id != route->path.area_id + || !ospf6_ls_origin_same(o_path, &route->path)) + continue; + + /* Cost is not same then delete current path */ + if ((o_path->cost == route->path.cost) + && (o_path->u.cost_e2 == route->path.u.cost_e2)) + continue; + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + zlog_debug( + "%s: route %pFX cost old %u new %u is not same, replace route", + __func__, &old_route->prefix, o_path->cost, + route->path.cost); + } + + /* Remove selected current rout path's nh from + * effective nh list. + */ + 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); + } + } + + listnode_delete(old_route->paths, o_path); + ospf6_path_free(o_path); + route_updated = true; + + /* Current route's path (adv_router info) is similar + * to route being added. + * Replace current route's path with paths list head. + * Update FIB with effective NHs. + */ + if (listcount(old_route->paths)) { + for (ALL_LIST_ELEMENTS(old_route->paths, + anode, anext, o_path)) { + ospf6_merge_nexthops( + old_route->nh_list, + o_path->nh_list); + } + /* Update RIB/FIB with effective + * nh_list + */ + if (ospf6->route_table->hook_add) + (*ospf6->route_table->hook_add)( + old_route); + + if (old_route->path.origin.id + == route->path.origin.id + && old_route->path.origin.adv_router + == route->path.origin + .adv_router) { + struct ospf6_path *h_path; + + h_path = (struct ospf6_path *) + listgetdata(listhead( + old_route->paths)); + old_route->path.origin.type = + h_path->origin.type; + old_route->path.origin.id = + h_path->origin.id; + old_route->path.origin.adv_router = + h_path->origin.adv_router; + } + } else { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + zlog_debug( + "%s: route %pFX old cost %u new cost %u, delete old entry.", + __func__, &old_route->prefix, + old_route->path.cost, + route->path.cost); + } + if (old == old_route) + old = next_route; + ospf6_route_remove(old_route, + ospf6->route_table); + } + } + if (route_updated) + break; + } + + /* Add new route */ + for (old_route = old; old_route; old_route = old_route->next) { + + /* The route linked-list is grouped in batches of prefix. + * If the new prefix is not the same as the one of interest + * then we have walked over the end of the batch and so we + * should break rather than continuing unnecessarily. + */ + if (!ospf6_route_is_same(old_route, route)) + break; + if (old_route->path.type != route->path.type) + continue; + + /* Old Route and New Route have Equal Cost, Merge NHs */ + if ((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)) { + zlog_debug( + "%s: old route %pFX path cost %u e2 %u", + __func__, &old_route->prefix, + old_route->path.cost, + old_route->path.u.cost_e2); + } + 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->area_id == route->path.area_id + && ospf6_ls_origin_same(o_path, &route->path)) + 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); + + /* Add the new path to route's path list */ + listnode_add_sort(old_route->paths, ecmp_path); + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + zlog_debug( + "%s: route %pFX another path added with nh %u, effective paths %u nh %u", + __func__, &route->prefix, + listcount(ecmp_path->nh_list), + old_route->paths ? listcount( + old_route->paths) + : 0, + listcount(old_route->nh_list)); + } + } else { + list_delete_all_node(o_path->nh_list); + ospf6_copy_nexthops(o_path->nh_list, + route->nh_list); + } + + /* Reset nexthop lists, rebuild from brouter table + * for each adv. router. + */ + list_delete_all_node(old_route->nh_list); + + for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, + o_path)) { + struct ospf6_route *asbr_entry; + + asbr_entry = ospf6_route_lookup( + &o_path->ls_prefix, + ospf6->brouter_table); + if (asbr_entry == NULL) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "%s: ls_prfix %pFX asbr_entry not found.", + __func__, + &old_route->prefix); + continue; + } + ospf6_route_merge_nexthops(old_route, + asbr_entry); + } + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "%s: route %pFX with effective paths %u nh %u", + __func__, &route->prefix, + old_route->paths + ? listcount(old_route->paths) + : 0, + 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); + } +} + +/* Check if the forwarding address is local address */ +static int ospf6_ase_forward_address_check(struct ospf6 *ospf6, + struct in6_addr *fwd_addr) +{ + struct listnode *anode, *node, *cnode; + struct ospf6_interface *oi; + struct ospf6_area *oa; + struct interface *ifp; + struct connected *c; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, anode, oa)) { + for (ALL_LIST_ELEMENTS_RO(oa->if_list, node, oi)) { + if (!if_is_operative(oi->interface) + || oi->type == OSPF_IFTYPE_VIRTUALLINK) + continue; + + ifp = oi->interface; + for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { + if (IPV6_ADDR_SAME(&c->address->u.prefix6, + fwd_addr)) + return 0; + } + } + } + + return 1; +} + +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, *old = NULL; + struct ospf6_path *path; + struct ospf6 *ospf6; + int type; + struct ospf6_area *oa = NULL; + struct prefix fwd_addr; + ptrdiff_t offset; + + type = ntohs(lsa->header->type); + oa = lsa->lsdb->data; + + external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( + lsa->header); + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Calculate AS-External route for %s", lsa->name); + + ospf6 = ospf6_get_by_lsdb(lsa); + + if (lsa->header->adv_router == ospf6->router_id) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Ignore self-originated AS-External-LSA"); + return; + } + + if (OSPF6_ASBR_METRIC(external) == OSPF_LS_INFINITY) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Ignore LSA with LSInfinity Metric"); + return; + } + + if (CHECK_FLAG(external->prefix.prefix_options, + OSPF6_PREFIX_OPTION_NU)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Ignore LSA with NU bit set Metric"); + return; + } + + ospf6_linkstate_prefix(lsa->header->adv_router, htonl(0), &asbr_id); + asbr_entry = ospf6_route_lookup(&asbr_id, ospf6->brouter_table); + if (asbr_entry == NULL) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("ASBR entry not found: %pFX", &asbr_id); + return; + } else { + /* The router advertising external LSA can be ASBR or ABR */ + if (!CHECK_FLAG(asbr_entry->path.router_bits, + OSPF6_ROUTER_BIT_E)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "External bit reset ASBR route entry : %pFX", + &asbr_id); + return; + } + + /* + * RFC 3101 - Section 2.5: + * "For a Type-7 LSA the matching routing table entry must + * specify an intra-area path through the LSA's originating + * NSSA". + */ + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7 + && (asbr_entry->path.area_id != oa->area_id + || asbr_entry->path.type != OSPF6_PATH_TYPE_INTRA)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "Intra-area route to NSSA ASBR not found: %pFX", + &asbr_id); + return; + } + } + + /* + * RFC 3101 - Section 2.5: + * "If the destination is a Type-7 default route (destination ID = + * DefaultDestination) and one of the following is true, then do + * nothing with this LSA and consider the next in the list: + * + * o The calculating router is a border router and the LSA has + * its P-bit clear. Appendix E describes a technique + * whereby an NSSA border router installs a Type-7 default + * LSA without propagating it. + * + * o The calculating router is a border router and is + * suppressing the import of summary routes as Type-3 + * summary-LSAs". + */ + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7 + && external->prefix.prefix_length == 0 + && CHECK_FLAG(ospf6->flag, OSPF6_FLAG_ABR) + && (CHECK_FLAG(external->prefix.prefix_options, + OSPF6_PREFIX_OPTION_P) + || oa->no_summary)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Skipping Type-7 default route"); + return; + } + + /* Check the forwarding address */ + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) { + offset = sizeof(*external) + + OSPF6_PREFIX_SPACE(external->prefix.prefix_length); + memset(&fwd_addr, 0, sizeof(fwd_addr)); + fwd_addr.family = AF_INET6; + fwd_addr.prefixlen = IPV6_MAX_BITLEN; + memcpy(&fwd_addr.u.prefix6, (caddr_t)external + offset, + sizeof(struct in6_addr)); + + if (!IN6_IS_ADDR_UNSPECIFIED(&fwd_addr.u.prefix6)) { + if (!ospf6_ase_forward_address_check( + ospf6, &fwd_addr.u.prefix6)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "Fwd address %pFX is local address", + &fwd_addr); + return; + } + + /* Find the forwarding entry */ + asbr_entry = ospf6_route_lookup_bestmatch( + &fwd_addr, ospf6->route_table); + if (asbr_entry == NULL) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "Fwd address not found: %pFX", + &fwd_addr); + return; + } + } + } + + route = ospf6_route_create(ospf6); + route->type = OSPF6_DEST_TYPE_NETWORK; + route->prefix.family = AF_INET6; + route->prefix.prefixlen = external->prefix.prefix_length; + ospf6_prefix_in6_addr(&route->prefix.u.prefix6, external, + &external->prefix); + route->prefix_options = external->prefix.prefix_options; + + route->path.area_id = asbr_entry->path.area_id; + route->path.origin.type = lsa->header->type; + route->path.origin.id = lsa->header->id; + route->path.origin.adv_router = lsa->header->adv_router; + memcpy(&route->path.ls_prefix, &asbr_id, sizeof(struct prefix)); + + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E)) { + route->path.type = OSPF6_PATH_TYPE_EXTERNAL2; + route->path.metric_type = 2; + route->path.cost = asbr_entry->path.cost; + route->path.u.cost_e2 = OSPF6_ASBR_METRIC(external); + } else { + route->path.type = OSPF6_PATH_TYPE_EXTERNAL1; + route->path.metric_type = 1; + route->path.cost = + asbr_entry->path.cost + OSPF6_ASBR_METRIC(external); + route->path.u.cost_e2 = 0; + } + + route->path.tag = ospf6_as_external_lsa_get_tag(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)) + zlog_debug( + "%s: %s %u route add %pFX cost %u(%u) nh %u", __func__, + (type == OSPF6_LSTYPE_AS_EXTERNAL) ? "AS-External" + : "NSSA", + (route->path.type == OSPF6_PATH_TYPE_EXTERNAL1) ? 1 : 2, + &route->prefix, route->path.cost, route->path.u.cost_e2, + listcount(route->nh_list)); + + if (type == OSPF6_LSTYPE_AS_EXTERNAL) + old = ospf6_route_lookup(&route->prefix, ospf6->route_table); + else if (type == OSPF6_LSTYPE_TYPE_7) + old = ospf6_route_lookup(&route->prefix, oa->route_table); + if (!old) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("%s: Adding new route", __func__); + /* Add the new route to ospf6 instance route table. */ + if (type == OSPF6_LSTYPE_AS_EXTERNAL) + ospf6_route_add(route, ospf6->route_table); + /* Add the route to the area route table */ + else if (type == OSPF6_LSTYPE_TYPE_7) { + ospf6_route_add(route, oa->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. + */ + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("%s : old route %pFX cost %u(%u) nh %u", + __func__, &route->prefix, route->path.cost, + route->path.u.cost_e2, + listcount(route->nh_list)); + ospf6_asbr_update_route_ecmp_path(old, route, ospf6); + } +} + +void ospf6_asbr_lsa_remove(struct ospf6_lsa *lsa, + struct ospf6_route *asbr_entry) +{ + struct ospf6_as_external_lsa *external; + struct prefix prefix; + struct ospf6_route *route, *nroute, *route_to_del; + struct ospf6_area *oa = NULL; + struct ospf6 *ospf6; + int type; + bool debug = false; + + external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( + lsa->header); + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL) || (IS_OSPF6_DEBUG_NSSA)) + debug = true; + + ospf6 = ospf6_get_by_lsdb(lsa); + type = ntohs(lsa->header->type); + + if (type == OSPF6_LSTYPE_TYPE_7) { + if (debug) + zlog_debug("%s: Withdraw Type 7 route for %s", + __func__, lsa->name); + oa = lsa->lsdb->data; + } else { + if (debug) + zlog_debug("%s: Withdraw AS-External route for %s", + __func__, lsa->name); + + if (ospf6_check_and_set_router_abr(ospf6)) + oa = ospf6->backbone; + else + oa = listnode_head(ospf6->area_list); + } + + if (oa == NULL) { + if (debug) + zlog_debug("%s: Invalid area", __func__); + return; + } + + if (lsa->header->adv_router == oa->ospf6->router_id) { + if (debug) + zlog_debug("Ignore self-originated AS-External-LSA"); + return; + } + + route_to_del = ospf6_route_create(ospf6); + route_to_del->type = OSPF6_DEST_TYPE_NETWORK; + route_to_del->prefix.family = AF_INET6; + route_to_del->prefix.prefixlen = external->prefix.prefix_length; + ospf6_prefix_in6_addr(&route_to_del->prefix.u.prefix6, external, + &external->prefix); + + route_to_del->path.origin.type = lsa->header->type; + route_to_del->path.origin.id = lsa->header->id; + route_to_del->path.origin.adv_router = lsa->header->adv_router; + + if (asbr_entry) { + route_to_del->path.area_id = asbr_entry->path.area_id; + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E)) { + route_to_del->path.type = OSPF6_PATH_TYPE_EXTERNAL2; + route_to_del->path.metric_type = 2; + route_to_del->path.cost = asbr_entry->path.cost; + route_to_del->path.u.cost_e2 = + OSPF6_ASBR_METRIC(external); + } else { + route_to_del->path.type = OSPF6_PATH_TYPE_EXTERNAL1; + route_to_del->path.metric_type = 1; + route_to_del->path.cost = asbr_entry->path.cost + + OSPF6_ASBR_METRIC(external); + route_to_del->path.u.cost_e2 = 0; + } + } + + memset(&prefix, 0, sizeof(struct prefix)); + prefix.family = AF_INET6; + prefix.prefixlen = external->prefix.prefix_length; + ospf6_prefix_in6_addr(&prefix.u.prefix6, external, &external->prefix); + + if (type == OSPF6_LSTYPE_TYPE_7) + route = ospf6_route_lookup(&prefix, oa->route_table); + else + route = ospf6_route_lookup(&prefix, oa->ospf6->route_table); + + if (route == NULL) { + if (debug) + zlog_debug("AS-External route %pFX not found", &prefix); + ospf6_route_delete(route_to_del); + return; + } + + if (debug) + zlog_debug( + "%s: Current route %pFX cost %u e2 %u, route to del cost %u e2 %u", + __func__, &prefix, route->path.cost, route->path.u.cost_e2, + route_to_del->path.cost, route_to_del->path.u.cost_e2); + + for (ospf6_route_lock(route); + route && ospf6_route_is_prefix(&prefix, route); route = nroute) { + nroute = ospf6_route_next(route); + + if (route->type != OSPF6_DEST_TYPE_NETWORK) + continue; + + /* Route has multiple ECMP paths, remove matching + * path. Update current route's effective nh list + * after removal of one of the path. + */ + 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) + || (o_path->origin.adv_router + != lsa->header->adv_router) + || (o_path->origin.id != lsa->header->id)) + continue; + + /* Compare LSA cost with current + * route info. + */ + if (asbr_entry + && (o_path->cost != route_to_del->path.cost + || o_path->u.cost_e2 + != route_to_del->path.u + .cost_e2)) { + if (IS_OSPF6_DEBUG_EXAMIN( + AS_EXTERNAL)) { + zlog_debug( + "%s: route %pFX to delete is not same, cost %u del cost %u. skip", + __func__, &prefix, + route->path.cost, + route_to_del->path + .cost); + } + continue; + } + + if (debug) { + zlog_debug( + "%s: route %pFX path found with cost %u nh %u to remove.", + __func__, &prefix, route->path.cost, + 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 (debug) { + zlog_debug( + "%s: AS-External %u route %pFX update paths %u nh %u", + __func__, + (route->path.type + == OSPF6_PATH_TYPE_EXTERNAL1) + ? 1 + : 2, + &route->prefix, listcount(route->paths), + route->nh_list ? listcount( + route->nh_list) + : 0); + } + + if (listcount(route->paths)) { + /* Update RIB/FIB with effective + * nh_list + */ + if (oa->ospf6->route_table->hook_add) + (*oa->ospf6->route_table + ->hook_add)(route); + + /* route's primary path is similar + * to LSA, replace route's primary + * 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; + } + } else { + if (type == OSPF6_LSTYPE_TYPE_7) + ospf6_route_remove( + route, oa->route_table); + else + ospf6_route_remove( + route, + oa->ospf6->route_table); + } + } + continue; + + } else { + /* Compare LSA origin and cost with current route info. + * if any check fails skip del this route node. + */ + if (asbr_entry + && (!ospf6_route_is_same_origin(route, route_to_del) + || (route->path.type != route_to_del->path.type) + || (route->path.cost != route_to_del->path.cost) + || (route->path.u.cost_e2 + != route_to_del->path.u.cost_e2))) { + if (debug) { + zlog_debug( + "%s: route %pFX to delete is not same, cost %u del cost %u. skip", + __func__, &prefix, route->path.cost, + route_to_del->path.cost); + } + continue; + } + + if ((route->path.origin.type != lsa->header->type) + || (route->path.origin.adv_router + != lsa->header->adv_router) + || (route->path.origin.id != lsa->header->id)) + continue; + } + if (debug) { + zlog_debug( + "%s: AS-External %u route remove %pFX cost %u(%u) nh %u", + __func__, + route->path.type == OSPF6_PATH_TYPE_EXTERNAL1 + ? 1 + : 2, + &route->prefix, route->path.cost, route->path.u.cost_e2, + listcount(route->nh_list)); + } + if (type == OSPF6_LSTYPE_TYPE_7) + ospf6_route_remove(route, oa->route_table); + else + ospf6_route_remove(route, oa->ospf6->route_table); + } + if (route != NULL) + ospf6_route_unlock(route); + + ospf6_route_delete(route_to_del); +} + +void ospf6_asbr_lsentry_add(struct ospf6_route *asbr_entry, struct ospf6 *ospf6) +{ + struct ospf6_lsa *lsa; + uint16_t type; + uint32_t router; + + if (!CHECK_FLAG(asbr_entry->flag, OSPF6_ROUTE_BEST)) { + char buf[16]; + inet_ntop(AF_INET, &ADV_ROUTER_IN_PREFIX(&asbr_entry->prefix), + buf, sizeof(buf)); + zlog_info("ignore non-best path: lsentry %s add", buf); + return; + } + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + router = ospf6_linkstate_prefix_adv_router(&asbr_entry->prefix); + for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, router, lsa)) { + if (!OSPF6_LSA_IS_MAXAGE(lsa)) + ospf6_asbr_lsa_add(lsa); + } +} + +void ospf6_asbr_lsentry_remove(struct ospf6_route *asbr_entry, + struct ospf6 *ospf6) +{ + struct ospf6_lsa *lsa; + uint16_t type; + uint32_t router; + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + router = ospf6_linkstate_prefix_adv_router(&asbr_entry->prefix); + for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, router, lsa)) + ospf6_asbr_lsa_remove(lsa, asbr_entry); +} + + +/* redistribute function */ +static void ospf6_asbr_routemap_set(struct ospf6_redist *red, + const char *mapname) +{ + if (ROUTEMAP_NAME(red)) { + route_map_counter_decrement(ROUTEMAP(red)); + free(ROUTEMAP_NAME(red)); + } + + ROUTEMAP_NAME(red) = strdup(mapname); + ROUTEMAP(red) = route_map_lookup_by_name(mapname); + route_map_counter_increment(ROUTEMAP(red)); +} + +static void ospf6_asbr_routemap_unset(struct ospf6_redist *red) +{ + if (ROUTEMAP_NAME(red)) + free(ROUTEMAP_NAME(red)); + + route_map_counter_decrement(ROUTEMAP(red)); + + ROUTEMAP_NAME(red) = NULL; + ROUTEMAP(red) = NULL; +} + +static void ospf6_asbr_routemap_update_timer(struct event *thread) +{ + struct ospf6 *ospf6 = EVENT_ARG(thread); + struct ospf6_redist *red; + int type; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + + if (!red) + continue; + + if (!CHECK_FLAG(red->flag, OSPF6_IS_RMAP_CHANGED)) + continue; + + if (ROUTEMAP_NAME(red)) + ROUTEMAP(red) = + route_map_lookup_by_name(ROUTEMAP_NAME(red)); + + if (ROUTEMAP(red)) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug( + "%s: route-map %s update, reset redist %s", + __func__, ROUTEMAP_NAME(red), + ZROUTE_NAME(type)); + + ospf6_zebra_no_redistribute(type, ospf6->vrf_id); + ospf6_zebra_redistribute(type, ospf6->vrf_id); + } + + UNSET_FLAG(red->flag, OSPF6_IS_RMAP_CHANGED); + } +} + +void ospf6_asbr_distribute_list_update(struct ospf6 *ospf6, + struct ospf6_redist *red) +{ + SET_FLAG(red->flag, OSPF6_IS_RMAP_CHANGED); + + if (event_is_scheduled(ospf6->t_distribute_update)) + return; + + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("%s: trigger redistribute reset thread", __func__); + + event_add_timer_msec(master, ospf6_asbr_routemap_update_timer, ospf6, + OSPF_MIN_LS_INTERVAL, &ospf6->t_distribute_update); +} + +void ospf6_asbr_routemap_update(const char *mapname) +{ + int type; + struct listnode *node, *nnode; + struct ospf6 *ospf6 = NULL; + struct ospf6_redist *red; + + if (om6 == NULL) + return; + + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red || (ROUTEMAP_NAME(red) == NULL)) + continue; + ROUTEMAP(red) = + route_map_lookup_by_name(ROUTEMAP_NAME(red)); + + if (mapname == NULL + || strcmp(ROUTEMAP_NAME(red), mapname)) + continue; + if (ROUTEMAP(red)) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug( + "%s: route-map %s update, reset redist %s", + __func__, + mapname, + ZROUTE_NAME( + type)); + + route_map_counter_increment(ROUTEMAP(red)); + ospf6_asbr_distribute_list_update(ospf6, red); + } else { + /* + * if the mapname matches a + * route-map on ospf6 but the + * map doesn't exist, it is + * being deleted. flush and then + * readvertise + */ + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug( + "%s: route-map %s deleted, reset redist %s", + __func__, + mapname, + ZROUTE_NAME( + type)); + ospf6_asbr_redistribute_unset(ospf6, red, type); + ospf6_asbr_routemap_set(red, mapname); + ospf6_asbr_redistribute_set(ospf6, type); + } + } + } +} + +static void ospf6_asbr_routemap_event(const char *name) +{ + int type; + struct listnode *node, *nnode; + struct ospf6 *ospf6; + struct ospf6_redist *red; + + if (om6 == NULL) + return; + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + if (red && ROUTEMAP_NAME(red) + && (strcmp(ROUTEMAP_NAME(red), name) == 0)) + ospf6_asbr_distribute_list_update(ospf6, red); + } + } +} + +int ospf6_asbr_is_asbr(struct ospf6 *o) +{ + return (o->external_table->count || IS_OSPF6_ASBR(o)); +} + +struct ospf6_redist *ospf6_redist_lookup(struct ospf6 *ospf6, int type, + unsigned short instance) +{ + struct list *red_list; + struct listnode *node; + struct ospf6_redist *red; + + red_list = ospf6->redist[type]; + if (!red_list) + return (NULL); + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) + if (red->instance == instance) + return red; + + return NULL; +} + +static struct ospf6_redist *ospf6_redist_add(struct ospf6 *ospf6, int type, + uint8_t instance) +{ + struct ospf6_redist *red; + + red = ospf6_redist_lookup(ospf6, type, instance); + if (red) + return red; + + if (!ospf6->redist[type]) + ospf6->redist[type] = list_new(); + + red = XCALLOC(MTYPE_OSPF6_REDISTRIBUTE, sizeof(struct ospf6_redist)); + red->instance = instance; + red->dmetric.type = -1; + red->dmetric.value = -1; + ROUTEMAP_NAME(red) = NULL; + ROUTEMAP(red) = NULL; + + listnode_add(ospf6->redist[type], red); + ospf6->redistribute++; + + return red; +} + +static void ospf6_redist_del(struct ospf6 *ospf6, struct ospf6_redist *red, + int type) +{ + if (red) { + listnode_delete(ospf6->redist[type], red); + if (!ospf6->redist[type]->count) { + list_delete(&ospf6->redist[type]); + } + XFREE(MTYPE_OSPF6_REDISTRIBUTE, red); + ospf6->redistribute--; + } +} + +/*Set the status of the ospf instance to ASBR based on the status parameter, + * rechedule SPF calculation, originate router LSA*/ +void ospf6_asbr_status_update(struct ospf6 *ospf6, int status) +{ + struct listnode *lnode, *lnnode; + struct ospf6_area *oa; + + zlog_info("ASBR[%s:Status:%d]: Update", ospf6->name, status); + + if (status) { + if (IS_OSPF6_ASBR(ospf6)) { + zlog_info("ASBR[%s:Status:%d]: Already ASBR", + ospf6->name, status); + return; + } + SET_FLAG(ospf6->flag, OSPF6_FLAG_ASBR); + } else { + if (!IS_OSPF6_ASBR(ospf6)) { + zlog_info("ASBR[%s:Status:%d]: Already non ASBR", + ospf6->name, status); + return; + } + UNSET_FLAG(ospf6->flag, OSPF6_FLAG_ASBR); + } + + /* Transition from/to status ASBR, schedule timer. */ + ospf6_spf_schedule(ospf6, OSPF6_SPF_FLAGS_ASBR_STATUS_CHANGE); + + /* Reoriginate router LSA for all areas */ + for (ALL_LIST_ELEMENTS(ospf6->area_list, lnode, lnnode, oa)) + OSPF6_ROUTER_LSA_SCHEDULE(oa); +} + +static void ospf6_asbr_redistribute_set(struct ospf6 *ospf6, int type) +{ + ospf6_zebra_redistribute(type, ospf6->vrf_id); + + ++ospf6->redist_count; + ospf6_asbr_status_update(ospf6, ospf6->redist_count); +} + +static void ospf6_asbr_redistribute_unset(struct ospf6 *ospf6, + struct ospf6_redist *red, int type) +{ + struct ospf6_route *route; + struct ospf6_external_info *info; + + ospf6_zebra_no_redistribute(type, ospf6->vrf_id); + + for (route = ospf6_route_head(ospf6->external_table); route; + route = ospf6_route_next(route)) { + info = route->route_option; + if (info->type != type) + continue; + + ospf6_asbr_redistribute_remove(info->type, 0, &route->prefix, + ospf6); + } + + ospf6_asbr_routemap_unset(red); + --ospf6->redist_count; + ospf6_asbr_status_update(ospf6, ospf6->redist_count); +} + +/* When an area is unstubified, flood all the external LSAs in the area */ +void ospf6_asbr_send_externals_to_area(struct ospf6_area *oa) +{ + struct ospf6_lsa *lsa, *lsanext; + + for (ALL_LSDB(oa->ospf6->lsdb, lsa, lsanext)) { + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("%s: Flooding AS-External LSA %s", + __func__, lsa->name); + + ospf6_flood_area(NULL, lsa, oa); + } + } +} + +/* When an area is stubified, remove all the external LSAs in the area */ +void ospf6_asbr_remove_externals_from_area(struct ospf6_area *oa) +{ + struct ospf6_lsa *lsa, *lsanext; + struct listnode *node, *nnode; + struct ospf6_area *area; + struct ospf6 *ospf6 = oa->ospf6; + const struct route_node *iterend; + + /* skip if router is in other non-stub/non-NSSA areas */ + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, area)) + if (!IS_AREA_STUB(area) && !IS_AREA_NSSA(area)) + return; + + /* if router is only in a stub area then purge AS-External LSAs */ + iterend = ospf6_lsdb_head(ospf6->lsdb, 0, 0, 0, &lsa); + while (lsa != NULL) { + assert(lsa->lock > 1); + lsanext = ospf6_lsdb_next(iterend, lsa); + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL) + ospf6_lsdb_remove(lsa, ospf6->lsdb); + lsa = lsanext; + } +} + +static struct ospf6_external_aggr_rt * +ospf6_external_aggr_match(struct ospf6 *ospf6, struct prefix *p) +{ + struct route_node *node; + + node = route_node_match(ospf6->rt_aggr_tbl, p); + if (node == NULL) + return NULL; + + if (IS_OSPF6_DEBUG_AGGR) { + struct ospf6_external_aggr_rt *ag = node->info; + zlog_debug("%s: Matching aggregator found.prefix: %pFX Aggregator %pFX", + __func__, + p, + &ag->p); + } + + route_unlock_node(node); + + return node->info; +} + +static void ospf6_external_lsa_fwd_addr_set(struct ospf6 *ospf6, + const struct in6_addr *nexthop, + struct in6_addr *fwd_addr) +{ + struct vrf *vrf; + struct interface *ifp; + struct prefix nh; + + /* Initialize forwarding address to zero. */ + memset(fwd_addr, 0, sizeof(*fwd_addr)); + + vrf = vrf_lookup_by_id(ospf6->vrf_id); + if (!vrf) + return; + + nh.family = AF_INET6; + nh.u.prefix6 = *nexthop; + nh.prefixlen = IPV6_MAX_BITLEN; + + /* + * Use the route's nexthop as the forwarding address if it meets the + * following conditions: + * - It's a global address. + * - The associated nexthop interface is OSPF-enabled. + */ + if (IN6_IS_ADDR_UNSPECIFIED(nexthop) || IN6_IS_ADDR_LINKLOCAL(nexthop)) + return; + + FOR_ALL_INTERFACES (vrf, ifp) { + struct ospf6_interface *oi = ifp->info; + struct connected *connected; + struct listnode *node; + + if (!oi || CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) + continue; + + FOR_ALL_INTERFACES_ADDRESSES (ifp, connected, node) { + if (connected->address->family != AF_INET6) + continue; + if (IN6_IS_ADDR_LINKLOCAL(&connected->address->u.prefix6)) + continue; + if (!prefix_match(connected->address, &nh)) + continue; + + *fwd_addr = *nexthop; + return; + } + } +} + +void ospf6_asbr_redistribute_add(int type, ifindex_t ifindex, + struct prefix *prefix, + unsigned int nexthop_num, + const struct in6_addr *nexthop, + route_tag_t tag, struct ospf6 *ospf6) +{ + route_map_result_t ret; + struct ospf6_route troute; + struct ospf6_external_info tinfo; + struct ospf6_route *route, *match; + struct ospf6_external_info *info; + struct ospf6_redist *red; + + red = ospf6_redist_lookup(ospf6, type, 0); + + if (!red) + return; + + if ((type != DEFAULT_ROUTE) + && !ospf6_zebra_is_redistribute(type, ospf6->vrf_id)) + return; + + memset(&troute, 0, sizeof(troute)); + memset(&tinfo, 0, sizeof(tinfo)); + + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("Redistribute %pFX (%s)", prefix, + type == DEFAULT_ROUTE + ? "default-information-originate" + : ZROUTE_NAME(type)); + + /* if route-map was specified but not found, do not advertise */ + if (ROUTEMAP_NAME(red)) { + if (ROUTEMAP(red) == NULL) + ospf6_asbr_routemap_update(NULL); + if (ROUTEMAP(red) == NULL) { + zlog_warn( + "route-map \"%s\" not found, suppress redistributing", + ROUTEMAP_NAME(red)); + return; + } + } + + /* apply route-map */ + if (ROUTEMAP(red)) { + troute.route_option = &tinfo; + troute.ospf6 = ospf6; + tinfo.ifindex = ifindex; + tinfo.tag = tag; + + ret = route_map_apply(ROUTEMAP(red), prefix, &troute); + if (ret == RMAP_DENYMATCH) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("Denied by route-map \"%s\"", + ROUTEMAP_NAME(red)); + ospf6_asbr_redistribute_remove(type, ifindex, prefix, + ospf6); + return; + } + } + + match = ospf6_route_lookup(prefix, ospf6->external_table); + if (match) { + info = match->route_option; + /* copy result of route-map */ + if (ROUTEMAP(red)) { + if (troute.path.metric_type) + match->path.metric_type = + troute.path.metric_type; + else + match->path.metric_type = + metric_type(ospf6, type, 0); + if (troute.path.cost) + match->path.cost = troute.path.cost; + else + match->path.cost = metric_value(ospf6, type, 0); + + if (!IN6_IS_ADDR_UNSPECIFIED(&tinfo.forwarding)) + memcpy(&info->forwarding, &tinfo.forwarding, + sizeof(struct in6_addr)); + info->tag = tinfo.tag; + } else { + /* If there is no route-map, simply update the tag and + * metric fields + */ + match->path.metric_type = metric_type(ospf6, type, 0); + match->path.cost = metric_value(ospf6, type, 0); + info->tag = tag; + } + + info->type = type; + + if (nexthop_num && nexthop) { + ospf6_route_add_nexthop(match, ifindex, nexthop); + ospf6_external_lsa_fwd_addr_set(ospf6, nexthop, + &info->forwarding); + } else + ospf6_route_add_nexthop(match, ifindex, NULL); + + match->path.origin.id = htonl(info->id); + ospf6_handle_external_lsa_origination(ospf6, match, prefix); + + ospf6_asbr_status_update(ospf6, ospf6->redistribute); + + return; + } + + /* create new entry */ + route = ospf6_route_create(ospf6); + route->type = OSPF6_DEST_TYPE_NETWORK; + prefix_copy(&route->prefix, prefix); + + info = (struct ospf6_external_info *)XCALLOC( + MTYPE_OSPF6_EXTERNAL_INFO, sizeof(struct ospf6_external_info)); + route->route_option = info; + + /* copy result of route-map */ + if (ROUTEMAP(red)) { + if (troute.path.metric_type) + route->path.metric_type = troute.path.metric_type; + else + route->path.metric_type = metric_type(ospf6, type, 0); + if (troute.path.cost) + route->path.cost = troute.path.cost; + else + route->path.cost = metric_value(ospf6, type, 0); + if (!IN6_IS_ADDR_UNSPECIFIED(&tinfo.forwarding)) + memcpy(&info->forwarding, &tinfo.forwarding, + sizeof(struct in6_addr)); + info->tag = tinfo.tag; + } else { + /* If there is no route-map, simply update the tag and metric + * fields + */ + route->path.metric_type = metric_type(ospf6, type, 0); + route->path.cost = metric_value(ospf6, type, 0); + info->tag = tag; + } + + info->type = type; + if (nexthop_num && nexthop) { + ospf6_route_add_nexthop(route, ifindex, nexthop); + ospf6_external_lsa_fwd_addr_set(ospf6, nexthop, + &info->forwarding); + } else + ospf6_route_add_nexthop(route, ifindex, NULL); + + route = ospf6_route_add(route, ospf6->external_table); + ospf6_handle_external_lsa_origination(ospf6, route, prefix); + + ospf6_asbr_status_update(ospf6, ospf6->redistribute); + +} + +static void ospf6_asbr_external_lsa_remove_by_id(struct ospf6 *ospf6, + uint32_t id) +{ + struct ospf6_lsa *lsa; + + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(id), ospf6->router_id, ospf6->lsdb); + if (!lsa) + return; + + ospf6_external_lsa_purge(ospf6, lsa); + +} + +static void +ospf6_link_route_to_aggr(struct ospf6_external_aggr_rt *aggr, + struct ospf6_route *rt) +{ + (void)hash_get(aggr->match_extnl_hash, rt, hash_alloc_intern); + rt->aggr_route = aggr; +} + +static void +ospf6_asbr_summary_remove_lsa_and_route(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + + /* Send a Max age LSA if it is already originated.*/ + if (!CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED)) + return; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Flushing Aggregate route (%pFX)", + __func__, + &aggr->p); + + ospf6_asbr_external_lsa_remove_by_id(ospf6, aggr->id); + + if (aggr->route) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "%s: Remove the blackhole route", + __func__); + + ospf6_zebra_route_update_remove(aggr->route, ospf6); + if (aggr->route->route_option) + XFREE(MTYPE_OSPF6_EXTERNAL_INFO, + aggr->route->route_option); + ospf6_route_delete(aggr->route); + aggr->route = NULL; + } + + aggr->id = 0; + /* Unset the Origination flag */ + UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); +} + +static void +ospf6_unlink_route_from_aggr(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr, + struct ospf6_route *rt) +{ + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Unlinking external route(%pFX) from aggregator(%pFX), external route count:%ld", + __func__, + &rt->prefix, + &aggr->p, + OSPF6_EXTERNAL_RT_COUNT(aggr)); + + hash_release(aggr->match_extnl_hash, rt); + rt->aggr_route = NULL; + + /* Flush the aggregate route if matching + * external route count becomes zero. + */ + if (!OSPF6_EXTERNAL_RT_COUNT(aggr)) + ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr); +} + +void ospf6_asbr_redistribute_remove(int type, ifindex_t ifindex, + struct prefix *prefix, struct ospf6 *ospf6) +{ + struct ospf6_route *match; + struct ospf6_external_info *info = NULL; + + match = ospf6_route_lookup(prefix, ospf6->external_table); + if (match == NULL) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("No such route %pFX to withdraw", prefix); + return; + } + + info = match->route_option; + assert(info); + + if (info->type != type) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("Original protocol mismatch: %pFX", prefix); + return; + } + + /* This means aggregation on this route was not done, hence remove LSA + * if any originated for this prefix + */ + if (!match->aggr_route) + ospf6_asbr_external_lsa_remove_by_id(ospf6, info->id); + else + ospf6_unlink_route_from_aggr(ospf6, match->aggr_route, match); + + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("Removing route from external table %pFX", + prefix); + + ospf6_route_remove(match, ospf6->external_table); + XFREE(MTYPE_OSPF6_EXTERNAL_INFO, info); + + ospf6_asbr_status_update(ospf6, ospf6->redistribute); +} + +DEFPY (ospf6_redistribute, + ospf6_redistribute_cmd, + "redistribute " FRR_REDIST_STR_OSPF6D "[{metric (0-16777214)|metric-type (1-2)$metric_type|route-map RMAP_NAME$rmap_str}]", + "Redistribute\n" + FRR_REDIST_HELP_STR_OSPF6D + "Metric for redistributed routes\n" + "OSPF default metric\n" + "OSPF exterior metric type for redistributed routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Route map name\n") +{ + int type; + struct ospf6_redist *red; + int idx_protocol = 1; + char *proto = argv[idx_protocol]->text; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + type = proto_redistnum(AFI_IP6, proto); + if (type < 0) + return CMD_WARNING_CONFIG_FAILED; + + if (!metric_str) + metric = -1; + if (!metric_type_str) + metric_type = -1; + + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) { + red = ospf6_redist_add(ospf6, type, 0); + } else { + /* Check if nothing has changed. */ + if (red->dmetric.value == metric + && red->dmetric.type == metric_type + && ((!ROUTEMAP_NAME(red) && !rmap_str) + || (ROUTEMAP_NAME(red) && rmap_str + && strmatch(ROUTEMAP_NAME(red), rmap_str)))) + return CMD_SUCCESS; + + ospf6_asbr_redistribute_unset(ospf6, red, type); + } + + red->dmetric.value = metric; + red->dmetric.type = metric_type; + if (rmap_str) + ospf6_asbr_routemap_set(red, rmap_str); + else + ospf6_asbr_routemap_unset(red); + ospf6_asbr_redistribute_set(ospf6, type); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_redistribute, + no_ospf6_redistribute_cmd, + "no redistribute " FRR_REDIST_STR_OSPF6D "[{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + NO_STR + "Redistribute\n" + FRR_REDIST_HELP_STR_OSPF6D + "Metric for redistributed routes\n" + "OSPF default metric\n" + "OSPF exterior metric type for redistributed routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Route map name\n") +{ + int type; + struct ospf6_redist *red; + int idx_protocol = 2; + char *proto = argv[idx_protocol]->text; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + type = proto_redistnum(AFI_IP6, proto); + if (type < 0) + return CMD_WARNING_CONFIG_FAILED; + + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) + return CMD_SUCCESS; + + ospf6_asbr_redistribute_unset(ospf6, red, type); + ospf6_redist_del(ospf6, red, type); + + return CMD_SUCCESS; +} + +int ospf6_redistribute_config_write(struct vty *vty, struct ospf6 *ospf6) +{ + int type; + struct ospf6_redist *red; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) + continue; + if (type == ZEBRA_ROUTE_OSPF6) + continue; + + vty_out(vty, " redistribute %s", ZROUTE_NAME(type)); + if (red->dmetric.value >= 0) + vty_out(vty, " metric %d", red->dmetric.value); + if (red->dmetric.type == 1) + vty_out(vty, " metric-type 1"); + if (ROUTEMAP_NAME(red)) + vty_out(vty, " route-map %s", ROUTEMAP_NAME(red)); + vty_out(vty, "\n"); + } + + return 0; +} + +static void ospf6_redistribute_show_config(struct vty *vty, struct ospf6 *ospf6, + json_object *json_array, + json_object *json, bool use_json) +{ + int type; + int nroute[ZEBRA_ROUTE_MAX]; + int total; + struct ospf6_route *route; + struct ospf6_external_info *info; + json_object *json_route; + struct ospf6_redist *red; + + total = 0; + memset(nroute, 0, sizeof(nroute)); + for (route = ospf6_route_head(ospf6->external_table); route; + route = ospf6_route_next(route)) { + info = route->route_option; + nroute[info->type]++; + total++; + } + + if (!use_json) + vty_out(vty, "Redistributing External Routes from:\n"); + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + + red = ospf6_redist_lookup(ospf6, type, 0); + + if (!red) + continue; + if (type == ZEBRA_ROUTE_OSPF6) + continue; + + if (use_json) { + json_route = json_object_new_object(); + json_object_string_add(json_route, "routeType", + ZROUTE_NAME(type)); + json_object_int_add(json_route, "numberOfRoutes", + nroute[type]); + json_object_boolean_add(json_route, + "routeMapNamePresent", + ROUTEMAP_NAME(red)); + } + + if (ROUTEMAP_NAME(red)) { + if (use_json) { + json_object_string_add(json_route, + "routeMapName", + ROUTEMAP_NAME(red)); + json_object_boolean_add(json_route, + "routeMapFound", + ROUTEMAP(red)); + } else + vty_out(vty, + " %d: %s with route-map \"%s\"%s\n", + nroute[type], ZROUTE_NAME(type), + ROUTEMAP_NAME(red), + (ROUTEMAP(red) ? "" + : " (not found !)")); + } else { + if (!use_json) + vty_out(vty, " %d: %s\n", nroute[type], + ZROUTE_NAME(type)); + } + + if (use_json) + json_object_array_add(json_array, json_route); + } + if (use_json) { + json_object_object_add(json, "redistributedRoutes", json_array); + json_object_int_add(json, "totalRoutes", total); + } else + vty_out(vty, "Total %d routes\n", total); +} + +static void ospf6_redistribute_default_set(struct ospf6 *ospf6, int originate) +{ + struct prefix_ipv6 p = {}; + struct in6_addr nexthop = {}; + int cur_originate = ospf6->default_originate; + + p.family = AF_INET6; + p.prefixlen = 0; + + ospf6->default_originate = originate; + + switch (cur_originate) { + case DEFAULT_ORIGINATE_NONE: + break; + case DEFAULT_ORIGINATE_ZEBRA: + zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, + zclient, AFI_IP6, ospf6->vrf_id); + ospf6_asbr_redistribute_remove(DEFAULT_ROUTE, 0, + (struct prefix *)&p, ospf6); + + break; + case DEFAULT_ORIGINATE_ALWAYS: + ospf6_asbr_redistribute_remove(DEFAULT_ROUTE, 0, + (struct prefix *)&p, ospf6); + break; + } + + switch (originate) { + case DEFAULT_ORIGINATE_NONE: + break; + case DEFAULT_ORIGINATE_ZEBRA: + zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_ADD, + zclient, AFI_IP6, ospf6->vrf_id); + + break; + case DEFAULT_ORIGINATE_ALWAYS: + ospf6_asbr_redistribute_add(DEFAULT_ROUTE, 0, + (struct prefix *)&p, 0, &nexthop, 0, + ospf6); + break; + } +} + +/* Default Route originate. */ +DEFPY (ospf6_default_route_originate, + ospf6_default_route_originate_cmd, + "default-information originate [{always$always|metric (0-16777214)$mval|metric-type (1-2)$mtype|route-map RMAP_NAME$rtmap}]", + "Control distribution of default route\n" + "Distribute a default route\n" + "Always advertise default route\n" + "OSPFv3 default metric\n" + "OSPFv3 metric\n" + "OSPFv3 metric type for default routes\n" + "Set OSPFv3 External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + int default_originate = DEFAULT_ORIGINATE_ZEBRA; + struct ospf6_redist *red; + bool sameRtmap = false; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + int cur_originate = ospf6->default_originate; + + red = ospf6_redist_add(ospf6, DEFAULT_ROUTE, 0); + + if (always != NULL) + default_originate = DEFAULT_ORIGINATE_ALWAYS; + + if (mval_str == NULL) + mval = -1; + + if (mtype_str == NULL) + mtype = -1; + + /* To check if user is providing same route map */ + if ((!rtmap && !ROUTEMAP_NAME(red)) || + (rtmap && ROUTEMAP_NAME(red) && + (strcmp(rtmap, ROUTEMAP_NAME(red)) == 0))) + sameRtmap = true; + + /* Don't allow if the same lsa is already originated. */ + if ((sameRtmap) && (red->dmetric.type == mtype) + && (red->dmetric.value == mval) + && (cur_originate == default_originate)) + return CMD_SUCCESS; + + /* Updating Metric details */ + red->dmetric.type = mtype; + red->dmetric.value = mval; + + /* updating route map details */ + if (rtmap) + ospf6_asbr_routemap_set(red, rtmap); + else + ospf6_asbr_routemap_unset(red); + + ospf6_redistribute_default_set(ospf6, default_originate); + return CMD_SUCCESS; +} + +DEFPY (no_ospf6_default_information_originate, + no_ospf6_default_information_originate_cmd, + "no default-information originate [{always|metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + NO_STR + "Control distribution of default information\n" + "Distribute a default route\n" + "Always advertise default route\n" + "OSPFv3 default metric\n" + "OSPFv3 metric\n" + "OSPFv3 metric type for default routes\n" + "Set OSPFv3 External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + struct ospf6_redist *red; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + red = ospf6_redist_lookup(ospf6, DEFAULT_ROUTE, 0); + if (!red) + return CMD_SUCCESS; + + ospf6_asbr_routemap_unset(red); + ospf6_redist_del(ospf6, red, DEFAULT_ROUTE); + + ospf6_redistribute_default_set(ospf6, DEFAULT_ORIGINATE_NONE); + return CMD_SUCCESS; +} + +/* Routemap Functions */ +static enum route_map_cmd_result_t +ospf6_routemap_rule_match_address_prefixlist(void *rule, + const struct prefix *prefix, + + void *object) +{ + struct prefix_list *plist; + + plist = prefix_list_lookup(AFI_IP6, (char *)rule); + if (plist == NULL) + return RMAP_NOMATCH; + + return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +static void * +ospf6_routemap_rule_match_address_prefixlist_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void ospf6_routemap_rule_match_address_prefixlist_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_match_address_prefixlist_cmd = { + "ipv6 address prefix-list", + ospf6_routemap_rule_match_address_prefixlist, + ospf6_routemap_rule_match_address_prefixlist_compile, + ospf6_routemap_rule_match_address_prefixlist_free, +}; + +/* `match interface IFNAME' */ +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +ospf6_routemap_rule_match_interface(void *rule, const struct prefix *prefix, + void *object) +{ + struct interface *ifp; + struct ospf6_route *route; + struct ospf6_external_info *ei; + + route = object; + ei = route->route_option; + ifp = if_lookup_by_name((char *)rule, route->ospf6->vrf_id); + + if (ifp != NULL && ei->ifindex == ifp->ifindex) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +/* Route map `interface' match statement. `arg' should be + interface name. */ +static void *ospf6_routemap_rule_match_interface_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `interface' value. */ +static void ospf6_routemap_rule_match_interface_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for interface matching. */ +static const struct route_map_rule_cmd + ospf6_routemap_rule_match_interface_cmd = { + "interface", + ospf6_routemap_rule_match_interface, + ospf6_routemap_rule_match_interface_compile, + ospf6_routemap_rule_match_interface_free +}; + +/* Match function for matching route tags */ +static enum route_map_cmd_result_t +ospf6_routemap_rule_match_tag(void *rule, const struct prefix *p, void *object) +{ + route_tag_t *tag = rule; + struct ospf6_route *route = object; + struct ospf6_external_info *info = route->route_option; + + if (info->tag == *tag) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_match_tag_cmd = { + "tag", + ospf6_routemap_rule_match_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +static enum route_map_cmd_result_t +ospf6_routemap_rule_set_metric_type(void *rule, const struct prefix *prefix, + void *object) +{ + char *metric_type = rule; + struct ospf6_route *route = object; + + if (strcmp(metric_type, "type-2") == 0) + route->path.metric_type = 2; + else + route->path.metric_type = 1; + + return RMAP_OKAY; +} + +static void *ospf6_routemap_rule_set_metric_type_compile(const char *arg) +{ + if (strcmp(arg, "type-2") && strcmp(arg, "type-1")) + return NULL; + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void ospf6_routemap_rule_set_metric_type_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_set_metric_type_cmd = { + "metric-type", + ospf6_routemap_rule_set_metric_type, + ospf6_routemap_rule_set_metric_type_compile, + ospf6_routemap_rule_set_metric_type_free, +}; + +static enum route_map_cmd_result_t +ospf6_routemap_rule_set_metric(void *rule, const struct prefix *prefix, + void *object) +{ + char *metric = rule; + struct ospf6_route *route = object; + + route->path.cost = atoi(metric); + return RMAP_OKAY; +} + +static void *ospf6_routemap_rule_set_metric_compile(const char *arg) +{ + uint32_t metric; + char *endp; + metric = strtoul(arg, &endp, 0); + if (metric > OSPF_LS_INFINITY || *endp != '\0') + return NULL; + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void ospf6_routemap_rule_set_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_set_metric_cmd = { + "metric", + ospf6_routemap_rule_set_metric, + ospf6_routemap_rule_set_metric_compile, + ospf6_routemap_rule_set_metric_free, +}; + +static enum route_map_cmd_result_t +ospf6_routemap_rule_set_forwarding(void *rule, const struct prefix *prefix, + void *object) +{ + char *forwarding = rule; + struct ospf6_route *route = object; + struct ospf6_external_info *info = route->route_option; + + if (inet_pton(AF_INET6, forwarding, &info->forwarding) != 1) { + memset(&info->forwarding, 0, sizeof(struct in6_addr)); + return RMAP_ERROR; + } + + return RMAP_OKAY; +} + +static void *ospf6_routemap_rule_set_forwarding_compile(const char *arg) +{ + struct in6_addr a; + if (inet_pton(AF_INET6, arg, &a) != 1) + return NULL; + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void ospf6_routemap_rule_set_forwarding_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_set_forwarding_cmd = { + "forwarding-address", + ospf6_routemap_rule_set_forwarding, + ospf6_routemap_rule_set_forwarding_compile, + ospf6_routemap_rule_set_forwarding_free, +}; + +static enum route_map_cmd_result_t +ospf6_routemap_rule_set_tag(void *rule, const struct prefix *p, void *object) +{ + route_tag_t *tag = rule; + struct ospf6_route *route = object; + struct ospf6_external_info *info = route->route_option; + + info->tag = *tag; + return RMAP_OKAY; +} + +static const struct route_map_rule_cmd ospf6_routemap_rule_set_tag_cmd = { + "tag", + ospf6_routemap_rule_set_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +/* add "set metric-type" */ +DEFUN_YANG (ospf6_routemap_set_metric_type, ospf6_routemap_set_metric_type_cmd, + "set metric-type ", + SET_STR + "Type of metric for destination routing protocol\n" + "OSPF[6] external type 1 metric\n" + "OSPF[6] external type 2 metric\n") +{ + char *ext = argv[2]->text; + + const char *xpath = + "./set-action[action='frr-ospf-route-map:metric-type']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-ospf-route-map:metric-type", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, ext); + return nb_cli_apply_changes(vty, NULL); +} + +/* delete "set metric-type" */ +DEFUN_YANG (ospf6_routemap_no_set_metric_type, ospf6_routemap_no_set_metric_type_cmd, + "no set metric-type []", + NO_STR + SET_STR + "Type of metric for destination routing protocol\n" + "OSPF[6] external type 1 metric\n" + "OSPF[6] external type 2 metric\n") +{ + const char *xpath = + "./set-action[action='frr-ospf-route-map:metric-type']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +/* add "set forwarding-address" */ +DEFUN_YANG (ospf6_routemap_set_forwarding, ospf6_routemap_set_forwarding_cmd, + "set forwarding-address X:X::X:X", + "Set value\n" + "Forwarding Address\n" + "IPv6 Address\n") +{ + int idx_ipv6 = 2; + const char *xpath = + "./set-action[action='frr-ospf6-route-map:forwarding-address']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-ospf6-route-map:ipv6-address", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_ipv6]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +/* delete "set forwarding-address" */ +DEFUN_YANG (ospf6_routemap_no_set_forwarding, ospf6_routemap_no_set_forwarding_cmd, + "no set forwarding-address [X:X::X:X]", + NO_STR + "Set value\n" + "Forwarding Address\n" + "IPv6 Address\n") +{ + const char *xpath = + "./set-action[action='frr-ospf6-route-map:forwarding-address']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +static void ospf6_routemap_init(void) +{ + route_map_init(); + + route_map_add_hook(ospf6_asbr_routemap_update); + route_map_delete_hook(ospf6_asbr_routemap_update); + route_map_event_hook(ospf6_asbr_routemap_event); + + route_map_set_metric_hook(generic_set_add); + route_map_no_set_metric_hook(generic_set_delete); + + route_map_set_tag_hook(generic_set_add); + route_map_no_set_tag_hook(generic_set_delete); + + route_map_match_tag_hook(generic_match_add); + route_map_no_match_tag_hook(generic_match_delete); + + route_map_match_ipv6_address_prefix_list_hook(generic_match_add); + route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete); + + route_map_match_interface_hook(generic_match_add); + route_map_no_match_interface_hook(generic_match_delete); + + route_map_install_match( + &ospf6_routemap_rule_match_address_prefixlist_cmd); + route_map_install_match(&ospf6_routemap_rule_match_interface_cmd); + route_map_install_match(&ospf6_routemap_rule_match_tag_cmd); + + route_map_install_set(&ospf6_routemap_rule_set_metric_type_cmd); + route_map_install_set(&ospf6_routemap_rule_set_metric_cmd); + route_map_install_set(&ospf6_routemap_rule_set_forwarding_cmd); + route_map_install_set(&ospf6_routemap_rule_set_tag_cmd); + + /* ASE Metric Type (e.g. Type-1/Type-2) */ + install_element(RMAP_NODE, &ospf6_routemap_set_metric_type_cmd); + install_element(RMAP_NODE, &ospf6_routemap_no_set_metric_type_cmd); + + /* ASE Metric */ + install_element(RMAP_NODE, &ospf6_routemap_set_forwarding_cmd); + install_element(RMAP_NODE, &ospf6_routemap_no_set_forwarding_cmd); +} + + +/* Display functions */ +static char *ospf6_as_external_lsa_get_prefix_str(struct ospf6_lsa *lsa, + char *buf, int buflen, + int pos) +{ + struct ospf6_as_external_lsa *external; + struct in6_addr in6; + int prefix_length = 0; + char tbuf[16]; + + if (lsa) { + external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( + lsa->header); + + if (pos == 0) { + ospf6_prefix_in6_addr(&in6, external, + &external->prefix); + prefix_length = external->prefix.prefix_length; + } else { + in6 = *((struct in6_addr + *)((caddr_t)external + + sizeof(struct + ospf6_as_external_lsa) + + OSPF6_PREFIX_SPACE( + external->prefix + .prefix_length))); + } + if (buf) { + inet_ntop(AF_INET6, &in6, buf, buflen); + if (prefix_length) { + snprintf(tbuf, sizeof(tbuf), "/%d", + prefix_length); + strlcat(buf, tbuf, buflen); + } + } + } + return (buf); +} + +static int ospf6_as_external_lsa_show(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_obj, bool use_json) +{ + struct ospf6_as_external_lsa *external; + char buf[64]; + + assert(lsa->header); + external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END( + lsa->header); + + /* bits */ + snprintf(buf, sizeof(buf), "%c%c%c", + (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E) ? 'E' + : '-'), + (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F) ? 'F' + : '-'), + (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T) ? 'T' + : '-')); + + if (use_json) { + json_object_string_add(json_obj, "bits", buf); + json_object_int_add(json_obj, "metric", + (unsigned long)OSPF6_ASBR_METRIC(external)); + ospf6_prefix_options_printbuf(external->prefix.prefix_options, + buf, sizeof(buf)); + json_object_string_add(json_obj, "prefixOptions", buf); + json_object_int_add( + json_obj, "referenceLsType", + ntohs(external->prefix.prefix_refer_lstype)); + json_object_string_add(json_obj, "prefix", + ospf6_as_external_lsa_get_prefix_str( + lsa, buf, sizeof(buf), 0)); + + /* Forwarding-Address */ + json_object_boolean_add( + json_obj, "forwardingAddressPresent", + CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)); + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) + json_object_string_add( + json_obj, "forwardingAddress", + ospf6_as_external_lsa_get_prefix_str( + lsa, buf, sizeof(buf), 1)); + + /* Tag */ + json_object_boolean_add( + json_obj, "tagPresent", + CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)); + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)) + json_object_int_add(json_obj, "tag", + ospf6_as_external_lsa_get_tag(lsa)); + } else { + vty_out(vty, " Bits: %s\n", buf); + vty_out(vty, " Metric: %5lu\n", + (unsigned long)OSPF6_ASBR_METRIC(external)); + + ospf6_prefix_options_printbuf(external->prefix.prefix_options, + buf, sizeof(buf)); + vty_out(vty, " Prefix Options: %s\n", buf); + + vty_out(vty, " Referenced LSType: %d\n", + ntohs(external->prefix.prefix_refer_lstype)); + + vty_out(vty, " Prefix: %s\n", + ospf6_as_external_lsa_get_prefix_str(lsa, buf, + sizeof(buf), 0)); + + /* Forwarding-Address */ + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) { + vty_out(vty, " Forwarding-Address: %s\n", + ospf6_as_external_lsa_get_prefix_str( + lsa, buf, sizeof(buf), 1)); + } + + /* Tag */ + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)) { + vty_out(vty, " Tag: %" ROUTE_TAG_PRI "\n", + ospf6_as_external_lsa_get_tag(lsa)); + } + } + + return 0; +} + +static void ospf6_asbr_external_route_show(struct vty *vty, + struct ospf6_route *route, + json_object *json_array, + bool use_json) +{ + struct ospf6_external_info *info = route->route_option; + char prefix[PREFIX2STR_BUFFER], id[16], forwarding[64]; + uint32_t tmp_id; + json_object *json_route; + char route_type[2]; + + prefix2str(&route->prefix, prefix, sizeof(prefix)); + tmp_id = ntohl(info->id); + inet_ntop(AF_INET, &tmp_id, id, sizeof(id)); + if (!IN6_IS_ADDR_UNSPECIFIED(&info->forwarding)) + inet_ntop(AF_INET6, &info->forwarding, forwarding, + sizeof(forwarding)); + else + snprintf(forwarding, sizeof(forwarding), ":: (ifindex %d)", + ospf6_route_get_first_nh_index(route)); + + if (use_json) { + json_route = json_object_new_object(); + snprintf(route_type, sizeof(route_type), "%c", + zebra_route_char(info->type)); + json_object_string_add(json_route, "routeType", route_type); + json_object_string_add(json_route, "destination", prefix); + json_object_string_add(json_route, "id", id); + json_object_int_add(json_route, "metricType", + route->path.metric_type); + json_object_int_add( + json_route, "routeCost", + (unsigned long)(route->path.metric_type == 2 + ? route->path.u.cost_e2 + : route->path.cost)); + json_object_string_add(json_route, "forwarding", forwarding); + + json_object_array_add(json_array, json_route); + } else + + vty_out(vty, "%c %-32pFX %-15s type-%d %5lu %s\n", + zebra_route_char(info->type), &route->prefix, id, + route->path.metric_type, + (unsigned long)(route->path.metric_type == 2 + ? route->path.u.cost_e2 + : route->path.cost), + forwarding); +} + +DEFUN(show_ipv6_ospf6_redistribute, show_ipv6_ospf6_redistribute_cmd, + "show ipv6 ospf6 [vrf ] redistribute [json]", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "redistributing External information\n" JSON_STR) +{ + struct ospf6_route *route; + struct ospf6 *ospf6 = NULL; + json_object *json = NULL; + bool uj = use_json(argc, argv); + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + json_object *json_array_routes = NULL; + json_object *json_array_redistribute = NULL; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (uj) { + json = json_object_new_object(); + json_array_routes = json_object_new_array(); + json_array_redistribute = json_object_new_array(); + } + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf + || ((ospf6->name == NULL && vrf_name == NULL) + || (ospf6->name && vrf_name + && strcmp(ospf6->name, vrf_name) == 0))) { + ospf6_redistribute_show_config( + vty, ospf6, json_array_redistribute, json, uj); + + for (route = ospf6_route_head(ospf6->external_table); + route; route = ospf6_route_next(route)) { + ospf6_asbr_external_route_show( + vty, route, json_array_routes, uj); + } + + if (uj) { + json_object_object_add(json, "routes", + json_array_routes); + vty_json(vty, json); + } + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +static struct ospf6_lsa_handler as_external_handler = { + .lh_type = OSPF6_LSTYPE_AS_EXTERNAL, + .lh_name = "AS-External", + .lh_short_name = "ASE", + .lh_show = ospf6_as_external_lsa_show, + .lh_get_prefix_str = ospf6_as_external_lsa_get_prefix_str, + .lh_debug = 0}; + +static struct ospf6_lsa_handler nssa_external_handler = { + .lh_type = OSPF6_LSTYPE_TYPE_7, + .lh_name = "NSSA", + .lh_short_name = "Type7", + .lh_show = ospf6_as_external_lsa_show, + .lh_get_prefix_str = ospf6_as_external_lsa_get_prefix_str, + .lh_debug = 0}; + +void ospf6_asbr_init(void) +{ + ospf6_routemap_init(); + + ospf6_install_lsa_handler(&as_external_handler); + ospf6_install_lsa_handler(&nssa_external_handler); + + install_element(VIEW_NODE, &show_ipv6_ospf6_redistribute_cmd); + + install_element(OSPF6_NODE, &ospf6_default_route_originate_cmd); + install_element(OSPF6_NODE, + &no_ospf6_default_information_originate_cmd); + install_element(OSPF6_NODE, &ospf6_redistribute_cmd); + install_element(OSPF6_NODE, &no_ospf6_redistribute_cmd); +} + +void ospf6_asbr_redistribute_disable(struct ospf6 *ospf6) +{ + int type; + struct ospf6_redist *red; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) + continue; + if (type == ZEBRA_ROUTE_OSPF6) + continue; + ospf6_asbr_redistribute_unset(ospf6, red, type); + ospf6_redist_del(ospf6, red, type); + } + red = ospf6_redist_lookup(ospf6, DEFAULT_ROUTE, 0); + if (red) { + ospf6_asbr_routemap_unset(red); + ospf6_redist_del(ospf6, red, type); + ospf6_redistribute_default_set(ospf6, DEFAULT_ORIGINATE_NONE); + } +} + +void ospf6_asbr_redistribute_reset(struct ospf6 *ospf6) +{ + int type; + struct ospf6_redist *red; + char buf[RMAP_NAME_MAXLEN]; + + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + buf[0] = '\0'; + if (type == ZEBRA_ROUTE_OSPF6) + continue; + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) + continue; + + if (type == DEFAULT_ROUTE) { + ospf6_redistribute_default_set( + ospf6, ospf6->default_originate); + continue; + } + if (ROUTEMAP_NAME(red)) + strlcpy(buf, ROUTEMAP_NAME(red), sizeof(buf)); + + ospf6_asbr_redistribute_unset(ospf6, red, type); + if (buf[0]) + ospf6_asbr_routemap_set(red, buf); + ospf6_asbr_redistribute_set(ospf6, type); + } +} + +void ospf6_asbr_terminate(void) +{ + /* Cleanup route maps */ + route_map_finish(); +} + +DEFUN (debug_ospf6_asbr, + debug_ospf6_asbr_cmd, + "debug ospf6 asbr", + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 ASBR function\n" + ) +{ + OSPF6_DEBUG_ASBR_ON(); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_asbr, + no_debug_ospf6_asbr_cmd, + "no debug ospf6 asbr", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 ASBR function\n" + ) +{ + OSPF6_DEBUG_ASBR_OFF(); + return CMD_SUCCESS; +} + +int config_write_ospf6_debug_asbr(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_ASBR) + vty_out(vty, "debug ospf6 asbr\n"); + return 0; +} + +static void ospf6_default_originate_write(struct vty *vty, struct ospf6 *o) +{ + struct ospf6_redist *red; + + vty_out(vty, " default-information originate"); + if (o->default_originate == DEFAULT_ORIGINATE_ALWAYS) + vty_out(vty, " always"); + + red = ospf6_redist_lookup(o, DEFAULT_ROUTE, 0); + if (red == NULL) { + vty_out(vty, "\n"); + return; + } + + if (red->dmetric.value >= 0) + vty_out(vty, " metric %d", red->dmetric.value); + + if (red->dmetric.type >= 0) + vty_out(vty, " metric-type %d", red->dmetric.type); + + if (ROUTEMAP_NAME(red)) + vty_out(vty, " route-map %s", ROUTEMAP_NAME(red)); + + vty_out(vty, "\n"); +} + +int ospf6_distribute_config_write(struct vty *vty, struct ospf6 *o) +{ + if (o == NULL) + return 0; + + /* Print default originate configuration. */ + if (o->default_originate != DEFAULT_ORIGINATE_NONE) + ospf6_default_originate_write(vty, o); + + return 0; +} + +void install_element_ospf6_debug_asbr(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_asbr_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_asbr_cmd); + install_element(CONFIG_NODE, &debug_ospf6_asbr_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_asbr_cmd); +} + +/* ASBR Summarisation */ +void ospf6_fill_aggr_route_details(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct ospf6_route *rt_aggr = aggr->route; + struct ospf6_external_info *ei_aggr = rt_aggr->route_option; + + rt_aggr->prefix = aggr->p; + ei_aggr->tag = aggr->tag; + ei_aggr->type = 0; + ei_aggr->id = aggr->id; + + /* When metric is not configured, apply the default metric */ + rt_aggr->path.cost = ((aggr->metric == -1) ? + DEFAULT_DEFAULT_METRIC + : (unsigned int)(aggr->metric)); + rt_aggr->path.metric_type = aggr->mtype; + + rt_aggr->path.origin.id = htonl(aggr->id); +} + +static void +ospf6_summary_add_aggr_route_and_blackhole(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct ospf6_route *rt_aggr; + struct ospf6_route *old_rt = NULL; + struct ospf6_external_info *info; + + /* Check if a route is already present. */ + if (aggr->route) + old_rt = aggr->route; + + /* Create summary route and save it. */ + rt_aggr = ospf6_route_create(ospf6); + rt_aggr->type = OSPF6_DEST_TYPE_NETWORK; + /* Needed to install route while calling zebra api */ + SET_FLAG(rt_aggr->flag, OSPF6_ROUTE_BEST); + + info = XCALLOC(MTYPE_OSPF6_EXTERNAL_INFO, sizeof(*info)); + rt_aggr->route_option = info; + aggr->route = rt_aggr; + + /* Prepare the external_info for aggregator + * Fill all the details which will get advertised + */ + ospf6_fill_aggr_route_details(ospf6, aggr); + + /* Add next-hop to Null interface. */ + ospf6_add_route_nexthop_blackhole(rt_aggr); + + /* Free the old route, if any. */ + if (old_rt) { + ospf6_zebra_route_update_remove(old_rt, ospf6); + + if (old_rt->route_option) + XFREE(MTYPE_OSPF6_EXTERNAL_INFO, old_rt->route_option); + + ospf6_route_delete(old_rt); + } + + ospf6_zebra_route_update_add(rt_aggr, ospf6); +} + +static void ospf6_originate_new_aggr_lsa(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct prefix prefix_id; + struct ospf6_lsa *lsa = NULL; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Originate new aggregate route(%pFX)", __func__, + &aggr->p); + + aggr->id = ospf6->external_id++; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "Advertise AS-External Id:%pI4 prefix %pFX metric %u", + &prefix_id.u.prefix4, &aggr->p, aggr->metric); + + ospf6_summary_add_aggr_route_and_blackhole(ospf6, aggr); + + /* Originate summary LSA */ + lsa = ospf6_originate_type5_type7_lsas(aggr->route, ospf6); + if (lsa) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Set the origination bit for aggregator", + __func__); + SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); + } +} + +static void +ospf6_aggr_handle_advertise_change(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + /* Check if advertise option modified. */ + if (CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Don't originate the summary address,It is configured to not-advertise.", + __func__); + ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr); + + return; + } + + /* There are no routes present under this aggregation config, hence + * nothing to originate here + */ + if (OSPF6_EXTERNAL_RT_COUNT(aggr) == 0) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: No routes present under this aggregation", + __func__); + return; + } + + if (!CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Now it is advertisable", + __func__); + + ospf6_originate_new_aggr_lsa(ospf6, aggr); + + return; + } +} + +static void +ospf6_originate_summary_lsa(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr, + struct ospf6_route *rt) +{ + struct ospf6_lsa *lsa = NULL, *aggr_lsa = NULL; + struct ospf6_external_info *info = NULL; + struct ospf6_external_aggr_rt *old_aggr; + struct ospf6_as_external_lsa *external; + struct ospf6_route *rt_aggr = NULL; + route_tag_t tag = 0; + unsigned int metric = 0; + int mtype; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Prepare to originate Summary route(%pFX)", + __func__, &aggr->p); + + /* This case to handle when the overlapping aggregator address + * is available. Best match will be considered.So need to delink + * from old aggregator and link to the new aggr. + */ + if (rt->aggr_route) { + if (rt->aggr_route != aggr) { + old_aggr = rt->aggr_route; + ospf6_unlink_route_from_aggr(ospf6, old_aggr, rt); + } + } + + /* Add the external route to hash table */ + ospf6_link_route_to_aggr(aggr, rt); + + /* The key for ID field is a running number and not prefix */ + info = rt->route_option; + assert(info); + if (info->id) + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(info->id), ospf6->router_id, + ospf6->lsdb); + + aggr_lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(aggr->id), ospf6->router_id, ospf6->lsdb); + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Aggr LSA ID: %d flags %x.", + __func__, aggr->id, aggr->aggrflags); + /* Don't originate external LSA, + * If it is configured not to advertise. + */ + if (CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) { + /* If it is already originated as external LSA, + * But, it is configured not to advertise then + * flush the originated external lsa. + */ + if (lsa) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Purge the external LSA %s.", + __func__, lsa->name); + ospf6_external_lsa_purge(ospf6, lsa); + info->id = 0; + rt->path.origin.id = 0; + } + + if (aggr_lsa) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Purge the aggr external LSA %s.", + __func__, lsa->name); + ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr); + } + + UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Don't originate the summary address,It is configured to not-advertise.", + __func__); + return; + } + + /* Summary route already originated, + * So, Do nothing. + */ + if (CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED)) { + if (!aggr_lsa) { + zlog_warn( + "%s: Could not refresh/originate %pFX", + __func__, + &aggr->p); + /* Remove the assert later */ + assert(aggr_lsa); + return; + } + + external = (struct ospf6_as_external_lsa *)OSPF6_LSA_HEADER_END + (aggr_lsa->header); + metric = (unsigned long)OSPF6_ASBR_METRIC(external); + tag = ospf6_as_external_lsa_get_tag(aggr_lsa); + mtype = CHECK_FLAG(external->bits_metric, + OSPF6_ASBR_BIT_E) ? 2 : 1; + + /* Prepare the external_info for aggregator */ + ospf6_fill_aggr_route_details(ospf6, aggr); + rt_aggr = aggr->route; + /* If tag/metric/metric-type modified , then re-originate the + * route with modified tag/metric/metric-type details. + */ + if ((tag != aggr->tag) + || (metric != (unsigned int)rt_aggr->path.cost) + || (mtype != aggr->mtype)) { + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "%s: Routetag(old:%d new:%d)/Metric(o:%u,n:%u)/mtype(o:%d n:%d) modified,So refresh the summary route.(%pFX)", + __func__, tag, aggr->tag, + metric, + aggr->metric, + mtype, aggr->mtype, + &aggr->p); + + aggr_lsa = ospf6_originate_type5_type7_lsas(aggr->route, + ospf6); + if (aggr_lsa) + SET_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_ORIGINATED); + } + + return; + } + + /* If the external route prefix same as aggregate route + * and if external route is already originated as TYPE-5 + * then just update the aggr info and remove the route info + */ + if (lsa && prefix_same(&aggr->p, &rt->prefix)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "%s: Route prefix is same as aggr so no need to re-originate LSA(%pFX)", + __PRETTY_FUNCTION__, &aggr->p); + + aggr->id = info->id; + info->id = 0; + rt->path.origin.id = 0; + + ospf6_summary_add_aggr_route_and_blackhole(ospf6, aggr); + + SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); + + return; + } + + ospf6_originate_new_aggr_lsa(ospf6, aggr); +} + +static void ospf6_aggr_handle_external_info(void *data) +{ + struct ospf6_route *rt = (struct ospf6_route *)data; + struct ospf6_external_aggr_rt *aggr = NULL; + struct ospf6_lsa *lsa = NULL; + struct ospf6_external_info *info; + struct ospf6 *ospf6 = NULL; + + rt->aggr_route = NULL; + + rt->to_be_processed = true; + + if (IS_OSPF6_DEBUG_ASBR || IS_OSPF6_DEBUG_ORIGINATE(AS_EXTERNAL)) + zlog_debug("%s: Handle external route for origination/refresh (%pFX)", + __func__, + &rt->prefix); + + ospf6 = rt->ospf6; + assert(ospf6); + + aggr = ospf6_external_aggr_match(ospf6, + &rt->prefix); + if (aggr) { + ospf6_originate_summary_lsa(ospf6, aggr, rt); + return; + } + + info = rt->route_option; + if (info->id) { + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(info->id), ospf6->router_id, + ospf6->lsdb); + if (lsa) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: LSA found, refresh it", + __func__); + EVENT_OFF(lsa->refresh); + event_add_event(master, ospf6_lsa_refresh, lsa, 0, + &lsa->refresh); + return; + } + } + + info->id = ospf6->external_id++; + rt->path.origin.id = htonl(info->id); + + (void)ospf6_originate_type5_type7_lsas(rt, ospf6); +} + +void ospf6_asbr_summary_config_delete(struct ospf6 *ospf6, + struct route_node *rn) +{ + struct ospf6_external_aggr_rt *aggr = rn->info; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Deleting Aggregate route (%pFX)", + __func__, + &aggr->p); + + ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr); + + rn->info = NULL; + route_unlock_node(rn); +} + +static int +ospf6_handle_external_aggr_modify(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct ospf6_lsa *lsa = NULL; + struct ospf6_as_external_lsa *asel = NULL; + struct ospf6_route *rt_aggr; + unsigned int metric = 0; + route_tag_t tag = 0; + int mtype; + + lsa = ospf6_lsdb_lookup( + htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(aggr->id), ospf6->router_id, + ospf6->lsdb); + if (!lsa) { + zlog_warn( + "%s: Could not refresh/originate %pFX", + __func__, + &aggr->p); + + return OSPF6_FAILURE; + } + + asel = (struct ospf6_as_external_lsa *) + OSPF6_LSA_HEADER_END(lsa->header); + metric = (unsigned long)OSPF6_ASBR_METRIC(asel); + tag = ospf6_as_external_lsa_get_tag(lsa); + mtype = CHECK_FLAG(asel->bits_metric, + OSPF6_ASBR_BIT_E) ? 2 : 1; + + /* Fill all the details for advertisement */ + ospf6_fill_aggr_route_details(ospf6, aggr); + rt_aggr = aggr->route; + /* If tag/metric/metric-type modified , then + * re-originate the route with modified + * tag/metric/metric-type details. + */ + if ((tag != aggr->tag) + || (metric + != (unsigned int)rt_aggr->path.cost) + || (mtype + != aggr->mtype)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "%s: Changed tag(old:%d new:%d)/metric(o:%u n:%d)/mtype(o:%d n:%d),So refresh the summary route.(%pFX)", + __func__, tag, + aggr->tag, + metric, + (unsigned int)rt_aggr->path.cost, + mtype, aggr->mtype, + &aggr->p); + + (void)ospf6_originate_type5_type7_lsas( + aggr->route, + ospf6); + } + + return OSPF6_SUCCESS; +} + +static void ospf6_handle_external_aggr_update(struct ospf6 *ospf6) +{ + struct route_node *rn = NULL; + int ret; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Process modified aggregators.", __func__); + + for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) { + struct ospf6_external_aggr_rt *aggr; + + if (!rn->info) + continue; + + aggr = rn->info; + + if (aggr->action == OSPF6_ROUTE_AGGR_DEL) { + aggr->action = OSPF6_ROUTE_AGGR_NONE; + ospf6_asbr_summary_config_delete(ospf6, rn); + + hash_clean_and_free(&aggr->match_extnl_hash, + ospf6_aggr_handle_external_info); + + XFREE(MTYPE_OSPF6_EXTERNAL_RT_AGGR, aggr); + + } else if (aggr->action == OSPF6_ROUTE_AGGR_MODIFY) { + + aggr->action = OSPF6_ROUTE_AGGR_NONE; + + /* Check if tag/metric/metric-type modified */ + if (CHECK_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_ORIGINATED) + && !CHECK_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) { + + ret = ospf6_handle_external_aggr_modify(ospf6, + aggr); + if (ret == OSPF6_FAILURE) + continue; + } + + /* Advertise option modified ? + * If so, handled it here. + */ + ospf6_aggr_handle_advertise_change(ospf6, aggr); + } + } +} + +static void ospf6_aggr_unlink_external_info(void *data) +{ + struct ospf6_route *rt = (struct ospf6_route *)data; + + rt->aggr_route = NULL; + + rt->to_be_processed = true; +} + +void ospf6_external_aggregator_free(struct ospf6_external_aggr_rt *aggr) +{ + hash_clean_and_free(&aggr->match_extnl_hash, + ospf6_aggr_unlink_external_info); + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Release the aggregator Address(%pFX)", + __func__, + &aggr->p); + + XFREE(MTYPE_OSPF6_EXTERNAL_RT_AGGR, aggr); +} + +static void +ospf6_delete_all_marked_aggregators(struct ospf6 *ospf6) +{ + struct route_node *rn = NULL; + struct ospf6_external_aggr_rt *aggr; + + /* Loop through all the aggregators, Delete all aggregators + * which are marked as DELETE. Set action to NONE for remaining + * aggregators + */ + for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + aggr = rn->info; + + if (aggr->action != OSPF6_ROUTE_AGGR_DEL) { + aggr->action = OSPF6_ROUTE_AGGR_NONE; + continue; + } + ospf6_asbr_summary_config_delete(ospf6, rn); + ospf6_external_aggregator_free(aggr); + } +} + +static void ospf6_handle_exnl_rt_after_aggr_del(struct ospf6 *ospf6, + struct ospf6_route *rt) +{ + struct ospf6_lsa *lsa; + + /* Process only marked external routes. + * These routes were part of a deleted + * aggregator.So, originate now. + */ + if (!rt->to_be_processed) + return; + + rt->to_be_processed = false; + + lsa = ospf6_find_external_lsa(ospf6, &rt->prefix); + + if (lsa) { + EVENT_OFF(lsa->refresh); + event_add_event(master, ospf6_lsa_refresh, lsa, 0, + &lsa->refresh); + } else { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Originate external route(%pFX)", + __func__, + &rt->prefix); + + (void)ospf6_originate_type5_type7_lsas(rt, ospf6); + } +} + +static void ospf6_handle_aggregated_exnl_rt(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr, + struct ospf6_route *rt) +{ + struct ospf6_lsa *lsa; + struct ospf6_as_external_lsa *ext_lsa; + struct ospf6_external_info *info; + + /* Handling the case where the external route prefix + * and aggegate prefix is same + * If same don't flush the originated external LSA. + */ + if (prefix_same(&aggr->p, &rt->prefix)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: External Route prefix same as Aggregator(%pFX), so don't flush.", + __func__, + &rt->prefix); + + return; + } + + info = rt->route_option; + assert(info); + + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(info->id), ospf6->router_id, ospf6->lsdb); + if (lsa) { + ext_lsa = (struct ospf6_as_external_lsa + *)((char *)(lsa->header) + + sizeof(struct ospf6_lsa_header)); + + if (rt->prefix.prefixlen != ext_lsa->prefix.prefix_length) + return; + + ospf6_external_lsa_purge(ospf6, lsa); + + /* Resetting the ID of route */ + rt->path.origin.id = 0; + info->id = 0; + } +} + +static void +ospf6_handle_external_aggr_add(struct ospf6 *ospf6) +{ + struct ospf6_route *rt = NULL; + struct ospf6_external_info *ei = NULL; + struct ospf6_external_aggr_rt *aggr; + + /* Delete all the aggregators which are marked as + * OSPF6_ROUTE_AGGR_DEL. + */ + ospf6_delete_all_marked_aggregators(ospf6); + + for (rt = ospf6_route_head(ospf6->external_table); rt; + rt = ospf6_route_next(rt)) { + ei = rt->route_option; + if (ei == NULL) + continue; + + if (is_default_prefix(&rt->prefix)) + continue; + + aggr = ospf6_external_aggr_match(ospf6, + &rt->prefix); + + /* If matching aggregator found, Add + * the external route refrenace to the + * aggregator and originate the aggr + * route if it is advertisable. + * flush the external LSA if it is + * already originated for this external + * prefix. + */ + if (aggr) { + ospf6_originate_summary_lsa(ospf6, aggr, rt); + + /* All aggregated external rts + * are handled here. + */ + ospf6_handle_aggregated_exnl_rt( + ospf6, aggr, rt); + continue; + } + + /* External routes which are only out + * of aggregation will be handled here. + */ + ospf6_handle_exnl_rt_after_aggr_del( + ospf6, rt); + } +} + +static void ospf6_asbr_summary_process(struct event *thread) +{ + struct ospf6 *ospf6 = EVENT_ARG(thread); + int operation = 0; + + operation = ospf6->aggr_action; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: operation:%d", + __func__, + operation); + + switch (operation) { + case OSPF6_ROUTE_AGGR_ADD: + ospf6_handle_external_aggr_add(ospf6); + break; + case OSPF6_ROUTE_AGGR_DEL: + case OSPF6_ROUTE_AGGR_MODIFY: + ospf6_handle_external_aggr_update(ospf6); + break; + default: + break; + } +} + +static void +ospf6_start_asbr_summary_delay_timer(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr, + ospf6_aggr_action_t operation) +{ + aggr->action = operation; + + if (event_is_scheduled(ospf6->t_external_aggr)) { + if (ospf6->aggr_action == OSPF6_ROUTE_AGGR_ADD) { + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Not required to restart timer,set is already added.", + __func__); + return; + } + + if (operation == OSPF6_ROUTE_AGGR_ADD) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s, Restarting Aggregator delay timer.", + __func__); + EVENT_OFF(ospf6->t_external_aggr); + } + } + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Start Aggregator delay timer %u(in seconds).", + __func__, ospf6->aggr_delay_interval); + + ospf6->aggr_action = operation; + event_add_timer(master, ospf6_asbr_summary_process, ospf6, + ospf6->aggr_delay_interval, &ospf6->t_external_aggr); +} + +int ospf6_asbr_external_rt_advertise(struct ospf6 *ospf6, + struct prefix *p) +{ + struct route_node *rn; + struct ospf6_external_aggr_rt *aggr; + + rn = route_node_lookup(ospf6->rt_aggr_tbl, p); + if (!rn) + return OSPF6_INVALID; + + aggr = rn->info; + + route_unlock_node(rn); + + if (!CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) + return OSPF6_INVALID; + + UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE); + + if (!OSPF6_EXTERNAL_RT_COUNT(aggr)) + return OSPF6_SUCCESS; + + ospf6_start_asbr_summary_delay_timer(ospf6, aggr, + OSPF6_ROUTE_AGGR_MODIFY); + + return OSPF6_SUCCESS; +} + +int ospf6_external_aggr_delay_timer_set(struct ospf6 *ospf6, uint16_t interval) +{ + ospf6->aggr_delay_interval = interval; + + return OSPF6_SUCCESS; +} + +static unsigned int ospf6_external_rt_hash_key(const void *data) +{ + const struct ospf6_route *rt = data; + unsigned int key = 0; + + key = prefix_hash_key(&rt->prefix); + return key; +} + +static bool ospf6_external_rt_hash_cmp(const void *d1, const void *d2) +{ + const struct ospf6_route *rt1 = d1; + const struct ospf6_route *rt2 = d2; + + return prefix_same(&rt1->prefix, &rt2->prefix); +} + +static struct ospf6_external_aggr_rt * +ospf6_external_aggr_new(struct prefix *p) +{ + struct ospf6_external_aggr_rt *aggr; + + aggr = XCALLOC(MTYPE_OSPF6_EXTERNAL_RT_AGGR, + sizeof(struct ospf6_external_aggr_rt)); + + prefix_copy(&aggr->p, p); + aggr->metric = -1; + aggr->mtype = DEFAULT_METRIC_TYPE; + aggr->match_extnl_hash = hash_create(ospf6_external_rt_hash_key, + ospf6_external_rt_hash_cmp, + "Ospf6 external route hash"); + return aggr; +} + +static void ospf6_external_aggr_add(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct route_node *rn; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Adding Aggregate route to Aggr table (%pFX)", + __func__, + &aggr->p); + + rn = route_node_get(ospf6->rt_aggr_tbl, &aggr->p); + if (rn->info) + route_unlock_node(rn); + else + rn->info = aggr; +} + +int ospf6_asbr_external_rt_no_advertise(struct ospf6 *ospf6, + struct prefix *p) +{ + struct ospf6_external_aggr_rt *aggr; + route_tag_t tag = 0; + + aggr = ospf6_external_aggr_config_lookup(ospf6, p); + if (aggr) { + if (CHECK_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) + return OSPF6_SUCCESS; + + SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE); + + aggr->tag = tag; + aggr->metric = -1; + + if (!OSPF6_EXTERNAL_RT_COUNT(aggr)) + return OSPF6_SUCCESS; + + ospf6_start_asbr_summary_delay_timer(ospf6, aggr, + OSPF6_ROUTE_AGGR_MODIFY); + } else { + aggr = ospf6_external_aggr_new(p); + + if (!aggr) + return OSPF6_FAILURE; + + SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE); + ospf6_external_aggr_add(ospf6, aggr); + ospf6_start_asbr_summary_delay_timer(ospf6, aggr, + OSPF6_ROUTE_AGGR_ADD); + } + + return OSPF6_SUCCESS; +} + +struct ospf6_external_aggr_rt * +ospf6_external_aggr_config_lookup(struct ospf6 *ospf6, struct prefix *p) +{ + struct route_node *rn; + + rn = route_node_lookup(ospf6->rt_aggr_tbl, p); + if (rn) { + route_unlock_node(rn); + return rn->info; + } + + return NULL; +} + + +int ospf6_external_aggr_config_set(struct ospf6 *ospf6, struct prefix *p, + route_tag_t tag, int metric, int mtype) +{ + struct ospf6_external_aggr_rt *aggregator; + + aggregator = ospf6_external_aggr_config_lookup(ospf6, p); + + if (aggregator) { + if (CHECK_FLAG(aggregator->aggrflags, + OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) + UNSET_FLAG(aggregator->aggrflags, + OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE); + else if ((aggregator->tag == tag) + && (aggregator->metric == metric) + && (aggregator->mtype == mtype)) + return OSPF6_SUCCESS; + + aggregator->tag = tag; + aggregator->metric = metric; + aggregator->mtype = mtype; + + ospf6_start_asbr_summary_delay_timer(ospf6, aggregator, + OSPF6_ROUTE_AGGR_MODIFY); + } else { + aggregator = ospf6_external_aggr_new(p); + if (!aggregator) + return OSPF6_FAILURE; + + aggregator->tag = tag; + aggregator->metric = metric; + aggregator->mtype = mtype; + + ospf6_external_aggr_add(ospf6, aggregator); + ospf6_start_asbr_summary_delay_timer(ospf6, aggregator, + OSPF6_ROUTE_AGGR_ADD); + } + + return OSPF6_SUCCESS; +} + +int ospf6_external_aggr_config_unset(struct ospf6 *ospf6, + struct prefix *p) +{ + struct route_node *rn; + struct ospf6_external_aggr_rt *aggr; + + rn = route_node_lookup(ospf6->rt_aggr_tbl, p); + if (!rn) + return OSPF6_INVALID; + + aggr = rn->info; + + route_unlock_node(rn); + + if (!OSPF6_EXTERNAL_RT_COUNT(aggr)) { + ospf6_asbr_summary_config_delete(ospf6, rn); + ospf6_external_aggregator_free(aggr); + return OSPF6_SUCCESS; + } + + ospf6_start_asbr_summary_delay_timer(ospf6, aggr, + OSPF6_ROUTE_AGGR_DEL); + + return OSPF6_SUCCESS; +} + +void ospf6_handle_external_lsa_origination(struct ospf6 *ospf6, + struct ospf6_route *rt, + struct prefix *p) +{ + + struct ospf6_external_aggr_rt *aggr; + struct ospf6_external_info *info; + struct prefix prefix_id; + + if (!is_default_prefix(p)) { + aggr = ospf6_external_aggr_match(ospf6, + p); + + if (aggr) { + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Send Aggregate LSA (%pFX)", + __func__, + &aggr->p); + + ospf6_originate_summary_lsa( + ospf6, aggr, rt); + + /* Handling the case where the + * external route prefix + * and aggegate prefix is same + * If same don't flush the + * originated + * external LSA. + */ + ospf6_handle_aggregated_exnl_rt( + ospf6, aggr, rt); + return; + } + } + + info = rt->route_option; + + /* When the info->id = 0, it means it is being originated for the + * first time. + */ + if (!info->id) { + info->id = ospf6->external_id++; + } else { + prefix_id.family = AF_INET; + prefix_id.prefixlen = 32; + prefix_id.u.prefix4.s_addr = htonl(info->id); + } + + rt->path.origin.id = htonl(info->id); + + if (IS_OSPF6_DEBUG_ASBR) { + zlog_debug("Advertise new AS-External Id:%pI4 prefix %pFX metric %u", + &prefix_id.u.prefix4, p, rt->path.metric_type); + } + + ospf6_originate_type5_type7_lsas(rt, ospf6); + +} + +void ospf6_unset_all_aggr_flag(struct ospf6 *ospf6) +{ + struct route_node *rn = NULL; + struct ospf6_external_aggr_rt *aggr; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("Unset the origination bit for all aggregator"); + + /* Resetting the running external ID counter so that the origination + * of external LSAs starts from the beginning 0.0.0.1 + */ + ospf6->external_id = OSPF6_EXT_INIT_LS_ID; + + for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + aggr = rn->info; + + UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); + } +} -- cgit v1.2.3