// SPDX-License-Identifier: GPL-2.0-or-later /* Route map function. * Copyright (C) 1998, 1999 Kunihiro Ishiguro */ #include #include "linklist.h" #include "memory.h" #include "command.h" #include "vector.h" #include "prefix.h" #include "vty.h" #include "routemap.h" #include "command.h" #include "log.h" #include "hash.h" #include "libfrr.h" #include "lib_errors.h" #include "table.h" #include "json.h" #include "jhash.h" #include "lib/routemap_clippy.c" DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP, "Route map"); DEFINE_MTYPE(LIB, ROUTE_MAP_NAME, "Route map name"); DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_INDEX, "Route map index"); DEFINE_MTYPE(LIB, ROUTE_MAP_RULE, "Route map rule"); DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_RULE_STR, "Route map rule str"); DEFINE_MTYPE(LIB, ROUTE_MAP_COMPILED, "Route map compiled"); DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_DEP, "Route map dependency"); DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_DEP_DATA, "Route map dependency data"); DEFINE_QOBJ_TYPE(route_map_index); DEFINE_QOBJ_TYPE(route_map); static int rmap_cmd_name_cmp(const struct route_map_rule_cmd_proxy *a, const struct route_map_rule_cmd_proxy *b) { return strcmp(a->cmd->str, b->cmd->str); } static uint32_t rmap_cmd_name_hash(const struct route_map_rule_cmd_proxy *item) { return jhash(item->cmd->str, strlen(item->cmd->str), 0xbfd69320); } DECLARE_HASH(rmap_cmd_name, struct route_map_rule_cmd_proxy, itm, rmap_cmd_name_cmp, rmap_cmd_name_hash); static struct rmap_cmd_name_head rmap_match_cmds[1] = { INIT_HASH(rmap_match_cmds[0]), }; static struct rmap_cmd_name_head rmap_set_cmds[1] = { INIT_HASH(rmap_set_cmds[0]), }; #define IPv4_PREFIX_LIST "ip address prefix-list" #define IPv6_PREFIX_LIST "ipv6 address prefix-list" #define IS_RULE_IPv4_PREFIX_LIST(S) \ (strncmp(S, IPv4_PREFIX_LIST, strlen(IPv4_PREFIX_LIST)) == 0) #define IS_RULE_IPv6_PREFIX_LIST(S) \ (strncmp(S, IPv6_PREFIX_LIST, strlen(IPv6_PREFIX_LIST)) == 0) struct route_map_pentry_dep { struct prefix_list_entry *pentry; const char *plist_name; route_map_event_t event; }; static void route_map_pfx_tbl_update(route_map_event_t event, struct route_map_index *index, afi_t afi, const char *plist_name); static void route_map_pfx_table_add_default(afi_t afi, struct route_map_index *index); static void route_map_pfx_table_del_default(afi_t afi, struct route_map_index *index); static void route_map_add_plist_entries(afi_t afi, struct route_map_index *index, const char *plist_name, struct prefix_list_entry *entry); static void route_map_del_plist_entries(afi_t afi, struct route_map_index *index, const char *plist_name, struct prefix_list_entry *entry); static struct hash *route_map_get_dep_hash(route_map_event_t event); static void route_map_free_map(struct route_map *map); struct route_map_match_set_hooks rmap_match_set_hook; /* match interface */ void route_map_match_interface_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_interface = func; } /* no match interface */ void route_map_no_match_interface_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_interface = func; } /* match ip address */ void route_map_match_ip_address_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ip_address = func; } /* no match ip address */ void route_map_no_match_ip_address_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ip_address = func; } /* match ip address prefix list */ void route_map_match_ip_address_prefix_list_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ip_address_prefix_list = func; } /* no match ip address prefix list */ void route_map_no_match_ip_address_prefix_list_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ip_address_prefix_list = func; } /* match ip next hop */ void route_map_match_ip_next_hop_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ip_next_hop = func; } /* no match ip next hop */ void route_map_no_match_ip_next_hop_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ip_next_hop = func; } /* match ipv6 next-hop */ void route_map_match_ipv6_next_hop_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ipv6_next_hop = func; } /* no match ipv6 next-hop */ void route_map_no_match_ipv6_next_hop_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ipv6_next_hop = func; } /* match ip next hop prefix list */ void route_map_match_ip_next_hop_prefix_list_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ip_next_hop_prefix_list = func; } /* no match ip next hop prefix list */ void route_map_no_match_ip_next_hop_prefix_list_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ip_next_hop_prefix_list = func; } /* match ip next-hop type */ void route_map_match_ip_next_hop_type_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ip_next_hop_type = func; } /* no match ip next-hop type */ void route_map_no_match_ip_next_hop_type_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ip_next_hop_type = func; } /* match ipv6 address */ void route_map_match_ipv6_address_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ipv6_address = func; } /* no match ipv6 address */ void route_map_no_match_ipv6_address_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ipv6_address = func; } /* match ipv6 address prefix list */ void route_map_match_ipv6_address_prefix_list_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ipv6_address_prefix_list = func; } /* no match ipv6 address prefix list */ void route_map_no_match_ipv6_address_prefix_list_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ipv6_address_prefix_list = func; } /* match ipv6 next-hop type */ void route_map_match_ipv6_next_hop_type_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ipv6_next_hop_type = func; } /* no match ipv6 next-hop type */ void route_map_no_match_ipv6_next_hop_type_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ipv6_next_hop_type = func; } /* match ipv6 next-hop prefix-list */ void route_map_match_ipv6_next_hop_prefix_list_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_ipv6_next_hop_prefix_list = func; } /* no match ipv6 next-hop prefix-list */ void route_map_no_match_ipv6_next_hop_prefix_list_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_ipv6_next_hop_prefix_list = func; } /* match metric */ void route_map_match_metric_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_metric = func; } /* no match metric */ void route_map_no_match_metric_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_metric = func; } /* match tag */ void route_map_match_tag_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.match_tag = func; } /* no match tag */ void route_map_no_match_tag_hook(int (*func)( struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_match_tag = func; } /* set sr-te color */ void route_map_set_srte_color_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.set_srte_color = func; } /* no set sr-te color */ void route_map_no_set_srte_color_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_set_srte_color = func; } /* set ip nexthop */ void route_map_set_ip_nexthop_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.set_ip_nexthop = func; } /* no set ip nexthop */ void route_map_no_set_ip_nexthop_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_set_ip_nexthop = func; } /* set ipv6 nexthop local */ void route_map_set_ipv6_nexthop_local_hook( int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.set_ipv6_nexthop_local = func; } /* no set ipv6 nexthop local */ void route_map_no_set_ipv6_nexthop_local_hook( int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_set_ipv6_nexthop_local = func; } /* set metric */ void route_map_set_metric_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.set_metric = func; } /* no set metric */ void route_map_no_set_metric_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_set_metric = func; } /* set min-metric */ void route_map_set_min_metric_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.set_min_metric = func; } /* no set min-metric */ void route_map_no_set_min_metric_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_set_min_metric = func; } /* set max-metric */ void route_map_set_max_metric_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.set_max_metric = func; } /* no set max-metric */ void route_map_no_set_max_metric_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_set_max_metric = func; } /* set tag */ void route_map_set_tag_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.set_tag = func; } /* no set tag */ void route_map_no_set_tag_hook(int (*func)(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len)) { rmap_match_set_hook.no_set_tag = func; } int generic_match_add(struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len) { enum rmap_compile_rets ret; ret = route_map_add_match(index, command, arg, type); switch (ret) { case RMAP_RULE_MISSING: snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; case RMAP_COMPILE_ERROR: snprintf(errmsg, errmsg_len, "%% [%s] Argument form is unsupported or malformed.", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; case RMAP_COMPILE_SUCCESS: /* * Nothing to do here move along */ break; } return CMD_SUCCESS; } int generic_match_delete(struct route_map_index *index, const char *command, const char *arg, route_map_event_t type, char *errmsg, size_t errmsg_len) { enum rmap_compile_rets ret; int retval = CMD_SUCCESS; char *dep_name = NULL; const char *tmpstr; char *rmap_name = NULL; if (type != RMAP_EVENT_MATCH_DELETED) { /* ignore the mundane, the types without any dependency */ if (arg == NULL) { if ((tmpstr = route_map_get_match_arg(index, command)) != NULL) dep_name = XSTRDUP(MTYPE_ROUTE_MAP_RULE, tmpstr); } else { dep_name = XSTRDUP(MTYPE_ROUTE_MAP_RULE, arg); } rmap_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, index->map->name); } ret = route_map_delete_match(index, command, dep_name, type); switch (ret) { case RMAP_RULE_MISSING: snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.", frr_protonameinst); retval = CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_ERROR: snprintf(errmsg, errmsg_len, "%% [%s] Argument form is unsupported or malformed.", frr_protonameinst); retval = CMD_WARNING_CONFIG_FAILED; break; case RMAP_COMPILE_SUCCESS: /* * Nothing to do here */ break; } XFREE(MTYPE_ROUTE_MAP_RULE, dep_name); XFREE(MTYPE_ROUTE_MAP_NAME, rmap_name); return retval; } int generic_set_add(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len) { enum rmap_compile_rets ret; ret = route_map_add_set(index, command, arg); switch (ret) { case RMAP_RULE_MISSING: snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; case RMAP_COMPILE_ERROR: snprintf(errmsg, errmsg_len, "%% [%s] Argument form is unsupported or malformed.", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; case RMAP_COMPILE_SUCCESS: break; } return CMD_SUCCESS; } int generic_set_delete(struct route_map_index *index, const char *command, const char *arg, char *errmsg, size_t errmsg_len) { enum rmap_compile_rets ret; ret = route_map_delete_set(index, command, arg); switch (ret) { case RMAP_RULE_MISSING: snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; case RMAP_COMPILE_ERROR: snprintf(errmsg, errmsg_len, "%% [%s] Argument form is unsupported or malformed.", frr_protonameinst); return CMD_WARNING_CONFIG_FAILED; case RMAP_COMPILE_SUCCESS: break; } return CMD_SUCCESS; } /* Master list of route map. */ struct route_map_list route_map_master = {NULL, NULL, NULL, NULL, NULL}; struct hash *route_map_master_hash = NULL; static unsigned int route_map_hash_key_make(const void *p) { const struct route_map *map = p; return string_hash_make(map->name); } static bool route_map_hash_cmp(const void *p1, const void *p2) { const struct route_map *map1 = p1; const struct route_map *map2 = p2; if (!strcmp(map1->name, map2->name)) return true; return false; } enum route_map_upd8_type { ROUTE_MAP_ADD = 1, ROUTE_MAP_DEL, }; /* all possible route-map dependency types */ enum route_map_dep_type { ROUTE_MAP_DEP_RMAP = 1, ROUTE_MAP_DEP_CLIST, ROUTE_MAP_DEP_ECLIST, ROUTE_MAP_DEP_LCLIST, ROUTE_MAP_DEP_PLIST, ROUTE_MAP_DEP_ASPATH, ROUTE_MAP_DEP_FILTER, ROUTE_MAP_DEP_MAX, }; struct route_map_dep { char *dep_name; struct hash *dep_rmap_hash; struct hash *this_hash; /* ptr to the hash structure this is part of */ }; struct route_map_dep_data { /* Route-map name. */ char *rname; /* Count of number of sequences of this * route-map that depend on the same entity. */ uint16_t refcnt; }; /* Hashes maintaining dependency between various sublists used by route maps */ static struct hash *route_map_dep_hash[ROUTE_MAP_DEP_MAX]; static unsigned int route_map_dep_hash_make_key(const void *p); static void route_map_clear_all_references(char *rmap_name); static void route_map_rule_delete(struct route_map_rule_list *, struct route_map_rule *); uint32_t rmap_debug; /* New route map allocation. Please note route map's name must be specified. */ static struct route_map *route_map_new(const char *name) { struct route_map *new; new = XCALLOC(MTYPE_ROUTE_MAP, sizeof(struct route_map)); new->name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); QOBJ_REG(new, route_map); return new; } /* Add new name to route_map. */ static struct route_map *route_map_add(const char *name) { struct route_map *map, *exist; struct route_map_list *list; map = route_map_new(name); list = &route_map_master; /* * Add map to the hash * * If the map already exists in the hash, then we know that * FRR is now in a sequence of delete/create. * All FRR needs to do here is set the to_be_processed * bit (to inherit from the old one */ exist = hash_release(route_map_master_hash, map); if (exist) { map->to_be_processed = exist->to_be_processed; route_map_free_map(exist); } hash_get(route_map_master_hash, map, hash_alloc_intern); /* Add new entry to the head of the list to match how it is added in the * hash table. This is to ensure that if the same route-map has been * created more than once and then marked for deletion (which can happen * if prior deletions haven't completed as BGP hasn't yet done the * route-map processing), the order of the entities is the same in both * the list and the hash table. Otherwise, since there is nothing to * distinguish between the two entries, the wrong entry could get freed. * TODO: This needs to be re-examined to handle it better - e.g., revive * a deleted entry if the route-map is created again. */ map->prev = NULL; map->next = list->head; if (list->head) list->head->prev = map; list->head = map; if (!list->tail) list->tail = map; /* Execute hook. */ if (route_map_master.add_hook) { (*route_map_master.add_hook)(name); route_map_notify_dependencies(name, RMAP_EVENT_CALL_ADDED); } if (!map->ipv4_prefix_table) map->ipv4_prefix_table = route_table_init(); if (!map->ipv6_prefix_table) map->ipv6_prefix_table = route_table_init(); if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Add route-map %s", name); return map; } /* this is supposed to be called post processing by * the delete hook function. Don't invoke delete_hook * again in this routine. */ static void route_map_free_map(struct route_map *map) { struct route_map_list *list; struct route_map_index *index; if (map == NULL) return; while ((index = map->head) != NULL) route_map_index_delete(index, 0); if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Deleting route-map %s", map->name); list = &route_map_master; QOBJ_UNREG(map); if (map->next) map->next->prev = map->prev; else list->tail = map->prev; if (map->prev) map->prev->next = map->next; else list->head = map->next; route_table_finish(map->ipv4_prefix_table); route_table_finish(map->ipv6_prefix_table); hash_release(route_map_master_hash, map); XFREE(MTYPE_ROUTE_MAP_NAME, map->name); XFREE(MTYPE_ROUTE_MAP, map); } /* Route map delete from list. */ void route_map_delete(struct route_map *map) { struct route_map_index *index; char *name; while ((index = map->head) != NULL) route_map_index_delete(index, 0); name = map->name; map->head = NULL; /* Clear all dependencies */ route_map_clear_all_references(name); map->deleted = true; /* Execute deletion hook. */ if (route_map_master.delete_hook) { (*route_map_master.delete_hook)(name); route_map_notify_dependencies(name, RMAP_EVENT_CALL_DELETED); } if (!map->to_be_processed) { route_map_free_map(map); } } /* Lookup route map by route map name string. */ struct route_map *route_map_lookup_by_name(const char *name) { struct route_map *map; struct route_map tmp_map; if (!name) return NULL; // map.deleted is false via memset memset(&tmp_map, 0, sizeof(tmp_map)); tmp_map.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); map = hash_lookup(route_map_master_hash, &tmp_map); XFREE(MTYPE_ROUTE_MAP_NAME, tmp_map.name); if (map && map->deleted) return NULL; return map; } /* Simple helper to warn if route-map does not exist. */ struct route_map *route_map_lookup_warn_noexist(struct vty *vty, const char *name) { struct route_map *route_map = route_map_lookup_by_name(name); if (!route_map) if (vty_shell_serv(vty)) vty_out(vty, "The route-map '%s' does not exist.\n", name); return route_map; } int route_map_mark_updated(const char *name) { struct route_map *map; int ret = -1; struct route_map tmp_map; if (!name) return (ret); map = route_map_lookup_by_name(name); /* If we did not find the routemap with deleted=false try again * with deleted=true */ if (!map) { memset(&tmp_map, 0, sizeof(tmp_map)); tmp_map.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); tmp_map.deleted = true; map = hash_lookup(route_map_master_hash, &tmp_map); XFREE(MTYPE_ROUTE_MAP_NAME, tmp_map.name); } if (map) { map->to_be_processed = true; ret = 0; } return (ret); } static void route_map_clear_updated(struct route_map *map) { if (map) { map->to_be_processed = false; if (map->deleted) route_map_free_map(map); } } /* Lookup route map. If there isn't route map create one and return it. */ struct route_map *route_map_get(const char *name) { struct route_map *map; map = route_map_lookup_by_name(name); if (map == NULL) map = route_map_add(name); return map; } void route_map_walk_update_list(void (*route_map_update_fn)(char *name)) { struct route_map *node; struct route_map *nnode = NULL; for (node = route_map_master.head; node; node = nnode) { if (node->to_be_processed) { /* DD: Should we add any thread yield code here */ route_map_update_fn(node->name); nnode = node->next; route_map_clear_updated(node); } else nnode = node->next; } } /* Return route map's type string. */ static const char *route_map_type_str(enum route_map_type type) { switch (type) { case RMAP_PERMIT: return "permit"; case RMAP_DENY: return "deny"; case RMAP_ANY: return ""; } return ""; } static const char *route_map_cmd_result_str(enum route_map_cmd_result_t res) { switch (res) { case RMAP_MATCH: return "match"; case RMAP_NOMATCH: return "no match"; case RMAP_NOOP: return "noop"; case RMAP_ERROR: return "error"; case RMAP_OKAY: return "okay"; } return "invalid"; } static const char *route_map_result_str(route_map_result_t res) { switch (res) { case RMAP_DENYMATCH: return "deny"; case RMAP_PERMITMATCH: return "permit"; } return "invalid"; } /* show route-map */ static void vty_show_route_map_entry(struct vty *vty, struct route_map *map, json_object *json) { struct route_map_index *index; struct route_map_rule *rule; json_object *json_rmap = NULL; json_object *json_rules = NULL; if (json) { json_rmap = json_object_new_object(); json_object_object_add(json, map->name, json_rmap); json_rules = json_object_new_array(); json_object_int_add(json_rmap, "invoked", map->applied - map->applied_clear); json_object_boolean_add(json_rmap, "disabledOptimization", map->optimization_disabled); json_object_boolean_add(json_rmap, "processedChange", map->to_be_processed); json_object_object_add(json_rmap, "rules", json_rules); } else { vty_out(vty, "route-map: %s Invoked: %" PRIu64 " Optimization: %s Processed Change: %s\n", map->name, map->applied - map->applied_clear, map->optimization_disabled ? "disabled" : "enabled", map->to_be_processed ? "true" : "false"); } for (index = map->head; index; index = index->next) { if (json) { json_object *json_rule; json_object *json_matches; json_object *json_sets; char action[BUFSIZ] = {}; json_rule = json_object_new_object(); json_object_array_add(json_rules, json_rule); json_object_int_add(json_rule, "sequenceNumber", index->pref); json_object_string_add(json_rule, "type", route_map_type_str(index->type)); json_object_int_add(json_rule, "invoked", index->applied - index->applied_clear); /* Description */ if (index->description) json_object_string_add(json_rule, "description", index->description); /* Match clauses */ json_matches = json_object_new_array(); json_object_object_add(json_rule, "matchClauses", json_matches); for (rule = index->match_list.head; rule; rule = rule->next) { char buf[BUFSIZ]; snprintf(buf, sizeof(buf), "%s %s", rule->cmd->str, rule->rule_str); json_array_string_add(json_matches, buf); } /* Set clauses */ json_sets = json_object_new_array(); json_object_object_add(json_rule, "setClauses", json_sets); for (rule = index->set_list.head; rule; rule = rule->next) { char buf[BUFSIZ]; snprintf(buf, sizeof(buf), "%s %s", rule->cmd->str, rule->rule_str); json_array_string_add(json_sets, buf); } /* Call clause */ if (index->nextrm) json_object_string_add(json_rule, "callClause", index->nextrm); /* Exit Policy */ if (index->exitpolicy == RMAP_GOTO) snprintf(action, sizeof(action), "Goto %d", index->nextpref); else if (index->exitpolicy == RMAP_NEXT) snprintf(action, sizeof(action), "Continue to next entry"); else if (index->exitpolicy == RMAP_EXIT) snprintf(action, sizeof(action), "Exit routemap"); if (action[0] != '\0') json_object_string_add(json_rule, "action", action); } else { vty_out(vty, " %s, sequence %d Invoked %" PRIu64 "\n", route_map_type_str(index->type), index->pref, index->applied - index->applied_clear); /* Description */ if (index->description) vty_out(vty, " Description:\n %s\n", index->description); /* Match clauses */ vty_out(vty, " Match clauses:\n"); for (rule = index->match_list.head; rule; rule = rule->next) vty_out(vty, " %s %s\n", rule->cmd->str, rule->rule_str); /* Set clauses */ vty_out(vty, " Set clauses:\n"); for (rule = index->set_list.head; rule; rule = rule->next) vty_out(vty, " %s %s\n", rule->cmd->str, rule->rule_str); /* Call clause */ vty_out(vty, " Call clause:\n"); if (index->nextrm) vty_out(vty, " Call %s\n", index->nextrm); /* Exit Policy */ vty_out(vty, " Action:\n"); if (index->exitpolicy == RMAP_GOTO) vty_out(vty, " Goto %d\n", index->nextpref); else if (index->exitpolicy == RMAP_NEXT) vty_out(vty, " Continue to next entry\n"); else if (index->exitpolicy == RMAP_EXIT) vty_out(vty, " Exit routemap\n"); } } } static int sort_route_map(const void **map1, const void **map2) { const struct route_map *m1 = *map1; const struct route_map *m2 = *map2; return strcmp(m1->name, m2->name); } static int vty_show_route_map(struct vty *vty, const char *name, bool use_json) { struct route_map *map; json_object *json = NULL; json_object *json_proto = NULL; if (use_json) { json = json_object_new_object(); json_proto = json_object_new_object(); json_object_object_add(json, frr_protonameinst, json_proto); } else vty_out(vty, "%s:\n", frr_protonameinst); if (name) { map = route_map_lookup_by_name(name); if (map) { vty_show_route_map_entry(vty, map, json_proto); } else if (!use_json) { vty_out(vty, "%s: 'route-map %s' not found\n", frr_protonameinst, name); } } else { struct list *maplist = list_new(); struct listnode *ln; for (map = route_map_master.head; map; map = map->next) listnode_add(maplist, map); list_sort(maplist, sort_route_map); for (ALL_LIST_ELEMENTS_RO(maplist, ln, map)) vty_show_route_map_entry(vty, map, json_proto); list_delete(&maplist); } return vty_json(vty, json); } /* Unused route map details */ static int vty_show_unused_route_map(struct vty *vty) { struct list *maplist = list_new(); struct listnode *ln; struct route_map *map; for (map = route_map_master.head; map; map = map->next) { /* If use_count is zero, No protocol is using this routemap. * so adding to the list. */ if (!map->use_count) listnode_add(maplist, map); } if (maplist->count > 0) { vty_out(vty, "\n%s:\n", frr_protonameinst); list_sort(maplist, sort_route_map); for (ALL_LIST_ELEMENTS_RO(maplist, ln, map)) vty_show_route_map_entry(vty, map, NULL); } else { vty_out(vty, "\n%s: None\n", frr_protonameinst); } list_delete(&maplist); return CMD_SUCCESS; } /* New route map allocation. Please note route map's name must be specified. */ static struct route_map_index *route_map_index_new(void) { struct route_map_index *new; new = XCALLOC(MTYPE_ROUTE_MAP_INDEX, sizeof(struct route_map_index)); new->exitpolicy = RMAP_EXIT; /* Default to Cisco-style */ TAILQ_INIT(&new->rhclist); QOBJ_REG(new, route_map_index); return new; } /* Free route map index. */ void route_map_index_delete(struct route_map_index *index, int notify) { struct routemap_hook_context *rhc; struct route_map_rule *rule; QOBJ_UNREG(index); if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Deleting route-map %s sequence %d", index->map->name, index->pref); /* Free route map entry description. */ XFREE(MTYPE_TMP, index->description); /* Free route map northbound hook contexts. */ while ((rhc = TAILQ_FIRST(&index->rhclist)) != NULL) routemap_hook_context_free(rhc); /* Free route match. */ while ((rule = index->match_list.head) != NULL) { if (IS_RULE_IPv4_PREFIX_LIST(rule->cmd->str)) route_map_pfx_tbl_update(RMAP_EVENT_PLIST_DELETED, index, AFI_IP, rule->rule_str); else if (IS_RULE_IPv6_PREFIX_LIST(rule->cmd->str)) route_map_pfx_tbl_update(RMAP_EVENT_PLIST_DELETED, index, AFI_IP6, rule->rule_str); route_map_rule_delete(&index->match_list, rule); } /* Free route set. */ while ((rule = index->set_list.head) != NULL) route_map_rule_delete(&index->set_list, rule); /* Remove index from route map list. */ if (index->next) index->next->prev = index->prev; else index->map->tail = index->prev; if (index->prev) index->prev->next = index->next; else index->map->head = index->next; /* Free 'char *nextrm' if not NULL */ XFREE(MTYPE_ROUTE_MAP_NAME, index->nextrm); route_map_pfx_tbl_update(RMAP_EVENT_INDEX_DELETED, index, 0, NULL); /* Execute event hook. */ if (route_map_master.event_hook && notify) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies(index->map->name, RMAP_EVENT_CALL_ADDED); } XFREE(MTYPE_ROUTE_MAP_INDEX, index); } /* Lookup index from route map. */ static struct route_map_index *route_map_index_lookup(struct route_map *map, enum route_map_type type, int pref) { struct route_map_index *index; for (index = map->head; index; index = index->next) if ((index->type == type || type == RMAP_ANY) && index->pref == pref) return index; return NULL; } /* Add new index to route map. */ static struct route_map_index * route_map_index_add(struct route_map *map, enum route_map_type type, int pref) { struct route_map_index *index; struct route_map_index *point; /* Allocate new route map inex. */ index = route_map_index_new(); index->map = map; index->type = type; index->pref = pref; /* Compare preference. */ for (point = map->head; point; point = point->next) if (point->pref >= pref) break; if (map->head == NULL) { map->head = map->tail = index; } else if (point == NULL) { index->prev = map->tail; map->tail->next = index; map->tail = index; } else if (point == map->head) { index->next = map->head; map->head->prev = index; map->head = index; } else { index->next = point; index->prev = point->prev; if (point->prev) point->prev->next = index; point->prev = index; } route_map_pfx_tbl_update(RMAP_EVENT_INDEX_ADDED, index, 0, NULL); /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(map->name); route_map_notify_dependencies(map->name, RMAP_EVENT_CALL_ADDED); } if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Route-map %s add sequence %d, type: %s", map->name, pref, route_map_type_str(type)); return index; } /* Get route map index. */ struct route_map_index * route_map_index_get(struct route_map *map, enum route_map_type type, int pref) { struct route_map_index *index; index = route_map_index_lookup(map, RMAP_ANY, pref); if (index && index->type != type) { /* Delete index from route map. */ route_map_index_delete(index, 1); index = NULL; } if (index == NULL) index = route_map_index_add(map, type, pref); return index; } /* New route map rule */ static struct route_map_rule *route_map_rule_new(void) { struct route_map_rule *new; new = XCALLOC(MTYPE_ROUTE_MAP_RULE, sizeof(struct route_map_rule)); return new; } /* Install rule command to the match list. */ void _route_map_install_match(struct route_map_rule_cmd_proxy *proxy) { rmap_cmd_name_add(rmap_match_cmds, proxy); } /* Install rule command to the set list. */ void _route_map_install_set(struct route_map_rule_cmd_proxy *proxy) { rmap_cmd_name_add(rmap_set_cmds, proxy); } /* Lookup rule command from match list. */ static const struct route_map_rule_cmd *route_map_lookup_match(const char *name) { struct route_map_rule_cmd refcmd = {.str = name}; struct route_map_rule_cmd_proxy ref = {.cmd = &refcmd}; struct route_map_rule_cmd_proxy *res; res = rmap_cmd_name_find(rmap_match_cmds, &ref); if (res) return res->cmd; return NULL; } /* Lookup rule command from set list. */ static const struct route_map_rule_cmd *route_map_lookup_set(const char *name) { struct route_map_rule_cmd refcmd = {.str = name}; struct route_map_rule_cmd_proxy ref = {.cmd = &refcmd}; struct route_map_rule_cmd_proxy *res; res = rmap_cmd_name_find(rmap_set_cmds, &ref); if (res) return res->cmd; return NULL; } /* Add match and set rule to rule list. */ static void route_map_rule_add(struct route_map_rule_list *list, struct route_map_rule *rule) { rule->next = NULL; rule->prev = list->tail; if (list->tail) list->tail->next = rule; else list->head = rule; list->tail = rule; } /* Delete rule from rule list. */ static void route_map_rule_delete(struct route_map_rule_list *list, struct route_map_rule *rule) { if (rule->cmd->func_free) (*rule->cmd->func_free)(rule->value); XFREE(MTYPE_ROUTE_MAP_RULE_STR, rule->rule_str); if (rule->next) rule->next->prev = rule->prev; else list->tail = rule->prev; if (rule->prev) rule->prev->next = rule->next; else list->head = rule->next; XFREE(MTYPE_ROUTE_MAP_RULE, rule); } /* strcmp wrapper function which don't crush even argument is NULL. */ static int rulecmp(const char *dst, const char *src) { if (dst == NULL) { if (src == NULL) return 0; else return 1; } else { if (src == NULL) return 1; else return strcmp(dst, src); } return 1; } /* Use this to return the already specified argument for this match. This is * useful to get the specified argument with a route map match rule when the * rule is being deleted and the argument is not provided. */ const char *route_map_get_match_arg(struct route_map_index *index, const char *match_name) { struct route_map_rule *rule; const struct route_map_rule_cmd *cmd; /* First lookup rule for add match statement. */ cmd = route_map_lookup_match(match_name); if (cmd == NULL) return NULL; for (rule = index->match_list.head; rule; rule = rule->next) if (rule->cmd == cmd && rule->rule_str != NULL) return (rule->rule_str); return NULL; } static route_map_event_t get_route_map_delete_event(route_map_event_t type) { switch (type) { case RMAP_EVENT_CALL_ADDED: return RMAP_EVENT_CALL_DELETED; case RMAP_EVENT_PLIST_ADDED: return RMAP_EVENT_PLIST_DELETED; case RMAP_EVENT_CLIST_ADDED: return RMAP_EVENT_CLIST_DELETED; case RMAP_EVENT_ECLIST_ADDED: return RMAP_EVENT_ECLIST_DELETED; case RMAP_EVENT_LLIST_ADDED: return RMAP_EVENT_LLIST_DELETED; case RMAP_EVENT_ASLIST_ADDED: return RMAP_EVENT_ASLIST_DELETED; case RMAP_EVENT_FILTER_ADDED: return RMAP_EVENT_FILTER_DELETED; case RMAP_EVENT_SET_ADDED: case RMAP_EVENT_SET_DELETED: case RMAP_EVENT_SET_REPLACED: case RMAP_EVENT_MATCH_ADDED: case RMAP_EVENT_MATCH_DELETED: case RMAP_EVENT_MATCH_REPLACED: case RMAP_EVENT_INDEX_ADDED: case RMAP_EVENT_INDEX_DELETED: case RMAP_EVENT_CALL_DELETED: case RMAP_EVENT_PLIST_DELETED: case RMAP_EVENT_CLIST_DELETED: case RMAP_EVENT_ECLIST_DELETED: case RMAP_EVENT_LLIST_DELETED: case RMAP_EVENT_ASLIST_DELETED: case RMAP_EVENT_FILTER_DELETED: /* This function returns the appropriate 'deleted' event type * for every 'added' event type passed to this function. * This is done only for named entities used in the * route-map match commands. * This function is not to be invoked for any of the other event * types. */ assert(0); } assert(0); /* * Return to make c happy but if we get here something has gone * terribly terribly wrong, so yes this return makes no sense. */ return RMAP_EVENT_CALL_ADDED; } /* Add match statement to route map. */ enum rmap_compile_rets route_map_add_match(struct route_map_index *index, const char *match_name, const char *match_arg, route_map_event_t type) { struct route_map_rule *rule; struct route_map_rule *next; const struct route_map_rule_cmd *cmd; void *compile; int8_t delete_rmap_event_type = 0; const char *rule_key; /* First lookup rule for add match statement. */ cmd = route_map_lookup_match(match_name); if (cmd == NULL) return RMAP_RULE_MISSING; /* Next call compile function for this match statement. */ if (cmd->func_compile) { compile = (*cmd->func_compile)(match_arg); if (compile == NULL) return RMAP_COMPILE_ERROR; } else compile = NULL; /* use the compiled results if applicable */ if (compile && cmd->func_get_rmap_rule_key) rule_key = (*cmd->func_get_rmap_rule_key) (compile); else rule_key = match_arg; /* If argument is completely same ignore it. */ for (rule = index->match_list.head; rule; rule = next) { next = rule->next; if (rule->cmd == cmd) { /* If the configured route-map match rule is exactly * the same as the existing configuration then, * ignore the duplicate configuration. */ if (rulecmp(match_arg, rule->rule_str) == 0) { if (cmd->func_free) (*cmd->func_free)(compile); return RMAP_COMPILE_SUCCESS; } /* If IPv4 or IPv6 prefix-list match criteria * has been delete to the route-map index, update * the route-map's prefix table. */ if (IS_RULE_IPv4_PREFIX_LIST(match_name)) route_map_pfx_tbl_update( RMAP_EVENT_PLIST_DELETED, index, AFI_IP, rule->rule_str); else if (IS_RULE_IPv6_PREFIX_LIST(match_name)) route_map_pfx_tbl_update( RMAP_EVENT_PLIST_DELETED, index, AFI_IP6, rule->rule_str); /* Remove the dependency of the route-map on the rule * that is being replaced. */ if (type >= RMAP_EVENT_CALL_ADDED) { delete_rmap_event_type = get_route_map_delete_event(type); route_map_upd8_dependency( delete_rmap_event_type, rule->rule_str, index->map->name); } route_map_rule_delete(&index->match_list, rule); } } /* Add new route map match rule. */ rule = route_map_rule_new(); rule->cmd = cmd; rule->value = compile; if (match_arg) rule->rule_str = XSTRDUP(MTYPE_ROUTE_MAP_RULE_STR, match_arg); else rule->rule_str = NULL; /* Add new route match rule to linked list. */ route_map_rule_add(&index->match_list, rule); /* If IPv4 or IPv6 prefix-list match criteria * has been added to the route-map index, update * the route-map's prefix table. */ if (IS_RULE_IPv4_PREFIX_LIST(match_name)) { route_map_pfx_tbl_update(RMAP_EVENT_PLIST_ADDED, index, AFI_IP, match_arg); } else if (IS_RULE_IPv6_PREFIX_LIST(match_name)) { route_map_pfx_tbl_update(RMAP_EVENT_PLIST_ADDED, index, AFI_IP6, match_arg); } /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies(index->map->name, RMAP_EVENT_CALL_ADDED); } if (type != RMAP_EVENT_MATCH_ADDED) route_map_upd8_dependency(type, rule_key, index->map->name); return RMAP_COMPILE_SUCCESS; } /* Delete specified route match rule. */ enum rmap_compile_rets route_map_delete_match(struct route_map_index *index, const char *match_name, const char *match_arg, route_map_event_t type) { struct route_map_rule *rule; const struct route_map_rule_cmd *cmd; const char *rule_key; cmd = route_map_lookup_match(match_name); if (cmd == NULL) return RMAP_RULE_MISSING; for (rule = index->match_list.head; rule; rule = rule->next) if (rule->cmd == cmd && (rulecmp(rule->rule_str, match_arg) == 0 || match_arg == NULL)) { /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies( index->map->name, RMAP_EVENT_CALL_ADDED); } if (cmd->func_get_rmap_rule_key) rule_key = (*cmd->func_get_rmap_rule_key) (rule->value); else rule_key = match_arg; if (type != RMAP_EVENT_MATCH_DELETED && rule_key) route_map_upd8_dependency(type, rule_key, index->map->name); route_map_rule_delete(&index->match_list, rule); /* If IPv4 or IPv6 prefix-list match criteria * has been delete from the route-map index, update * the route-map's prefix table. */ if (IS_RULE_IPv4_PREFIX_LIST(match_name)) { route_map_pfx_tbl_update( RMAP_EVENT_PLIST_DELETED, index, AFI_IP, match_arg); } else if (IS_RULE_IPv6_PREFIX_LIST(match_name)) { route_map_pfx_tbl_update( RMAP_EVENT_PLIST_DELETED, index, AFI_IP6, match_arg); } return RMAP_COMPILE_SUCCESS; } /* Can't find matched rule. */ return RMAP_RULE_MISSING; } /* Add route-map set statement to the route map. */ enum rmap_compile_rets route_map_add_set(struct route_map_index *index, const char *set_name, const char *set_arg) { struct route_map_rule *rule; struct route_map_rule *next; const struct route_map_rule_cmd *cmd; void *compile; cmd = route_map_lookup_set(set_name); if (cmd == NULL) return RMAP_RULE_MISSING; /* Next call compile function for this match statement. */ if (cmd->func_compile) { compile = (*cmd->func_compile)(set_arg); if (compile == NULL) return RMAP_COMPILE_ERROR; } else compile = NULL; /* Add by WJL. if old set command of same kind exist, delete it first to ensure only one set command of same kind exist under a route_map_index. */ for (rule = index->set_list.head; rule; rule = next) { next = rule->next; if (rule->cmd == cmd) route_map_rule_delete(&index->set_list, rule); } /* Add new route map match rule. */ rule = route_map_rule_new(); rule->cmd = cmd; rule->value = compile; if (set_arg) rule->rule_str = XSTRDUP(MTYPE_ROUTE_MAP_RULE_STR, set_arg); else rule->rule_str = NULL; /* Add new route match rule to linked list. */ route_map_rule_add(&index->set_list, rule); /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies(index->map->name, RMAP_EVENT_CALL_ADDED); } return RMAP_COMPILE_SUCCESS; } /* Delete route map set rule. */ enum rmap_compile_rets route_map_delete_set(struct route_map_index *index, const char *set_name, const char *set_arg) { struct route_map_rule *rule; const struct route_map_rule_cmd *cmd; cmd = route_map_lookup_set(set_name); if (cmd == NULL) return RMAP_RULE_MISSING; for (rule = index->set_list.head; rule; rule = rule->next) if ((rule->cmd == cmd) && (rulecmp(rule->rule_str, set_arg) == 0 || set_arg == NULL)) { route_map_rule_delete(&index->set_list, rule); /* Execute event hook. */ if (route_map_master.event_hook) { (*route_map_master.event_hook)(index->map->name); route_map_notify_dependencies( index->map->name, RMAP_EVENT_CALL_ADDED); } return RMAP_COMPILE_SUCCESS; } /* Can't find matched rule. */ return RMAP_RULE_MISSING; } static enum route_map_cmd_result_t route_map_apply_match(struct route_map_rule_list *match_list, const struct prefix *prefix, void *object) { enum route_map_cmd_result_t ret = RMAP_NOMATCH; struct route_map_rule *match; bool is_matched = false; /* Check all match rule and if there is no match rule, go to the set statement. */ if (!match_list->head) ret = RMAP_MATCH; else { for (match = match_list->head; match; match = match->next) { /* * Try each match statement. If any match does not * return RMAP_MATCH or RMAP_NOOP, return. * Otherwise continue on to next match statement. * All match statements must MATCH for * end-result to be a match. * (Exception:If match stmts result in a mix of * MATCH/NOOP, then also end-result is a match) * If all result in NOOP, end-result is NOOP. */ ret = (*match->cmd->func_apply)(match->value, prefix, object); /* * If the consolidated result of func_apply is: * ----------------------------------------------- * | MATCH | NOMATCH | NOOP | Final Result | * ------------------------------------------------ * | yes | yes | yes | NOMATCH | * | no | no | yes | NOOP | * | yes | no | yes | MATCH | * | no | yes | yes | NOMATCH | * |----------------------------------------------- * * Traditionally, all rules within route-map * should match for it to MATCH. * If there are noops within the route-map rules, * it follows the above matrix. * * Eg: route-map rm1 permit 10 * match rule1 * match rule2 * match rule3 * .... * route-map rm1 permit 20 * match ruleX * match ruleY * ... */ switch (ret) { case RMAP_MATCH: is_matched = true; break; case RMAP_NOMATCH: return ret; case RMAP_NOOP: if (is_matched) ret = RMAP_MATCH; break; case RMAP_OKAY: case RMAP_ERROR: break; } } } return ret; } static struct list *route_map_get_index_list(struct route_node **rn, const struct prefix *prefix, struct route_table *table) { struct route_node *tmp_rn = NULL; if (!(*rn)) { *rn = route_node_match(table, prefix); if (!(*rn)) return NULL; if ((*rn)->info) return (struct list *)((*rn)->info); /* If rn->info is NULL, get the parent. * Store the rn in tmp_rn and unlock it later. */ tmp_rn = *rn; } do { *rn = (*rn)->parent; if (tmp_rn) route_unlock_node(tmp_rn); if (!(*rn)) break; if ((*rn)->info) { route_lock_node(*rn); return (struct list *)((*rn)->info); } } while (!(*rn)->info); return NULL; } /* * This function returns the route-map index that best matches the prefix. */ static struct route_map_index * route_map_get_index(struct route_map *map, const struct prefix *prefix, void *object, enum route_map_cmd_result_t *match_ret) { enum route_map_cmd_result_t ret = RMAP_NOMATCH; struct list *candidate_rmap_list = NULL; struct route_node *rn = NULL; struct listnode *ln = NULL, *nn = NULL; struct route_map_index *index = NULL, *best_index = NULL; struct route_map_index *head_index = NULL; struct route_table *table = NULL; /* Route-map optimization relies on LPM lookups of the prefix to reduce * the amount of route-map clauses a given prefix needs to be processed * against. These LPM trees are IPv4/IPv6-specific and prefix->family * must be AF_INET or AF_INET6 in order for the lookup to succeed. So if * the AF doesn't line up with the LPM trees, skip the optimization. */ if (map->optimization_disabled) { if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) zlog_debug( "Skipping route-map optimization for route-map: %s, pfx: %pFX, family: %d", map->name, prefix, prefix->family); return map->head; } if (prefix->family == AF_INET) table = map->ipv4_prefix_table; else table = map->ipv6_prefix_table; do { candidate_rmap_list = route_map_get_index_list(&rn, prefix, table); if (!rn) break; /* If the index at the head of the list is of seq higher * than that in best_index, ignore the list and get the * parent node's list. */ head_index = (struct route_map_index *)(listgetdata( listhead(candidate_rmap_list))); if (best_index && head_index && (best_index->pref < head_index->pref)) { route_unlock_node(rn); continue; } for (ALL_LIST_ELEMENTS(candidate_rmap_list, ln, nn, index)) { /* If the index is of seq higher than that in * best_index, ignore the list and get the parent * node's list. */ if (best_index && (best_index->pref < index->pref)) break; ret = route_map_apply_match(&index->match_list, prefix, object); if (ret == RMAP_MATCH) { *match_ret = ret; best_index = index; break; } else if (ret == RMAP_NOOP) { /* * If match_ret is denymatch, even if we see * more noops, we retain this return value and * return this eventually if there are no * matches. * If a best match route-map index already * exists, do not reset the match_ret. */ if (!best_index && (*match_ret != RMAP_NOMATCH)) *match_ret = ret; } else { /* * ret is RMAP_NOMATCH. * If a best match route-map index already * exists, do not reset the match_ret. */ if (!best_index) *match_ret = ret; } } route_unlock_node(rn); } while (rn); return best_index; } static int route_map_candidate_list_cmp(struct route_map_index *idx1, struct route_map_index *idx2) { return idx1->pref - idx2->pref; } /* * This function adds the route-map index into the default route's * route-node in the route-map's IPv4/IPv6 prefix-table. */ static void route_map_pfx_table_add_default(afi_t afi, struct route_map_index *index) { struct route_node *rn = NULL; struct list *rmap_candidate_list = NULL; struct prefix p; bool updated_rn = false; struct route_table *table = NULL; memset(&p, 0, sizeof(p)); p.family = afi2family(afi); p.prefixlen = 0; if (p.family == AF_INET) table = index->map->ipv4_prefix_table; else table = index->map->ipv6_prefix_table; /* Add default route to table */ rn = route_node_get(table, &p); if (!rn) return; if (!rn->info) { rmap_candidate_list = list_new(); rmap_candidate_list->cmp = (int (*)(void *, void *))route_map_candidate_list_cmp; rn->info = rmap_candidate_list; } else { rmap_candidate_list = (struct list *)rn->info; updated_rn = true; } listnode_add_sort_nodup(rmap_candidate_list, index); if (updated_rn) route_unlock_node(rn); } /* * This function removes the route-map index from the default route's * route-node in the route-map's IPv4/IPv6 prefix-table. */ static void route_map_pfx_table_del_default(afi_t afi, struct route_map_index *index) { struct route_node *rn = NULL; struct list *rmap_candidate_list = NULL; struct prefix p; struct route_table *table = NULL; memset(&p, 0, sizeof(p)); p.family = afi2family(afi); p.prefixlen = 0; if (p.family == AF_INET) table = index->map->ipv4_prefix_table; else table = index->map->ipv6_prefix_table; /* Remove RMAP index from default route in table */ rn = route_node_lookup(table, &p); if (!rn || !rn->info) return; rmap_candidate_list = (struct list *)rn->info; listnode_delete(rmap_candidate_list, index); if (listcount(rmap_candidate_list) == 0) { list_delete(&rmap_candidate_list); rn->info = NULL; route_unlock_node(rn); } route_unlock_node(rn); } /* * This function adds the route-map index to the route-node for * the prefix-entry in the route-map's IPv4/IPv6 prefix-table. */ static void route_map_pfx_table_add(struct route_table *table, struct route_map_index *index, struct prefix_list_entry *pentry) { struct route_node *rn = NULL; struct list *rmap_candidate_list = NULL; bool updated_rn = false; rn = route_node_get(table, &pentry->prefix); if (!rn) return; if (!rn->info) { rmap_candidate_list = list_new(); rmap_candidate_list->cmp = (int (*)(void *, void *))route_map_candidate_list_cmp; rn->info = rmap_candidate_list; } else { rmap_candidate_list = (struct list *)rn->info; updated_rn = true; } listnode_add_sort_nodup(rmap_candidate_list, index); if (updated_rn) route_unlock_node(rn); } /* * This function removes the route-map index from the route-node for * the prefix-entry in the route-map's IPv4/IPv6 prefix-table. */ static void route_map_pfx_table_del(struct route_table *table, struct route_map_index *index, struct prefix_list_entry *pentry) { struct route_node *rn = NULL; struct list *rmap_candidate_list = NULL; rn = route_node_lookup(table, &pentry->prefix); if (!rn || !rn->info) return; rmap_candidate_list = (struct list *)rn->info; listnode_delete(rmap_candidate_list, index); if (listcount(rmap_candidate_list) == 0) { list_delete(&rmap_candidate_list); rn->info = NULL; route_unlock_node(rn); } route_unlock_node(rn); } /* This function checks for the presence of an IPv4 prefix-list * match rule in the given route-map index. */ static bool route_map_is_ip_pfx_list_rule_present(struct route_map_index *index) { struct route_map_rule_list *match_list = NULL; struct route_map_rule *rule = NULL; match_list = &index->match_list; for (rule = match_list->head; rule; rule = rule->next) if (IS_RULE_IPv4_PREFIX_LIST(rule->cmd->str)) return true; return false; } /* This function checks for the presence of an IPv6 prefix-list * match rule in the given route-map index. */ static bool route_map_is_ipv6_pfx_list_rule_present(struct route_map_index *index) { struct route_map_rule_list *match_list = NULL; struct route_map_rule *rule = NULL; match_list = &index->match_list; for (rule = match_list->head; rule; rule = rule->next) if (IS_RULE_IPv6_PREFIX_LIST(rule->cmd->str)) return true; return false; } /* This function does the following: * 1) If plist_name is not present, search for a IPv4 or IPv6 prefix-list * match clause (based on the afi passed to this foo) and get the * prefix-list name. * 2) Look up the prefix-list using the name. * 3) If the prefix-list is not found then, add the index to the IPv4/IPv6 * default-route's node in the trie (based on the afi passed to this foo). * 4) If the prefix-list is found then, remove the index from the IPv4/IPv6 * default-route's node in the trie (based on the afi passed to this foo). * 5) If a prefix-entry is passed then, create a route-node for this entry and * add this index to the route-node. * 6) If prefix-entry is not passed then, for every prefix-entry in the * prefix-list, create a route-node for this entry and * add this index to the route-node. */ static void route_map_add_plist_entries(afi_t afi, struct route_map_index *index, const char *plist_name, struct prefix_list_entry *entry) { struct route_map_rule_list *match_list = NULL; struct route_map_rule *match = NULL; struct prefix_list *plist = NULL; struct prefix_list_entry *pentry = NULL; bool plist_rule_is_present = false; if (!plist_name) { match_list = &index->match_list; for (match = match_list->head; match; match = match->next) { if (afi == AFI_IP) { if (IS_RULE_IPv4_PREFIX_LIST(match->cmd->str)) { plist_rule_is_present = true; break; } } else { if (IS_RULE_IPv6_PREFIX_LIST(match->cmd->str)) { plist_rule_is_present = true; break; } } } if (plist_rule_is_present) plist = prefix_list_lookup(afi, match->rule_str); } else { plist = prefix_list_lookup(afi, plist_name); } if (!plist) { route_map_pfx_table_add_default(afi, index); return; } /* Default entry should be deleted only if the first entry of the * prefix-list is created. */ if (entry) { if (plist->count == 1) route_map_pfx_table_del_default(afi, index); } else { route_map_pfx_table_del_default(afi, index); } if (entry) { if (afi == AFI_IP) { route_map_pfx_table_add(index->map->ipv4_prefix_table, index, entry); } else { route_map_pfx_table_add(index->map->ipv6_prefix_table, index, entry); } } else { for (pentry = plist->head; pentry; pentry = pentry->next) { if (afi == AFI_IP) { route_map_pfx_table_add( index->map->ipv4_prefix_table, index, pentry); } else { route_map_pfx_table_add( index->map->ipv6_prefix_table, index, pentry); } } } } /* This function does the following: * 1) If plist_name is not present, search for a IPv4 or IPv6 prefix-list * match clause (based on the afi passed to this foo) and get the * prefix-list name. * 2) Look up the prefix-list using the name. * 3) If the prefix-list is not found then, delete the index from the IPv4/IPv6 * default-route's node in the trie (based on the afi passed to this foo). * 4) If a prefix-entry is passed then, remove this index from the route-node * for the prefix in this prefix-entry. * 5) If prefix-entry is not passed then, for every prefix-entry in the * prefix-list, remove this index from the route-node * for the prefix in this prefix-entry. */ static void route_map_del_plist_entries(afi_t afi, struct route_map_index *index, const char *plist_name, struct prefix_list_entry *entry) { struct route_map_rule_list *match_list = NULL; struct route_map_rule *match = NULL; struct prefix_list *plist = NULL; struct prefix_list_entry *pentry = NULL; bool plist_rule_is_present = false; if (!plist_name) { match_list = &index->match_list; for (match = match_list->head; match; match = match->next) { if (afi == AFI_IP) { if (IS_RULE_IPv4_PREFIX_LIST(match->cmd->str)) { plist_rule_is_present = true; break; } } else { if (IS_RULE_IPv6_PREFIX_LIST(match->cmd->str)) { plist_rule_is_present = true; break; } } } if (plist_rule_is_present) plist = prefix_list_lookup(afi, match->rule_str); } else { plist = prefix_list_lookup(afi, plist_name); } if (!plist) { route_map_pfx_table_del_default(afi, index); return; } if (entry) { if (afi == AFI_IP) { route_map_pfx_table_del(index->map->ipv4_prefix_table, index, entry); } else { route_map_pfx_table_del(index->map->ipv6_prefix_table, index, entry); } } else { for (pentry = plist->head; pentry; pentry = pentry->next) { if (afi == AFI_IP) { route_map_pfx_table_del( index->map->ipv4_prefix_table, index, pentry); } else { route_map_pfx_table_del( index->map->ipv6_prefix_table, index, pentry); } } } } /* * This function handles the cases where a prefix-list is added/removed * as a match command from a particular route-map index. * It updates the prefix-table of the route-map accordingly. */ static void route_map_trie_update(afi_t afi, route_map_event_t event, struct route_map_index *index, const char *plist_name) { if (event == RMAP_EVENT_PLIST_ADDED) { if (afi == AFI_IP) { if (!route_map_is_ipv6_pfx_list_rule_present(index)) { route_map_pfx_table_del_default(AFI_IP6, index); route_map_add_plist_entries(afi, index, plist_name, NULL); } else { route_map_del_plist_entries(AFI_IP6, index, NULL, NULL); } } else { if (!route_map_is_ip_pfx_list_rule_present(index)) { route_map_pfx_table_del_default(AFI_IP, index); route_map_add_plist_entries(afi, index, plist_name, NULL); } else { route_map_del_plist_entries(AFI_IP, index, NULL, NULL); } } } else if (event == RMAP_EVENT_PLIST_DELETED) { if (afi == AFI_IP) { route_map_del_plist_entries(afi, index, plist_name, NULL); /* If IPv6 prefix-list match rule is not present, * add this index to the IPv4 default route's trie * node. * Also, add this index to the trie nodes created * for each of the prefix-entries within the IPv6 * prefix-list, if the IPv6 prefix-list match rule * is present. Else, add this index to the IPv6 * default route's trie node. */ if (!route_map_is_ipv6_pfx_list_rule_present(index)) route_map_pfx_table_add_default(afi, index); route_map_add_plist_entries(AFI_IP6, index, NULL, NULL); } else { route_map_del_plist_entries(afi, index, plist_name, NULL); /* If IPv4 prefix-list match rule is not present, * add this index to the IPv6 default route's trie * node. * Also, add this index to the trie nodes created * for each of the prefix-entries within the IPv4 * prefix-list, if the IPv4 prefix-list match rule * is present. Else, add this index to the IPv4 * default route's trie node. */ if (!route_map_is_ip_pfx_list_rule_present(index)) route_map_pfx_table_add_default(afi, index); route_map_add_plist_entries(AFI_IP, index, NULL, NULL); } } } /* * This function handles the cases where a route-map index and * prefix-list is added/removed. * It updates the prefix-table of the route-map accordingly. */ static void route_map_pfx_tbl_update(route_map_event_t event, struct route_map_index *index, afi_t afi, const char *plist_name) { if (!index) return; if (event == RMAP_EVENT_INDEX_ADDED) { route_map_pfx_table_add_default(AFI_IP, index); route_map_pfx_table_add_default(AFI_IP6, index); return; } if (event == RMAP_EVENT_INDEX_DELETED) { route_map_pfx_table_del_default(AFI_IP, index); route_map_pfx_table_del_default(AFI_IP6, index); return; } /* Handle prefix-list match rule addition/deletion. */ route_map_trie_update(afi, event, index, plist_name); } /* * This function handles the cases where a new prefix-entry is added to * a prefix-list or, an existing prefix-entry is removed from the prefix-list. * It updates the prefix-table of the route-map accordingly. */ static void route_map_pentry_update(route_map_event_t event, const char *plist_name, struct route_map_index *index, struct prefix_list_entry *pentry) { struct prefix_list *plist = NULL; afi_t afi; unsigned char family = pentry->prefix.family; if (family == AF_INET) { afi = AFI_IP; plist = prefix_list_lookup(AFI_IP, plist_name); } else { afi = AFI_IP6; plist = prefix_list_lookup(AFI_IP6, plist_name); } if (event == RMAP_EVENT_PLIST_ADDED) { if (afi == AFI_IP) { if (!route_map_is_ipv6_pfx_list_rule_present(index)) route_map_add_plist_entries(afi, index, plist_name, pentry); } else { if (!route_map_is_ip_pfx_list_rule_present(index)) route_map_add_plist_entries(afi, index, plist_name, pentry); } } else if (event == RMAP_EVENT_PLIST_DELETED) { route_map_del_plist_entries(afi, index, plist_name, pentry); if (plist->count == 1) { if (afi == AFI_IP) { if (!route_map_is_ipv6_pfx_list_rule_present( index)) route_map_pfx_table_add_default(afi, index); } else { if (!route_map_is_ip_pfx_list_rule_present( index)) route_map_pfx_table_add_default(afi, index); } } } } static void route_map_pentry_process_dependency(struct hash_bucket *bucket, void *data) { char *rmap_name = NULL; struct route_map *rmap = NULL; struct route_map_index *index = NULL; struct route_map_rule_list *match_list = NULL; struct route_map_rule *match = NULL; struct route_map_dep_data *dep_data = NULL; struct route_map_pentry_dep *pentry_dep = (struct route_map_pentry_dep *)data; unsigned char family = pentry_dep->pentry->prefix.family; dep_data = (struct route_map_dep_data *)bucket->data; if (!dep_data) return; rmap_name = dep_data->rname; rmap = route_map_lookup_by_name(rmap_name); if (!rmap || !rmap->head) return; for (index = rmap->head; index; index = index->next) { match_list = &index->match_list; if (!match_list) continue; for (match = match_list->head; match; match = match->next) { if (strcmp(match->rule_str, pentry_dep->plist_name) == 0) { if (IS_RULE_IPv4_PREFIX_LIST(match->cmd->str) && family == AF_INET) { route_map_pentry_update( pentry_dep->event, pentry_dep->plist_name, index, pentry_dep->pentry); } else if (IS_RULE_IPv6_PREFIX_LIST( match->cmd->str) && family == AF_INET6) { route_map_pentry_update( pentry_dep->event, pentry_dep->plist_name, index, pentry_dep->pentry); } } } } } void route_map_notify_pentry_dependencies(const char *affected_name, struct prefix_list_entry *pentry, route_map_event_t event) { struct route_map_dep *dep = NULL; struct hash *upd8_hash = NULL; struct route_map_pentry_dep pentry_dep; if (!affected_name || !pentry) return; upd8_hash = route_map_get_dep_hash(event); if (!upd8_hash) return; dep = (struct route_map_dep *)hash_get(upd8_hash, (void *)affected_name, NULL); if (dep) { if (!dep->this_hash) dep->this_hash = upd8_hash; memset(&pentry_dep, 0, sizeof(pentry_dep)); pentry_dep.pentry = pentry; pentry_dep.plist_name = affected_name; pentry_dep.event = event; hash_iterate(dep->dep_rmap_hash, route_map_pentry_process_dependency, (void *)&pentry_dep); } } /* Apply route map's each index to the object. The matrix for a route-map looks like this: (note, this includes the description for the "NEXT" and "GOTO" frobs now | Match | No Match | No op |-----------|--------------|------- permit | action | cont | cont. | | default:deny | default:permit -------------------+----------------------- | deny | cont | cont. deny | | default:deny | default:permit |-----------|--------------|-------- action) -Apply Set statements, accept route -If Call statement is present jump to the specified route-map, if it denies the route we finish. -If NEXT is specified, goto NEXT statement -If GOTO is specified, goto the first clause where pref > nextpref -If nothing is specified, do as Cisco and finish deny) -Route is denied by route-map. cont) -Goto Next index If we get no matches after we've processed all updates, then the route is dropped too. Some notes on the new "CALL", "NEXT" and "GOTO" call WORD - If this clause is matched, then the set statements are executed and then we jump to route-map 'WORD'. If this route-map denies the route, we finish, in other case we do whatever the exit policy (EXIT, NEXT or GOTO) tells. on-match next - If this clause is matched, then the set statements are executed and then we drop through to the next clause on-match goto n - If this clause is matched, then the set statements are executed and then we goto the nth clause, or the first clause greater than this. In order to ensure route-maps *always* exit, you cannot jump backwards. Sorry ;) We need to make sure our route-map processing matches the above */ route_map_result_t route_map_apply_ext(struct route_map *map, const struct prefix *prefix, void *match_object, void *set_object, int *pref) { static int recursion = 0; enum route_map_cmd_result_t match_ret = RMAP_NOMATCH; route_map_result_t ret = RMAP_PERMITMATCH; struct route_map_index *index = NULL; struct route_map_rule *set = NULL; bool skip_match_clause = false; struct prefix conv; if (recursion > RMAP_RECURSION_LIMIT) { if (map) map->applied++; flog_warn( EC_LIB_RMAP_RECURSION_LIMIT, "route-map recursion limit (%d) reached, discarding route", RMAP_RECURSION_LIMIT); recursion = 0; return RMAP_DENYMATCH; } if (map == NULL || map->head == NULL) { if (map) map->applied++; ret = RMAP_DENYMATCH; goto route_map_apply_end; } map->applied++; /* * Handling for matching evpn_routes in the prefix table. * * We convert type2/5 prefix to ipv4/6 prefix to do longest * prefix matching on. */ if (prefix->family == AF_EVPN) { if (evpn_prefix2prefix(prefix, &conv) != 0) { if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) zlog_debug( "Unable to convert EVPN prefix %pFX into IPv4/IPv6 prefix. Falling back to non-optimized route-map lookup", prefix); } else { if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) zlog_debug( "Converted EVPN prefix %pFX into %pFX for optimized route-map lookup", prefix, &conv); prefix = &conv; } } index = route_map_get_index(map, prefix, match_object, &match_ret); if (index) { index->applied++; if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug( "Best match route-map: %s, sequence: %d for pfx: %pFX, result: %s", map->name, index->pref, prefix, route_map_cmd_result_str(match_ret)); } else { if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug( "No best match sequence for pfx: %pFX in route-map: %s, result: %s", prefix, map->name, route_map_cmd_result_str(match_ret)); /* * No index matches this prefix. Return deny unless, * match_ret = RMAP_NOOP. */ if (match_ret == RMAP_NOOP) ret = RMAP_PERMITMATCH; else ret = RMAP_DENYMATCH; goto route_map_apply_end; } skip_match_clause = true; for (; index; index = index->next) { if (!skip_match_clause) { index->applied++; /* Apply this index. */ match_ret = route_map_apply_match(&index->match_list, prefix, match_object); if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) { zlog_debug( "Route-map: %s, sequence: %d, prefix: %pFX, result: %s", map->name, index->pref, prefix, route_map_cmd_result_str(match_ret)); } } else skip_match_clause = false; /* Now we apply the matrix from above */ if (match_ret == RMAP_NOOP) /* * Do not change the return value. Retain the previous * return value. Previous values can be: * 1)permitmatch (if a nomatch was never * seen before in this route-map.) * 2)denymatch (if a nomatch was seen earlier in one * of the previous sequences) */ /* * 'cont' from matrix - continue to next route-map * sequence */ continue; else if (match_ret == RMAP_NOMATCH) { /* * The return value is now changed to denymatch. * So from here on out, even if we see more noops, * we retain this return value and return this * eventually if there are no matches. */ ret = RMAP_DENYMATCH; /* * 'cont' from matrix - continue to next route-map * sequence */ continue; } else if (match_ret == RMAP_MATCH) { if (index->type == RMAP_PERMIT) /* 'action' */ { /* Match succeeded, rmap is of type permit */ ret = RMAP_PERMITMATCH; /* permit+match must execute sets */ for (set = index->set_list.head; set; set = set->next) /* * set cmds return RMAP_OKAY or * RMAP_ERROR. We do not care if * set succeeded or not. So, ignore * return code. */ (void)(*set->cmd->func_apply)( set->value, prefix, set_object); /* Call another route-map if available */ if (index->nextrm) { struct route_map *nextrm = route_map_lookup_by_name( index->nextrm); if (nextrm) /* Target route-map found, jump to it */ { recursion++; ret = route_map_apply_ext( nextrm, prefix, match_object, set_object, NULL); recursion--; } /* If nextrm returned 'deny', finish. */ if (ret == RMAP_DENYMATCH) goto route_map_apply_end; } switch (index->exitpolicy) { case RMAP_EXIT: goto route_map_apply_end; case RMAP_NEXT: continue; case RMAP_GOTO: { /* Find the next clause to jump to */ struct route_map_index *next = index->next; int nextpref = index->nextpref; while (next && next->pref < nextpref) { index = next; next = next->next; } if (next == NULL) { /* No clauses match! */ goto route_map_apply_end; } } } } else if (index->type == RMAP_DENY) /* 'deny' */ { ret = RMAP_DENYMATCH; goto route_map_apply_end; } } } route_map_apply_end: if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Route-map: %s, prefix: %pFX, result: %s", (map ? map->name : "null"), prefix, route_map_result_str(ret)); if (pref) { if (index != NULL && ret == RMAP_PERMITMATCH) *pref = index->pref; else *pref = 65536; } return (ret); } void route_map_add_hook(void (*func)(const char *)) { route_map_master.add_hook = func; } void route_map_delete_hook(void (*func)(const char *)) { route_map_master.delete_hook = func; } void route_map_event_hook(void (*func)(const char *name)) { route_map_master.event_hook = func; } /* Routines for route map dependency lists and dependency processing */ static bool route_map_rmap_hash_cmp(const void *p1, const void *p2) { return strcmp(((const struct route_map_dep_data *)p1)->rname, ((const struct route_map_dep_data *)p2)->rname) == 0; } static bool route_map_dep_hash_cmp(const void *p1, const void *p2) { return (strcmp(((const struct route_map_dep *)p1)->dep_name, (const char *)p2) == 0); } static void route_map_clear_reference(struct hash_bucket *bucket, void *arg) { struct route_map_dep *dep = bucket->data; struct route_map_dep_data *dep_data = NULL, tmp_dep_data; memset(&tmp_dep_data, 0, sizeof(tmp_dep_data)); tmp_dep_data.rname = arg; dep_data = hash_release(dep->dep_rmap_hash, &tmp_dep_data); if (dep_data) { if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Clearing reference for %s to %s count: %d", dep->dep_name, tmp_dep_data.rname, dep_data->refcnt); XFREE(MTYPE_ROUTE_MAP_NAME, dep_data->rname); XFREE(MTYPE_ROUTE_MAP_DEP_DATA, dep_data); } if (!dep->dep_rmap_hash->count) { dep = hash_release(dep->this_hash, (void *)dep->dep_name); hash_free(dep->dep_rmap_hash); XFREE(MTYPE_ROUTE_MAP_NAME, dep->dep_name); XFREE(MTYPE_ROUTE_MAP_DEP, dep); } } static void route_map_clear_all_references(char *rmap_name) { int i; if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Clearing references for %s", rmap_name); for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) { hash_iterate(route_map_dep_hash[i], route_map_clear_reference, (void *)rmap_name); } } static unsigned int route_map_dep_data_hash_make_key(const void *p) { const struct route_map_dep_data *dep_data = p; return string_hash_make(dep_data->rname); } static void *route_map_dep_hash_alloc(void *p) { char *dep_name = (char *)p; struct route_map_dep *dep_entry; dep_entry = XCALLOC(MTYPE_ROUTE_MAP_DEP, sizeof(struct route_map_dep)); dep_entry->dep_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, dep_name); dep_entry->dep_rmap_hash = hash_create_size(8, route_map_dep_data_hash_make_key, route_map_rmap_hash_cmp, "Route Map Dep Hash"); dep_entry->this_hash = NULL; return dep_entry; } static void *route_map_name_hash_alloc(void *p) { struct route_map_dep_data *dep_data = NULL, *tmp_dep_data = NULL; dep_data = XCALLOC(MTYPE_ROUTE_MAP_DEP_DATA, sizeof(struct route_map_dep_data)); tmp_dep_data = p; dep_data->rname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, tmp_dep_data->rname); return dep_data; } static unsigned int route_map_dep_hash_make_key(const void *p) { return (string_hash_make((char *)p)); } static void route_map_print_dependency(struct hash_bucket *bucket, void *data) { struct route_map_dep_data *dep_data = bucket->data; char *rmap_name = dep_data->rname; char *dep_name = data; zlog_debug("%s: Dependency for %s: %s", __func__, dep_name, rmap_name); } static int route_map_dep_update(struct hash *dephash, const char *dep_name, const char *rmap_name, route_map_event_t type) { struct route_map_dep *dep = NULL; char *dname, *rname; int ret = 0; struct route_map_dep_data *dep_data = NULL, *ret_dep_data = NULL; struct route_map_dep_data tmp_dep_data; dname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, dep_name); rname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_name); switch (type) { case RMAP_EVENT_PLIST_ADDED: case RMAP_EVENT_CLIST_ADDED: case RMAP_EVENT_ECLIST_ADDED: case RMAP_EVENT_ASLIST_ADDED: case RMAP_EVENT_LLIST_ADDED: case RMAP_EVENT_CALL_ADDED: case RMAP_EVENT_FILTER_ADDED: if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Adding dependency for filter %s in route-map %s", dep_name, rmap_name); dep = (struct route_map_dep *)hash_get( dephash, dname, route_map_dep_hash_alloc); if (!dep) { ret = -1; goto out; } if (!dep->this_hash) dep->this_hash = dephash; memset(&tmp_dep_data, 0, sizeof(tmp_dep_data)); tmp_dep_data.rname = rname; dep_data = hash_lookup(dep->dep_rmap_hash, &tmp_dep_data); if (!dep_data) dep_data = hash_get(dep->dep_rmap_hash, &tmp_dep_data, route_map_name_hash_alloc); dep_data->refcnt++; break; case RMAP_EVENT_PLIST_DELETED: case RMAP_EVENT_CLIST_DELETED: case RMAP_EVENT_ECLIST_DELETED: case RMAP_EVENT_ASLIST_DELETED: case RMAP_EVENT_LLIST_DELETED: case RMAP_EVENT_CALL_DELETED: case RMAP_EVENT_FILTER_DELETED: if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Deleting dependency for filter %s in route-map %s", dep_name, rmap_name); dep = (struct route_map_dep *)hash_get(dephash, dname, NULL); if (!dep) { goto out; } memset(&tmp_dep_data, 0, sizeof(tmp_dep_data)); tmp_dep_data.rname = rname; dep_data = hash_lookup(dep->dep_rmap_hash, &tmp_dep_data); /* * If dep_data is NULL then something has gone seriously * wrong in route-map handling. Note it and prevent * the crash. */ if (!dep_data) { zlog_warn( "route-map dependency for route-map %s: %s is not correct", rmap_name, dep_name); goto out; } dep_data->refcnt--; if (!dep_data->refcnt) { ret_dep_data = hash_release(dep->dep_rmap_hash, &tmp_dep_data); if (ret_dep_data) { XFREE(MTYPE_ROUTE_MAP_NAME, ret_dep_data->rname); XFREE(MTYPE_ROUTE_MAP_DEP_DATA, ret_dep_data); } } if (!dep->dep_rmap_hash->count) { dep = hash_release(dephash, dname); hash_free(dep->dep_rmap_hash); XFREE(MTYPE_ROUTE_MAP_NAME, dep->dep_name); XFREE(MTYPE_ROUTE_MAP_DEP, dep); } break; case RMAP_EVENT_SET_ADDED: case RMAP_EVENT_SET_DELETED: case RMAP_EVENT_SET_REPLACED: case RMAP_EVENT_MATCH_ADDED: case RMAP_EVENT_MATCH_DELETED: case RMAP_EVENT_MATCH_REPLACED: case RMAP_EVENT_INDEX_ADDED: case RMAP_EVENT_INDEX_DELETED: break; } if (dep) { if (CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP)) hash_iterate(dep->dep_rmap_hash, route_map_print_dependency, dname); } out: XFREE(MTYPE_ROUTE_MAP_NAME, rname); XFREE(MTYPE_ROUTE_MAP_NAME, dname); return ret; } static struct hash *route_map_get_dep_hash(route_map_event_t event) { struct hash *upd8_hash = NULL; switch (event) { case RMAP_EVENT_PLIST_ADDED: case RMAP_EVENT_PLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_PLIST]; break; case RMAP_EVENT_CLIST_ADDED: case RMAP_EVENT_CLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_CLIST]; break; case RMAP_EVENT_ECLIST_ADDED: case RMAP_EVENT_ECLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_ECLIST]; break; case RMAP_EVENT_ASLIST_ADDED: case RMAP_EVENT_ASLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_ASPATH]; break; case RMAP_EVENT_LLIST_ADDED: case RMAP_EVENT_LLIST_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_LCLIST]; break; case RMAP_EVENT_CALL_ADDED: case RMAP_EVENT_CALL_DELETED: case RMAP_EVENT_MATCH_ADDED: case RMAP_EVENT_MATCH_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_RMAP]; break; case RMAP_EVENT_FILTER_ADDED: case RMAP_EVENT_FILTER_DELETED: upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_FILTER]; break; /* * Should we actually be ignoring these? * I am not sure but at this point in time, let * us get them into this switch and we can peel * them into the appropriate place in the future */ case RMAP_EVENT_SET_ADDED: case RMAP_EVENT_SET_DELETED: case RMAP_EVENT_SET_REPLACED: case RMAP_EVENT_MATCH_REPLACED: case RMAP_EVENT_INDEX_ADDED: case RMAP_EVENT_INDEX_DELETED: upd8_hash = NULL; break; } return (upd8_hash); } static void route_map_process_dependency(struct hash_bucket *bucket, void *data) { struct route_map_dep_data *dep_data = NULL; char *rmap_name = NULL; dep_data = bucket->data; rmap_name = dep_data->rname; if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Notifying %s of dependency", rmap_name); if (route_map_master.event_hook) (*route_map_master.event_hook)(rmap_name); } void route_map_upd8_dependency(route_map_event_t type, const char *arg, const char *rmap_name) { struct hash *upd8_hash = NULL; if ((upd8_hash = route_map_get_dep_hash(type))) { route_map_dep_update(upd8_hash, arg, rmap_name, type); if (type == RMAP_EVENT_CALL_ADDED) { /* Execute hook. */ if (route_map_master.add_hook) (*route_map_master.add_hook)(rmap_name); } else if (type == RMAP_EVENT_CALL_DELETED) { /* Execute hook. */ if (route_map_master.delete_hook) (*route_map_master.delete_hook)(rmap_name); } } } void route_map_notify_dependencies(const char *affected_name, route_map_event_t event) { struct route_map_dep *dep; struct hash *upd8_hash; char *name; if (!affected_name) return; name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, affected_name); if ((upd8_hash = route_map_get_dep_hash(event)) == NULL) { XFREE(MTYPE_ROUTE_MAP_NAME, name); return; } dep = (struct route_map_dep *)hash_get(upd8_hash, name, NULL); if (dep) { if (!dep->this_hash) dep->this_hash = upd8_hash; if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) zlog_debug("Filter %s updated", dep->dep_name); hash_iterate(dep->dep_rmap_hash, route_map_process_dependency, (void *)event); } XFREE(MTYPE_ROUTE_MAP_NAME, name); } /* VTY related functions. */ static void clear_route_map_helper(struct route_map *map) { struct route_map_index *index; map->applied_clear = map->applied; for (index = map->head; index; index = index->next) index->applied_clear = index->applied; } DEFPY (rmap_clear_counters, rmap_clear_counters_cmd, "clear route-map counters [RMAP_NAME$rmapname]", CLEAR_STR "route-map information\n" "counters associated with the specified route-map\n" "route-map name\n") { struct route_map *map; if (rmapname) { map = route_map_lookup_by_name(rmapname); if (map) clear_route_map_helper(map); else { vty_out(vty, "%s: 'route-map %s' not found\n", frr_protonameinst, rmapname); return CMD_SUCCESS; } } else { for (map = route_map_master.head; map; map = map->next) clear_route_map_helper(map); } return CMD_SUCCESS; } DEFUN (rmap_show_name, rmap_show_name_cmd, "show route-map [WORD] [json]", SHOW_STR "route-map information\n" "route-map name\n" JSON_STR) { bool uj = use_json(argc, argv); int idx = 0; const char *name = NULL; if (argv_find(argv, argc, "WORD", &idx)) name = argv[idx]->arg; return vty_show_route_map(vty, name, uj); } DEFUN (rmap_show_unused, rmap_show_unused_cmd, "show route-map-unused", SHOW_STR "unused route-map information\n") { return vty_show_unused_route_map(vty); } DEFPY (debug_rmap, debug_rmap_cmd, "debug route-map [detail]$detail", DEBUG_STR "Debug option set for route-maps\n" "Detailed output\n") { if (!detail) SET_FLAG(rmap_debug, DEBUG_ROUTEMAP); else SET_FLAG(rmap_debug, DEBUG_ROUTEMAP | DEBUG_ROUTEMAP_DETAIL); return CMD_SUCCESS; } DEFPY (no_debug_rmap, no_debug_rmap_cmd, "no debug route-map [detail]$detail", NO_STR DEBUG_STR "Debug option set for route-maps\n" "Detailed output\n") { if (!detail) UNSET_FLAG(rmap_debug, DEBUG_ROUTEMAP); else UNSET_FLAG(rmap_debug, DEBUG_ROUTEMAP | DEBUG_ROUTEMAP_DETAIL); return CMD_SUCCESS; } /* Debug node. */ static int rmap_config_write_debug(struct vty *vty); static struct cmd_node rmap_debug_node = { .name = "route-map debug", .node = RMAP_DEBUG_NODE, .prompt = "", .config_write = rmap_config_write_debug, }; void route_map_show_debug(struct vty *vty) { if (CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP)) vty_out(vty, "debug route-map\n"); } /* Configuration write function. */ static int rmap_config_write_debug(struct vty *vty) { int write = 0; if (CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP)) { vty_out(vty, "debug route-map\n"); write++; } return write; } /* Common route map rules */ void *route_map_rule_tag_compile(const char *arg) { unsigned long int tmp; char *endptr; route_tag_t *tag; errno = 0; tmp = strtoul(arg, &endptr, 0); if (arg[0] == '\0' || *endptr != '\0' || errno || tmp > ROUTE_TAG_MAX) return NULL; tag = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*tag)); *tag = tmp; return tag; } void route_map_rule_tag_free(void *rule) { XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); } void route_map_finish(void) { int i; struct route_map_rule_cmd_proxy *proxy; /* these 2 hash tables have INIT_HASH initializers, so the "default" * state is "initialized & empty" => fini() followed by init() to * return to that same state */ while ((proxy = rmap_cmd_name_pop(rmap_match_cmds))) (void)proxy; rmap_cmd_name_fini(rmap_match_cmds); rmap_cmd_name_init(rmap_match_cmds); while ((proxy = rmap_cmd_name_pop(rmap_set_cmds))) (void)proxy; rmap_cmd_name_fini(rmap_set_cmds); rmap_cmd_name_init(rmap_set_cmds); /* * All protocols are setting these to NULL * by default on shutdown( route_map_finish ) * Why are we making them do this work? */ route_map_master.add_hook = NULL; route_map_master.delete_hook = NULL; route_map_master.event_hook = NULL; /* cleanup route_map */ while (route_map_master.head) { struct route_map *map = route_map_master.head; map->to_be_processed = false; route_map_delete(map); } for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) { hash_free(route_map_dep_hash[i]); route_map_dep_hash[i] = NULL; } hash_free(route_map_master_hash); route_map_master_hash = NULL; } /* Increment the use_count counter while attaching the route map */ void route_map_counter_increment(struct route_map *map) { if (map) map->use_count++; } /* Decrement the use_count counter while detaching the route map. */ void route_map_counter_decrement(struct route_map *map) { if (map) { if (map->use_count <= 0) return; map->use_count--; } } DEFUN_HIDDEN(show_route_map_pfx_tbl, show_route_map_pfx_tbl_cmd, "show route-map RMAP_NAME prefix-table", SHOW_STR "route-map\n" "route-map name\n" "internal prefix-table\n") { const char *rmap_name = argv[2]->arg; struct route_map *rmap = NULL; struct route_table *rm_pfx_tbl4 = NULL; struct route_table *rm_pfx_tbl6 = NULL; struct route_node *rn = NULL, *prn = NULL; struct list *rmap_index_list = NULL; struct listnode *ln = NULL, *nln = NULL; struct route_map_index *index = NULL; uint8_t len = 54; vty_out(vty, "%s:\n", frr_protonameinst); rmap = route_map_lookup_by_name(rmap_name); if (rmap) { rm_pfx_tbl4 = rmap->ipv4_prefix_table; if (rm_pfx_tbl4) { vty_out(vty, "\n%s%43s%s\n", "IPv4 Prefix", "", "Route-map Index List"); vty_out(vty, "%s%39s%s\n", "_______________", "", "____________________"); for (rn = route_top(rm_pfx_tbl4); rn; rn = route_next(rn)) { vty_out(vty, " %pRN (%d)\n", rn, route_node_get_lock_count(rn)); vty_out(vty, "(P) "); prn = rn->parent; if (prn) { vty_out(vty, "%pRN\n", prn); } vty_out(vty, "\n"); rmap_index_list = (struct list *)rn->info; if (!rmap_index_list || !listcount(rmap_index_list)) vty_out(vty, "%*s%s\n", len, "", "-"); else for (ALL_LIST_ELEMENTS(rmap_index_list, ln, nln, index)) { vty_out(vty, "%*s%s seq %d\n", len, "", index->map->name, index->pref); } vty_out(vty, "\n"); } } rm_pfx_tbl6 = rmap->ipv6_prefix_table; if (rm_pfx_tbl6) { vty_out(vty, "\n%s%43s%s\n", "IPv6 Prefix", "", "Route-map Index List"); vty_out(vty, "%s%39s%s\n", "_______________", "", "____________________"); for (rn = route_top(rm_pfx_tbl6); rn; rn = route_next(rn)) { vty_out(vty, " %pRN (%d)\n", rn, route_node_get_lock_count(rn)); vty_out(vty, "(P) "); prn = rn->parent; if (prn) { vty_out(vty, "%pRN\n", prn); } vty_out(vty, "\n"); rmap_index_list = (struct list *)rn->info; if (!rmap_index_list || !listcount(rmap_index_list)) vty_out(vty, "%*s%s\n", len, "", "-"); else for (ALL_LIST_ELEMENTS(rmap_index_list, ln, nln, index)) { vty_out(vty, "%*s%s seq %d\n", len, "", index->map->name, index->pref); } vty_out(vty, "\n"); } } } vty_out(vty, "\n"); return CMD_SUCCESS; } /* Initialization of route map vector. */ void route_map_init(void) { int i; route_map_master_hash = hash_create_size(8, route_map_hash_key_make, route_map_hash_cmp, "Route Map Master Hash"); for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) route_map_dep_hash[i] = hash_create_size( 8, route_map_dep_hash_make_key, route_map_dep_hash_cmp, "Route Map Dep Hash"); UNSET_FLAG(rmap_debug, DEBUG_ROUTEMAP); route_map_cli_init(); /* Install route map top node. */ install_node(&rmap_debug_node); /* Install route map commands. */ install_element(CONFIG_NODE, &debug_rmap_cmd); install_element(CONFIG_NODE, &no_debug_rmap_cmd); /* Install show command */ install_element(ENABLE_NODE, &rmap_clear_counters_cmd); install_element(ENABLE_NODE, &rmap_show_name_cmd); install_element(ENABLE_NODE, &rmap_show_unused_cmd); install_element(ENABLE_NODE, &debug_rmap_cmd); install_element(ENABLE_NODE, &no_debug_rmap_cmd); install_element(ENABLE_NODE, &show_route_map_pfx_tbl_cmd); }