2015-05-20 02:40:34 +02:00
|
|
|
/* Zebra next hop tracking code
|
|
|
|
* Copyright (C) 2013 Cumulus Networks, Inc.
|
|
|
|
*
|
|
|
|
* This file is part of GNU Zebra.
|
|
|
|
*
|
|
|
|
* GNU Zebra 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, or (at your option) any
|
|
|
|
* later version.
|
|
|
|
*
|
|
|
|
* GNU Zebra 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 GNU Zebra; see the file COPYING. If not, write to the Free
|
|
|
|
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
|
|
* 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <zebra.h>
|
|
|
|
|
|
|
|
#include "prefix.h"
|
|
|
|
#include "table.h"
|
|
|
|
#include "memory.h"
|
|
|
|
#include "str.h"
|
|
|
|
#include "command.h"
|
|
|
|
#include "if.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "sockunion.h"
|
|
|
|
#include "linklist.h"
|
|
|
|
#include "thread.h"
|
|
|
|
#include "workqueue.h"
|
|
|
|
#include "prefix.h"
|
|
|
|
#include "routemap.h"
|
|
|
|
#include "stream.h"
|
|
|
|
#include "nexthop.h"
|
2015-05-22 11:39:56 +02:00
|
|
|
#include "vrf.h"
|
2015-05-20 02:40:34 +02:00
|
|
|
|
|
|
|
#include "zebra/rib.h"
|
|
|
|
#include "zebra/rt.h"
|
|
|
|
#include "zebra/zserv.h"
|
|
|
|
#include "zebra/redistribute.h"
|
|
|
|
#include "zebra/debug.h"
|
|
|
|
#include "zebra/zebra_rnh.h"
|
|
|
|
|
2015-05-20 02:47:22 +02:00
|
|
|
/* Default rtm_table for all clients */
|
|
|
|
extern struct zebra_t zebrad;
|
|
|
|
|
|
|
|
static void free_state(struct rib *rib, struct route_node *rn);
|
2015-05-20 03:04:20 +02:00
|
|
|
static void copy_state(struct rnh *rnh, struct rib *rib,
|
|
|
|
struct route_node *rn);
|
2015-05-22 11:39:56 +02:00
|
|
|
#define lookup_rnh_table(v, f) \
|
|
|
|
({ \
|
|
|
|
struct zebra_vrf *zvrf; \
|
|
|
|
struct route_table *t = NULL; \
|
|
|
|
zvrf = zebra_vrf_lookup(v); \
|
|
|
|
if (zvrf) \
|
|
|
|
t = zvrf->rnh_table[family2afi(f)]; \
|
|
|
|
t; \
|
|
|
|
})
|
|
|
|
|
2015-05-20 02:40:34 +02:00
|
|
|
static int compare_state(struct rib *r1, struct rib *r2);
|
2015-05-20 03:04:20 +02:00
|
|
|
static int send_client(struct rnh *rnh, struct zserv *client, rnh_type_t type);
|
2015-05-20 02:40:34 +02:00
|
|
|
static void print_rnh(struct route_node *rn, struct vty *vty);
|
|
|
|
|
2015-05-20 03:04:16 +02:00
|
|
|
int zebra_rnh_ip_default_route = 0;
|
|
|
|
int zebra_rnh_ipv6_default_route = 0;
|
|
|
|
|
2015-05-22 11:39:56 +02:00
|
|
|
static inline struct route_table *get_rnh_table(vrf_id_t vrfid, int family,
|
2015-05-20 03:04:20 +02:00
|
|
|
rnh_type_t type)
|
|
|
|
{
|
2015-05-22 11:39:56 +02:00
|
|
|
struct zebra_vrf *zvrf;
|
2015-05-20 03:04:20 +02:00
|
|
|
struct route_table *t = NULL;
|
|
|
|
|
2015-05-22 11:39:56 +02:00
|
|
|
zvrf = zebra_vrf_lookup(vrfid);
|
|
|
|
if (zvrf)
|
2015-05-20 03:04:20 +02:00
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case RNH_NEXTHOP_TYPE:
|
2015-05-22 11:39:56 +02:00
|
|
|
t = zvrf->rnh_table[family2afi(family)];
|
2015-05-20 03:04:20 +02:00
|
|
|
break;
|
|
|
|
case RNH_IMPORT_CHECK_TYPE:
|
2015-05-22 11:39:56 +02:00
|
|
|
t = zvrf->import_check_table[family2afi(family)];
|
2015-05-20 03:04:20 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *rnh_str (struct rnh *rnh, char *buf, int size)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
prefix2str(&(rnh->node->p), buf, size);
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct rnh *
|
2015-05-22 11:39:56 +02:00
|
|
|
zebra_add_rnh (struct prefix *p, vrf_id_t vrfid, rnh_type_t type)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
struct route_table *table;
|
|
|
|
struct route_node *rn;
|
|
|
|
struct rnh *rnh = NULL;
|
|
|
|
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT)
|
|
|
|
{
|
|
|
|
char buf[INET6_ADDRSTRLEN];
|
|
|
|
prefix2str(p, buf, INET6_ADDRSTRLEN);
|
|
|
|
zlog_debug("add rnh %s in vrf %d", buf, vrfid);
|
|
|
|
}
|
2015-05-20 03:04:20 +02:00
|
|
|
table = get_rnh_table(vrfid, PREFIX_FAMILY(p), type);
|
2015-05-20 02:40:34 +02:00
|
|
|
if (!table)
|
|
|
|
{
|
|
|
|
zlog_debug("add_rnh: rnh table not found\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make it sure prefixlen is applied to the prefix. */
|
|
|
|
apply_mask (p);
|
|
|
|
|
|
|
|
/* Lookup (or add) route node.*/
|
|
|
|
rn = route_node_get (table, p);
|
|
|
|
|
|
|
|
if (!rn->info)
|
|
|
|
{
|
|
|
|
rnh = XCALLOC(MTYPE_RNH, sizeof(struct rnh));
|
|
|
|
rnh->client_list = list_new();
|
2015-05-20 02:47:22 +02:00
|
|
|
rnh->zebra_static_route_list = list_new();
|
2015-05-20 02:40:34 +02:00
|
|
|
route_lock_node (rn);
|
|
|
|
rn->info = rnh;
|
|
|
|
rnh->node = rn;
|
|
|
|
}
|
|
|
|
|
|
|
|
route_unlock_node (rn);
|
|
|
|
return (rn->info);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct rnh *
|
2015-05-22 11:39:56 +02:00
|
|
|
zebra_lookup_rnh (struct prefix *p, vrf_id_t vrfid, rnh_type_t type)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
struct route_table *table;
|
|
|
|
struct route_node *rn;
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
table = get_rnh_table(vrfid, PREFIX_FAMILY(p), type);
|
2015-05-20 02:40:34 +02:00
|
|
|
if (!table)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Make it sure prefixlen is applied to the prefix. */
|
|
|
|
apply_mask (p);
|
|
|
|
|
|
|
|
/* Lookup route node.*/
|
|
|
|
rn = route_node_lookup (table, p);
|
|
|
|
if (!rn)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
route_unlock_node (rn);
|
|
|
|
return (rn->info);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-05-20 03:04:20 +02:00
|
|
|
zebra_delete_rnh (struct rnh *rnh, rnh_type_t type)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
struct route_node *rn;
|
|
|
|
|
2015-05-20 03:04:10 +02:00
|
|
|
if (!rnh || (rnh->flags & ZEBRA_NHT_DELETED) || !(rn = rnh->node))
|
2015-05-20 02:40:34 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT)
|
|
|
|
{
|
|
|
|
char buf[INET6_ADDRSTRLEN];
|
|
|
|
zlog_debug("delete rnh %s", rnh_str(rnh, buf, INET6_ADDRSTRLEN));
|
|
|
|
}
|
|
|
|
|
2015-05-20 03:04:10 +02:00
|
|
|
rnh->flags |= ZEBRA_NHT_DELETED;
|
2015-05-20 02:40:34 +02:00
|
|
|
list_free(rnh->client_list);
|
2015-05-20 02:47:22 +02:00
|
|
|
list_free(rnh->zebra_static_route_list);
|
|
|
|
free_state(rnh->state, rn);
|
2015-05-20 02:40:34 +02:00
|
|
|
XFREE(MTYPE_RNH, rn->info);
|
|
|
|
rn->info = NULL;
|
|
|
|
route_unlock_node (rn);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-05-20 03:04:20 +02:00
|
|
|
zebra_add_rnh_client (struct rnh *rnh, struct zserv *client, rnh_type_t type)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT)
|
|
|
|
{
|
|
|
|
char buf[INET6_ADDRSTRLEN];
|
|
|
|
zlog_debug("client %s registers rnh %s",
|
|
|
|
zebra_route_string(client->proto),
|
|
|
|
rnh_str(rnh, buf, INET6_ADDRSTRLEN));
|
|
|
|
}
|
|
|
|
if (!listnode_lookup(rnh->client_list, client))
|
|
|
|
{
|
|
|
|
listnode_add(rnh->client_list, client);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-05-20 03:04:20 +02:00
|
|
|
zebra_remove_rnh_client (struct rnh *rnh, struct zserv *client, rnh_type_t type)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT)
|
|
|
|
{
|
|
|
|
char buf[INET6_ADDRSTRLEN];
|
|
|
|
zlog_debug("client %s unregisters rnh %s",
|
|
|
|
zebra_route_string(client->proto),
|
|
|
|
rnh_str(rnh, buf, INET6_ADDRSTRLEN));
|
|
|
|
}
|
|
|
|
listnode_delete(rnh->client_list, client);
|
2015-05-20 02:47:22 +02:00
|
|
|
if (list_isempty(rnh->client_list) &&
|
|
|
|
list_isempty(rnh->zebra_static_route_list))
|
2015-05-20 03:04:20 +02:00
|
|
|
zebra_delete_rnh(rnh, type);
|
2015-05-20 02:47:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
zebra_register_rnh_static_nh(struct prefix *nh, struct route_node *static_rn)
|
|
|
|
{
|
|
|
|
struct rnh *rnh;
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
rnh = zebra_add_rnh(nh, 0, RNH_NEXTHOP_TYPE);
|
2015-05-20 02:47:22 +02:00
|
|
|
if (rnh && !listnode_lookup(rnh->zebra_static_route_list, static_rn))
|
|
|
|
{
|
|
|
|
listnode_add(rnh->zebra_static_route_list, static_rn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
zebra_deregister_rnh_static_nh(struct prefix *nh, struct route_node *static_rn)
|
|
|
|
{
|
|
|
|
struct rnh *rnh;
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
rnh = zebra_lookup_rnh(nh, 0, RNH_NEXTHOP_TYPE);
|
2015-05-20 03:04:10 +02:00
|
|
|
if (!rnh || (rnh->flags & ZEBRA_NHT_DELETED))
|
2015-05-20 02:47:22 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
listnode_delete(rnh->zebra_static_route_list, static_rn);
|
|
|
|
|
|
|
|
if (list_isempty(rnh->client_list) &&
|
|
|
|
list_isempty(rnh->zebra_static_route_list))
|
2015-05-20 03:04:20 +02:00
|
|
|
zebra_delete_rnh(rnh, RNH_NEXTHOP_TYPE);
|
2015-05-20 02:40:34 +02:00
|
|
|
}
|
|
|
|
|
2015-05-20 02:47:22 +02:00
|
|
|
static int
|
|
|
|
zebra_evaluate_rnh_nexthops(int family, struct rib *rib, struct route_node *prn,
|
|
|
|
int proto)
|
|
|
|
{
|
|
|
|
int at_least_one = 0;
|
|
|
|
int rmap_family; /* Route map has diff AF family enum */
|
|
|
|
struct nexthop *nexthop;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
rmap_family = (family == AF_INET) ? AFI_IP : AFI_IP6;
|
|
|
|
|
|
|
|
if (prn && rib)
|
|
|
|
{
|
|
|
|
for (nexthop = rib->nexthop; nexthop; nexthop = nexthop->next)
|
|
|
|
{
|
|
|
|
ret = zebra_nht_route_map_check(rmap_family, proto, &prn->p, rib,
|
|
|
|
nexthop);
|
|
|
|
if (ret != RMAP_DENYMATCH)
|
|
|
|
{
|
|
|
|
SET_FLAG (nexthop->flags, NEXTHOP_FLAG_ACTIVE);
|
|
|
|
at_least_one++; /* at least one valid NH */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
UNSET_FLAG (nexthop->flags, NEXTHOP_FLAG_ACTIVE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (at_least_one);
|
|
|
|
}
|
|
|
|
|
2015-05-20 02:40:34 +02:00
|
|
|
int
|
2015-05-22 11:39:56 +02:00
|
|
|
zebra_evaluate_rnh (vrf_id_t vrfid, int family, int force, rnh_type_t type,
|
2015-05-20 03:04:20 +02:00
|
|
|
struct prefix *p)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
struct route_table *ptable;
|
|
|
|
struct route_table *ntable;
|
2015-05-20 03:04:20 +02:00
|
|
|
struct route_node *prn = NULL;
|
|
|
|
struct route_node *nrn = NULL;
|
2015-05-20 02:40:34 +02:00
|
|
|
struct rnh *rnh;
|
|
|
|
struct zserv *client;
|
|
|
|
struct listnode *node;
|
2015-05-20 02:47:22 +02:00
|
|
|
struct rib *rib, *srib;
|
2015-05-20 02:47:20 +02:00
|
|
|
int state_changed = 0;
|
|
|
|
int at_least_one = 0;
|
|
|
|
char bufn[INET6_ADDRSTRLEN];
|
|
|
|
char bufp[INET6_ADDRSTRLEN];
|
2015-05-20 02:47:22 +02:00
|
|
|
char bufs[INET6_ADDRSTRLEN];
|
|
|
|
struct route_node *static_rn;
|
2015-05-20 03:04:20 +02:00
|
|
|
struct nexthop *nexthop, *tnexthop;
|
|
|
|
int recursing;
|
2015-05-20 03:04:16 +02:00
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
ntable = get_rnh_table(vrfid, family, type);
|
2015-05-20 02:40:34 +02:00
|
|
|
if (!ntable)
|
|
|
|
{
|
|
|
|
zlog_debug("evaluate_rnh_table: rnh table not found\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-05-22 11:39:56 +02:00
|
|
|
ptable = zebra_vrf_table(family2afi(family), SAFI_UNICAST, vrfid);
|
2015-05-20 02:40:34 +02:00
|
|
|
if (!ptable)
|
|
|
|
{
|
|
|
|
zlog_debug("evaluate_rnh_table: prefix table not found\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
if (p)
|
|
|
|
nrn = route_node_lookup(ntable, p);
|
|
|
|
else
|
|
|
|
nrn = route_top (ntable);
|
|
|
|
|
|
|
|
while (nrn != NULL)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
if (!nrn->info)
|
2015-05-20 03:04:20 +02:00
|
|
|
goto loopend;
|
2015-05-20 02:40:34 +02:00
|
|
|
|
2015-05-20 02:47:20 +02:00
|
|
|
rnh = nrn->info;
|
2015-05-20 02:47:22 +02:00
|
|
|
at_least_one = 0;
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
/* free stale stuff first */
|
|
|
|
if (prn)
|
|
|
|
route_unlock_node(prn);
|
|
|
|
|
2015-05-20 02:40:34 +02:00
|
|
|
prn = route_node_match(ptable, &nrn->p);
|
2015-05-20 03:04:20 +02:00
|
|
|
|
|
|
|
/* Do not resolve over default route unless allowed &&
|
|
|
|
* match route to be exact if so specified
|
|
|
|
*/
|
|
|
|
if (!prn)
|
|
|
|
rib = NULL;
|
|
|
|
else if ((type == RNH_NEXTHOP_TYPE) &&
|
2015-06-11 18:11:12 +02:00
|
|
|
(is_default_prefix (&prn->p) &&
|
|
|
|
!nh_resolve_via_default(prn->p.family)))
|
2015-05-20 03:04:20 +02:00
|
|
|
rib = NULL;
|
|
|
|
else if ((type == RNH_IMPORT_CHECK_TYPE) &&
|
2015-06-11 18:11:12 +02:00
|
|
|
((is_default_prefix(&prn->p)) ||
|
2015-05-20 03:04:20 +02:00
|
|
|
((CHECK_FLAG(rnh->flags, ZEBRA_NHT_EXACT_MATCH)) &&
|
|
|
|
!prefix_same(&nrn->p, &prn->p))))
|
2015-05-20 02:40:34 +02:00
|
|
|
rib = NULL;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
RNODE_FOREACH_RIB(prn, rib)
|
|
|
|
{
|
|
|
|
if (CHECK_FLAG (rib->status, RIB_ENTRY_REMOVED))
|
|
|
|
continue;
|
|
|
|
if (CHECK_FLAG (rib->flags, ZEBRA_FLAG_SELECTED))
|
2015-05-20 02:47:21 +02:00
|
|
|
{
|
|
|
|
if (CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED))
|
|
|
|
{
|
|
|
|
if (rib->type == ZEBRA_ROUTE_CONNECT)
|
|
|
|
break;
|
|
|
|
}
|
2015-05-20 03:04:20 +02:00
|
|
|
else if ((type == RNH_IMPORT_CHECK_TYPE) &&
|
|
|
|
(rib->type == ZEBRA_ROUTE_BGP))
|
|
|
|
continue;
|
2015-05-20 02:47:21 +02:00
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
2015-05-20 02:40:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-20 02:47:20 +02:00
|
|
|
state_changed = 0;
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
/* Handle import check first as its simpler */
|
|
|
|
if (type == RNH_IMPORT_CHECK_TYPE)
|
|
|
|
{
|
|
|
|
if (rib && (rnh->state == NULL))
|
|
|
|
{
|
|
|
|
for (ALL_NEXTHOPS_RO(rib->nexthop, nexthop, tnexthop, recursing))
|
|
|
|
if (CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_FIB))
|
|
|
|
{
|
|
|
|
state_changed = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!rib && (rnh->state != NULL))
|
|
|
|
state_changed = 1;
|
|
|
|
|
|
|
|
if (compare_state(rib, rnh->state))
|
|
|
|
copy_state(rnh, rib, nrn);
|
|
|
|
|
|
|
|
if (state_changed || force)
|
|
|
|
{
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT)
|
|
|
|
{
|
|
|
|
prefix2str(&nrn->p, bufn, INET6_ADDRSTRLEN);
|
|
|
|
zlog_debug("rnh import check %s for %s, notifying clients\n",
|
|
|
|
rnh->state ? "passed" : "failed", bufn);
|
|
|
|
}
|
|
|
|
/* state changed, notify clients */
|
|
|
|
for (ALL_LIST_ELEMENTS_RO(rnh->client_list, node, client))
|
|
|
|
{
|
|
|
|
send_client(rnh, client, RNH_IMPORT_CHECK_TYPE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
goto loopend;
|
|
|
|
}
|
|
|
|
|
2015-06-11 18:11:12 +02:00
|
|
|
/* If nexthop cannot be resolved and that is also the existing state,
|
|
|
|
* there is nothing further to do.
|
|
|
|
*/
|
|
|
|
if (!rib && rnh->state == NULL)
|
|
|
|
goto loopend;
|
|
|
|
|
2015-05-20 03:04:16 +02:00
|
|
|
/* Ensure prefixes we're resolving over have stayed the same */
|
|
|
|
if (!prefix_same(&rnh->resolved_route, &prn->p))
|
|
|
|
{
|
|
|
|
if (rib)
|
|
|
|
UNSET_FLAG(rib->status, RIB_ENTRY_NEXTHOPS_CHANGED);
|
|
|
|
|
|
|
|
if (prn)
|
|
|
|
prefix_copy(&rnh->resolved_route, &prn->p);
|
|
|
|
else
|
|
|
|
memset(&rnh->resolved_route, 0, sizeof(struct prefix));
|
|
|
|
|
|
|
|
copy_state(rnh, rib, nrn);
|
|
|
|
state_changed = 1;
|
|
|
|
}
|
|
|
|
else if (compare_state(rib, rnh->state))
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
2015-05-20 02:47:22 +02:00
|
|
|
if (rib)
|
|
|
|
UNSET_FLAG(rib->status, RIB_ENTRY_NEXTHOPS_CHANGED);
|
|
|
|
|
|
|
|
copy_state(rnh, rib, nrn);
|
2015-05-20 02:47:20 +02:00
|
|
|
state_changed = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT && (state_changed || force))
|
|
|
|
{
|
|
|
|
prefix2str(&nrn->p, bufn, INET6_ADDRSTRLEN);
|
|
|
|
if (prn)
|
|
|
|
prefix2str(&prn->p, bufp, INET6_ADDRSTRLEN);
|
|
|
|
else
|
|
|
|
strcpy(bufp, "null");
|
2015-05-20 02:47:22 +02:00
|
|
|
|
|
|
|
zlog_debug("%s: State changed for %s/%s", __FUNCTION__, bufn, bufp);
|
|
|
|
|
2015-05-20 02:40:34 +02:00
|
|
|
}
|
2015-05-20 02:47:20 +02:00
|
|
|
|
|
|
|
/* Notify registered clients */
|
2015-05-20 02:47:22 +02:00
|
|
|
rib = rnh->state;
|
|
|
|
|
2015-05-20 02:47:20 +02:00
|
|
|
if (state_changed || force)
|
2015-05-20 02:47:22 +02:00
|
|
|
{
|
|
|
|
for (ALL_LIST_ELEMENTS_RO(rnh->client_list, node, client))
|
|
|
|
{
|
|
|
|
if (prn && rib)
|
|
|
|
{
|
|
|
|
at_least_one = zebra_evaluate_rnh_nexthops(family, rib, prn,
|
|
|
|
client->proto);
|
|
|
|
if (at_least_one)
|
|
|
|
rnh->filtered[client->proto] = 0;
|
|
|
|
else
|
|
|
|
rnh->filtered[client->proto] = 1;
|
|
|
|
}
|
|
|
|
else if (state_changed)
|
2015-05-20 02:47:20 +02:00
|
|
|
rnh->filtered[client->proto] = 0;
|
2015-05-20 02:47:22 +02:00
|
|
|
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT && (state_changed || force))
|
|
|
|
zlog_debug("%srnh %s resolved through route %s - sending "
|
|
|
|
"nexthop %s event to clients",
|
|
|
|
at_least_one ? "":"(filtered)", bufn, bufp,
|
|
|
|
rib ? "reachable" : "unreachable");
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
send_client(rnh, client, RNH_NEXTHOP_TYPE); /* Route-map passed */
|
2015-05-20 02:47:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Now evaluate static client */
|
|
|
|
if (prn && rib)
|
|
|
|
{
|
|
|
|
at_least_one = zebra_evaluate_rnh_nexthops(family, rib, prn,
|
|
|
|
ZEBRA_ROUTE_STATIC);
|
|
|
|
if (at_least_one)
|
|
|
|
rnh->filtered[ZEBRA_ROUTE_STATIC] = 0;
|
|
|
|
else
|
|
|
|
rnh->filtered[ZEBRA_ROUTE_STATIC] = 1;
|
|
|
|
}
|
|
|
|
else if (state_changed)
|
|
|
|
rnh->filtered[ZEBRA_ROUTE_STATIC] = 0;
|
|
|
|
|
|
|
|
for (ALL_LIST_ELEMENTS_RO(rnh->zebra_static_route_list, node,
|
|
|
|
static_rn))
|
|
|
|
{
|
|
|
|
RNODE_FOREACH_RIB(static_rn, srib)
|
|
|
|
{
|
2015-09-21 06:20:29 +02:00
|
|
|
if (srib->type == ZEBRA_ROUTE_STATIC)
|
|
|
|
break; /* currently works for only 1 static route. */
|
2015-05-20 02:47:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!srib)
|
|
|
|
{
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT)
|
|
|
|
{
|
|
|
|
prefix2str(&static_rn->p, bufs, INET6_ADDRSTRLEN);
|
|
|
|
zlog_debug("%s: Unable to find RIB for static route %s, skipping NH resolution",
|
|
|
|
__FUNCTION__, bufs);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Mark the appropriate static route's NH as filtered */
|
|
|
|
for (nexthop = srib->nexthop; nexthop; nexthop = nexthop->next)
|
|
|
|
{
|
|
|
|
switch (nexthop->type)
|
|
|
|
{
|
|
|
|
case NEXTHOP_TYPE_IPV4:
|
|
|
|
case NEXTHOP_TYPE_IPV4_IFINDEX:
|
|
|
|
/* Don't see a use case for *_IFNAME */
|
|
|
|
if (nexthop->gate.ipv4.s_addr == nrn->p.u.prefix4.s_addr)
|
|
|
|
{
|
|
|
|
if (at_least_one)
|
|
|
|
UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FILTERED);
|
|
|
|
else
|
|
|
|
SET_FLAG(nexthop->flags, NEXTHOP_FLAG_FILTERED);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case NEXTHOP_TYPE_IPV6:
|
|
|
|
case NEXTHOP_TYPE_IPV6_IFINDEX:
|
|
|
|
/* Don't see a use case for *_IFNAME */
|
|
|
|
if (memcmp(&nexthop->gate.ipv6,&nrn->p.u.prefix6, 16) == 0)
|
|
|
|
{
|
|
|
|
if (at_least_one)
|
|
|
|
UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FILTERED);
|
|
|
|
else
|
|
|
|
SET_FLAG(nexthop->flags, NEXTHOP_FLAG_FILTERED);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT && (state_changed || force))
|
|
|
|
zlog_debug("%srnh %s resolved through route %s - sending "
|
|
|
|
"nexthop %s event to zebra",
|
|
|
|
at_least_one ? "":"(filtered)", bufn, bufp,
|
|
|
|
rib ? "reachable" : "unreachable");
|
|
|
|
|
|
|
|
if (srib && (state_changed || force))
|
|
|
|
{
|
|
|
|
SET_FLAG(srib->flags, ZEBRA_FLAG_CHANGED);
|
|
|
|
SET_FLAG(srib->status, RIB_ENTRY_NEXTHOPS_CHANGED);
|
|
|
|
rib_queue_add(&zebrad, static_rn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-20 03:04:20 +02:00
|
|
|
loopend:
|
|
|
|
if (p)
|
|
|
|
{
|
|
|
|
route_unlock_node(nrn);
|
|
|
|
nrn = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* route_next takes care of unlocking nrn */
|
|
|
|
nrn = route_next(nrn);
|
|
|
|
}
|
2015-05-20 02:40:34 +02:00
|
|
|
}
|
2015-05-20 03:04:20 +02:00
|
|
|
|
|
|
|
if (prn)
|
|
|
|
route_unlock_node(prn);
|
|
|
|
|
2015-05-20 02:40:34 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2015-05-22 11:39:56 +02:00
|
|
|
zebra_dispatch_rnh_table (vrf_id_t vrfid, int family, struct zserv *client,
|
2015-05-20 03:04:20 +02:00
|
|
|
rnh_type_t type)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
struct route_table *ntable;
|
|
|
|
struct route_node *nrn;
|
|
|
|
struct rnh *rnh;
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
ntable = get_rnh_table(vrfid, family, type);
|
2015-05-20 02:40:34 +02:00
|
|
|
if (!ntable)
|
|
|
|
{
|
|
|
|
zlog_debug("dispatch_rnh_table: rnh table not found\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (nrn = route_top (ntable); nrn; nrn = route_next (nrn))
|
|
|
|
{
|
|
|
|
if (!nrn->info)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
rnh = nrn->info;
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT)
|
|
|
|
{
|
|
|
|
char bufn[INET6_ADDRSTRLEN];
|
|
|
|
prefix2str(&nrn->p, bufn, INET6_ADDRSTRLEN);
|
|
|
|
zlog_debug("rnh %s - sending nexthop %s event to client %s", bufn,
|
|
|
|
rnh->state ? "reachable" : "unreachable",
|
|
|
|
zebra_route_string(client->proto));
|
|
|
|
}
|
2015-05-20 03:04:20 +02:00
|
|
|
send_client(rnh, client, RNH_NEXTHOP_TYPE);
|
2015-05-20 02:40:34 +02:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-05-22 11:39:56 +02:00
|
|
|
zebra_print_rnh_table (vrf_id_t vrfid, int af, struct vty *vty, rnh_type_t type)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
struct route_table *table;
|
|
|
|
struct route_node *rn;
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
table = get_rnh_table(vrfid, af, type);
|
2015-05-20 02:40:34 +02:00
|
|
|
if (!table)
|
|
|
|
{
|
|
|
|
zlog_debug("print_rnhs: rnh table not found\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (rn = route_top(table); rn; rn = route_next(rn))
|
|
|
|
if (rn->info)
|
|
|
|
print_rnh(rn, vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2015-05-22 11:39:56 +02:00
|
|
|
zebra_cleanup_rnh_client (vrf_id_t vrfid, int family, struct zserv *client,
|
2015-05-20 03:04:20 +02:00
|
|
|
rnh_type_t type)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
struct route_table *ntable;
|
|
|
|
struct route_node *nrn;
|
|
|
|
struct rnh *rnh;
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
ntable = get_rnh_table(vrfid, family, type);
|
2015-05-20 02:40:34 +02:00
|
|
|
if (!ntable)
|
|
|
|
{
|
|
|
|
zlog_debug("cleanup_rnh_client: rnh table not found\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (nrn = route_top (ntable); nrn; nrn = route_next (nrn))
|
|
|
|
{
|
|
|
|
if (!nrn->info)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
rnh = nrn->info;
|
|
|
|
if (IS_ZEBRA_DEBUG_NHT)
|
|
|
|
{
|
|
|
|
char bufn[INET6_ADDRSTRLEN];
|
|
|
|
prefix2str(&nrn->p, bufn, INET6_ADDRSTRLEN);
|
|
|
|
zlog_debug("rnh %s - cleaning state for client %s", bufn,
|
|
|
|
zebra_route_string(client->proto));
|
|
|
|
}
|
2015-05-20 03:04:20 +02:00
|
|
|
zebra_remove_rnh_client(rnh, client, type);
|
2015-05-20 02:40:34 +02:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* free_state - free up the rib structure associated with the rnh.
|
|
|
|
*/
|
|
|
|
static void
|
2015-05-20 02:47:22 +02:00
|
|
|
free_state (struct rib *rib, struct route_node *rn)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
|
|
|
|
if (!rib)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* free RIB and nexthops */
|
2015-05-20 02:47:22 +02:00
|
|
|
nexthops_free(rib->nexthop, rn);
|
2015-05-20 02:40:34 +02:00
|
|
|
XFREE (MTYPE_RIB, rib);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2015-05-20 02:47:22 +02:00
|
|
|
copy_state (struct rnh *rnh, struct rib *rib, struct route_node *rn)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
struct rib *state;
|
|
|
|
struct nexthop *nh;
|
|
|
|
|
|
|
|
if (rnh->state)
|
|
|
|
{
|
2015-05-20 02:47:22 +02:00
|
|
|
free_state(rnh->state, rn);
|
2015-05-20 02:40:34 +02:00
|
|
|
rnh->state = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rib)
|
|
|
|
return;
|
|
|
|
|
|
|
|
state = XCALLOC (MTYPE_RIB, sizeof (struct rib));
|
|
|
|
state->type = rib->type;
|
|
|
|
state->metric = rib->metric;
|
|
|
|
|
|
|
|
for (nh = rib->nexthop; nh; nh = nh->next)
|
2015-05-20 02:47:22 +02:00
|
|
|
copy_nexthops(state, nh);
|
2015-05-20 02:40:34 +02:00
|
|
|
rnh->state = state;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
compare_state (struct rib *r1, struct rib *r2)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (!r1 && !r2)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ((!r1 && r2) || (r1 && !r2))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (r1->metric != r2->metric)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (r1->nexthop_num != r2->nexthop_num)
|
|
|
|
return 1;
|
|
|
|
|
2015-05-20 02:47:22 +02:00
|
|
|
if (CHECK_FLAG(r1->status, RIB_ENTRY_NEXTHOPS_CHANGED))
|
|
|
|
return 1;
|
2015-05-20 02:40:34 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2015-05-20 03:04:20 +02:00
|
|
|
send_client (struct rnh *rnh, struct zserv *client, rnh_type_t type)
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
struct stream *s;
|
|
|
|
struct rib *rib;
|
|
|
|
unsigned long nump;
|
|
|
|
u_char num;
|
|
|
|
struct nexthop *nexthop;
|
|
|
|
struct route_node *rn;
|
2015-05-20 03:04:20 +02:00
|
|
|
int cmd = (type == RNH_IMPORT_CHECK_TYPE)
|
|
|
|
? ZEBRA_IMPORT_CHECK_UPDATE : ZEBRA_NEXTHOP_UPDATE;
|
2015-05-20 02:40:34 +02:00
|
|
|
|
|
|
|
rn = rnh->node;
|
|
|
|
rib = rnh->state;
|
|
|
|
|
|
|
|
/* Get output stream. */
|
|
|
|
s = client->obuf;
|
|
|
|
stream_reset (s);
|
|
|
|
|
2015-05-20 03:04:20 +02:00
|
|
|
zserv_create_header (s, cmd);
|
2015-05-20 02:40:34 +02:00
|
|
|
stream_putw(s, rn->p.family);
|
2015-05-20 03:04:20 +02:00
|
|
|
switch (rn->p.family)
|
|
|
|
{
|
|
|
|
case AF_INET:
|
|
|
|
stream_putc(s, rn->p.prefixlen);
|
|
|
|
stream_put_in_addr(s, &rn->p.u.prefix4);
|
|
|
|
break;
|
|
|
|
case AF_INET6:
|
|
|
|
stream_putc(s, rn->p.prefixlen);
|
|
|
|
stream_put(s, &rn->p.u.prefix6, IPV6_MAX_BYTELEN);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
zlog_err("%s: Unknown family (%d) notification attempted\n",
|
|
|
|
__FUNCTION__, rn->p.family);
|
|
|
|
break;
|
|
|
|
}
|
2015-05-20 02:40:34 +02:00
|
|
|
if (rib)
|
|
|
|
{
|
|
|
|
stream_putl (s, rib->metric);
|
|
|
|
num = 0;
|
|
|
|
nump = stream_get_endp(s);
|
|
|
|
stream_putc (s, 0);
|
|
|
|
for (nexthop = rib->nexthop; nexthop; nexthop = nexthop->next)
|
2015-05-20 03:03:44 +02:00
|
|
|
if ((CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_FIB) ||
|
|
|
|
CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) &&
|
2015-05-20 02:47:20 +02:00
|
|
|
CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_ACTIVE))
|
2015-05-20 02:40:34 +02:00
|
|
|
{
|
|
|
|
stream_putc (s, nexthop->type);
|
|
|
|
switch (nexthop->type)
|
|
|
|
{
|
|
|
|
case ZEBRA_NEXTHOP_IPV4:
|
|
|
|
stream_put_in_addr (s, &nexthop->gate.ipv4);
|
|
|
|
break;
|
|
|
|
case ZEBRA_NEXTHOP_IFINDEX:
|
|
|
|
case ZEBRA_NEXTHOP_IFNAME:
|
|
|
|
stream_putl (s, nexthop->ifindex);
|
|
|
|
break;
|
|
|
|
case ZEBRA_NEXTHOP_IPV4_IFINDEX:
|
|
|
|
case ZEBRA_NEXTHOP_IPV4_IFNAME:
|
|
|
|
stream_put_in_addr (s, &nexthop->gate.ipv4);
|
|
|
|
stream_putl (s, nexthop->ifindex);
|
|
|
|
break;
|
|
|
|
#ifdef HAVE_IPV6
|
|
|
|
case ZEBRA_NEXTHOP_IPV6:
|
|
|
|
stream_put (s, &nexthop->gate.ipv6, 16);
|
|
|
|
break;
|
|
|
|
case ZEBRA_NEXTHOP_IPV6_IFINDEX:
|
|
|
|
case ZEBRA_NEXTHOP_IPV6_IFNAME:
|
|
|
|
stream_put (s, &nexthop->gate.ipv6, 16);
|
|
|
|
stream_putl (s, nexthop->ifindex);
|
|
|
|
break;
|
|
|
|
#endif /* HAVE_IPV6 */
|
|
|
|
default:
|
|
|
|
/* do nothing */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
num++;
|
|
|
|
}
|
|
|
|
stream_putc_at (s, nump, num);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
stream_putl (s, 0);
|
|
|
|
stream_putc (s, 0);
|
|
|
|
}
|
|
|
|
stream_putw_at (s, 0, stream_get_endp (s));
|
2015-05-20 02:47:22 +02:00
|
|
|
|
|
|
|
client->nh_last_upd_time = quagga_time(NULL);
|
2015-05-20 03:04:20 +02:00
|
|
|
client->last_write_cmd = cmd;
|
2015-05-20 02:40:34 +02:00
|
|
|
return zebra_server_send_message(client);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_nh (struct nexthop *nexthop, struct vty *vty)
|
|
|
|
{
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
|
|
|
|
switch (nexthop->type)
|
|
|
|
{
|
|
|
|
case NEXTHOP_TYPE_IPV4:
|
|
|
|
case NEXTHOP_TYPE_IPV4_IFINDEX:
|
|
|
|
vty_out (vty, " via %s", inet_ntoa (nexthop->gate.ipv4));
|
|
|
|
if (nexthop->ifindex)
|
|
|
|
vty_out (vty, ", %s", ifindex2ifname (nexthop->ifindex));
|
|
|
|
break;
|
|
|
|
case NEXTHOP_TYPE_IPV6:
|
|
|
|
case NEXTHOP_TYPE_IPV6_IFINDEX:
|
|
|
|
case NEXTHOP_TYPE_IPV6_IFNAME:
|
|
|
|
vty_out (vty, " %s",
|
|
|
|
inet_ntop (AF_INET6, &nexthop->gate.ipv6, buf, BUFSIZ));
|
|
|
|
if (nexthop->type == NEXTHOP_TYPE_IPV6_IFNAME)
|
|
|
|
vty_out (vty, ", %s", nexthop->ifname);
|
|
|
|
else if (nexthop->ifindex)
|
|
|
|
vty_out (vty, ", via %s", ifindex2ifname (nexthop->ifindex));
|
|
|
|
break;
|
|
|
|
case NEXTHOP_TYPE_IFINDEX:
|
|
|
|
vty_out (vty, " is directly connected, %s",
|
|
|
|
ifindex2ifname (nexthop->ifindex));
|
|
|
|
break;
|
|
|
|
case NEXTHOP_TYPE_IFNAME:
|
|
|
|
vty_out (vty, " is directly connected, %s", nexthop->ifname);
|
|
|
|
break;
|
|
|
|
case NEXTHOP_TYPE_BLACKHOLE:
|
|
|
|
vty_out (vty, " is directly connected, Null0");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
vty_out(vty, "%s", VTY_NEWLINE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_rnh (struct route_node *rn, struct vty *vty)
|
|
|
|
{
|
|
|
|
struct rnh *rnh;
|
|
|
|
struct nexthop *nexthop;
|
|
|
|
struct listnode *node;
|
|
|
|
struct zserv *client;
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
|
|
|
|
rnh = rn->info;
|
2015-05-20 03:45:53 +02:00
|
|
|
vty_out(vty, "%s%s%s", inet_ntop(rn->p.family, &rn->p.u.prefix, buf, BUFSIZ),
|
|
|
|
CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED) ? "(Connected)" : "",
|
2015-05-20 02:40:34 +02:00
|
|
|
VTY_NEWLINE);
|
|
|
|
if (rnh->state)
|
|
|
|
{
|
|
|
|
vty_out(vty, " resolved via %s%s",
|
|
|
|
zebra_route_string(rnh->state->type), VTY_NEWLINE);
|
|
|
|
for (nexthop = rnh->state->nexthop; nexthop; nexthop = nexthop->next)
|
|
|
|
print_nh(nexthop, vty);
|
|
|
|
}
|
|
|
|
else
|
2015-05-20 02:47:21 +02:00
|
|
|
vty_out(vty, " unresolved%s%s",
|
|
|
|
CHECK_FLAG(rnh->flags, ZEBRA_NHT_CONNECTED) ? "(Connected)" : "",
|
|
|
|
VTY_NEWLINE);
|
2015-05-20 02:40:34 +02:00
|
|
|
|
|
|
|
vty_out(vty, " Client list:");
|
|
|
|
for (ALL_LIST_ELEMENTS_RO(rnh->client_list, node, client))
|
2015-05-20 02:47:20 +02:00
|
|
|
vty_out(vty, " %s(fd %d)%s", zebra_route_string(client->proto),
|
|
|
|
client->sock, rnh->filtered[client->proto] ? "(filtered)" : "");
|
2015-05-20 02:47:22 +02:00
|
|
|
if (!list_isempty(rnh->zebra_static_route_list))
|
|
|
|
vty_out(vty, " zebra%s", rnh->filtered[ZEBRA_ROUTE_STATIC] ? "(filtered)" : "");
|
2015-05-20 02:40:34 +02:00
|
|
|
vty_out(vty, "%s", VTY_NEWLINE);
|
|
|
|
}
|