blob: 690ae4720c866082949b161631fd93bf9cd59b12 [file] [log] [blame]
/*
* IS-IS Rout(e)ing protocol - isis_redist.c
*
* Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
*
* 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;
if (family == AF_INET6)
return 1;
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;
if (p->family == AF_INET6)
if (IN6_IS_ADDR_UNSPECIFIED(&p->u.prefix6) && p->prefixlen == 0)
return 1;
return 0;
}
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];
}
static struct route_node *
isis_redist_route_node_create(route_table_delegate_t *delegate,
struct route_table *table)
{
struct route_node *node;
node = XCALLOC(MTYPE_ROUTE_NODE, sizeof(*node));
return node;
}
static void
isis_redist_route_node_destroy(route_table_delegate_t *delegate,
struct route_table *table,
struct route_node *node)
{
if (node->info)
XFREE(MTYPE_ISIS, node->info);
XFREE (MTYPE_ROUTE_NODE, node);
}
static route_table_delegate_t isis_redist_rt_delegate = {
.create_node = isis_redist_route_node_create,
.destroy_node = isis_redist_route_node_destroy
};
/* 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));
}
else if (family == AF_INET6)
{
p.family = AF_INET6;
p.prefixlen = 0;
memset(&p.u.prefix6, 0, sizeof(p.u.prefix6));
}
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;
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));
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;
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));
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_update_zebra_subscriptions(struct isis *isis)
{
struct listnode *node;
struct isis_area *area;
int type;
int level;
int protocol;
char do_subscribe[ZEBRA_ROUTE_MAX + 1];
memset(do_subscribe, 0, sizeof(do_subscribe));
for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++)
for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++)
for (level = 0; level < ISIS_LEVELS; level++)
if (area->redist_settings[protocol][type][level].redist)
do_subscribe[type] = 1;
for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++)
{
/* This field is actually controlling transmission of the IS-IS
* routes to Zebra and has nothing to do with redistribution,
* so skip it. */
if (type == ZEBRA_ROUTE_ISIS)
continue;
if (do_subscribe[type])
isis_zebra_redistribute_set(type);
else
isis_zebra_redistribute_unset(type);
}
}
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_with_delegate(&isis_redist_rt_delegate);
}
for (i = 0; i < REDIST_PROTOCOL_COUNT; i++)
if (!area->isis->ext_info[i])
{
area->isis->ext_info[i] =
route_table_init_with_delegate(&isis_redist_rt_delegate);
}
isis_redist_update_zebra_subscriptions(area->isis);
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);
isis_redist_update_zebra_subscriptions(area->isis);
}
void
isis_redist_area_finish(struct isis_area *area)
{
int protocol;
int level;
int type;
for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++)
for (level = 0; level < ISIS_LEVELS; level++)
{
for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++)
{
struct isis_redist *redist;
redist = &area->redist_settings[protocol][type][level];
redist->redist = 0;
if (redist->map_name)
XFREE(MTYPE_ISIS, redist->map_name);
}
route_table_finish(area->ext_reach[protocol][level]);
}
isis_redist_update_zebra_subscriptions(area->isis);
}
DEFUN(isis_redistribute,
isis_redistribute_cmd,
"redistribute (ipv4|ipv6) " QUAGGA_REDIST_STR_ISISD
" (level-1|level-2) {metric <0-16777215>|route-map WORD}",
REDIST_STR
"Redistribute IPv4 routes\n"
"Redistribute IPv6 routes\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 afi;
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;
afi = family2afi(family);
if (!afi)
return CMD_WARNING;
type = proto_redistnum(afi, 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,
no_isis_redistribute_cmd,
"no redistribute (ipv4|ipv6) " QUAGGA_REDIST_STR_ISISD
" (level-1|level-2)",
NO_STR
REDIST_STR
"Redistribute IPv4 routes\n"
"Redistribute IPv6 routes\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;
int afi;
if (argc < 3)
return CMD_WARNING;
family = str2family(argv[0]);
if (family < 0)
return CMD_WARNING;
afi = family2afi(family);
if (!afi)
return CMD_WARNING;
type = proto_redistnum(afi, 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,
isis_default_originate_cmd,
"default-information originate (ipv4|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 IPv4\n"
"Distribute default route for IPv6\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;
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);
}
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,
no_isis_default_originate_cmd,
"no default-information originate (ipv4|ipv6) (level-1|level-2)",
NO_STR
"Control distribution of default information\n"
"Distribute a default route\n"
"Distribute default route for IPv4\n"
"Distribute default route for IPv6\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;
}
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";
else if (family == AF_INET6)
family_str = "ipv6";
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_cmd);
install_element(ISIS_NODE, &no_isis_redistribute_cmd);
install_element(ISIS_NODE, &isis_default_originate_cmd);
install_element(ISIS_NODE, &no_isis_default_originate_cmd);
}