diff options
Diffstat (limited to 'isisd/isis_redist.c')
-rw-r--r-- | isisd/isis_redist.c | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/isisd/isis_redist.c b/isisd/isis_redist.c new file mode 100644 index 00000000..f0ad6941 --- /dev/null +++ b/isisd/isis_redist.c @@ -0,0 +1,815 @@ +/* + * 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 <zebra.h> + +#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 +} |