diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
commit | e2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch) | |
tree | f0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /ospfd/ospf_opaque.c | |
parent | Initial commit. (diff) | |
download | frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip |
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ospfd/ospf_opaque.c')
-rw-r--r-- | ospfd/ospf_opaque.c | 2166 |
1 files changed, 2166 insertions, 0 deletions
diff --git a/ospfd/ospf_opaque.c b/ospfd/ospf_opaque.c new file mode 100644 index 0000000..27f47a6 --- /dev/null +++ b/ospfd/ospf_opaque.c @@ -0,0 +1,2166 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of rfc2370. + * Copyright (C) 2001 KDD R&D Laboratories, Inc. + * http://www.kddlabs.co.jp/ + */ + +#include <zebra.h> + +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "stream.h" +#include "log.h" +#include "frrevent.h" +#include "hash.h" +#include "sockunion.h" /* for inet_aton() */ +#include "printfrr.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_te.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ri.h" +#include "ospfd/ospf_ext.h" +#include "ospfd/ospf_errors.h" + +DEFINE_MTYPE_STATIC(OSPFD, OSPF_OPAQUE_FUNCTAB, "OSPF opaque function table"); +DEFINE_MTYPE_STATIC(OSPFD, OPAQUE_INFO_PER_TYPE, "OSPF opaque per-type info"); +DEFINE_MTYPE_STATIC(OSPFD, OPAQUE_INFO_PER_ID, "OSPF opaque per-ID info"); + +/*------------------------------------------------------------------------* + * Following are initialize/terminate functions for Opaque-LSAs handling. + *------------------------------------------------------------------------*/ + +#ifdef SUPPORT_OSPF_API +int ospf_apiserver_init(void); +void ospf_apiserver_term(void); +/* Init apiserver? It's disabled by default. */ +int ospf_apiserver_enable; +#endif /* SUPPORT_OSPF_API */ + +static void ospf_opaque_register_vty(void); +static void ospf_opaque_funclist_init(void); +static void ospf_opaque_funclist_term(void); +static void free_opaque_info_per_type_del(void *val); +static void free_opaque_info_per_id(void *val); +static int ospf_opaque_lsa_install_hook(struct ospf_lsa *lsa); +static int ospf_opaque_lsa_delete_hook(struct ospf_lsa *lsa); + +void ospf_opaque_init(void) +{ + ospf_opaque_register_vty(); + ospf_opaque_funclist_init(); + + if (ospf_mpls_te_init() != 0) + exit(1); + + /* Segment Routing init */ + if (ospf_sr_init() != 0) + exit(1); + + if (ospf_router_info_init() != 0) + exit(1); + + if (ospf_ext_init() != 0) + exit(1); + +#ifdef SUPPORT_OSPF_API + if ((ospf_apiserver_enable) && (ospf_apiserver_init() != 0)) + exit(1); +#endif /* SUPPORT_OSPF_API */ + + return; +} + +void ospf_opaque_term(void) +{ + ospf_mpls_te_term(); + + ospf_router_info_term(); + + ospf_ext_term(); + + ospf_sr_term(); + +#ifdef SUPPORT_OSPF_API + ospf_apiserver_term(); +#endif /* SUPPORT_OSPF_API */ + + ospf_opaque_funclist_term(); + return; +} + +void ospf_opaque_finish(void) +{ + ospf_mpls_te_finish(); + + ospf_router_info_finish(); + + ospf_ext_finish(); + +#ifdef SUPPORT_OSPF_API + ospf_apiserver_term(); +#endif + + ospf_sr_finish(); +} + +int ospf_opaque_type9_lsa_init(struct ospf_interface *oi) +{ + if (oi->opaque_lsa_self != NULL) + list_delete(&oi->opaque_lsa_self); + + oi->opaque_lsa_self = list_new(); + oi->opaque_lsa_self->del = free_opaque_info_per_type_del; + oi->t_opaque_lsa_self = NULL; + return 0; +} + +void ospf_opaque_type9_lsa_term(struct ospf_interface *oi) +{ + EVENT_OFF(oi->t_opaque_lsa_self); + if (oi->opaque_lsa_self != NULL) + list_delete(&oi->opaque_lsa_self); + oi->opaque_lsa_self = NULL; + return; +} + +int ospf_opaque_type10_lsa_init(struct ospf_area *area) +{ + if (area->opaque_lsa_self != NULL) + list_delete(&area->opaque_lsa_self); + + area->opaque_lsa_self = list_new(); + area->opaque_lsa_self->del = free_opaque_info_per_type_del; + area->t_opaque_lsa_self = NULL; + +#ifdef MONITOR_LSDB_CHANGE + area->lsdb->new_lsa_hook = ospf_opaque_lsa_install_hook; + area->lsdb->del_lsa_hook = ospf_opaque_lsa_delete_hook; +#endif /* MONITOR_LSDB_CHANGE */ + return 0; +} + +void ospf_opaque_type10_lsa_term(struct ospf_area *area) +{ +#ifdef MONITOR_LSDB_CHANGE + area->lsdb->new_lsa_hook = area->lsdb->del_lsa_hook = NULL; +#endif /* MONITOR_LSDB_CHANGE */ + + EVENT_OFF(area->t_opaque_lsa_self); + if (area->opaque_lsa_self != NULL) + list_delete(&area->opaque_lsa_self); + return; +} + +int ospf_opaque_type11_lsa_init(struct ospf *top) +{ + if (top->opaque_lsa_self != NULL) + list_delete(&top->opaque_lsa_self); + + top->opaque_lsa_self = list_new(); + top->opaque_lsa_self->del = free_opaque_info_per_type_del; + top->t_opaque_lsa_self = NULL; + +#ifdef MONITOR_LSDB_CHANGE + top->lsdb->new_lsa_hook = ospf_opaque_lsa_install_hook; + top->lsdb->del_lsa_hook = ospf_opaque_lsa_delete_hook; +#endif /* MONITOR_LSDB_CHANGE */ + return 0; +} + +void ospf_opaque_type11_lsa_term(struct ospf *top) +{ +#ifdef MONITOR_LSDB_CHANGE + top->lsdb->new_lsa_hook = top->lsdb->del_lsa_hook = NULL; +#endif /* MONITOR_LSDB_CHANGE */ + + EVENT_OFF(top->t_opaque_lsa_self); + if (top->opaque_lsa_self != NULL) + list_delete(&top->opaque_lsa_self); + return; +} + +static const char *ospf_opaque_type_name(uint8_t opaque_type) +{ + const char *name = "Unknown"; + + switch (opaque_type) { + case OPAQUE_TYPE_WILDCARD: /* This is a special assignment! */ + name = "Wildcard"; + break; + case OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA: + name = "Traffic Engineering LSA"; + break; + case OPAQUE_TYPE_SYCAMORE_OPTICAL_TOPOLOGY_DESC: + name = "Sycamore optical topology description"; + break; + case OPAQUE_TYPE_GRACE_LSA: + name = "Grace-LSA"; + break; + case OPAQUE_TYPE_INTER_AS_LSA: + name = "Inter-AS TE-v2 LSA"; + break; + case OPAQUE_TYPE_ROUTER_INFORMATION_LSA: + name = "Router Information LSA"; + break; + case OPAQUE_TYPE_EXTENDED_PREFIX_LSA: + name = "Extended Prefix Opaque LSA"; + break; + case OPAQUE_TYPE_EXTENDED_LINK_LSA: + name = "Extended Link Opaque LSA"; + break; + default: + if (OPAQUE_TYPE_RANGE_UNASSIGNED(opaque_type)) + name = "Unassigned"; + else { + uint32_t bigger_range = opaque_type; + /* + * Get around type-limits warning: comparison is always + * true due to limited range of data type + */ + if (OPAQUE_TYPE_RANGE_RESERVED(bigger_range)) + name = "Private/Experimental"; + } + break; + } + return name; +} + +/*------------------------------------------------------------------------* + * Following are management functions to store user specified callbacks. + *------------------------------------------------------------------------*/ + +struct opaque_info_per_type; /* Forward declaration. */ + +static void free_opaque_info_per_type(struct opaque_info_per_type *oipt, + bool cleanup_owner); + +struct ospf_opaque_functab { + uint8_t opaque_type; + struct opaque_info_per_type *oipt; + + int (*new_if_hook)(struct interface *ifp); + int (*del_if_hook)(struct interface *ifp); + void (*ism_change_hook)(struct ospf_interface *oi, int old_status); + void (*nsm_change_hook)(struct ospf_neighbor *nbr, int old_status); + void (*config_write_router)(struct vty *vty); + void (*config_write_if)(struct vty *vty, struct interface *ifp); + void (*config_write_debug)(struct vty *vty); + void (*show_opaque_info)(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa); + int (*lsa_originator)(void *arg); + struct ospf_lsa *(*lsa_refresher)(struct ospf_lsa *lsa); + int (*new_lsa_hook)(struct ospf_lsa *lsa); + int (*del_lsa_hook)(struct ospf_lsa *lsa); +}; + +/* Handle LSA-9/10/11 altogether. */ +static struct list *ospf_opaque_wildcard_funclist; +static struct list *ospf_opaque_type9_funclist; +static struct list *ospf_opaque_type10_funclist; +static struct list *ospf_opaque_type11_funclist; + +static void ospf_opaque_del_functab(void *val) +{ + XFREE(MTYPE_OSPF_OPAQUE_FUNCTAB, val); + return; +} + +static void ospf_opaque_funclist_init(void) +{ + struct list *funclist; + + funclist = ospf_opaque_wildcard_funclist = list_new(); + funclist->del = ospf_opaque_del_functab; + + funclist = ospf_opaque_type9_funclist = list_new(); + funclist->del = ospf_opaque_del_functab; + + funclist = ospf_opaque_type10_funclist = list_new(); + funclist->del = ospf_opaque_del_functab; + + funclist = ospf_opaque_type11_funclist = list_new(); + funclist->del = ospf_opaque_del_functab; + return; +} + +static void ospf_opaque_funclist_term(void) +{ + struct list *funclist; + + funclist = ospf_opaque_wildcard_funclist; + list_delete(&funclist); + + funclist = ospf_opaque_type9_funclist; + list_delete(&funclist); + + funclist = ospf_opaque_type10_funclist; + list_delete(&funclist); + + funclist = ospf_opaque_type11_funclist; + list_delete(&funclist); + return; +} + +static struct list *ospf_get_opaque_funclist(uint8_t lsa_type) +{ + struct list *funclist = NULL; + + switch (lsa_type) { + case OPAQUE_TYPE_WILDCARD: + /* XXX + * This is an ugly trick to handle type-9/10/11 LSA altogether. + * Yes, "OPAQUE_TYPE_WILDCARD (value 0)" is not an LSA-type, nor + * an officially assigned opaque-type. + * Though it is possible that the value might be officially used + * in the future, we use it internally as a special label, for + * now. + */ + funclist = ospf_opaque_wildcard_funclist; + break; + case OSPF_OPAQUE_LINK_LSA: + funclist = ospf_opaque_type9_funclist; + break; + case OSPF_OPAQUE_AREA_LSA: + funclist = ospf_opaque_type10_funclist; + break; + case OSPF_OPAQUE_AS_LSA: + funclist = ospf_opaque_type11_funclist; + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa_type); + break; + } + return funclist; +} + +/* XXX: such a huge argument list can /not/ be healthy... */ +int ospf_register_opaque_functab( + uint8_t lsa_type, uint8_t opaque_type, + int (*new_if_hook)(struct interface *ifp), + int (*del_if_hook)(struct interface *ifp), + void (*ism_change_hook)(struct ospf_interface *oi, int old_status), + void (*nsm_change_hook)(struct ospf_neighbor *nbr, int old_status), + void (*config_write_router)(struct vty *vty), + void (*config_write_if)(struct vty *vty, struct interface *ifp), + void (*config_write_debug)(struct vty *vty), + void (*show_opaque_info)(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa), + int (*lsa_originator)(void *arg), + struct ospf_lsa *(*lsa_refresher)(struct ospf_lsa *lsa), + int (*new_lsa_hook)(struct ospf_lsa *lsa), + int (*del_lsa_hook)(struct ospf_lsa *lsa)) +{ + struct list *funclist; + struct ospf_opaque_functab *new; + + if ((funclist = ospf_get_opaque_funclist(lsa_type)) == NULL) + return -1; + + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->opaque_type == opaque_type) { + flog_warn( + EC_OSPF_LSA, + "%s: Duplicated entry?: lsa_type(%u), opaque_type(%u)", + __func__, lsa_type, opaque_type); + return -1; + } + + new = XCALLOC(MTYPE_OSPF_OPAQUE_FUNCTAB, + sizeof(struct ospf_opaque_functab)); + + new->opaque_type = opaque_type; + new->oipt = NULL; + new->new_if_hook = new_if_hook; + new->del_if_hook = del_if_hook; + new->ism_change_hook = ism_change_hook; + new->nsm_change_hook = nsm_change_hook; + new->config_write_router = config_write_router; + new->config_write_if = config_write_if; + new->config_write_debug = config_write_debug; + new->show_opaque_info = show_opaque_info; + new->lsa_originator = lsa_originator; + new->lsa_refresher = lsa_refresher; + new->new_lsa_hook = new_lsa_hook; + new->del_lsa_hook = del_lsa_hook; + + listnode_add(funclist, new); + + return 0; +} + +void ospf_delete_opaque_functab(uint8_t lsa_type, uint8_t opaque_type) +{ + struct list *funclist; + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + if ((funclist = ospf_get_opaque_funclist(lsa_type)) != NULL) + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) { + if (functab->opaque_type == opaque_type) { + /* Cleanup internal control information, if it + * still remains. */ + if (functab->oipt != NULL) + free_opaque_info_per_type(functab->oipt, + true); + /* Dequeue listnode entry from the list. */ + listnode_delete(funclist, functab); + + XFREE(MTYPE_OSPF_OPAQUE_FUNCTAB, functab); + break; + } + } + + return; +} + +static struct ospf_opaque_functab * +ospf_opaque_functab_lookup(struct ospf_lsa *lsa) +{ + struct list *funclist; + struct listnode *node; + struct ospf_opaque_functab *functab; + uint8_t key = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + + if ((funclist = ospf_get_opaque_funclist(lsa->data->type)) != NULL) + for (ALL_LIST_ELEMENTS_RO(funclist, node, functab)) + if (functab->opaque_type == key) + return functab; + + return NULL; +} + +/*------------------------------------------------------------------------* + * Following are management functions for self-originated LSA entries. + *------------------------------------------------------------------------*/ + +/* + * Opaque-LSA control information per opaque-type. + * Single Opaque-Type may have multiple instances; each of them will be + * identified by their opaque-id. + */ +struct opaque_info_per_type { + uint8_t lsa_type; + uint8_t opaque_type; + + enum { PROC_NORMAL, PROC_SUSPEND } status; + + /* + * Thread for (re-)origination scheduling for this opaque-type. + * + * Initial origination of Opaque-LSAs is controlled by generic + * Opaque-LSA handling module so that same opaque-type entries are + * called all at once when certain conditions are met. + * However, there might be cases that some Opaque-LSA clients need + * to (re-)originate their own Opaque-LSAs out-of-sync with others. + * This thread is prepared for that specific purpose. + */ + struct event *t_opaque_lsa_self; + + /* + * Backpointer to an "owner" which is LSA-type dependent. + * type-9: struct ospf_interface + * type-10: struct ospf_area + * type-11: struct ospf + */ + void *owner; + + /* Collection of callback functions for this opaque-type. */ + struct ospf_opaque_functab *functab; + + /* List of Opaque-LSA control information per opaque-id. */ + struct list *id_list; +}; + +/* Opaque-LSA control information per opaque-id. */ +struct opaque_info_per_id { + uint32_t opaque_id; + + /* Thread for refresh/flush scheduling for this opaque-type/id. */ + struct event *t_opaque_lsa_self; + + /* Backpointer to Opaque-LSA control information per opaque-type. */ + struct opaque_info_per_type *opqctl_type; + + /* Here comes an actual Opaque-LSA entry for this opaque-type/id. */ + struct ospf_lsa *lsa; +}; + +static struct opaque_info_per_type * +register_opaque_info_per_type(struct ospf_opaque_functab *functab, + struct ospf_lsa *new); +static struct opaque_info_per_type * +lookup_opaque_info_by_type(struct ospf_lsa *lsa); +static struct opaque_info_per_id * +register_opaque_info_per_id(struct opaque_info_per_type *oipt, + struct ospf_lsa *new); +static struct opaque_info_per_id * +lookup_opaque_info_by_id(struct opaque_info_per_type *oipt, + struct ospf_lsa *lsa); +static struct opaque_info_per_id *register_opaque_lsa(struct ospf_lsa *new); + + +static struct opaque_info_per_type * +register_opaque_info_per_type(struct ospf_opaque_functab *functab, + struct ospf_lsa *new) +{ + struct ospf *top; + struct opaque_info_per_type *oipt; + + oipt = XCALLOC(MTYPE_OPAQUE_INFO_PER_TYPE, + sizeof(struct opaque_info_per_type)); + + switch (new->data->type) { + case OSPF_OPAQUE_LINK_LSA: + oipt->owner = new->oi; + listnode_add(new->oi->opaque_lsa_self, oipt); + break; + case OSPF_OPAQUE_AREA_LSA: + oipt->owner = new->area; + listnode_add(new->area->opaque_lsa_self, oipt); + break; + case OSPF_OPAQUE_AS_LSA: + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (new->area != NULL && (top = new->area->ospf) == NULL) { + free_opaque_info_per_type(oipt, true); + oipt = NULL; + goto out; /* This case may not exist. */ + } + oipt->owner = top; + listnode_add(top->opaque_lsa_self, oipt); + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, new->data->type); + free_opaque_info_per_type(oipt, true); + oipt = NULL; + goto out; /* This case may not exist. */ + } + + oipt->lsa_type = new->data->type; + oipt->opaque_type = GET_OPAQUE_TYPE(ntohl(new->data->id.s_addr)); + oipt->status = PROC_NORMAL; + oipt->functab = functab; + functab->oipt = oipt; + oipt->id_list = list_new(); + oipt->id_list->del = free_opaque_info_per_id; + +out: + return oipt; +} + +static void free_opaque_info_per_type(struct opaque_info_per_type *oipt, + bool cleanup_owner) +{ + struct opaque_info_per_id *oipi; + struct ospf_lsa *lsa; + struct listnode *node, *nnode; + struct list *l; + + /* Control information per opaque-id may still exist. */ + for (ALL_LIST_ELEMENTS(oipt->id_list, node, nnode, oipi)) { + if ((lsa = oipi->lsa) == NULL) + continue; + if (IS_LSA_MAXAGE(lsa)) + continue; + ospf_opaque_lsa_flush_schedule(lsa); + } + + EVENT_OFF(oipt->t_opaque_lsa_self); + list_delete(&oipt->id_list); + if (cleanup_owner) { + /* Remove from its owner's self-originated LSA list. */ + switch (oipt->lsa_type) { + case OSPF_OPAQUE_LINK_LSA: + l = ((struct ospf_interface *)oipt->owner) + ->opaque_lsa_self; + break; + case OSPF_OPAQUE_AREA_LSA: + l = ((struct ospf_area *)oipt->owner)->opaque_lsa_self; + break; + case OSPF_OPAQUE_AS_LSA: + l = ((struct ospf *)oipt->owner)->opaque_lsa_self; + break; + default: + flog_warn( + EC_OSPF_LSA_UNEXPECTED, + "free_opaque_info_owner: Unexpected LSA-type(%u)", + oipt->lsa_type); + return; + } + listnode_delete(l, oipt); + } + XFREE(MTYPE_OPAQUE_INFO_PER_TYPE, oipt); + return; +} + +static void free_opaque_info_per_type_del(void *val) +{ + free_opaque_info_per_type((struct opaque_info_per_type *)val, false); +} + +static struct opaque_info_per_type * +lookup_opaque_info_by_type(struct ospf_lsa *lsa) +{ + struct ospf *top; + struct ospf_area *area; + struct ospf_interface *oi; + struct list *listtop = NULL; + struct listnode *node, *nnode; + struct opaque_info_per_type *oipt = NULL; + uint8_t key = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + if ((oi = lsa->oi) != NULL) + listtop = oi->opaque_lsa_self; + else + flog_warn( + EC_OSPF_LSA, + "Type-9 Opaque-LSA: Reference to OI is missing?"); + break; + case OSPF_OPAQUE_AREA_LSA: + if ((area = lsa->area) != NULL) + listtop = area->opaque_lsa_self; + else + flog_warn( + EC_OSPF_LSA, + "Type-10 Opaque-LSA: Reference to AREA is missing?"); + break; + case OSPF_OPAQUE_AS_LSA: + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if ((area = lsa->area) != NULL && (top = area->ospf) == NULL) { + flog_warn( + EC_OSPF_LSA, + "Type-11 Opaque-LSA: Reference to OSPF is missing?"); + break; /* Unlikely to happen. */ + } + listtop = top->opaque_lsa_self; + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + break; + } + + if (listtop != NULL) + for (ALL_LIST_ELEMENTS(listtop, node, nnode, oipt)) + if (oipt->opaque_type == key) + return oipt; + + return NULL; +} + +static struct opaque_info_per_id * +register_opaque_info_per_id(struct opaque_info_per_type *oipt, + struct ospf_lsa *new) +{ + struct opaque_info_per_id *oipi; + + oipi = XCALLOC(MTYPE_OPAQUE_INFO_PER_ID, + sizeof(struct opaque_info_per_id)); + + oipi->opaque_id = GET_OPAQUE_ID(ntohl(new->data->id.s_addr)); + oipi->opqctl_type = oipt; + oipi->lsa = ospf_lsa_lock(new); + + listnode_add(oipt->id_list, oipi); + + return oipi; +} + +static void free_opaque_info_per_id(void *val) +{ + struct opaque_info_per_id *oipi = (struct opaque_info_per_id *)val; + + EVENT_OFF(oipi->t_opaque_lsa_self); + if (oipi->lsa != NULL) + ospf_lsa_unlock(&oipi->lsa); + XFREE(MTYPE_OPAQUE_INFO_PER_ID, oipi); + return; +} + +static struct opaque_info_per_id * +lookup_opaque_info_by_id(struct opaque_info_per_type *oipt, + struct ospf_lsa *lsa) +{ + struct listnode *node, *nnode; + struct opaque_info_per_id *oipi; + uint32_t key = GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)); + + for (ALL_LIST_ELEMENTS(oipt->id_list, node, nnode, oipi)) + if (oipi->opaque_id == key) + return oipi; + + return NULL; +} + +static struct opaque_info_per_id *register_opaque_lsa(struct ospf_lsa *new) +{ + struct ospf_opaque_functab *functab; + struct opaque_info_per_type *oipt; + struct opaque_info_per_id *oipi = NULL; + + if ((functab = ospf_opaque_functab_lookup(new)) == NULL) + goto out; + + if ((oipt = lookup_opaque_info_by_type(new)) == NULL + && (oipt = register_opaque_info_per_type(functab, new)) == NULL) + goto out; + + if ((oipi = register_opaque_info_per_id(oipt, new)) == NULL) + goto out; + +out: + return oipi; +} + +int ospf_opaque_is_owned(struct ospf_lsa *lsa) +{ + struct opaque_info_per_type *oipt = lookup_opaque_info_by_type(lsa); + + return (oipt != NULL && lookup_opaque_info_by_id(oipt, lsa) != NULL); +} + +/*------------------------------------------------------------------------* + * Following are (vty) configuration functions for Opaque-LSAs handling. + *------------------------------------------------------------------------*/ + +DEFUN (capability_opaque, + capability_opaque_cmd, + "capability opaque", + "Enable specific OSPF feature\n" + "Opaque LSA\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + /* Check that OSPF is using default VRF */ + if (ospf->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "OSPF Opaque LSA is only supported in default VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Turn on the "master switch" of opaque-lsa capability. */ + if (!CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Opaque capability: OFF -> ON"); + + SET_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE); + ospf_renegotiate_optional_capabilities(ospf); + } + return CMD_SUCCESS; +} + +DEFUN (ospf_opaque, + ospf_opaque_cmd, + "ospf opaque-lsa", + "OSPF specific commands\n" + "Enable the Opaque-LSA capability (rfc2370)\n") +{ + return capability_opaque(self, vty, argc, argv); +} + +DEFUN (no_capability_opaque, + no_capability_opaque_cmd, + "no capability opaque", + NO_STR + "Enable specific OSPF feature\n" + "Opaque LSA\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + /* Turn off the "master switch" of opaque-lsa capability. */ + if (CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Opaque capability: ON -> OFF"); + + UNSET_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE); + ospf_renegotiate_optional_capabilities(ospf); + } + return CMD_SUCCESS; +} + +DEFUN (no_ospf_opaque, + no_ospf_opaque_cmd, + "no ospf opaque-lsa", + NO_STR + "OSPF specific commands\n" + "Enable the Opaque-LSA capability (rfc2370)\n") +{ + return no_capability_opaque(self, vty, argc, argv); +} + +static void ospf_opaque_register_vty(void) +{ + install_element(OSPF_NODE, &capability_opaque_cmd); + install_element(OSPF_NODE, &no_capability_opaque_cmd); + install_element(OSPF_NODE, &ospf_opaque_cmd); + install_element(OSPF_NODE, &no_ospf_opaque_cmd); + return; +} + +/*------------------------------------------------------------------------* + * Following are collection of user-registered function callers. + *------------------------------------------------------------------------*/ + +static int opaque_lsa_new_if_callback(struct list *funclist, + struct interface *ifp) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->new_if_hook != NULL) + if ((*functab->new_if_hook)(ifp) != 0) + goto out; + rc = 0; +out: + return rc; +} + +static int opaque_lsa_del_if_callback(struct list *funclist, + struct interface *ifp) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->del_if_hook != NULL) + if ((*functab->del_if_hook)(ifp) != 0) + goto out; + rc = 0; +out: + return rc; +} + +static void opaque_lsa_ism_change_callback(struct list *funclist, + struct ospf_interface *oi, + int old_status) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->ism_change_hook != NULL) + (*functab->ism_change_hook)(oi, old_status); + + return; +} + +static void opaque_lsa_nsm_change_callback(struct list *funclist, + struct ospf_neighbor *nbr, + int old_status) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->nsm_change_hook != NULL) + (*functab->nsm_change_hook)(nbr, old_status); + return; +} + +static void opaque_lsa_config_write_router_callback(struct list *funclist, + struct vty *vty) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->config_write_router != NULL) + (*functab->config_write_router)(vty); + return; +} + +static void opaque_lsa_config_write_if_callback(struct list *funclist, + struct vty *vty, + struct interface *ifp) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->config_write_if != NULL) + (*functab->config_write_if)(vty, ifp); + return; +} + +static void opaque_lsa_config_write_debug_callback(struct list *funclist, + struct vty *vty) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->config_write_debug != NULL) + (*functab->config_write_debug)(vty); + return; +} + +static int opaque_lsa_originate_callback(struct list *funclist, + void *lsa_type_dependent) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->lsa_originator != NULL) + if ((*functab->lsa_originator)(lsa_type_dependent) != 0) + goto out; + rc = 0; +out: + return rc; +} + +static int new_lsa_callback(struct list *funclist, struct ospf_lsa *lsa) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + /* This function handles ALL types of LSAs, not only opaque ones. */ + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->new_lsa_hook != NULL) + if ((*functab->new_lsa_hook)(lsa) != 0) + goto out; + rc = 0; +out: + return rc; +} + +static int del_lsa_callback(struct list *funclist, struct ospf_lsa *lsa) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + /* This function handles ALL types of LSAs, not only opaque ones. */ + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->del_lsa_hook != NULL) + if ((*functab->del_lsa_hook)(lsa) != 0) + goto out; + rc = 0; +out: + return rc; +} + +/*------------------------------------------------------------------------* + * Following are glue functions to call Opaque-LSA specific processing. + *------------------------------------------------------------------------*/ + +int ospf_opaque_new_if(struct interface *ifp) +{ + struct list *funclist; + int rc = -1; + + funclist = ospf_opaque_wildcard_funclist; + if (opaque_lsa_new_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type9_funclist; + if (opaque_lsa_new_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type10_funclist; + if (opaque_lsa_new_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type11_funclist; + if (opaque_lsa_new_if_callback(funclist, ifp) != 0) + goto out; + + rc = 0; +out: + return rc; +} + +int ospf_opaque_del_if(struct interface *ifp) +{ + struct list *funclist; + int rc = -1; + + funclist = ospf_opaque_wildcard_funclist; + if (opaque_lsa_del_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type9_funclist; + if (opaque_lsa_del_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type10_funclist; + if (opaque_lsa_del_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type11_funclist; + if (opaque_lsa_del_if_callback(funclist, ifp) != 0) + goto out; + + rc = 0; +out: + return rc; +} + +void ospf_opaque_ism_change(struct ospf_interface *oi, int old_status) +{ + struct list *funclist; + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_ism_change_callback(funclist, oi, old_status); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_ism_change_callback(funclist, oi, old_status); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_ism_change_callback(funclist, oi, old_status); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_ism_change_callback(funclist, oi, old_status); + + return; +} + +void ospf_opaque_nsm_change(struct ospf_neighbor *nbr, int old_state) +{ + struct ospf *top; + struct list *funclist; + + if ((top = oi_to_top(nbr->oi)) == NULL) + goto out; + + if (old_state != NSM_Full && nbr->state == NSM_Full) { + if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) { + if (!CHECK_FLAG(top->opaque, + OPAQUE_OPERATION_READY_BIT)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Opaque-LSA: Now get operational!"); + + SET_FLAG(top->opaque, + OPAQUE_OPERATION_READY_BIT); + } + + ospf_opaque_lsa_originate_schedule(nbr->oi, NULL); + } + } else if (old_state == NSM_Full && nbr->state != NSM_Full) { +#ifdef NOTYET +/* + * If no more opaque-capable full-state neighbor remains in the + * flooding scope which corresponds to Opaque-LSA type, periodic + * LS flooding should be stopped. + */ +#endif /* NOTYET */ + ; + } + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_nsm_change_callback(funclist, nbr, old_state); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_nsm_change_callback(funclist, nbr, old_state); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_nsm_change_callback(funclist, nbr, old_state); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_nsm_change_callback(funclist, nbr, old_state); + +out: + return; +} + +void ospf_opaque_config_write_router(struct vty *vty, struct ospf *ospf) +{ + struct list *funclist; + + if (CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) + vty_out(vty, " capability opaque\n"); + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_config_write_router_callback(funclist, vty); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_config_write_router_callback(funclist, vty); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_config_write_router_callback(funclist, vty); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_config_write_router_callback(funclist, vty); + + return; +} + +void ospf_opaque_config_write_if(struct vty *vty, struct interface *ifp) +{ + struct list *funclist; + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_config_write_if_callback(funclist, vty, ifp); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_config_write_if_callback(funclist, vty, ifp); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_config_write_if_callback(funclist, vty, ifp); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_config_write_if_callback(funclist, vty, ifp); + + return; +} + +void ospf_opaque_config_write_debug(struct vty *vty) +{ + struct list *funclist; + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_config_write_debug_callback(funclist, vty); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_config_write_debug_callback(funclist, vty); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_config_write_debug_callback(funclist, vty); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_config_write_debug_callback(funclist, vty); + + return; +} + +void show_opaque_info_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + char buf[128], *bp; + struct lsa_header *lsah = lsa->data; + uint32_t lsid = ntohl(lsah->id.s_addr); + uint8_t opaque_type = GET_OPAQUE_TYPE(lsid); + uint32_t opaque_id = GET_OPAQUE_ID(lsid); + struct ospf_opaque_functab *functab; + int len, lenValid; + + /* Switch output functionality by vty address. */ + if (vty != NULL) { + if (!json) { + vty_out(vty, " Opaque-Type %u (%s)\n", opaque_type, + ospf_opaque_type_name(opaque_type)); + vty_out(vty, " Opaque-ID 0x%x\n", opaque_id); + + vty_out(vty, " Opaque-Info: %u octets of data%s\n", + ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE, + VALID_OPAQUE_INFO_LEN(lsah) + ? "" + : "(Invalid length?)"); + } else { + json_object_string_add( + json, "opaqueType", + ospf_opaque_type_name(opaque_type)); + json_object_int_add(json, "opaqueId", opaque_id); + len = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; + json_object_int_add(json, "opaqueDataLength", len); + lenValid = VALID_OPAQUE_INFO_LEN(lsah); + json_object_boolean_add(json, "opaqueDataLengthValid", + lenValid); + if (lenValid) { + bp = asnprintfrr(MTYPE_TMP, buf, sizeof(buf), + "%*pHXn", (int)len, + (lsah + 1)); + json_object_string_add(json, "opaqueData", buf); + if (bp != buf) + XFREE(MTYPE_TMP, bp); + } + } + } else { + zlog_debug(" Opaque-Type %u (%s)", opaque_type, + ospf_opaque_type_name(opaque_type)); + zlog_debug(" Opaque-ID 0x%x", opaque_id); + + zlog_debug(" Opaque-Info: %u octets of data%s", + ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE, + VALID_OPAQUE_INFO_LEN(lsah) ? "" + : "(Invalid length?)"); + } + + /* Call individual output functions. */ + if ((functab = ospf_opaque_functab_lookup(lsa)) != NULL) + if (functab->show_opaque_info != NULL) + (*functab->show_opaque_info)(vty, json, lsa); + + return; +} + +void ospf_opaque_lsa_dump(struct stream *s, uint16_t length) +{ + struct ospf_lsa lsa = {}; + + lsa.data = (struct lsa_header *)stream_pnt(s); + lsa.size = length; + show_opaque_info_detail(NULL, &lsa, NULL); + return; +} + +static int ospf_opaque_lsa_install_hook(struct ospf_lsa *lsa) +{ + struct list *funclist; + int rc = -1; + + /* + * Some Opaque-LSA user may want to monitor every LSA installation + * into the LSDB, regardless with target LSA type. + */ + funclist = ospf_opaque_wildcard_funclist; + if (new_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type9_funclist; + if (new_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type10_funclist; + if (new_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type11_funclist; + if (new_lsa_callback(funclist, lsa) != 0) + goto out; + + rc = 0; +out: + return rc; +} + +static int ospf_opaque_lsa_delete_hook(struct ospf_lsa *lsa) +{ + struct list *funclist; + int rc = -1; + + /* + * Some Opaque-LSA user may want to monitor every LSA deletion + * from the LSDB, regardless with target LSA type. + */ + funclist = ospf_opaque_wildcard_funclist; + if (del_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type9_funclist; + if (del_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type10_funclist; + if (del_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type11_funclist; + if (del_lsa_callback(funclist, lsa) != 0) + goto out; + + rc = 0; +out: + return rc; +} + +/*------------------------------------------------------------------------* + * Following are Opaque-LSA origination/refresh management functions. + *------------------------------------------------------------------------*/ + +static void ospf_opaque_type9_lsa_originate(struct event *t); +static void ospf_opaque_type10_lsa_originate(struct event *t); +static void ospf_opaque_type11_lsa_originate(struct event *t); +static void ospf_opaque_lsa_reoriginate_resume(struct list *listtop, void *arg); + +void ospf_opaque_lsa_originate_schedule(struct ospf_interface *oi, int *delay0) +{ + struct ospf *top; + struct ospf_area *area; + struct listnode *node, *nnode; + struct opaque_info_per_type *oipt; + int delay = 0; + + if ((top = oi_to_top(oi)) == NULL || (area = oi->area) == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Invalid argument?", __func__); + return; + } + + /* It may not a right time to schedule origination now. */ + if (!CHECK_FLAG(top->opaque, OPAQUE_OPERATION_READY_BIT)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Not operational.", __func__); + return; /* This is not an error. */ + } + + if (delay0 != NULL) + delay = *delay0; + + /* + * There might be some entries that have been waiting for triggering + * of per opaque-type re-origination get resumed. + */ + ospf_opaque_lsa_reoriginate_resume(oi->opaque_lsa_self, (void *)oi); + ospf_opaque_lsa_reoriginate_resume(area->opaque_lsa_self, (void *)area); + ospf_opaque_lsa_reoriginate_resume(top->opaque_lsa_self, (void *)top); + + /* + * Now, schedule origination of all Opaque-LSAs per opaque-type. + */ + if (!list_isempty(ospf_opaque_type9_funclist) + && list_isempty(oi->opaque_lsa_self) + && oi->t_opaque_lsa_self == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-9 Opaque-LSA origination in %d ms later.", + delay); + oi->t_opaque_lsa_self = NULL; + event_add_timer_msec(master, ospf_opaque_type9_lsa_originate, + oi, delay, &oi->t_opaque_lsa_self); + delay += top->min_ls_interval; + } + + if (!list_isempty(ospf_opaque_type10_funclist) + && list_isempty(area->opaque_lsa_self) + && area->t_opaque_lsa_self == NULL) { + /* + * One AREA may contain multiple OIs, but above 2nd and 3rd + * conditions prevent from scheduling the originate function + * again and again. + */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-10 Opaque-LSA origination in %d ms later.", + delay); + area->t_opaque_lsa_self = NULL; + event_add_timer_msec(master, ospf_opaque_type10_lsa_originate, + area, delay, &area->t_opaque_lsa_self); + delay += top->min_ls_interval; + } + + if (!list_isempty(ospf_opaque_type11_funclist) + && list_isempty(top->opaque_lsa_self) + && top->t_opaque_lsa_self == NULL) { + /* + * One OSPF may contain multiple AREAs, but above 2nd and 3rd + * conditions prevent from scheduling the originate function + * again and again. + */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-11 Opaque-LSA origination in %d ms later.", + delay); + top->t_opaque_lsa_self = NULL; + event_add_timer_msec(master, ospf_opaque_type11_lsa_originate, + top, delay, &top->t_opaque_lsa_self); + delay += top->min_ls_interval; + } + + /* + * Following section treats a special situation that this node's + * opaque capability has changed as "ON -> OFF -> ON". + */ + if (!list_isempty(ospf_opaque_type9_funclist) + && !list_isempty(oi->opaque_lsa_self)) { + for (ALL_LIST_ELEMENTS(oi->opaque_lsa_self, node, nnode, + oipt)) { + /* + * removed the test for + * (! list_isempty (oipt->id_list)) * Handler is + * already active. * + * because opaque cababilities ON -> OFF -> ON result in + * list_isempty (oipt->id_list) + * not being empty. + */ + if (oipt->t_opaque_lsa_self + != NULL /* Waiting for a thread call. */ + || oipt->status == PROC_SUSPEND) /* Cannot + originate + now. */ + continue; + + ospf_opaque_lsa_reoriginate_schedule( + (void *)oi, OSPF_OPAQUE_LINK_LSA, + oipt->opaque_type); + } + } + + if (!list_isempty(ospf_opaque_type10_funclist) + && !list_isempty(area->opaque_lsa_self)) { + for (ALL_LIST_ELEMENTS(area->opaque_lsa_self, node, nnode, + oipt)) { + /* + * removed the test for + * (! list_isempty (oipt->id_list)) * Handler is + * already active. * + * because opaque cababilities ON -> OFF -> ON result in + * list_isempty (oipt->id_list) + * not being empty. + */ + if (oipt->t_opaque_lsa_self + != NULL /* Waiting for a thread call. */ + || oipt->status == PROC_SUSPEND) /* Cannot + originate + now. */ + continue; + + ospf_opaque_lsa_reoriginate_schedule( + (void *)area, OSPF_OPAQUE_AREA_LSA, + oipt->opaque_type); + } + } + + if (!list_isempty(ospf_opaque_type11_funclist) + && !list_isempty(top->opaque_lsa_self)) { + for (ALL_LIST_ELEMENTS(top->opaque_lsa_self, node, nnode, + oipt)) { + /* + * removed the test for + * (! list_isempty (oipt->id_list)) * Handler is + * already active. * + * because opaque cababilities ON -> OFF -> ON result in + * list_isempty (oipt->id_list) + * not being empty. + */ + if (oipt->t_opaque_lsa_self + != NULL /* Waiting for a thread call. */ + || oipt->status == PROC_SUSPEND) /* Cannot + originate + now. */ + continue; + + ospf_opaque_lsa_reoriginate_schedule((void *)top, + OSPF_OPAQUE_AS_LSA, + oipt->opaque_type); + } + } + + if (delay0 != NULL) + *delay0 = delay; +} + +static void ospf_opaque_type9_lsa_originate(struct event *t) +{ + struct ospf_interface *oi; + + oi = EVENT_ARG(t); + oi->t_opaque_lsa_self = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Timer[Type9-LSA]: Originate Opaque-LSAs for OI %s", + IF_NAME(oi)); + + opaque_lsa_originate_callback(ospf_opaque_type9_funclist, oi); +} + +static void ospf_opaque_type10_lsa_originate(struct event *t) +{ + struct ospf_area *area; + + area = EVENT_ARG(t); + area->t_opaque_lsa_self = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type10-LSA]: Originate Opaque-LSAs for Area %pI4", + &area->area_id); + + opaque_lsa_originate_callback(ospf_opaque_type10_funclist, area); +} + +static void ospf_opaque_type11_lsa_originate(struct event *t) +{ + struct ospf *top; + + top = EVENT_ARG(t); + top->t_opaque_lsa_self = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type11-LSA]: Originate AS-External Opaque-LSAs"); + + opaque_lsa_originate_callback(ospf_opaque_type11_funclist, top); +} + +static void ospf_opaque_lsa_reoriginate_resume(struct list *listtop, void *arg) +{ + struct listnode *node, *nnode; + struct opaque_info_per_type *oipt; + struct ospf_opaque_functab *functab; + + if (listtop == NULL) + goto out; + + /* + * Pickup oipt entries those which in SUSPEND status, and give + * them a chance to start re-origination now. + */ + for (ALL_LIST_ELEMENTS(listtop, node, nnode, oipt)) { + if (oipt->status != PROC_SUSPEND) + continue; + + oipt->status = PROC_NORMAL; + + if ((functab = oipt->functab) == NULL + || functab->lsa_originator == NULL) + continue; + + if ((*functab->lsa_originator)(arg) != 0) { + flog_warn(EC_OSPF_LSA, "%s: Failed (opaque-type=%u)", + __func__, oipt->opaque_type); + continue; + } + } + +out: + return; +} + +struct ospf_lsa *ospf_opaque_lsa_install(struct ospf_lsa *lsa, int rt_recalc) +{ + struct ospf_lsa *new = NULL; + struct opaque_info_per_type *oipt; + struct opaque_info_per_id *oipi; + struct ospf *top; + + /* Don't take "rt_recalc" into consideration for now. */ /* XXX */ + + if (!IS_LSA_SELF(lsa)) { + new = lsa; /* Don't touch this LSA. */ + goto out; + } + + if (IS_DEBUG_OSPF(lsa, LSA_INSTALL)) + zlog_debug( + "Install Type-%u Opaque-LSA: [opaque-type=%u, opaque-id=%x]", + lsa->data->type, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), + GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); + + /* Replace the existing lsa with the new one. */ + if ((oipt = lookup_opaque_info_by_type(lsa)) != NULL + && (oipi = lookup_opaque_info_by_id(oipt, lsa)) != NULL) { + ospf_lsa_unlock(&oipi->lsa); + oipi->lsa = ospf_lsa_lock(lsa); + } + /* Register the new lsa entry */ + else if (register_opaque_lsa(lsa) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: register_opaque_lsa() ?", __func__); + goto out; + } + + /* + * Make use of a common mechanism (ospf_lsa_refresh_walker) + * for periodic refresh of self-originated Opaque-LSAs. + */ + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + if ((top = oi_to_top(lsa->oi)) == NULL) { + /* Above conditions must have passed. */ + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", + __func__); + goto out; + } + break; + case OSPF_OPAQUE_AREA_LSA: + if (lsa->area == NULL || (top = lsa->area->ospf) == NULL) { + /* Above conditions must have passed. */ + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", + __func__); + goto out; + } + break; + case OSPF_OPAQUE_AS_LSA: + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (lsa->area != NULL && (top = lsa->area->ospf) == NULL) { + /* Above conditions must have passed. */ + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", + __func__); + goto out; + } + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + goto out; + } + + ospf_refresher_register_lsa(top, lsa); + new = lsa; + +out: + return new; +} + +struct ospf_lsa *ospf_opaque_lsa_refresh(struct ospf_lsa *lsa) +{ + struct ospf *ospf; + struct ospf_opaque_functab *functab; + struct ospf_lsa *new = NULL; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + if ((functab = ospf_opaque_functab_lookup(lsa)) == NULL + || functab->lsa_refresher == NULL) { + /* + * Though this LSA seems to have originated on this node, the + * handling module for this "lsa-type and opaque-type" was + * already deleted sometime ago. + * Anyway, this node still has a responsibility to flush this + * LSA from the routing domain. + */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("LSA[Type%d:%pI4]: Flush stray Opaque-LSA", + lsa->data->type, &lsa->data->id); + + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + ospf_lsa_flush(ospf, lsa); + } else + new = (*functab->lsa_refresher)(lsa); + + return new; +} + +/*------------------------------------------------------------------------* + * Following are re-origination/refresh/flush operations of Opaque-LSAs, + * triggered by external interventions (vty session, signaling, etc). + *------------------------------------------------------------------------*/ + +#define OSPF_OPAQUE_TIMER_ON(T, F, L, V) \ + event_add_timer_msec(master, (F), (L), (V), &(T)) + +static struct ospf_lsa *pseudo_lsa(struct ospf_interface *oi, + struct ospf_area *area, uint8_t lsa_type, + uint8_t opaque_type); +static void ospf_opaque_type9_lsa_reoriginate_timer(struct event *t); +static void ospf_opaque_type10_lsa_reoriginate_timer(struct event *t); +static void ospf_opaque_type11_lsa_reoriginate_timer(struct event *t); +static void ospf_opaque_lsa_refresh_timer(struct event *t); + +void ospf_opaque_lsa_reoriginate_schedule(void *lsa_type_dependent, + uint8_t lsa_type, uint8_t opaque_type) +{ + struct ospf *top = NULL; + struct ospf_area dummy, *area = NULL; + struct ospf_interface *oi = NULL; + + struct ospf_lsa *lsa; + struct opaque_info_per_type *oipt; + void (*func)(struct event * t) = NULL; + int delay; + + switch (lsa_type) { + case OSPF_OPAQUE_LINK_LSA: + if ((oi = (struct ospf_interface *)lsa_type_dependent) + == NULL) { + flog_warn(EC_OSPF_LSA, + "%s: Type-9 Opaque-LSA: Invalid parameter?", + __func__); + goto out; + } + if ((top = oi_to_top(oi)) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: OI(%s) -> TOP?", __func__, + IF_NAME(oi)); + goto out; + } + if (!list_isempty(ospf_opaque_type9_funclist) + && list_isempty(oi->opaque_lsa_self) + && oi->t_opaque_lsa_self != NULL) { + flog_warn( + EC_OSPF_LSA, + "Type-9 Opaque-LSA (opaque_type=%u): Common origination for OI(%s) has already started", + opaque_type, IF_NAME(oi)); + goto out; + } + func = ospf_opaque_type9_lsa_reoriginate_timer; + break; + case OSPF_OPAQUE_AREA_LSA: + if ((area = (struct ospf_area *)lsa_type_dependent) == NULL) { + flog_warn(EC_OSPF_LSA, + "%s: Type-10 Opaque-LSA: Invalid parameter?", + __func__); + goto out; + } + if ((top = area->ospf) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: AREA(%pI4) -> TOP?", + __func__, &area->area_id); + goto out; + } + if (!list_isempty(ospf_opaque_type10_funclist) + && list_isempty(area->opaque_lsa_self) + && area->t_opaque_lsa_self != NULL) { + flog_warn( + EC_OSPF_LSA, + "Type-10 Opaque-LSA (opaque_type=%u): Common origination for AREA(%pI4) has already started", + opaque_type, &area->area_id); + goto out; + } + func = ospf_opaque_type10_lsa_reoriginate_timer; + break; + case OSPF_OPAQUE_AS_LSA: + if ((top = (struct ospf *)lsa_type_dependent) == NULL) { + flog_warn(EC_OSPF_LSA, + "%s: Type-11 Opaque-LSA: Invalid parameter?", + __func__); + goto out; + } + if (!list_isempty(ospf_opaque_type11_funclist) + && list_isempty(top->opaque_lsa_self) + && top->t_opaque_lsa_self != NULL) { + flog_warn( + EC_OSPF_LSA, + "Type-11 Opaque-LSA (opaque_type=%u): Common origination has already started", + opaque_type); + goto out; + } + + /* Fake "area" to pass "ospf" to a lookup function later. */ + dummy.ospf = top; + area = &dummy; + + func = ospf_opaque_type11_lsa_reoriginate_timer; + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa_type); + goto out; + } + + /* It may not a right time to schedule reorigination now. */ + if (!CHECK_FLAG(top->opaque, OPAQUE_OPERATION_READY_BIT)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Not operational.", __func__); + goto out; /* This is not an error. */ + } + + /* Generate a dummy lsa to be passed for a lookup function. */ + lsa = pseudo_lsa(oi, area, lsa_type, opaque_type); + lsa->vrf_id = VRF_DEFAULT; + + if ((oipt = lookup_opaque_info_by_type(lsa)) == NULL) { + struct ospf_opaque_functab *functab; + if ((functab = ospf_opaque_functab_lookup(lsa)) == NULL) { + flog_warn( + EC_OSPF_LSA, + "%s: No associated function?: lsa_type(%u), opaque_type(%u)", + __func__, lsa_type, opaque_type); + goto out; + } + if ((oipt = register_opaque_info_per_type(functab, lsa)) + == NULL) { + flog_warn( + EC_OSPF_LSA, + "%s: Cannot get a control info?: lsa_type(%u), opaque_type(%u)", + __func__, lsa_type, opaque_type); + goto out; + } + } + + if (oipt->t_opaque_lsa_self != NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Type-%u Opaque-LSA has already scheduled to RE-ORIGINATE: [opaque-type=%u]", + lsa_type, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr))); + goto out; + } + + /* + * Different from initial origination time, in which various conditions + * (opaque capability, neighbor status etc) are assured by caller of + * the originating function "ospf_opaque_lsa_originate_schedule ()", + * it is highly possible that these conditions might not be satisfied + * at the time of re-origination function is to be called. + */ + delay = top->min_ls_interval; /* XXX */ + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-%u Opaque-LSA to RE-ORIGINATE in %d ms later: [opaque-type=%u]", + lsa_type, delay, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr))); + + OSPF_OPAQUE_TIMER_ON(oipt->t_opaque_lsa_self, func, oipt, delay); + +out: + return; +} + +static struct ospf_lsa *pseudo_lsa(struct ospf_interface *oi, + struct ospf_area *area, uint8_t lsa_type, + uint8_t opaque_type) +{ + static struct ospf_lsa lsa = {0}; + static struct lsa_header lsah = {0}; + uint32_t tmp; + + lsa.oi = oi; + lsa.area = area; + lsa.data = &lsah; + lsa.vrf_id = VRF_DEFAULT; + + lsah.type = lsa_type; + tmp = SET_OPAQUE_LSID(opaque_type, 0); /* Opaque-ID is unused here. */ + lsah.id.s_addr = htonl(tmp); + + return &lsa; +} + +static void ospf_opaque_type9_lsa_reoriginate_timer(struct event *t) +{ + struct opaque_info_per_type *oipt; + struct ospf_opaque_functab *functab; + struct ospf *top; + struct ospf_interface *oi; + + oipt = EVENT_ARG(t); + + if ((functab = oipt->functab) == NULL + || functab->lsa_originator == NULL) { + flog_warn(EC_OSPF_LSA, "%s: No associated function?", __func__); + return; + } + + oi = (struct ospf_interface *)oipt->owner; + if ((top = oi_to_top(oi)) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + return; + } + + if (!CHECK_FLAG(top->config, OSPF_OPAQUE_CAPABLE) || + !OSPF_IF_PARAM(oi, opaque_capable) || !ospf_if_is_enable(oi) || + ospf_nbr_count_opaque_capable(oi) == 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Suspend re-origination of Type-9 Opaque-LSAs (opaque-type=%u) for a while...", + oipt->opaque_type); + + oipt->status = PROC_SUSPEND; + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type9-LSA]: Re-originate Opaque-LSAs (opaque-type=%u) for OI (%s)", + oipt->opaque_type, IF_NAME(oi)); + + (*functab->lsa_originator)(oi); +} + +static void ospf_opaque_type10_lsa_reoriginate_timer(struct event *t) +{ + struct opaque_info_per_type *oipt; + struct ospf_opaque_functab *functab; + struct listnode *node, *nnode; + struct ospf *top; + struct ospf_area *area; + struct ospf_interface *oi; + int n; + + oipt = EVENT_ARG(t); + + if ((functab = oipt->functab) == NULL + || functab->lsa_originator == NULL) { + flog_warn(EC_OSPF_LSA, "%s: No associated function?", __func__); + return; + } + + area = (struct ospf_area *)oipt->owner; + if (area == NULL || (top = area->ospf) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + return; + } + + /* There must be at least one "opaque-capable, full-state" neighbor. */ + n = 0; + for (ALL_LIST_ELEMENTS(area->oiflist, node, nnode, oi)) { + if ((n = ospf_nbr_count_opaque_capable(oi)) > 0) + break; + } + + if (n == 0 || !CHECK_FLAG(top->config, OSPF_OPAQUE_CAPABLE)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Suspend re-origination of Type-10 Opaque-LSAs (opaque-type=%u) for a while...", + oipt->opaque_type); + + oipt->status = PROC_SUSPEND; + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type10-LSA]: Re-originate Opaque-LSAs (opaque-type=%u) for Area %pI4", + oipt->opaque_type, &area->area_id); + + (*functab->lsa_originator)(area); +} + +static void ospf_opaque_type11_lsa_reoriginate_timer(struct event *t) +{ + struct opaque_info_per_type *oipt; + struct ospf_opaque_functab *functab; + struct ospf *top; + + oipt = EVENT_ARG(t); + + if ((functab = oipt->functab) == NULL + || functab->lsa_originator == NULL) { + flog_warn(EC_OSPF_LSA, "%s: No associated function?", __func__); + return; + } + + if ((top = (struct ospf *)oipt->owner) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + return; + } + + if (!CHECK_FLAG(top->config, OSPF_OPAQUE_CAPABLE)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Suspend re-origination of Type-11 Opaque-LSAs (opaque-type=%u) for a while...", + oipt->opaque_type); + + oipt->status = PROC_SUSPEND; + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type11-LSA]: Re-originate Opaque-LSAs (opaque-type=%u).", + oipt->opaque_type); + + (*functab->lsa_originator)(top); +} + +void ospf_opaque_lsa_refresh_schedule(struct ospf_lsa *lsa0) +{ + struct opaque_info_per_type *oipt; + struct opaque_info_per_id *oipi; + struct ospf_lsa *lsa; + struct ospf *top; + int delay; + + if ((oipt = lookup_opaque_info_by_type(lsa0)) == NULL + || (oipi = lookup_opaque_info_by_id(oipt, lsa0)) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Invalid parameter?", __func__); + goto out; + } + + /* Given "lsa0" and current "oipi->lsa" may different, but harmless. */ + if ((lsa = oipi->lsa) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + goto out; + } + + if (oipi->t_opaque_lsa_self != NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Type-%u Opaque-LSA has already scheduled to REFRESH: [opaque-type=%u, opaque-id=%x]", + lsa->data->type, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), + GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); + goto out; + } + + /* Delete this lsa from neighbor retransmit-list. */ + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + ospf_ls_retransmit_delete_nbr_area(lsa->area, lsa); + break; + case OSPF_OPAQUE_AS_LSA: + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if ((lsa0->area != NULL) && (lsa0->area->ospf != NULL)) + top = lsa0->area->ospf; + ospf_ls_retransmit_delete_nbr_as(top, lsa); + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + goto out; + } + + delay = ospf_lsa_refresh_delay(lsa); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-%u Opaque-LSA to REFRESH in %d sec later: [opaque-type=%u, opaque-id=%x]", + lsa->data->type, delay, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), + GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); + + OSPF_OPAQUE_TIMER_ON(oipi->t_opaque_lsa_self, + ospf_opaque_lsa_refresh_timer, oipi, delay * 1000); +out: + return; +} + +static void ospf_opaque_lsa_refresh_timer(struct event *t) +{ + struct opaque_info_per_id *oipi; + struct ospf_opaque_functab *functab; + struct ospf_lsa *lsa; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Timer[Opaque-LSA]: (Opaque-LSA Refresh expire)"); + + oipi = EVENT_ARG(t); + + if ((lsa = oipi->lsa) != NULL) + if ((functab = oipi->opqctl_type->functab) != NULL) + if (functab->lsa_refresher != NULL) + (*functab->lsa_refresher)(lsa); +} + +void ospf_opaque_lsa_flush_schedule(struct ospf_lsa *lsa0) +{ + struct opaque_info_per_type *oipt; + struct opaque_info_per_id *oipi; + struct ospf_lsa *lsa; + struct ospf *top; + + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + if ((oipt = lookup_opaque_info_by_type(lsa0)) == NULL + || (oipi = lookup_opaque_info_by_id(oipt, lsa0)) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Invalid parameter?", __func__); + goto out; + } + + /* Given "lsa0" and current "oipi->lsa" may different, but harmless. */ + if ((lsa = oipi->lsa) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + goto out; + } + + if (lsa->opaque_zero_len_delete && + lsa->data->length != htons(sizeof(struct lsa_header))) { + /* minimize the size of the withdrawal: */ + /* increment the sequence number and make len just header */ + /* and update checksum */ + lsa->data->ls_seqnum = lsa_seqnum_increment(lsa); + lsa->data->length = htons(sizeof(struct lsa_header)); + lsa->data->checksum = 0; + lsa->data->checksum = ospf_lsa_checksum(lsa->data); + } + + /* Delete this lsa from neighbor retransmit-list. */ + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + ospf_ls_retransmit_delete_nbr_area(lsa->area, lsa); + break; + case OSPF_OPAQUE_AS_LSA: + if ((lsa0->area != NULL) && (lsa0->area->ospf != NULL)) + top = lsa0->area->ospf; + ospf_ls_retransmit_delete_nbr_as(top, lsa); + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + goto out; + } + + /* This lsa will be flushed and removed eventually. */ + ospf_lsa_flush(top, lsa); + + /* Dequeue listnode entry from the list. */ + listnode_delete(oipt->id_list, oipi); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-%u Opaque-LSA to FLUSH: [opaque-type=%u, opaque-id=%x]", + lsa->data->type, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), + GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); + + /* Disassociate internal control information with the given lsa. */ + free_opaque_info_per_id((void *)oipi); + +out: + return; +} + +void ospf_opaque_self_originated_lsa_received(struct ospf_neighbor *nbr, + struct ospf_lsa *lsa) +{ + struct ospf *top; + + if ((top = oi_to_top(nbr->oi)) == NULL) + return; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type%d:%pI4]: processing self-originated Opaque-LSA", + lsa->data->type, &lsa->data->id); + + /* + * Install the stale LSA into the Link State Database, add it to the + * MaxAge list, and flush it from the OSPF routing domain. For other + * LSA types, the installation is done in the refresh function. It is + * done inline here since the opaque refresh function is dynamically + * registered when opaque LSAs are originated (which is not the case + * for stale LSAs). + */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + ospf_lsa_install( + top, (lsa->data->type == OSPF_OPAQUE_LINK_LSA) ? nbr->oi : NULL, + lsa); + ospf_lsa_maxage(top, lsa); + + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + ospf_flood_through_area(nbr->oi->area, NULL /*inbr*/, lsa); + break; + case OSPF_OPAQUE_AS_LSA: + ospf_flood_through_as(top, NULL /*inbr*/, lsa); + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + return; + } +} + +/*------------------------------------------------------------------------* + * Following are util functions; probably be used by Opaque-LSAs only... + *------------------------------------------------------------------------*/ + +struct ospf *oi_to_top(struct ospf_interface *oi) +{ + struct ospf *top = NULL; + struct ospf_area *area; + + if (oi == NULL || (area = oi->area) == NULL + || (top = area->ospf) == NULL) + flog_warn(EC_OSPF_LSA, + "Broken relationship for \"OI -> AREA -> OSPF\"?"); + + return top; +} |