/* * IS-IS Rout(e)ing protocol - isis_redist.c * * Copyright (C) 2013 Christian Franke * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful,but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "command.h" #include "if.h" #include "linklist.h" #include "memory.h" #include "memtypes.h" #include "prefix.h" #include "routemap.h" #include "stream.h" #include "table.h" #include "vty.h" #include "isisd/dict.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_flags.h" #include "isisd/isis_misc.h" #include "isisd/isis_circuit.h" #include "isisd/isis_tlv.h" #include "isisd/isisd.h" #include "isisd/isis_lsp.h" #include "isisd/isis_route.h" #include "isisd/isis_zebra.h" static int redist_protocol(int family) { if (family == AF_INET) return 0; #ifdef HAVE_IPV6 if (family == AF_INET6) return 1; #endif assert(!"Unsupported address family!"); } static int is_default(struct prefix *p) { if (p->family == AF_INET) if (p->u.prefix4.s_addr == 0 && p->prefixlen == 0) return 1; #ifdef HAVE_IPV6 if (p->family == AF_INET6) if (IN6_IS_ADDR_UNSPECIFIED(&p->u.prefix6) && p->prefixlen == 0) return 1; #endif return 0; } static int str2family(const char *string) { if (!strcmp("ipv4", string)) return AF_INET; #ifdef HAVE_IPV6 else if (!strcmp("ipv6", string)) return AF_INET6; #endif else return -1; } static struct route_table* get_ext_info(struct isis *i, int family) { int protocol = redist_protocol(family); return i->ext_info[protocol]; } static struct isis_redist* get_redist_settings(struct isis_area *area, int family, int type, int level) { int protocol = redist_protocol(family); return &area->redist_settings[protocol][type][level-1]; } struct route_table* get_ext_reach(struct isis_area *area, int family, int level) { int protocol = redist_protocol(family); return area->ext_reach[protocol][level-1]; } /* Install external reachability information into a * specific area for a specific level. * Schedule an lsp regenerate if necessary */ static void isis_redist_install(struct isis_area *area, int level, struct prefix *p, struct isis_ext_info *info) { int family = p->family; struct route_table *er_table = get_ext_reach(area, family, level); struct route_node *er_node; if (!er_table) { zlog_warn("%s: External reachability table of area %s" " is not initialized.", __func__, area->area_tag); return; } er_node = route_node_get(er_table, p); if (er_node->info) { route_unlock_node(er_node); /* Don't update/reschedule lsp generation if nothing changed. */ if (!memcmp(er_node->info, info, sizeof(*info))) return; } else { er_node->info = XMALLOC(MTYPE_ISIS, sizeof(*info)); } memcpy(er_node->info, info, sizeof(*info)); lsp_regenerate_schedule(area, level, 0); } /* Remove external reachability information from a * specific area for a specific level. * Schedule an lsp regenerate if necessary. */ static void isis_redist_uninstall(struct isis_area *area, int level, struct prefix *p) { int family = p->family; struct route_table *er_table = get_ext_reach(area, family, level); struct route_node *er_node; if (!er_table) { zlog_warn("%s: External reachability table of area %s" " is not initialized.", __func__, area->area_tag); return; } er_node = route_node_lookup(er_table, p); if (!er_node) return; else route_unlock_node(er_node); if (!er_node->info) return; XFREE(MTYPE_ISIS, er_node->info); route_unlock_node(er_node); lsp_regenerate_schedule(area, level, 0); } /* Update external reachability info of area for a given level * and prefix, using the given redistribution settings. */ static void isis_redist_update_ext_reach(struct isis_area *area, int level, struct isis_redist *redist, struct prefix *p, struct isis_ext_info *info) { struct isis_ext_info area_info; route_map_result_t map_ret; memcpy(&area_info, info, sizeof(area_info)); if (redist->metric != 0xffffffff) area_info.metric = redist->metric; if (redist->map_name) { map_ret = route_map_apply(redist->map, p, RMAP_ISIS, &area_info); if (map_ret == RMAP_DENYMATCH) area_info.distance = 255; } /* Allow synthesized default routes only on always orignate */ if (area_info.origin == DEFAULT_ROUTE && redist->redist != DEFAULT_ORIGINATE_ALWAYS) area_info.distance = 255; if (area_info.distance < 255) isis_redist_install(area, level, p, &area_info); else isis_redist_uninstall(area, level, p); } static void isis_redist_ensure_default(struct isis *isis, int family) { struct prefix p; struct route_table *ei_table = get_ext_info(isis, family); struct route_node *ei_node; struct isis_ext_info *info; if (family == AF_INET) { p.family = AF_INET; p.prefixlen = 0; memset(&p.u.prefix4, 0, sizeof(p.u.prefix4)); } #ifdef HAVE_IPV6 else if (family == AF_INET6) { p.family = AF_INET6; p.prefixlen = 0; memset(&p.u.prefix6, 0, sizeof(p.u.prefix6)); } #endif else assert(!"Unknown family!"); ei_node = route_node_get(ei_table, &p); if (ei_node->info) { route_unlock_node(ei_node); return; } ei_node->info = XCALLOC(MTYPE_ISIS, sizeof(struct isis_ext_info)); info = ei_node->info; info->origin = DEFAULT_ROUTE; info->distance = 254; info->metric = MAX_WIDE_PATH_METRIC; } /* Handle notification about route being added */ void isis_redist_add(int type, struct prefix *p, u_char distance, uint32_t metric) { int family = p->family; struct route_table *ei_table = get_ext_info(isis, family); struct route_node *ei_node; struct isis_ext_info *info; struct listnode *node; struct isis_area *area; int level; struct isis_redist *redist; #ifdef EXTREME_DEBUG char debug_buf[BUFSIZ]; prefix2str(p, debug_buf, sizeof(debug_buf)); zlog_debug("%s: New route %s from %s.", __func__, debug_buf, zebra_route_string(type)); #endif if (!ei_table) { zlog_warn("%s: External information table not initialized.", __func__); return; } ei_node = route_node_get(ei_table, p); if (ei_node->info) route_unlock_node(ei_node); else ei_node->info = XCALLOC(MTYPE_ISIS, sizeof(struct isis_ext_info)); info = ei_node->info; info->origin = type; info->distance = distance; info->metric = metric; if (is_default(p)) type = DEFAULT_ROUTE; for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) for (level = 1; level <= ISIS_LEVELS; level++) { redist = get_redist_settings(area, family, type, level); if (!redist->redist) continue; isis_redist_update_ext_reach(area, level, redist, p, info); } } void isis_redist_delete(int type, struct prefix *p) { int family = p->family; struct route_table *ei_table = get_ext_info(isis, family); struct route_node *ei_node; struct listnode *node; struct isis_area *area; int level; struct isis_redist *redist; #ifdef EXTREME_DEBUG char debug_buf[BUFSIZ]; prefix2str(p, debug_buf, sizeof(debug_buf)); zlog_debug("%s: Removing route %s from %s.", __func__, debug_buf, zebra_route_string(type)); #endif if (is_default(p)) { /* Don't remove default route but add synthetic route for use * by "default-information originate always". Areas without the * "always" setting will ignore routes with origin DEFAULT_ROUTE. */ isis_redist_add(DEFAULT_ROUTE, p, 254, MAX_WIDE_PATH_METRIC); return; } if (!ei_table) { zlog_warn("%s: External information table not initialized.", __func__); return; } ei_node = route_node_lookup(ei_table, p); if (!ei_node || !ei_node->info) { char buf[BUFSIZ]; prefix2str(p, buf, sizeof(buf)); zlog_warn("%s: Got a delete for %s route %s, but that route" " was never added.", __func__, zebra_route_string(type), buf); if (ei_node) route_unlock_node(ei_node); return; } route_unlock_node(ei_node); for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) for (level = 1; level < ISIS_LEVELS; level++) { redist = get_redist_settings(area, family, type, level); if (!redist->redist) continue; isis_redist_uninstall(area, level, p); } XFREE(MTYPE_ISIS, ei_node->info); route_unlock_node(ei_node); } static void isis_redist_routemap_set(struct isis_redist *redist, const char *routemap) { if (redist->map_name) { XFREE(MTYPE_ISIS, redist->map_name); redist->map = NULL; } if (routemap && strlen(routemap)) { redist->map_name = XSTRDUP(MTYPE_ISIS, routemap); redist->map = route_map_lookup_by_name(routemap); } } static void isis_redist_set(struct isis_area *area, int level, int family, int type, uint32_t metric, const char *routemap, int originate_type) { int protocol = redist_protocol(family); struct isis_redist *redist = get_redist_settings(area, family, type, level); int i; struct route_table *ei_table; struct route_node *rn; struct isis_ext_info *info; redist->redist = (type == DEFAULT_ROUTE) ? originate_type : 1; redist->metric = metric; isis_redist_routemap_set(redist, routemap); if (!area->ext_reach[protocol][level-1]) area->ext_reach[protocol][level-1] = route_table_init(); for (i = 0; i < REDIST_PROTOCOL_COUNT; i++) if (!area->isis->ext_info[i]) area->isis->ext_info[i] = route_table_init(); isis_zebra_redistribute_set(type); if (type == DEFAULT_ROUTE && originate_type == DEFAULT_ORIGINATE_ALWAYS) isis_redist_ensure_default(area->isis, family); ei_table = get_ext_info(area->isis, family); for (rn = route_top(ei_table); rn; rn = route_next(rn)) { if (!rn->info) continue; info = rn->info; if (type == DEFAULT_ROUTE) { if (!is_default(&rn->p)) continue; } else { if (info->origin != type) continue; } isis_redist_update_ext_reach(area, level, redist, &rn->p, info); } } static void isis_redist_unset(struct isis_area *area, int level, int family, int type) { struct isis_redist *redist = get_redist_settings(area, family, type, level); struct route_table *er_table = get_ext_reach(area, family, level); struct route_node *rn; struct isis_ext_info *info; if (!redist->redist) return; redist->redist = 0; if (!er_table) { zlog_warn("%s: External reachability table uninitialized.", __func__); return; } for (rn = route_top(er_table); rn; rn = route_next(rn)) { if (!rn->info) continue; info = rn->info; if (type == DEFAULT_ROUTE) { if (!is_default(&rn->p)) continue; } else { if (info->origin != type) continue; } XFREE(MTYPE_ISIS, rn->info); route_unlock_node(rn); } lsp_regenerate_schedule(area, level, 0); } DEFUN(isis_redistribute_ipv4, isis_redistribute_ipv4_cmd, "redistribute (ipv4|) " QUAGGA_REDIST_STR_ISISD " (level-1|level-2) {metric <0-16777215>|route-map WORD}", REDIST_STR "Redistribute IPv4 routes\n" "DUMMY\n" QUAGGA_REDIST_HELP_STR_ISISD "Redistribute into level-1\n" "Redistribute into level-2\n" "Metric for redistributed routes\n" "ISIS default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { struct isis_area *area = vty->index; int family; int type; int level; unsigned long metric; const char *routemap; if (argc < 5) return CMD_WARNING; family = str2family(argv[0]); if (family < 0) return CMD_WARNING; type = proto_redistnum(AFI_IP, argv[1]); if (type < 0 || type == ZEBRA_ROUTE_ISIS) return CMD_WARNING; if (!strcmp("level-1", argv[2])) level = 1; else if (!strcmp("level-2", argv[2])) level = 2; else return CMD_WARNING; if ((area->is_type & level) != level) { vty_out(vty, "Node is not a level-%d IS%s", level, VTY_NEWLINE); return CMD_WARNING; } if (argv[3]) { char *endp; metric = strtoul(argv[3], &endp, 10); if (argv[3][0] == '\0' || *endp != '\0') return CMD_WARNING; } else { metric = 0xffffffff; } routemap = argv[4]; isis_redist_set(area, level, family, type, metric, routemap, 0); return 0; } DEFUN(no_isis_redistribute_ipv4, no_isis_redistribute_ipv4_cmd, "no redistribute (ipv4|) " QUAGGA_REDIST_STR_ISISD " (level-1|level-2)", NO_STR REDIST_STR "Redistribute IPv4 routes\n" "DUMMY\n" QUAGGA_REDIST_HELP_STR_ISISD "Redistribute into level-1\n" "Redistribute into level-2\n") { struct isis_area *area = vty->index; int type; int level; int family; if (argc < 3) return CMD_WARNING; family = str2family(argv[0]); if (family < 0) return CMD_WARNING; type = proto_redistnum(AFI_IP, argv[1]); if (type < 0 || type == ZEBRA_ROUTE_ISIS) return CMD_WARNING; if (!strcmp("level-1", argv[2])) level = 1; else if (!strcmp("level-2", argv[2])) level = 2; else return CMD_WARNING; isis_redist_unset(area, level, family, type); return 0; } DEFUN(isis_default_originate_ipv4, isis_default_originate_ipv4_cmd, "default-information originate (ipv4|) (level-1|level-2) " "{always|metric <0-16777215>|route-map WORD}", "Control distribution of default information\n" "Distribute a default route\n" "Distribute default route for IPv4\n" "DUMMY\n" "Distribute default route into level-1\n" "Distribute default route into level-2\n" "Always advertise default route\n" "Metric for default route\n" "ISIS default metric\n" "Route map reference\n" "Pointer to route-map entries\n") { struct isis_area *area = vty->index; int family; int originate_type; int level; unsigned long metric; const char *routemap; if (argc < 5) return CMD_WARNING; family = str2family(argv[0]); if (family < 0) return CMD_WARNING; if (!strcmp("level-1", argv[1])) level = 1; else if (!strcmp("level-2", argv[1])) level = 2; else return CMD_WARNING; if ((area->is_type & level) != level) { vty_out(vty, "Node is not a level-%d IS%s", level, VTY_NEWLINE); return CMD_WARNING; } if (argv[2] && *argv[2] != '\0') originate_type = DEFAULT_ORIGINATE_ALWAYS; else originate_type = DEFAULT_ORIGINATE; #ifdef HAVE_IPV6 if (family == AF_INET6 && originate_type != DEFAULT_ORIGINATE_ALWAYS) { vty_out(vty, "Zebra doesn't implement default-originate for IPv6 yet%s", VTY_NEWLINE); vty_out(vty, "so use with care or use default-originate always.%s", VTY_NEWLINE); } #endif if (argv[3]) { char *endp; metric = strtoul(argv[3], &endp, 10); if (argv[3][0] == '\0' || *endp != '\0') return CMD_WARNING; } else { metric = 0xffffffff; } routemap = argv[4]; isis_redist_set(area, level, family, DEFAULT_ROUTE, metric, routemap, originate_type); return 0; } DEFUN(no_isis_default_originate_ipv4, no_isis_default_originate_ipv4_cmd, "no default-information originate (ipv4|) (level-1|level-2)", NO_STR "Control distribution of default information\n" "Distribute a default route\n" "Distribute default route for IPv4\n" "DUMMY\n" "Distribute default route into level-1\n" "Distribute default route into level-2\n") { struct isis_area *area = vty->index; int family; int level; if (argc < 2) return CMD_WARNING; family = str2family(argv[0]); if (family < 0) return CMD_WARNING; if (!strcmp("level-1", argv[1])) level = 1; else if (!strcmp("level-2", argv[1])) level = 2; else return CMD_WARNING; isis_redist_unset(area, level, family, DEFAULT_ROUTE); return 0; } #ifdef HAVE_IPV6 ALIAS(isis_redistribute_ipv4, isis_redistribute_ipv6_cmd, "redistribute (ipv6|)" QUAGGA_REDIST_STR_ISISD " (level-1|level-2) {metric <0-16777215>|route-map WORD}", REDIST_STR "Redistribute IPv6 routes\n" "DUMMY\n" QUAGGA_REDIST_HELP_STR_ISISD "Redistribute into level-1\n" "Redistribute into level-2\n" "Metric for redistributed routes\n" "ISIS default metric\n" "Route map reference\n" "Pointer to route-map entries\n" ); ALIAS(no_isis_redistribute_ipv4, no_isis_redistribute_ipv6_cmd, "no redistribute (ipv6|)" QUAGGA_REDIST_STR_ISISD " (level-1|level-2)", NO_STR REDIST_STR "Redistribute IPv6 routes\n" "DUMMY\n" QUAGGA_REDIST_HELP_STR_ISISD "Redistribute into level-1\n" "Redistribute into level-2\n" ); ALIAS(isis_default_originate_ipv4, isis_default_originate_ipv6_cmd, "default-information originate (ipv6|) (level-1|level-2) " "{always|metric <0-16777215>|route-map WORD}", "Control distribution of default information\n" "Distribute a default route\n" "Distribute default route for IPv6\n" "DUMMY\n" "Distribute default route into level-1\n" "Distribute default route into level-2\n" "Always advertise default route\n" "Metric for default route\n" "ISIS default metric\n" "Route map reference\n" "Pointer to route-map entries\n") ALIAS(no_isis_default_originate_ipv4, no_isis_default_originate_ipv6_cmd, "no default-information originate (ipv6|) (level-1|level-2)", NO_STR "Control distribution of default information\n" "Distribute a default route\n" "Distribute default route for IPv6\n" "DUMMY\n" "Distribute default route into level-1\n" "Distribute default route into level-2\n" ); #endif int isis_redist_config_write(struct vty *vty, struct isis_area *area, int family) { int type; int level; int write = 0; struct isis_redist *redist; const char *family_str; if (family == AF_INET) family_str = "ipv4"; #ifdef HAVE_IPV6 else if (family == AF_INET6) family_str = "ipv6"; #endif else return 0; for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { if (type == ZEBRA_ROUTE_ISIS) continue; for (level = 1; level <= ISIS_LEVELS; level++) { redist = get_redist_settings(area, family, type, level); if (!redist->redist) continue; vty_out(vty, " redistribute %s %s level-%d", family_str, zebra_route_string(type), level); if (redist->metric != 0xffffffff) vty_out(vty, " metric %u", redist->metric); if (redist->map_name) vty_out(vty, " route-map %s", redist->map_name); vty_out(vty, "%s", VTY_NEWLINE); write++; } } for (level = 1; level <= ISIS_LEVELS; level++) { redist = get_redist_settings(area, family, DEFAULT_ROUTE, level); if (!redist->redist) continue; vty_out(vty, " default-information originate %s level-%d", family_str, level); if (redist->redist == DEFAULT_ORIGINATE_ALWAYS) vty_out(vty, " always"); if (redist->metric != 0xffffffff) vty_out(vty, " metric %u", redist->metric); if (redist->map_name) vty_out(vty, " route-map %s", redist->map_name); vty_out(vty, "%s", VTY_NEWLINE); write++; } return write; } void isis_redist_init(void) { install_element(ISIS_NODE, &isis_redistribute_ipv4_cmd); install_element(ISIS_NODE, &no_isis_redistribute_ipv4_cmd); install_element(ISIS_NODE, &isis_default_originate_ipv4_cmd); install_element(ISIS_NODE, &no_isis_default_originate_ipv4_cmd); #ifdef HAVE_IPV6 install_element(ISIS_NODE, &isis_redistribute_ipv6_cmd); install_element(ISIS_NODE, &no_isis_redistribute_ipv6_cmd); install_element(ISIS_NODE, &isis_default_originate_ipv6_cmd); install_element(ISIS_NODE, &no_isis_default_originate_ipv6_cmd); #endif }