blob: 9cca809200255b559eec8e1576cba1de14e30bc0 [file] [log] [blame]
/*
* IS-IS Rout(e)ing protocol - isis_route.c
* Copyright (C) 2001,2002 Sampo Saaristo
* Tampere University of Technology
* Institute of Communications Engineering
*
* based on ../ospf6d/ospf6_route.[ch]
* by Yasuhiro Ohara
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public Licenseas 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 "thread.h"
#include "linklist.h"
#include "vty.h"
#include "log.h"
#include "memory.h"
#include "prefix.h"
#include "hash.h"
#include "if.h"
#include "table.h"
#include "isis_constants.h"
#include "isis_common.h"
#include "isis_flags.h"
#include "dict.h"
#include "isisd.h"
#include "isis_misc.h"
#include "isis_adjacency.h"
#include "isis_circuit.h"
#include "isis_tlv.h"
#include "isis_pdu.h"
#include "isis_lsp.h"
#include "isis_spf.h"
#include "isis_route.h"
#include "isis_zebra.h"
static struct isis_nexthop *
isis_nexthop_create (struct in_addr *ip, ifindex_t ifindex)
{
struct listnode *node;
struct isis_nexthop *nexthop;
for (ALL_LIST_ELEMENTS_RO (isis->nexthops, node, nexthop))
{
if (nexthop->ifindex != ifindex)
continue;
if (ip && memcmp (&nexthop->ip, ip, sizeof (struct in_addr)) != 0)
continue;
nexthop->lock++;
return nexthop;
}
nexthop = XCALLOC (MTYPE_ISIS_NEXTHOP, sizeof (struct isis_nexthop));
nexthop->ifindex = ifindex;
memcpy (&nexthop->ip, ip, sizeof (struct in_addr));
listnode_add (isis->nexthops, nexthop);
nexthop->lock++;
return nexthop;
}
static void
isis_nexthop_delete (struct isis_nexthop *nexthop)
{
nexthop->lock--;
if (nexthop->lock == 0)
{
listnode_delete (isis->nexthops, nexthop);
XFREE (MTYPE_ISIS_NEXTHOP, nexthop);
}
return;
}
static int
nexthoplookup (struct list *nexthops, struct in_addr *ip,
ifindex_t ifindex)
{
struct listnode *node;
struct isis_nexthop *nh;
for (ALL_LIST_ELEMENTS_RO (nexthops, node, nh))
{
if (!(memcmp (ip, &nh->ip, sizeof (struct in_addr))) &&
ifindex == nh->ifindex)
return 1;
}
return 0;
}
#ifdef EXTREME_DEBUG
static void
nexthop_print (struct isis_nexthop *nh)
{
u_char buf[BUFSIZ];
inet_ntop (AF_INET, &nh->ip, (char *) buf, BUFSIZ);
zlog_debug (" %s %u", buf, nh->ifindex);
}
static void
nexthops_print (struct list *nhs)
{
struct listnode *node;
struct isis_nexthop *nh;
for (ALL_LIST_ELEMENTS_RO (nhs, node, nh))
nexthop_print (nh);
}
#endif /* EXTREME_DEBUG */
#ifdef HAVE_IPV6
static struct isis_nexthop6 *
isis_nexthop6_new (struct in6_addr *ip6, ifindex_t ifindex)
{
struct isis_nexthop6 *nexthop6;
nexthop6 = XCALLOC (MTYPE_ISIS_NEXTHOP6, sizeof (struct isis_nexthop6));
nexthop6->ifindex = ifindex;
memcpy (&nexthop6->ip6, ip6, sizeof (struct in6_addr));
nexthop6->lock++;
return nexthop6;
}
static struct isis_nexthop6 *
isis_nexthop6_create (struct in6_addr *ip6, ifindex_t ifindex)
{
struct listnode *node;
struct isis_nexthop6 *nexthop6;
for (ALL_LIST_ELEMENTS_RO (isis->nexthops6, node, nexthop6))
{
if (nexthop6->ifindex != ifindex)
continue;
if (ip6 && memcmp (&nexthop6->ip6, ip6, sizeof (struct in6_addr)) != 0)
continue;
nexthop6->lock++;
return nexthop6;
}
nexthop6 = isis_nexthop6_new (ip6, ifindex);
return nexthop6;
}
static void
isis_nexthop6_delete (struct isis_nexthop6 *nexthop6)
{
nexthop6->lock--;
if (nexthop6->lock == 0)
{
listnode_delete (isis->nexthops6, nexthop6);
XFREE (MTYPE_ISIS_NEXTHOP6, nexthop6);
}
return;
}
static int
nexthop6lookup (struct list *nexthops6, struct in6_addr *ip6,
ifindex_t ifindex)
{
struct listnode *node;
struct isis_nexthop6 *nh6;
for (ALL_LIST_ELEMENTS_RO (nexthops6, node, nh6))
{
if (!(memcmp (ip6, &nh6->ip6, sizeof (struct in6_addr))) &&
ifindex == nh6->ifindex)
return 1;
}
return 0;
}
#ifdef EXTREME_DEBUG
static void
nexthop6_print (struct isis_nexthop6 *nh6)
{
u_char buf[BUFSIZ];
inet_ntop (AF_INET6, &nh6->ip6, (char *) buf, BUFSIZ);
zlog_debug (" %s %u", buf, nh6->ifindex);
}
static void
nexthops6_print (struct list *nhs6)
{
struct listnode *node;
struct isis_nexthop6 *nh6;
for (ALL_LIST_ELEMENTS_RO (nhs6, node, nh6))
nexthop6_print (nh6);
}
#endif /* EXTREME_DEBUG */
#endif /* HAVE_IPV6 */
static void
adjinfo2nexthop (struct list *nexthops, struct isis_adjacency *adj)
{
struct isis_nexthop *nh;
struct listnode *node;
struct in_addr *ipv4_addr;
if (adj->ipv4_addrs == NULL)
return;
for (ALL_LIST_ELEMENTS_RO (adj->ipv4_addrs, node, ipv4_addr))
{
if (!nexthoplookup (nexthops, ipv4_addr,
adj->circuit->interface->ifindex))
{
nh = isis_nexthop_create (ipv4_addr,
adj->circuit->interface->ifindex);
nh->router_address = adj->router_address;
listnode_add (nexthops, nh);
}
}
}
#ifdef HAVE_IPV6
static void
adjinfo2nexthop6 (struct list *nexthops6, struct isis_adjacency *adj)
{
struct listnode *node;
struct in6_addr *ipv6_addr;
struct isis_nexthop6 *nh6;
if (!adj->ipv6_addrs)
return;
for (ALL_LIST_ELEMENTS_RO (adj->ipv6_addrs, node, ipv6_addr))
{
if (!nexthop6lookup (nexthops6, ipv6_addr,
adj->circuit->interface->ifindex))
{
nh6 = isis_nexthop6_create (ipv6_addr,
adj->circuit->interface->ifindex);
nh6->router_address6 = adj->router_address6;
listnode_add (nexthops6, nh6);
}
}
}
#endif /* HAVE_IPV6 */
static struct isis_route_info *
isis_route_info_new (struct prefix *prefix, uint32_t cost, uint32_t depth,
struct list *adjacencies)
{
struct isis_route_info *rinfo;
struct isis_adjacency *adj;
struct listnode *node;
rinfo = XCALLOC (MTYPE_ISIS_ROUTE_INFO, sizeof (struct isis_route_info));
if (prefix->family == AF_INET)
{
rinfo->nexthops = list_new ();
for (ALL_LIST_ELEMENTS_RO (adjacencies, node, adj))
{
/* check for force resync this route */
if (CHECK_FLAG (adj->circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF))
SET_FLAG (rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC);
/* update neighbor router address */
if (depth == 2 && prefix->prefixlen == 32)
adj->router_address = prefix->u.prefix4;
adjinfo2nexthop (rinfo->nexthops, adj);
}
}
#ifdef HAVE_IPV6
if (prefix->family == AF_INET6)
{
rinfo->nexthops6 = list_new ();
for (ALL_LIST_ELEMENTS_RO (adjacencies, node, adj))
{
/* check for force resync this route */
if (CHECK_FLAG (adj->circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF))
SET_FLAG (rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC);
/* update neighbor router address */
if (depth == 2 && prefix->prefixlen == 128)
adj->router_address6 = prefix->u.prefix6;
adjinfo2nexthop6 (rinfo->nexthops6, adj);
}
}
#endif /* HAVE_IPV6 */
rinfo->cost = cost;
rinfo->depth = depth;
return rinfo;
}
static void
isis_route_info_delete (struct isis_route_info *route_info)
{
if (route_info->nexthops)
{
route_info->nexthops->del = (void (*)(void *)) isis_nexthop_delete;
list_delete (route_info->nexthops);
}
#ifdef HAVE_IPV6
if (route_info->nexthops6)
{
route_info->nexthops6->del = (void (*)(void *)) isis_nexthop6_delete;
list_delete (route_info->nexthops6);
}
#endif /* HAVE_IPV6 */
XFREE (MTYPE_ISIS_ROUTE_INFO, route_info);
}
static int
isis_route_info_same_attrib (struct isis_route_info *new,
struct isis_route_info *old)
{
if (new->cost != old->cost)
return 0;
if (new->depth != old->depth)
return 0;
return 1;
}
static int
isis_route_info_same (struct isis_route_info *new,
struct isis_route_info *old, u_char family)
{
struct listnode *node;
struct isis_nexthop *nexthop;
#ifdef HAVE_IPV6
struct isis_nexthop6 *nexthop6;
#endif /* HAVE_IPV6 */
if (!CHECK_FLAG (old->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED))
return 0;
if (CHECK_FLAG (new->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC))
return 0;
if (!isis_route_info_same_attrib (new, old))
return 0;
if (family == AF_INET)
{
for (ALL_LIST_ELEMENTS_RO (new->nexthops, node, nexthop))
if (nexthoplookup (old->nexthops, &nexthop->ip, nexthop->ifindex)
== 0)
return 0;
for (ALL_LIST_ELEMENTS_RO (old->nexthops, node, nexthop))
if (nexthoplookup (new->nexthops, &nexthop->ip, nexthop->ifindex)
== 0)
return 0;
}
#ifdef HAVE_IPV6
else if (family == AF_INET6)
{
for (ALL_LIST_ELEMENTS_RO (new->nexthops6, node, nexthop6))
if (nexthop6lookup (old->nexthops6, &nexthop6->ip6,
nexthop6->ifindex) == 0)
return 0;
for (ALL_LIST_ELEMENTS_RO (old->nexthops6, node, nexthop6))
if (nexthop6lookup (new->nexthops6, &nexthop6->ip6,
nexthop6->ifindex) == 0)
return 0;
}
#endif /* HAVE_IPV6 */
return 1;
}
struct isis_route_info *
isis_route_create (struct prefix *prefix, u_int32_t cost, u_int32_t depth,
struct list *adjacencies, struct isis_area *area,
int level)
{
struct route_node *route_node;
struct isis_route_info *rinfo_new, *rinfo_old, *route_info = NULL;
u_char buff[BUFSIZ];
u_char family;
family = prefix->family;
/* for debugs */
prefix2str (prefix, (char *) buff, BUFSIZ);
rinfo_new = isis_route_info_new (prefix, cost, depth, adjacencies);
if (family == AF_INET)
route_node = route_node_get (area->route_table[level - 1], prefix);
#ifdef HAVE_IPV6
else if (family == AF_INET6)
route_node = route_node_get (area->route_table6[level - 1], prefix);
#endif /* HAVE_IPV6 */
else
{
isis_route_info_delete (rinfo_new);
return NULL;
}
rinfo_old = route_node->info;
if (!rinfo_old)
{
if (isis->debugs & DEBUG_RTE_EVENTS)
zlog_debug ("ISIS-Rte (%s) route created: %s", area->area_tag, buff);
route_info = rinfo_new;
UNSET_FLAG (route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
}
else
{
if (isis->debugs & DEBUG_RTE_EVENTS)
zlog_debug ("ISIS-Rte (%s) route already exists: %s", area->area_tag,
buff);
if (isis_route_info_same (rinfo_new, rinfo_old, family))
{
if (isis->debugs & DEBUG_RTE_EVENTS)
zlog_debug ("ISIS-Rte (%s) route unchanged: %s", area->area_tag,
buff);
isis_route_info_delete (rinfo_new);
route_info = rinfo_old;
}
else
{
if (isis->debugs & DEBUG_RTE_EVENTS)
zlog_debug ("ISIS-Rte (%s) route changed: %s", area->area_tag,
buff);
isis_route_info_delete (rinfo_old);
route_info = rinfo_new;
UNSET_FLAG (route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED);
}
}
SET_FLAG (route_info->flag, ISIS_ROUTE_FLAG_ACTIVE);
route_node->info = route_info;
return route_info;
}
static void
isis_route_delete (struct prefix *prefix, struct route_table *table)
{
struct route_node *rode;
struct isis_route_info *rinfo;
char buff[BUFSIZ];
/* for log */
prefix2str (prefix, buff, BUFSIZ);
rode = route_node_get (table, prefix);
rinfo = rode->info;
if (rinfo == NULL)
{
if (isis->debugs & DEBUG_RTE_EVENTS)
zlog_debug ("ISIS-Rte: tried to delete non-existant route %s", buff);
return;
}
if (CHECK_FLAG (rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED))
{
UNSET_FLAG (rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE);
if (isis->debugs & DEBUG_RTE_EVENTS)
zlog_debug ("ISIS-Rte: route delete %s", buff);
isis_zebra_route_update (prefix, rinfo);
}
isis_route_info_delete (rinfo);
rode->info = NULL;
return;
}
/* Validating routes in particular table. */
static void
isis_route_validate_table (struct isis_area *area, struct route_table *table)
{
struct route_node *rnode, *drnode;
struct isis_route_info *rinfo;
u_char buff[BUFSIZ];
for (rnode = route_top (table); rnode; rnode = route_next (rnode))
{
if (rnode->info == NULL)
continue;
rinfo = rnode->info;
if (isis->debugs & DEBUG_RTE_EVENTS)
{
prefix2str (&rnode->p, (char *) buff, BUFSIZ);
zlog_debug ("ISIS-Rte (%s): route validate: %s %s %s %s",
area->area_tag,
(CHECK_FLAG (rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED) ?
"synced" : "not-synced"),
(CHECK_FLAG (rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC) ?
"resync" : "not-resync"),
(CHECK_FLAG (rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE) ?
"active" : "inactive"), buff);
}
isis_zebra_route_update (&rnode->p, rinfo);
if (!CHECK_FLAG (rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE))
{
/* Area is either L1 or L2 => we use level route tables directly for
* validating => no problems with deleting routes. */
if (area->is_type != IS_LEVEL_1_AND_2)
{
isis_route_delete (&rnode->p, table);
continue;
}
/* If area is L1L2, we work with merge table and therefore must
* delete node from level tables as well before deleting route info.
* FIXME: Is it performance problem? There has to be the better way.
* Like not to deal with it here at all (see the next comment)? */
if (rnode->p.family == AF_INET)
{
drnode = route_node_get (area->route_table[0], &rnode->p);
if (drnode->info == rnode->info)
drnode->info = NULL;
drnode = route_node_get (area->route_table[1], &rnode->p);
if (drnode->info == rnode->info)
drnode->info = NULL;
}
#ifdef HAVE_IPV6
if (rnode->p.family == AF_INET6)
{
drnode = route_node_get (area->route_table6[0], &rnode->p);
if (drnode->info == rnode->info)
drnode->info = NULL;
drnode = route_node_get (area->route_table6[1], &rnode->p);
if (drnode->info == rnode->info)
drnode->info = NULL;
}
#endif
isis_route_delete (&rnode->p, table);
}
}
}
/* Function to validate route tables for L1L2 areas. In this case we can't use
* level route tables directly, we have to merge them at first. L1 routes are
* preferred over the L2 ones.
*
* Merge algorithm is trivial (at least for now). All L1 paths are copied into
* merge table at first, then L2 paths are added if L1 path for same prefix
* doesn't already exists there.
*
* FIXME: Is it right place to do it at all? Maybe we should push both levels
* to the RIB with different zebra route types and let RIB handle this? */
static void
isis_route_validate_merge (struct isis_area *area, int family)
{
struct route_table *table = NULL;
struct route_table *merge;
struct route_node *rnode, *mrnode;
merge = route_table_init ();
if (family == AF_INET)
table = area->route_table[0];
#ifdef HAVE_IPV6
else if (family == AF_INET6)
table = area->route_table6[0];
#endif
for (rnode = route_top (table); rnode; rnode = route_next (rnode))
{
if (rnode->info == NULL)
continue;
mrnode = route_node_get (merge, &rnode->p);
mrnode->info = rnode->info;
}
if (family == AF_INET)
table = area->route_table[1];
#ifdef HAVE_IPV6
else if (family == AF_INET6)
table = area->route_table6[1];
#endif
for (rnode = route_top (table); rnode; rnode = route_next (rnode))
{
if (rnode->info == NULL)
continue;
mrnode = route_node_get (merge, &rnode->p);
if (mrnode->info != NULL)
continue;
mrnode->info = rnode->info;
}
isis_route_validate_table (area, merge);
route_table_finish (merge);
}
/* Walk through route tables and propagate necessary changes into RIB. In case
* of L1L2 area, level tables have to be merged at first. */
void
isis_route_validate (struct isis_area *area)
{
struct listnode *node;
struct isis_circuit *circuit;
if (area->is_type == IS_LEVEL_1)
isis_route_validate_table (area, area->route_table[0]);
else if (area->is_type == IS_LEVEL_2)
isis_route_validate_table (area, area->route_table[1]);
else
isis_route_validate_merge (area, AF_INET);
#ifdef HAVE_IPV6
if (area->is_type == IS_LEVEL_1)
isis_route_validate_table (area, area->route_table6[0]);
else if (area->is_type == IS_LEVEL_2)
isis_route_validate_table (area, area->route_table6[1]);
else
isis_route_validate_merge (area, AF_INET6);
#endif
/* walk all circuits and reset any spf specific flags */
for (ALL_LIST_ELEMENTS_RO (area->circuit_list, node, circuit))
UNSET_FLAG(circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF);
return;
}
void
isis_route_invalidate_table (struct isis_area *area, struct route_table *table)
{
struct route_node *rode;
struct isis_route_info *rinfo;
for (rode = route_top (table); rode; rode = route_next (rode))
{
if (rode->info == NULL)
continue;
rinfo = rode->info;
UNSET_FLAG (rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE);
}
}
void
isis_route_invalidate (struct isis_area *area)
{
if (area->is_type & IS_LEVEL_1)
isis_route_invalidate_table (area, area->route_table[0]);
if (area->is_type & IS_LEVEL_2)
isis_route_invalidate_table (area, area->route_table[1]);
}