1134 lines
29 KiB
C
1134 lines
29 KiB
C
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
/**
|
|
* \file bgp/dump_isolario.c
|
|
*
|
|
* Isolario `bgpscanner`-like dump format.
|
|
*
|
|
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
|
|
* \author Lorenzo Cogotti
|
|
*/
|
|
|
|
#include "bgp/bgp_local.h"
|
|
#include "bgp/dump.h"
|
|
#include "sys/endian.h"
|
|
#include "sys/vt100.h"
|
|
#include "bufio.h"
|
|
#include "numlib.h"
|
|
#include "strlib.h"
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#define OPEN_MARKER '>'
|
|
#define ANNOUNCE_MARKER '+'
|
|
#define RIB_MARKER '='
|
|
#define STATE_CHANGE_MARKER '#'
|
|
#define WITHDRAW_MARKER '-'
|
|
#define NOTIFY_MARKER '!'
|
|
#define KEEPALIVE_MARKER '@'
|
|
#define ROUTE_REFRESH_MARKER '^'
|
|
#define CLOSE_MARKER '<'
|
|
#define UNKNOWN_MARKER '?'
|
|
|
|
#define SEP_CHAR '|'
|
|
#define SEP_CHAR_BAD '~'
|
|
|
|
#define CORRUPT_WARN "CORRUPTED"
|
|
|
|
// Aggregated information context for MRT dumps,
|
|
// useful for line trailing fields and for MP NEXT_HOP reconstruction
|
|
typedef struct Dumpfmtctx Dumpfmtctx;
|
|
struct Dumpfmtctx {
|
|
Boolean8 hasPeer;
|
|
Boolean8 hasTimestamp;
|
|
Boolean8 isAsn32bit;
|
|
Boolean8 isAddPath;
|
|
Boolean8 isRibv2;
|
|
Boolean8 withColors;
|
|
|
|
const Mrthdr *hdr; // NULL if not MRT
|
|
|
|
Uint32 peerAs; // host endian
|
|
Uint32 pathId; // host endian
|
|
Uint32 timestamp; // host endian
|
|
Uint32 microsecs; // host endian - non-zero if extended timestamp is available
|
|
Ipadr peerAddr;
|
|
};
|
|
|
|
/**
|
|
* Binary Tree Indexed by PATH ID:
|
|
*
|
|
* e.g.
|
|
* Root -> +---------------+
|
|
* | PATH ID: 0 |
|
|
* | Prefix List |
|
|
* | +--------------------------------------------+
|
|
* | | head --------------------------------+ |
|
|
* +--| | | Circular list, last
|
|
* / | nextPrefix V | element is the tree
|
|
* / | | P[N] | <-- ... <-- | P[1] | <-- | P[0] | | node itself.
|
|
* / +--------------------------------------------+
|
|
* / \
|
|
* / children[0] \ children[1]
|
|
* +--------------+ +--------------+
|
|
* | PATH ID: 1 | | Path ID: 2 |
|
|
* | Prefix List | | Prefix List |
|
|
* | [...] | | [...] |
|
|
* +--------------+ +--------------+
|
|
*/
|
|
typedef struct Prefixtree Prefixtree;
|
|
struct Prefixtree {
|
|
Uint32 pathId; // host endian
|
|
Afi afi;
|
|
RawPrefix *pfx;
|
|
|
|
union {
|
|
Prefixtree *nextPrefix; // for prefix list element
|
|
Prefixtree *head; // for tree node
|
|
};
|
|
|
|
Prefixtree *children[2];
|
|
};
|
|
|
|
#define MAXSEPS 10
|
|
|
|
// Empty separators buffer for state-change/unknown BGP4MP subtypes
|
|
static const char SEPS_BUF[MAXSEPS] = {
|
|
SEP_CHAR, SEP_CHAR, SEP_CHAR, SEP_CHAR,
|
|
SEP_CHAR, SEP_CHAR, SEP_CHAR, SEP_CHAR,
|
|
SEP_CHAR, SEP_CHAR
|
|
};
|
|
|
|
static const char *Bgp_CapabilityToString(BgpCapCode code)
|
|
{
|
|
switch (code) {
|
|
case BGP_CAP_RESERVED: return "RESERVED";
|
|
case BGP_CAP_MULTIPROTOCOL: return "MULTIPROTOCOL";
|
|
case BGP_CAP_ROUTE_REFRESH: return "ROUTE_REFRESH";
|
|
case BGP_CAP_COOPERATIVE_ROUTE_FILTERING: return "COOPERATIVE_ROUTE_FILTERING";
|
|
case BGP_CAP_MULTIPLE_ROUTES_DEST: return "MULTIPLE_ROUTES_DEST";
|
|
case BGP_CAP_EXTENDED_NEXT_HOP: return "EXTENDED_NEXT_HOP";
|
|
case BGP_CAP_EXTENDED_MESSAGE: return "EXTENDED_MESSAGE";
|
|
case BGP_CAP_BGPSEC: return "BGPSEC";
|
|
case BGP_CAP_MULTIPLE_LABELS: return "MULTIPLE_LABELS";
|
|
case BGP_CAP_BGP_ROLE: return "BGP_ROLE";
|
|
case BGP_CAP_GRACEFUL_RESTART: return "GRACEFUL_RESTART";
|
|
case BGP_CAP_ASN32BIT: return "ASN32BIT";
|
|
case BGP_CAP_DYNAMIC_CISCO: return "DYNAMIC_CISCO";
|
|
case BGP_CAP_DYNAMIC: return "DYNAMIC";
|
|
case BGP_CAP_MULTISESSION: return "MULTISESSION";
|
|
case BGP_CAP_ADD_PATH: return "ADD_PATH";
|
|
case BGP_CAP_ENHANCED_ROUTE_REFRESH: return "ENHANCED_ROUTE_REFRESH";
|
|
case BGP_CAP_LONG_LIVED_GRACEFUL_RESTART: return "LONG_LIVED_GRACEFUL_RESTART";
|
|
case BGP_CAP_CP_ORF: return "CP_ORF";
|
|
case BGP_CAP_FQDN: return "FQDN";
|
|
|
|
case BGP_CAP_ROUTE_REFRESH_CISCO: return "ROUTE_REFRESH_CISCO";
|
|
case BGP_CAP_ORF_CISCO: return "ORF_CISCO";
|
|
case BGP_CAP_MULTISESSION_CISCO: return "MULTISESSION_CISCO";
|
|
default: return NULL;
|
|
}
|
|
}
|
|
|
|
static const char *Bgp_KnownCommunityToString(BgpCommCode code)
|
|
{
|
|
switch (code) {
|
|
case BGP_COMMUNITY_PLANNED_SHUT: return "PLANNED_SHUT";
|
|
case BGP_COMMUNITY_ACCEPT_OWN: return "ACCEPT_OWN";
|
|
case BGP_COMMUNITY_ROUTE_FILTER_TRANSLATED_V4: return "ROUTE_FILTER_TRANSLATED_V4";
|
|
case BGP_COMMUNITY_ROUTE_FILTER_V4: return "ROUTE_FILTER_V4";
|
|
case BGP_COMMUNITY_ROUTE_FILTER_TRANSLATED_V6: return "ROUTE_FILTER_TRANSLATED_V6";
|
|
case BGP_COMMUNITY_ROUTE_FILTER_V6: return "ROUTE_FILTER_V6";
|
|
case BGP_COMMUNITY_LLGR_STALE: return "LLGR_STALE";
|
|
case BGP_COMMUNITY_NO_LLGR: return "NO_LLGR";
|
|
case BGP_COMMUNITY_ACCEPT_OWN_NEXTHOP: return "ACCEPT_OWN_NEXTHOP";
|
|
case BGP_COMMUNITY_STANDBY_PE: return "STANDBY_PE";
|
|
case BGP_COMMUNITY_BLACKHOLE: return "BLACKHOLE";
|
|
case BGP_COMMUNITY_NO_EXPORT: return "NO_EXPORT";
|
|
case BGP_COMMUNITY_NO_ADVERTISE: return "NO_ADVERTISE";
|
|
case BGP_COMMUNITY_NO_EXPORT_SUBCONFED: return "NO_EXPORT_SUBCONFED";
|
|
case BGP_COMMUNITY_NO_PEER: return "NO_PEER";
|
|
default: return NULL;
|
|
}
|
|
}
|
|
|
|
static void NormalizeExtendedTimestamp(Dumpfmtctx *ctx)
|
|
{
|
|
ctx->timestamp += ctx->microsecs / 1000000;
|
|
ctx->microsecs %= 1000000;
|
|
}
|
|
|
|
static void DumpUnknown(Stmbuf *sb, BgpType type)
|
|
{
|
|
Bufio_Putc(sb, UNKNOWN_MARKER);
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
Bufio_Putx(sb, type);
|
|
Bufio_Putsn(sb, SEPS_BUF, 8);
|
|
}
|
|
|
|
static Sint32 DumpCaps(Stmbuf *sb, Bgpcapiter *caps, const Dumpfmtctx *ctx)
|
|
{
|
|
const char *s;
|
|
Bgpcap *cap;
|
|
char buf[16];
|
|
|
|
USED(ctx->withColors); // TODO
|
|
|
|
Sint32 ncaps = 0;
|
|
while ((cap = Bgp_NextCap(caps)) != NULL) {
|
|
s = Bgp_CapabilityToString(cap->code);
|
|
if (!s) {
|
|
Utoa(cap->code, buf);
|
|
s = buf;
|
|
}
|
|
|
|
if (ncaps > 0)
|
|
Bufio_Putc(sb, ' ');
|
|
|
|
Bufio_Puts(sb, s);
|
|
ncaps++;
|
|
}
|
|
|
|
return ncaps;
|
|
}
|
|
|
|
static Judgement DumpAttributes(Stmbuf *sb,
|
|
const Bgpattrseg *tpa,
|
|
Bgpattrtab table,
|
|
const Dumpfmtctx *ctx)
|
|
{
|
|
Aspathiter ases;
|
|
Nexthopiter nh;
|
|
Asn asn;
|
|
Ipadr *addr;
|
|
char buf[128];
|
|
|
|
// AS_PATH
|
|
if (Bgp_StartRealAsPath(&ases, tpa, ctx->isAsn32bit, table) != OK)
|
|
return NG;
|
|
|
|
static const Asseg emptySeqSeg = { AS_SEQUENCE, 0 };
|
|
|
|
Boolean firstAsn = TRUE;
|
|
const Asseg *lastSeg = &emptySeqSeg;
|
|
while ((asn = Bgp_NextAsPath(&ases)) != -1) {
|
|
if (lastSeg != BGP_CURASSEG(&ases)) {
|
|
// Segment changed, we might need to close a SET
|
|
if (lastSeg->type == AS_SET) {
|
|
// Close pending AS_SET
|
|
Bufio_Putc(sb, '}');
|
|
firstAsn = FALSE; // an empty set still counts as an AS
|
|
}
|
|
|
|
// Update last segment type
|
|
lastSeg = BGP_CURASSEG(&ases);
|
|
if (lastSeg->type == AS_SET) {
|
|
// Open a new AS_SET
|
|
if (!firstAsn)
|
|
Bufio_Putc(sb, ' ');
|
|
|
|
Bufio_Putc(sb, '{');
|
|
firstAsn = TRUE;
|
|
}
|
|
}
|
|
|
|
// Separate consecutive ASN
|
|
if (!firstAsn)
|
|
Bufio_Putc(sb, (lastSeg->type == AS_SET) ? ',' : ' ');
|
|
|
|
// Write current ASN
|
|
Bufio_Putu(sb, beswap32(ASN(asn)));
|
|
firstAsn = FALSE;
|
|
}
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG;
|
|
|
|
// Close last AS_SET, if necessary
|
|
if (lastSeg->type == AS_SET)
|
|
Bufio_Putc(sb, '}');
|
|
|
|
// NEXT_HOP
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
if (ctx->isRibv2)
|
|
Bgp_StartAllRibv2NextHops(&nh, ctx->hdr, tpa, table);
|
|
else
|
|
Bgp_StartAllNextHops(&nh, tpa, table);
|
|
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG;
|
|
|
|
Boolean firstNextHop = TRUE;
|
|
while ((addr = Bgp_NextNextHop(&nh)) != NULL) {
|
|
if (!firstNextHop)
|
|
Bufio_Putc(sb, ' ');
|
|
|
|
char *ep = Ip_AdrToString(addr, buf);
|
|
Bufio_Putsn(sb, buf, ep - buf);
|
|
}
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG;
|
|
|
|
// ORIGIN
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
Bgpattr *origin = Bgp_GetUpdateAttribute(tpa, BGP_ATTR_ORIGIN, table);
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG;
|
|
|
|
if (origin) {
|
|
if (!BGP_CHKORIGINSIZ(origin))
|
|
return Bgp_SetErrStat(BGPETRUNCATTR); // TODO BGPEBADORIGIN
|
|
|
|
switch (BGP_ORIGIN(origin)) {
|
|
case BGP_ORIGIN_IGP: Bufio_Putc(sb, 'i'); break;
|
|
case BGP_ORIGIN_EGP: Bufio_Putc(sb, 'e'); break;
|
|
case BGP_ORIGIN_INCOMPLETE: Bufio_Putc(sb, '?'); break;
|
|
|
|
default:
|
|
return Bgp_SetErrStat(BGPETRUNCATTR); // TODO BGPEBADORIGIN
|
|
}
|
|
}
|
|
|
|
// ATOMIC_AGGREGATE
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
Bgpattr *atomicAggr = Bgp_GetUpdateAttribute(tpa, BGP_ATTR_ATOMIC_AGGREGATE, table);
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG;
|
|
|
|
if (atomicAggr) {
|
|
if (!BGP_CHKATOMICAGGRSIZ(atomicAggr))
|
|
return Bgp_SetErrStat(BGPETRUNCATTR); // TODO BGPEBADATOMICAGGR
|
|
|
|
Bufio_Puts(sb, "AT");
|
|
}
|
|
|
|
// AGGREGATOR
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
Boolean isAggr32bit = ctx->isAsn32bit;
|
|
Bgpattr *aggr = Bgp_GetRealAggregator(tpa, &isAggr32bit, table);
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG;
|
|
|
|
if (aggr) {
|
|
if (!BGP_CHKAGGRSIZ(aggr, isAggr32bit))
|
|
return Bgp_SetErrStat(BGPETRUNCATTR); // TODO BGPEBADAGGR
|
|
|
|
const Bgpaggr *aggrp = (const Bgpaggr *) BGP_ATTRPTR(aggr);
|
|
if (isAggr32bit) {
|
|
char *ep = Ipv4_AdrToString(&aggrp->a32.addr, buf);
|
|
|
|
Bufio_Putu(sb, beswap32(aggrp->a32.asn));
|
|
Bufio_Putc(sb, ' ');
|
|
Bufio_Putsn(sb, buf, ep - buf);
|
|
} else {
|
|
char *ep = Ipv4_AdrToString(&aggrp->a16.addr, buf);
|
|
|
|
Bufio_Putu(sb, beswap16(aggrp->a16.asn));
|
|
Bufio_Putc(sb, ' ');
|
|
Bufio_Putsn(sb, buf, ep - buf);
|
|
}
|
|
}
|
|
|
|
// Communities
|
|
// => COMMUNITY
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
Bgpcommiter communities;
|
|
Bgpattr *community = Bgp_GetUpdateAttribute(tpa, BGP_ATTR_COMMUNITY, table);
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG;
|
|
|
|
unsigned ncommunities = 0;
|
|
if (community) {
|
|
Bgpcomm *comm;
|
|
|
|
if (Bgp_StartCommunity(&communities, community) != OK)
|
|
return NG;
|
|
|
|
while ((comm = Bgp_NextCommunity(&communities)) != NULL) {
|
|
if (ncommunities > 0)
|
|
Bufio_Putc(sb, ' ');
|
|
|
|
const char *knownString = Bgp_KnownCommunityToString(comm->code);
|
|
if (knownString)
|
|
Bufio_Puts(sb, knownString);
|
|
|
|
else {
|
|
char *ep = Utoa(beswap16(comm->hi), buf);
|
|
*ep++ = ':';
|
|
ep = Utoa(beswap16(comm->lo), ep);
|
|
Bufio_Putsn(sb, buf, ep - buf);
|
|
}
|
|
ncommunities++;
|
|
}
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG; // iteration failed
|
|
}
|
|
|
|
// => TODO EXTENDED_COMMUNITY
|
|
|
|
// => LARGE_COMMUNITY
|
|
Bgpattr *largeCommunity = Bgp_GetUpdateAttribute(tpa, BGP_ATTR_LARGE_COMMUNITY, table);
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG;
|
|
|
|
if (largeCommunity) {
|
|
Bgplgcomm *comm;
|
|
|
|
if (Bgp_StartCommunity(&communities, largeCommunity) != OK)
|
|
return NG;
|
|
|
|
while ((comm = Bgp_NextLargeCommunity(&communities)) != NULL) {
|
|
if (ncommunities > 0)
|
|
Bufio_Putc(sb, ' ');
|
|
|
|
char *ep = Utoa(beswap32(comm->global), buf);
|
|
*ep++ = ':';
|
|
ep = Utoa(beswap32(comm->local1), ep);
|
|
*ep++ = ':';
|
|
ep = Utoa(beswap32(comm->local2), ep);
|
|
|
|
Bufio_Putsn(sb, buf, ep - buf);
|
|
ncommunities++;
|
|
}
|
|
if (Bgp_GetErrStat(NULL))
|
|
return NG;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static void DumpMrtInfoTrailer(Stmbuf *sb, const Dumpfmtctx *ctx)
|
|
{
|
|
char buf[128];
|
|
|
|
// Peer address, ASN
|
|
if (ctx->hasPeer) {
|
|
char *ep = Ip_AdrToString(&ctx->peerAddr, buf);
|
|
|
|
Bufio_Putsn(sb, buf, ep - buf);
|
|
Bufio_Putc(sb, ' ');
|
|
Bufio_Putu(sb, ctx->peerAs);
|
|
if (ctx->isAddPath) {
|
|
Bufio_Putc(sb, ' ');
|
|
Bufio_Putu(sb, ctx->pathId);
|
|
}
|
|
}
|
|
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
// MRT timestamp + microseconds if extended
|
|
if (ctx->hasTimestamp) {
|
|
Bufio_Putu(sb, ctx->timestamp);
|
|
if (ctx->microsecs > 0) {
|
|
Utoa(ctx->microsecs, buf);
|
|
Df_strpadl(buf, '0', 6);
|
|
|
|
Bufio_Putc(sb, '.');
|
|
Bufio_Puts(sb, buf);
|
|
}
|
|
}
|
|
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
// ASN32BIT flag
|
|
Bufio_Putc(sb, ctx->isAsn32bit ? '1' : '0');
|
|
Bufio_Puts(sb, EOLN);
|
|
}
|
|
|
|
static void WarnCorrupted(Stmbuf *sb, const Dumpfmtctx *ctx)
|
|
{
|
|
Bufio_Putc(sb, SEP_CHAR_BAD);
|
|
if (ctx->withColors)
|
|
Bufio_Puts(sb, VTSGR(VTINV) CORRUPT_WARN VTSGR(VTNOINV));
|
|
else
|
|
Bufio_Puts(sb, CORRUPT_WARN);
|
|
|
|
Bufio_Putc(sb, SEP_CHAR_BAD);
|
|
}
|
|
|
|
static Sint32 DumpRoutesFast(char marker,
|
|
Stmbuf *sb,
|
|
Bgpmpiter *prefixes,
|
|
const Bgpattrseg *tpa,
|
|
Bgpattrtab table,
|
|
const Dumpfmtctx *ctx)
|
|
{
|
|
Prefix *pfx;
|
|
char buf[PFX_STRLEN + 1];
|
|
|
|
assert(!ctx->isAddPath);
|
|
|
|
Sint32 nprefixes = 0;
|
|
while ((pfx = Bgp_NextMpPrefix(prefixes)) != NULL) {
|
|
char *ep = Bgp_PrefixToString(pfx->afi, PLAINPFX(pfx), buf);
|
|
if (nprefixes == 0) {
|
|
Bufio_Putc(sb, marker);
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
} else
|
|
Bufio_Putc(sb, ' ');
|
|
|
|
Bufio_Putsn(sb, buf, ep - buf);
|
|
nprefixes++;
|
|
}
|
|
if (Bgp_GetErrStat(NULL))
|
|
return -1;
|
|
if (nprefixes == 0)
|
|
return 0; // drop line, nothing to report
|
|
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
if (DumpAttributes(sb, tpa, table, ctx) != OK)
|
|
return -1;
|
|
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
DumpMrtInfoTrailer(sb, ctx);
|
|
return nprefixes;
|
|
}
|
|
|
|
// Add node 'n' to the prefix tree rooted at '*pr'.
|
|
static unsigned InsertRoute(Prefixtree **pr, Prefixtree *n)
|
|
{
|
|
unsigned depth = 0;
|
|
|
|
Prefixtree *i;
|
|
while ((i = *pr) && n->pathId != i->pathId) {
|
|
int idx = (n->pathId > i->pathId);
|
|
|
|
pr = &i->children[idx];
|
|
depth++;
|
|
}
|
|
if (i) {
|
|
// Collision, PATH ID already seen,
|
|
// copy childen pointers over
|
|
n->nextPrefix = i->head;
|
|
i->head = n;
|
|
} else {
|
|
// First prefix with this PATH ID, becomes list head
|
|
n->head = n;
|
|
n->children[0] = n->children[1] = NULL;
|
|
|
|
*pr = n;
|
|
depth++;
|
|
}
|
|
return depth;
|
|
}
|
|
|
|
static Sint32 DumpRouteWithPathId(char marker,
|
|
Stmbuf *sb,
|
|
const Prefixtree *n,
|
|
const Bgpattrseg *tpa,
|
|
Bgpattrtab table,
|
|
Dumpfmtctx *ctx)
|
|
{
|
|
char buf[PFX_STRLEN + 1];
|
|
|
|
assert(ctx->isAddPath);
|
|
ctx->pathId = n->pathId;
|
|
|
|
Bufio_Putc(sb, marker);
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
Sint32 nprefixes = 0;
|
|
|
|
Prefixtree *i = n->head;
|
|
do {
|
|
char *ep = Bgp_PrefixToString(i->afi, i->pfx, buf);
|
|
if (nprefixes > 0)
|
|
Bufio_Putc(sb, ' ');
|
|
|
|
Bufio_Putsn(sb, buf, ep - buf);
|
|
nprefixes++;
|
|
|
|
i = i->nextPrefix;
|
|
} while (i != n);
|
|
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
|
|
if (DumpAttributes(sb, tpa, table, ctx) != OK)
|
|
return -1;
|
|
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
DumpMrtInfoTrailer(sb, ctx);
|
|
return nprefixes;
|
|
}
|
|
|
|
static Sint32 DumpRoutes(char marker,
|
|
Stmbuf *sb,
|
|
Bgpmpiter *prefixes,
|
|
const Bgpattrseg *tpa,
|
|
Bgpattrtab table,
|
|
Dumpfmtctx *ctx)
|
|
{
|
|
Prefix *pfx;
|
|
Prefixtree *n;
|
|
|
|
// Fast case when PATH ID is absent
|
|
if (!ctx->isAddPath)
|
|
return DumpRoutesFast(marker, sb, prefixes, tpa, table, ctx);
|
|
|
|
// When PATH ID is available, we build a net tree grouped by PATH ID
|
|
// directly on stack
|
|
Sint32 nprefixes = 0;
|
|
unsigned maxDepth = 0;
|
|
Prefixtree *root = NULL;
|
|
while ((pfx = Bgp_NextMpPrefix(prefixes)) != NULL) {
|
|
n = (Prefixtree *) alloca(sizeof(*n));
|
|
|
|
n->pathId = beswap32(pfx->pathId);
|
|
n->afi = pfx->afi;
|
|
n->pfx = BGP_CURMPRAWPFX(prefixes);
|
|
|
|
unsigned depth = InsertRoute(&root, n);
|
|
if (depth > maxDepth)
|
|
maxDepth = depth;
|
|
}
|
|
if (Bgp_GetErrStat(NULL))
|
|
return -1;
|
|
|
|
// Print every route inside tree
|
|
Prefixtree **stackb = (Prefixtree **) alloca(maxDepth * sizeof(*stackb));
|
|
unsigned si = 0;
|
|
|
|
n = root;
|
|
while (TRUE) {
|
|
while (n) {
|
|
stackb[si++] = n;
|
|
|
|
n = n->children[0];
|
|
}
|
|
if (si == 0)
|
|
break;
|
|
|
|
n = stackb[--si];
|
|
|
|
// Generate new line
|
|
nprefixes += DumpRouteWithPathId(marker, sb, n, tpa, table, ctx);
|
|
n = n->children[1];
|
|
}
|
|
|
|
return nprefixes;
|
|
}
|
|
|
|
static Judgement DumpBgp(Stmbuf *sb,
|
|
BgpType type,
|
|
const void *data,
|
|
size_t nbytes,
|
|
Bgpattrtab table,
|
|
Dumpfmtctx *ctx)
|
|
{
|
|
Bgpcapiter caps;
|
|
Bgpmpiter prefixes;
|
|
Bgpwithdrawnseg *withdrawn;
|
|
Bgpparmseg *parms;
|
|
Bgpattrseg *tpa;
|
|
Bgpattr *mpAttr;
|
|
void *nlri;
|
|
size_t nlriSize;
|
|
|
|
switch (type) {
|
|
case BGP_OPEN:
|
|
Bufio_Putc(sb, OPEN_MARKER);
|
|
|
|
parms = Bgp_GetParmsFromMemory(data, nbytes);
|
|
if (!parms)
|
|
goto corrupted;
|
|
|
|
Bgp_StartCaps(&caps, parms);
|
|
Bufio_Putc(sb, SEP_CHAR);
|
|
DumpCaps(sb, &caps, ctx);
|
|
Bufio_Putsn(sb, SEPS_BUF, 7);
|
|
DumpMrtInfoTrailer(sb, ctx);
|
|
break;
|
|
|
|
case BGP_UPDATE:
|
|
withdrawn = Bgp_GetWithdrawnFromMemory(data, nbytes);
|
|
if (!withdrawn)
|
|
goto corrupted;
|
|
|
|
tpa = Bgp_GetAttributesFromMemory(data, nbytes);
|
|
if (!tpa)
|
|
goto corrupted;
|
|
|
|
mpAttr = Bgp_GetUpdateAttribute(tpa, BGP_ATTR_MP_UNREACH_NLRI, table);
|
|
if (Bgp_GetErrStat(NULL))
|
|
goto corrupted;
|
|
|
|
Bgp_InitMpWithdrawn(&prefixes, withdrawn, mpAttr, ctx->isAddPath);
|
|
if (DumpRoutes(WITHDRAW_MARKER, sb, &prefixes, tpa, table, ctx) == -1)
|
|
goto corrupted;
|
|
|
|
nlri = Bgp_GetNlriFromMemory(data, nbytes, &nlriSize);
|
|
if (!nlri)
|
|
goto corrupted;
|
|
|
|
mpAttr = Bgp_GetUpdateAttribute(tpa, BGP_ATTR_MP_REACH_NLRI, table);
|
|
if (Bgp_GetErrStat(NULL))
|
|
goto corrupted;
|
|
|
|
Bgp_InitMpNlri(&prefixes, nlri, nlriSize, mpAttr, ctx->isAddPath);
|
|
if (DumpRoutes(ANNOUNCE_MARKER, sb, &prefixes, tpa, table, ctx) == -1)
|
|
goto corrupted;
|
|
|
|
break;
|
|
|
|
case BGP_NOTIFICATION:
|
|
Bufio_Putc(sb, NOTIFY_MARKER);
|
|
Bufio_Putsn(sb, SEPS_BUF, 8);
|
|
// TODO dump message
|
|
DumpMrtInfoTrailer(sb, ctx);
|
|
break;
|
|
|
|
case BGP_KEEPALIVE:
|
|
Bufio_Putc(sb, KEEPALIVE_MARKER);
|
|
Bufio_Putsn(sb, SEPS_BUF, 8);
|
|
DumpMrtInfoTrailer(sb, ctx);
|
|
break;
|
|
|
|
case BGP_ROUTE_REFRESH:
|
|
Bufio_Putc(sb, ROUTE_REFRESH_MARKER);
|
|
Bufio_Putsn(sb, SEPS_BUF, 8);
|
|
DumpMrtInfoTrailer(sb, ctx);
|
|
break;
|
|
|
|
case BGP_CLOSE:
|
|
Bufio_Putc(sb, CLOSE_MARKER);
|
|
Bufio_Putsn(sb, SEPS_BUF, 8);
|
|
DumpMrtInfoTrailer(sb, ctx);
|
|
break;
|
|
|
|
default:
|
|
DumpUnknown(sb, type);
|
|
DumpMrtInfoTrailer(sb, ctx);
|
|
break;
|
|
}
|
|
|
|
return OK;
|
|
|
|
corrupted:
|
|
WarnCorrupted(sb, ctx);
|
|
return NG;
|
|
}
|
|
|
|
static Sint64 Isolario_DumpMsg(const Bgphdr *hdr,
|
|
unsigned flags,
|
|
void *streamp,
|
|
const StmOps *ops,
|
|
Bgpattrtab table)
|
|
{
|
|
Stmbuf sb;
|
|
|
|
size_t nbytes = beswap16(hdr->len);
|
|
assert(nbytes >= BGP_HDRSIZ);
|
|
nbytes -= BGP_HDRSIZ;
|
|
|
|
Dumpfmtctx ctx;
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.isAsn32bit = BGP_ISASN32BIT(flags);
|
|
ctx.isAddPath = BGP_ISADDPATH(flags);
|
|
|
|
Bufio_Init(&sb, streamp, ops);
|
|
DumpBgp(&sb, hdr->type, hdr + 1, nbytes, table, &ctx);
|
|
return Bufio_Flush(&sb);
|
|
}
|
|
|
|
static Sint64 Isolario_DumpMsgWc(const Bgphdr *hdr,
|
|
unsigned flags,
|
|
void *streamp,
|
|
const StmOps *ops,
|
|
Bgpattrtab table)
|
|
{
|
|
Stmbuf sb;
|
|
|
|
size_t nbytes = beswap16(hdr->len);
|
|
assert(nbytes >= BGP_HDRSIZ);
|
|
nbytes -= BGP_HDRSIZ;
|
|
|
|
Dumpfmtctx ctx;
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.isAsn32bit = BGP_ISASN32BIT(flags);
|
|
ctx.isAddPath = BGP_ISADDPATH(flags);
|
|
ctx.withColors = TRUE;
|
|
|
|
Bufio_Init(&sb, streamp, ops);
|
|
DumpBgp(&sb, hdr->type, hdr + 1, nbytes, table, &ctx);
|
|
|
|
return Bufio_Flush(&sb);
|
|
}
|
|
|
|
static Sint64 Isolario_DumpRibv2(const Mrthdr *hdr,
|
|
const Mrtpeerentv2 *peer,
|
|
const Mrtribentv2 *rib,
|
|
void *streamp,
|
|
const StmOps *ops,
|
|
Bgpattrtab table)
|
|
{
|
|
assert(hdr->type == MRT_TABLE_DUMPV2);
|
|
assert(TABLE_DUMPV2_ISRIB(hdr->subtype));
|
|
|
|
Stmbuf sb;
|
|
Dumpfmtctx ctx;
|
|
char buf[APPFX_STRLEN + 1];
|
|
char *ep;
|
|
|
|
Bufio_Init(&sb, streamp, ops);
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.hasPeer = TRUE;
|
|
ctx.hasTimestamp = TRUE;
|
|
ctx.isAsn32bit = TRUE; // always the case for TABLE_DUMPV2
|
|
ctx.isRibv2 = TRUE;
|
|
ctx.hdr = hdr;
|
|
ctx.timestamp = beswap32(rib->originatedTime);
|
|
|
|
switch (peer->type & (MRT_PEER_ASN32BIT|MRT_PEER_IP6)) {
|
|
case MRT_PEER_ASN32BIT | MRT_PEER_IP6:
|
|
ctx.peerAs = beswap32(peer->a32v6.asn);
|
|
ctx.peerAddr.family = IP6;
|
|
ctx.peerAddr.v6 = peer->a32v6.addr;
|
|
break;
|
|
case MRT_PEER_IP6:
|
|
ctx.peerAs = beswap16(peer->a16v6.asn);
|
|
ctx.peerAddr.family = IP6;
|
|
ctx.peerAddr.v6 = peer->a16v6.addr;
|
|
break;
|
|
case MRT_PEER_ASN32BIT:
|
|
ctx.peerAs = beswap32(peer->a32v4.asn);
|
|
ctx.peerAddr.family = IP4;
|
|
ctx.peerAddr.v4 = peer->a16v4.addr;
|
|
break;
|
|
default:
|
|
ctx.peerAs = beswap16(peer->a16v4.asn);
|
|
ctx.peerAddr.family = IP4;
|
|
ctx.peerAddr.v4 = peer->a16v4.addr;
|
|
break;
|
|
}
|
|
|
|
const Mrtribhdrv2 *ribhdr = RIBV2_HDR(hdr);
|
|
const Bgpattrseg *tpa = RIBV2_GETATTRIBS(hdr->subtype, rib);
|
|
|
|
switch (hdr->subtype) {
|
|
case TABLE_DUMPV2_RIB_IPV4_UNICAST_ADDPATH:
|
|
case TABLE_DUMPV2_RIB_IPV4_MULTICAST_ADDPATH:
|
|
ctx.isAddPath = TRUE;
|
|
ctx.pathId = beswap32(RIBV2_GETPATHID(rib));
|
|
/*FALLTHROUGH*/
|
|
case TABLE_DUMPV2_RIB_IPV4_UNICAST:
|
|
case TABLE_DUMPV2_RIB_IPV4_MULTICAST:
|
|
ep = Bgp_PrefixToString(AFI_IP, &ribhdr->nlri, buf);
|
|
break;
|
|
case TABLE_DUMPV2_RIB_IPV6_UNICAST_ADDPATH:
|
|
case TABLE_DUMPV2_RIB_IPV6_MULTICAST_ADDPATH:
|
|
ctx.isAddPath = TRUE;
|
|
ctx.pathId = beswap32(RIBV2_GETPATHID(rib));
|
|
/*FALLTHROUGH*/
|
|
case TABLE_DUMPV2_RIB_IPV6_UNICAST:
|
|
case TABLE_DUMPV2_RIB_IPV6_MULTICAST:
|
|
ep = Bgp_PrefixToString(AFI_IP6, &ribhdr->nlri, buf);
|
|
break;
|
|
case TABLE_DUMPV2_RIB_GENERIC_ADDPATH:
|
|
ctx.isAddPath = TRUE;
|
|
ctx.pathId = beswap32(RIBV2_GETPATHID(rib));
|
|
/*FALLTHROUGH*/
|
|
case TABLE_DUMPV2_RIB_GENERIC:
|
|
ep = Bgp_PrefixToString(ribhdr->gen.afi, &ribhdr->gen.nlri, buf);
|
|
break;
|
|
default:
|
|
UNREACHABLE;
|
|
break;
|
|
}
|
|
|
|
assert(ep != NULL);
|
|
|
|
Bufio_Putc(&sb, RIB_MARKER);
|
|
Bufio_Putc(&sb, SEP_CHAR);
|
|
|
|
Bufio_Putsn(&sb, buf, ep - buf);
|
|
Bufio_Putc(&sb, SEP_CHAR);
|
|
|
|
DumpAttributes(&sb, tpa, table, &ctx);
|
|
Bufio_Putc(&sb, SEP_CHAR);
|
|
|
|
DumpMrtInfoTrailer(&sb, &ctx);
|
|
return Bufio_Flush(&sb);
|
|
}
|
|
|
|
static Sint64 Isolario_DumpRib(const Mrthdr *hdr,
|
|
const Mrtribent *rib,
|
|
void *streamp,
|
|
const StmOps *ops,
|
|
Bgpattrtab table)
|
|
{
|
|
assert(hdr->type == MRT_TABLE_DUMP);
|
|
|
|
Stmbuf sb;
|
|
Dumpfmtctx ctx;
|
|
RawPrefix pfx;
|
|
char buf[APPFX_STRLEN + 1];
|
|
char *ep;
|
|
|
|
Bufio_Init(&sb, streamp, ops);
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.hasPeer = TRUE;
|
|
ctx.hasTimestamp = TRUE;
|
|
ctx.hdr = hdr;
|
|
|
|
const Bgpattrseg *tpa = RIB_GETATTRIBS(hdr->subtype, rib);
|
|
|
|
switch (hdr->subtype) {
|
|
case AFI_IP:
|
|
pfx.width = rib->v4.prefixLen;
|
|
pfx.v4 = rib->v4.prefix;
|
|
assert(pfx.width < IPV4_WIDTH);
|
|
|
|
ctx.timestamp = beswap32(rib->v4.originatedTime);
|
|
|
|
ctx.peerAddr.family = IP4;
|
|
ctx.peerAddr.v4 = rib->v4.peerAddr;
|
|
ctx.peerAs = beswap16(rib->v4.peerAs);
|
|
break;
|
|
case AFI_IP6:
|
|
pfx.width = rib->v6.prefixLen;
|
|
pfx.v6 = rib->v6.prefix;
|
|
assert(pfx.width < IPV6_WIDTH);
|
|
|
|
ctx.timestamp = beswap32(rib->v6.originatedTime);
|
|
|
|
ctx.peerAddr.family = IP6;
|
|
ctx.peerAddr.v6 = rib->v6.peerAddr;
|
|
ctx.peerAs = beswap16(rib->v6.peerAs);
|
|
break;
|
|
default:
|
|
UNREACHABLE;
|
|
break;
|
|
}
|
|
|
|
ep = Bgp_PrefixToString(hdr->subtype, &pfx, buf);
|
|
|
|
Bufio_Putc(&sb, RIB_MARKER);
|
|
Bufio_Putc(&sb, SEP_CHAR);
|
|
|
|
Bufio_Putsn(&sb, buf, ep - buf);
|
|
Bufio_Putc(&sb, SEP_CHAR);
|
|
|
|
DumpAttributes(&sb, tpa, table, &ctx);
|
|
Bufio_Putc(&sb, SEP_CHAR);
|
|
|
|
DumpMrtInfoTrailer(&sb, &ctx);
|
|
return Bufio_Flush(&sb);
|
|
}
|
|
|
|
static Sint64 Isolario_DumpBgp4mp(const Mrthdr *hdr,
|
|
void *streamp,
|
|
const StmOps *ops,
|
|
Bgpattrtab table)
|
|
{
|
|
assert(hdr->type == MRT_BGP4MP || hdr->type == MRT_BGP4MP_ET);
|
|
|
|
Stmbuf sb;
|
|
Dumpfmtctx ctx;
|
|
size_t offset; // offset to BGP4MP payload
|
|
size_t len;
|
|
|
|
const Bgp4mphdr *bgp4mp = BGP4MP_HDR(hdr);
|
|
|
|
Bufio_Init(&sb, streamp, ops);
|
|
|
|
len = beswap32(hdr->len);
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.hasPeer = TRUE;
|
|
ctx.hasTimestamp = TRUE;
|
|
ctx.isAsn32bit = BGP4MP_ISASN32BIT(hdr->subtype);
|
|
ctx.isAddPath = BGP4MP_ISADDPATH(hdr->subtype);
|
|
ctx.timestamp = beswap32(hdr->timestamp);
|
|
if (hdr->type == MRT_BGP4MP_ET) {
|
|
// Length must account for extended timestamp
|
|
if (len < 4) goto corrupt;
|
|
|
|
ctx.microsecs = beswap32(((const Mrthdrex *) hdr)->microsecs);
|
|
len -= 4;
|
|
|
|
NormalizeExtendedTimestamp(&ctx);
|
|
}
|
|
|
|
ctx.hdr = hdr;
|
|
|
|
if (ctx.isAsn32bit) {
|
|
if (len < sizeof(bgp4mp->a32)) goto corrupt;
|
|
|
|
ctx.peerAs = beswap32(bgp4mp->a32.peerAs);
|
|
if (bgp4mp->a32.afi == AFI_IP) {
|
|
if (len < sizeof(bgp4mp->a32v4)) goto corrupt;
|
|
|
|
ctx.peerAddr.family = IP4;
|
|
ctx.peerAddr.v4 = bgp4mp->a32v4.peerAddr;
|
|
|
|
offset = sizeof(bgp4mp->a32v4);
|
|
} else {
|
|
assert(bgp4mp->a32.afi == AFI_IP6);
|
|
|
|
if (len < sizeof(bgp4mp->a32v6)) goto corrupt;
|
|
|
|
ctx.peerAddr.family = IP6;
|
|
ctx.peerAddr.v6 = bgp4mp->a32v6.peerAddr;
|
|
|
|
offset = sizeof(bgp4mp->a32v6);
|
|
}
|
|
} else {
|
|
if (len < sizeof(bgp4mp->a16)) goto corrupt;
|
|
|
|
ctx.peerAs = beswap16(bgp4mp->a16.peerAs);
|
|
if (bgp4mp->a16.afi == AFI_IP) {
|
|
if (len < sizeof(bgp4mp->a16v4)) goto corrupt;
|
|
|
|
ctx.peerAddr.family = IP4;
|
|
ctx.peerAddr.v4 = bgp4mp->a16v4.peerAddr;
|
|
|
|
offset = sizeof(bgp4mp->a16v4);
|
|
} else {
|
|
assert(bgp4mp->a16.afi == AFI_IP6);
|
|
|
|
if (len < sizeof(bgp4mp->a16v6)) goto corrupt;
|
|
|
|
ctx.peerAddr.family = IP6;
|
|
ctx.peerAddr.v6 = bgp4mp->a16v6.peerAddr;
|
|
|
|
offset = sizeof(bgp4mp->a16v6);
|
|
}
|
|
}
|
|
|
|
assert(len >= offset);
|
|
|
|
// Write contents
|
|
if (BGP4MP_ISSTATECHANGE(hdr->subtype)) {
|
|
// Dump state change
|
|
BgpFsmState chng[2];
|
|
|
|
Bufio_Putc(&sb, STATE_CHANGE_MARKER);
|
|
Bufio_Putc(&sb, SEP_CHAR);
|
|
if (len < offset + sizeof(chng)) goto corrupt;
|
|
|
|
memcpy(chng, (Uint8 *) bgp4mp + offset, sizeof(chng));
|
|
|
|
Bufio_Putu(&sb, beswap16(chng[0]));
|
|
Bufio_Putc(&sb, '-');
|
|
Bufio_Putu(&sb, beswap16(chng[1]));
|
|
Bufio_Putsn(&sb, SEPS_BUF, 7);
|
|
DumpMrtInfoTrailer(&sb, &ctx);
|
|
|
|
} else if (BGP4MP_ISMESSAGE(hdr->subtype)) {
|
|
// Wraps BGP message, wrapped message includes header and marker
|
|
const Bgphdr *msg = (const Bgphdr *) ((Uint8 *) bgp4mp + offset);
|
|
size_t nbytes = len - offset;
|
|
|
|
nbytes = Bgp_CheckMsgHdr(msg, nbytes, /*allowExtendedSize=*/TRUE);
|
|
if (nbytes == 0) {
|
|
// Corrupted BGP4MP with invalid BGP data
|
|
Bufio_Putc(&sb, UNKNOWN_MARKER);
|
|
Bufio_Putc(&sb, SEP_CHAR);
|
|
goto corrupt;
|
|
}
|
|
|
|
DumpBgp(&sb, msg->type, msg + 1, nbytes - BGP_HDRSIZ, table, &ctx);
|
|
} else {
|
|
// Deprecated/Unknown type
|
|
Bufio_Putc(&sb, UNKNOWN_MARKER);
|
|
Bufio_Putsn(&sb, SEPS_BUF, 9);
|
|
DumpMrtInfoTrailer(&sb, &ctx);
|
|
}
|
|
|
|
return Bufio_Flush(&sb);
|
|
|
|
corrupt:
|
|
WarnCorrupted(&sb, &ctx);
|
|
DumpMrtInfoTrailer(&sb, &ctx);
|
|
return Bufio_Flush(&sb);
|
|
}
|
|
|
|
static Sint64 Isolario_DumpZebra(const Mrthdr *hdr,
|
|
void *streamp,
|
|
const StmOps *ops,
|
|
Bgpattrtab table)
|
|
{
|
|
assert(hdr->type == MRT_BGP);
|
|
|
|
Stmbuf sb;
|
|
Dumpfmtctx ctx;
|
|
|
|
const Zebrahdr *zebra = ZEBRA_HDR(hdr);
|
|
|
|
Bufio_Init(&sb, streamp, ops);
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
ctx.hasPeer = TRUE;
|
|
ctx.hasTimestamp = TRUE;
|
|
ctx.timestamp = beswap32(hdr->timestamp);
|
|
ctx.hdr = hdr;
|
|
ctx.peerAddr.family = IP4;
|
|
ctx.peerAddr.v4 = zebra->peerAddr;
|
|
|
|
if (hdr->subtype == ZEBRA_STATE_CHANGE) {
|
|
const Zebrastatchng *chng = (const Zebrastatchng *) zebra;
|
|
|
|
Bufio_Putc(&sb, STATE_CHANGE_MARKER);
|
|
Bufio_Putc(&sb, SEP_CHAR);
|
|
Bufio_Putu(&sb, beswap16(chng->oldState));
|
|
Bufio_Putc(&sb, '-');
|
|
Bufio_Putu(&sb, beswap16(chng->newState));
|
|
Bufio_Putsn(&sb, SEPS_BUF, 7);
|
|
DumpMrtInfoTrailer(&sb, &ctx);
|
|
|
|
} else if (ZEBRA_ISMESSAGE(hdr->subtype)) {
|
|
const Zebramsghdr *zebramsg = (const Zebramsghdr *) zebra;
|
|
size_t len = beswap32(hdr->len);
|
|
size_t offset = offsetof(Zebramsghdr, msg);
|
|
|
|
size_t nbytes = len - offset;
|
|
|
|
BgpType type;
|
|
switch (hdr->subtype) {
|
|
case ZEBRA_UPDATE: type = BGP_UPDATE; break;
|
|
case ZEBRA_OPEN: type = BGP_OPEN; break;
|
|
case ZEBRA_NOTIFY: type = BGP_NOTIFICATION; break;
|
|
case ZEBRA_KEEPALIVE: type = BGP_KEEPALIVE; break;
|
|
default:
|
|
UNREACHABLE;
|
|
return -1;
|
|
}
|
|
|
|
DumpBgp(&sb, type, zebramsg->msg, nbytes, table, &ctx);
|
|
|
|
} else {
|
|
// Unknown type
|
|
Bufio_Putc(&sb, UNKNOWN_MARKER);
|
|
Bufio_Putsn(&sb, SEPS_BUF, 9);
|
|
DumpMrtInfoTrailer(&sb, &ctx);
|
|
}
|
|
|
|
return Bufio_Flush(&sb);
|
|
}
|
|
|
|
static const BgpDumpfmt bgp_isolarioTable = {
|
|
Isolario_DumpMsg,
|
|
Isolario_DumpRibv2,
|
|
Isolario_DumpRib,
|
|
Isolario_DumpBgp4mp,
|
|
Isolario_DumpZebra
|
|
};
|
|
static const BgpDumpfmt bgp_isolarioTableWc = {
|
|
Isolario_DumpMsgWc,
|
|
Isolario_DumpRibv2,
|
|
Isolario_DumpRib,
|
|
Isolario_DumpBgp4mp,
|
|
Isolario_DumpZebra
|
|
};
|
|
|
|
const BgpDumpfmt *const Bgp_IsolarioFmt = &bgp_isolarioTable;
|
|
const BgpDumpfmt *const Bgp_IsolarioFmtWc = &bgp_isolarioTableWc;
|