diff --git a/tools/peerindex/peerindex.1.in b/tools/peerindex/peerindex.1.in new file mode 100644 index 0000000..bbbdedc --- /dev/null +++ b/tools/peerindex/peerindex.1.in @@ -0,0 +1,247 @@ +'\" et +.TH PEERINDEX 1 @VERSION@ PEERINDEX "User Commands" +.\" +.SH NAME +@UTILITY@ +\(em filter and print BGP data within MRT dumps +.SH SYNOPSIS +.LP +.nf +@UTILITY@ \fB[\fIOPTIONS\fB]\fR... \fB[\fIFILES\fB]\fR... +.fi + +.SH DESCRIPTION +The +.IR @UTILITY@ +utility reads each possibly compressed Multithreaded Routing Toolkit +(MRT) +Routing Information Base +(RIB) +file specified by +.IR FILES , +and extracts information related to PEER INDEX TABLE +records. +.IR @UTILITY@ +processes and formats such records according to the options specified by +.IR OPTIONS . +.IR @UTILITY@ +prints diagnostics to standard error, +it detects and tolerates data corruption as much as possible. +If data corruption cannot be tolerated, the entire MRT dump file is dropped, +.IR @UTILITY@ +will then move to the next file in +.IR FILES , +if any. +.P +Such events are always reported with reasonable diagnostic errors. +.P +See the +.IR EXAMPLES +section below for usage examples. + +.SH OPTIONS +The following options are supported: + +.IP "\fB\-r or \-\-only\-refs\fP" 10 +By default +.IR @UTILITY@ +writes all PEER INDEX TABLE entries, this option causes +.IR @UTILITY@ +to only print peers that are referenced by RIB entries. + +.IP "\fB\-h or \-\-help\fP" 10 +Prints a short help message, summarizing +.IR @UTILITY@ +functionality. + +.IP "\fB\-?\fP" 10 +Equivalent to +.BR \-h . + +.SH "LINE ORIENTED OUTPUT" +.IR @UTILITY@ +splits each PEER INDEX TABLE record into multiple lines, every line represents a peer. +Note that +.IR OPTIONS +may cause some peers inside PEER INDEX TABLES to be discarded (See the +.IR OPTIONS +section for details). + + +Each peer is formatted as the following `|' separated fields: +.RS 4 +.sp +.RS 4 +.nf + +PEER ENTRY|ASN32BIT +.fi +.P +.RE +.P +Fields have the following meaning: + +.IP "\fBPEER ENTRY\fP" 4 +The peer entry inside PEER INDEX TABLE, displayed as `peer\-address peer\-asn'. + +.IP "\fBASN32BIT\fP" 4 +May be either 1, if the peer AS number was encoded within 32 bits, or 0 otherwise. +.P +Note that this flag being 0 does not necessarily imply that the peer does not have +ASN32BIT capability, it is merely an indicator of how the peer entry was encoded within +the PEER INDEX TABLE record (namely, it only used 2 octets to encode its ASN). +.P +.RE + +Multiple PEER INDEX TABLES are concatenated within the output, meaning that +the same peer appearing in multiple PEER INDEX TABLE records may appear twice in +.IR @UTILITY@ +output. + +.SH "EXIT STATUS" +The following exit values are returned: +.IP "\00" 6 +All input data was scanned successfully, +and data was written to output correctly. +.IP >0 6 +Errors were detected in input data, write error occurred, +or an unrecoverable error occurred (such as out of memory errors). + +.SH STDIN +The standard input is used only if no +.IR FILES +arguments are provided, or when any of the specified +.IR FILES +arguments is `\-' , in which case MRT data is read from standard input at that +point, up to an . +.P +Whenever +.IR @UTILITY@ +reads from standard input, MRT data is assumed to be uncompressed. + +.SH "INPUT FILES" +.IR @UTILITY@ +supports most MRT dump formats as written by the majority of Route Collecting +projects (see the +.IR STANDARDS +section below for additional references). +MRT dumps may be provided either in their plain uncompressed form, or +as files compressed by +.IR gzip (1), +.IR bzip2 (1), +or +.IR xz (1). +.IR @UTILITY@ +performs appropriate decompression on the fly. +File extension is used, in a case insensitive way, to discriminate among +supported compression formats. If the file extension is not recognized, +or there is no extension, then it is assumed to be uncompressed. + +.SH STDOUT +The standard output is used to print a human readable text representation of +PEER INDEX TABLE contents, nothing else shall be written to the standard output. +.IR @UTILITY@ +may detect and treat as error whenever the standard output is a regular file, +and is the same file as any of the +.IR FILES +arguments. +The default output format used by +.IR @UTILITY@ +is documented in the +.IR "LINE ORIENTED OUTPUT" +section. + +.SH STDERR +The standard error is used only for diagnostic messages and error reporting. + +.SH EXAMPLES +This section contains some useful examples, demonstrating basic +.IR @UTILITY@ +usage. + +.IP \[bu] +The following is the simplest way to invoke +.IR @UTILITY@ : +.nf +\& +.in +2m +@UTILITY@ +.in +\& +.fi +It formats and prints all peers inside the uncompressed MRT +RIB input data it receives from standard input. + +.IP \[bu] +The following command: +.nf +\& +.in +2m +@UTILITY@ -r +.in +\& +.fi +formats and print only peer entries referenced by at least one RIB entry +inside the uncompressed MRT RIB input data coming from standard input. + +.IP \[bu] +The following is equivalent to the previous example: +.nf +\& +.in +2m +@UTILITY@ -r ./rib.1.bz2 +.in +\& +.fi +but takes MRT input data from a +.IR bzip (1) +compressed file. + +.IP \[bu] +The following is equivalent to the previous example: +.nf +\& +.in +2m +@UTILITY@ -r ./rib.*.gz +.in +\& +.fi +but takes MRT input data from multiple +.IR gzip (1) +compressed files resulting from the glob expansion. +Most likely the command output is going to +contain data coming from multiple PEER INDEX TABLES, +peers may appear more than once. + +.IP \[bu] +The following command: +.nf +\& +.in +2m +@UTILITY@ ./rib.*.gz +.in +\& +.fi +is similar to the previous example, but does not remove +unreferenced peer entries from output. + +.SH SEE ALSO +.IR bgpgrep (1), +.IR awk (1), +.IR grep (1) + +.SH STANDARDS +The +.IR @UTILITY@ +utility conforms to: +.IP \[bu] 2m +.IR "RFC 6396" " \-" "Multi\-Threaded Routing Toolkit (MRT) Routing Information Export Format" +.IP \[bu] 2m +.IR "RFC 8050" " \- " "Multi\-Threaded Routing Toolkit (MRT) Routing Information Export Format with BGP Additional Path Extensions" + +.SH AUTHOR +.IR @UTILITY@ +was written by +.UR lcg@\:inventati.\:org +Lorenzo Cogotti +.UE . diff --git a/tools/peerindex/peerindex.c b/tools/peerindex/peerindex.c new file mode 100644 index 0000000..7c48e8c --- /dev/null +++ b/tools/peerindex/peerindex.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * \file peerindex.c + * + * `peerindex` main entry point and general command line parsing. + * + * \copyright The DoubleFourteen Code Forge (C) All Rights Reserved + * \author Lorenzo Cogotti + */ + +#include "peerindex_local.h" + +#include "bgp/bytebuf.h" +#include "cpr/flate.h" +#include "cpr/bzip2.h" +#include "cpr/xz.h" +#include "sys/endian.h" +#include "sys/fs.h" +#include "sys/con.h" +#include "sys/sys.h" +#include "argv.h" +#include "bufio.h" +#include "strlib.h" + +#include +#include +#include +#include + +#define BYTEBUFSIZ (256 * 1024) + +typedef enum { + ONLY_REFS_FLAG, + + NUM_FLAGS +} PeerindexOpt; + +static Optflag options[] = { + [ONLY_REFS_FLAG] = { + 'r', "only-refs", NULL, "Only dump peers referenced by RIBs", ARG_NONE + }, + + [NUM_FLAGS] = { '\0' } +}; + +static PeerindexState S; +static BGP_FIXBYTEBUF(BYTEBUFSIZ) bgp_msgBuf = { BYTEBUFSIZ }; + +static void Peerindex_SetupCommandLine(char *argv0) +{ + Sys_StripPath(argv0, NULL); + Sys_StripFileExtension(argv0, NULL); + + com_progName = argv0; + com_synopsis = "[OPTIONS...] [FILES...]"; + com_shortDescr = "MRT TABLE_DUMPV2 Peer Index Table inspection tool"; + com_longDescr = + "Reads MRT dumps in various formats, and inspects the\n" + "MRT TABLE_DUMPV2 peer index table, and prints it to stdout.\n" + "If - is found inside FILES, then input is read from stdin.\n" + "If no FILES arguments are provided, then input\n" + "is implicitly expected from stdin.\n" + "Any diagnostic message is logged to stderr."; +} + +static void Peerindex_Warning(const char *fmt, ...) +{ + va_list va; + + Sys_Print(STDERR, com_progName); + Sys_Print(STDERR, ": "); + if (S.filename) { + Sys_Print(STDERR, S.filename); + Sys_Print(STDERR, ": "); + } + + Sys_Print(STDERR, "WARNING: "); + + va_start(va, fmt); + Sys_VPrintf(STDERR, fmt, va); + va_end(va); + + Sys_Print(STDERR, "\n"); +} + +void Peerindex_DropRecord(const char *fmt, ...) +{ + va_list va; + + Sys_Print(STDERR, com_progName); + Sys_Print(STDERR, ": "); + + assert(S.filename); + Sys_Print(STDERR, S.filename); + Sys_Print(STDERR, ": "); + + va_start(va, fmt); + Sys_VPrintf(STDERR, fmt, va); + va_end(va); + + Sys_Print(STDERR, "\n"); + S.nerrors++; + + // If we're dropping a record we have to kill S.rec + Bgp_ClearMrt(&S.rec); + + // ...but don't drop PEER_INDEX_TABLE + + longjmp(S.dropRecordFrame, 1); +} + +void Peerindex_DropFile(const char *fmt, ...) +{ + va_list va; + + Sys_Print(STDERR, com_progName); + Sys_Print(STDERR, ": "); + + assert(S.filename); + Sys_Print(STDERR, S.filename); + Sys_Print(STDERR, ": "); + + va_start(va, fmt); + Sys_VPrintf(STDERR, fmt, va); + va_end(va); + + Sys_Print(STDERR, "\n"); + S.nerrors++; + + // Must clear PEER_INDEX_TABLE also, along with S.rec... + Bgp_ClearMrt(&S.rec); + Bgp_ClearMrt(&S.peerIndex); + S.hasPeerIndex = FALSE; + + longjmp(S.dropFileFrame, 1); +} + +NOINLINE static void Peerindex_HandleError(BgpRet err, Srcloc *loc, void *obj) +{ + USED(obj); + + // On out of memory we die with an appropriate backtrace + if (err == BGPENOMEM) { + loc->call_depth++; // include this function itself + + _Sys_OutOfMemory(loc->filename, loc->func, loc->line, loc->call_depth); + } + // On I/O error we drop the entire file + if (err == BGPEIO) + Peerindex_DropFile("I/O error while reading MRT dump"); // TODO: better diagnostics + + // Hopefully we are not dealing with a programming error... + assert(err != BGPEBADMRTTYPE); + assert(BGP_ISMRTERR(err)); + + if (err == BGPEBADPEERIDXCNT || err == BGPEBADRIBV2CNT) { + // Only warrant a warning + Peerindex_Warning("CORRUPT MRT RECORD: %s", Bgp_ErrorString(err)); + S.nerrors++; + return; + } + + // Anything else requires a full record drop + Peerindex_DropRecord("SKIPPING MRT RECORD: %s", Bgp_ErrorString(err)); +} + +static void Peerindex_ApplyProgramOptions(void) +{ + if (options[ONLY_REFS_FLAG].flagged) + S.peerIndexClearVal = 0; + else + S.peerIndexClearVal = 0xff; // so we always print the full table +} + +static void Peerindex_Init(void) +{ + S.rec.allocp = S.rec.allocp = &bgp_msgBuf; +} + +static const StmOps *Peerindex_OpenMrtDump(const char *filename, void **phn) +{ + Fildes fh = Sys_Fopen(filename, FM_READ, FH_SEQ); + if (fh == FILDES_BAD) + Peerindex_DropFile("Can't open file"); + + const char *ext = Sys_GetFileExtension(filename); + + void *hn; + const StmOps *ops; + + if (Df_stricmp(ext, ".bz2") == 0) { + hn = Bzip2_OpenDecompress(STM_FILDES(fh), Stm_FildesOps, /*opts=*/NULL); + ops = Bzip2_StmOps; + + Bzip2Ret err = Bzip2_GetErrStat(); + if (err) + Peerindex_DropFile("Can't read Bz2 archive: %s", Bzip2_ErrorString(err)); + + } else if (Df_stricmp(ext, ".gz") == 0 || Df_stricmp(ext, ".z") == 0) { + hn = Zlib_InflateOpen(STM_FILDES(fh), Stm_FildesOps, /*opts=*/NULL); + ops = Zlib_StmOps; + + ZlibRet err = Zlib_GetErrStat(); + if (err) + Peerindex_DropFile("Can't read Zlib archive: %s", Zlib_ErrorString(err)); + + } else if (Df_stricmp(ext, ".xz") == 0) { + hn = Xz_OpenDecompress(STM_FILDES(fh), Stm_FildesOps, /*opts=*/NULL); + ops = Xz_StmOps; + + XzRet err = Xz_GetErrStat(); + if (err) + Peerindex_DropFile("Can't read LZMA archive: %s", Xz_ErrorString(err)); + + } else { + // Assume uncompressed file + hn = STM_FILDES(fh); + ops = Stm_FildesOps; + } + + *phn = hn; + return ops; +} + +static void Peerindex_MarkPeerRefs(void) +{ + Mrtribiterv2 it; + Mrtribentv2 *rib; + + Bgp_StartMrtRibEntriesv2(&it, &S.rec); + while ((rib = Bgp_NextRibEntryv2(&it)) != NULL) { + Uint16 idx = beswap16(rib->peerIndex); + if (idx >= S.npeers) { + Peerindex_Warning("CORRUPT MRT RECORD: Peer index '%u' is out of range", (unsigned) idx); + continue; + } + + MARKPEERINDEX(S.peerIndexRefs, idx); + } +} + +static void Peerindex_DumpPeerIndexTable(void) +{ + char buf[IPV6_STRLEN + 1]; + Stmbuf sb; + + Ipadr adr; + + Mrtpeeriterv2 it; + Mrtpeerentv2 *peer; + + if (!S.hasPeerIndex) + return; // NOP + + Uint16 idx = 0; + + Bufio_Init(&sb, STM_CONHN(STDOUT), Stm_ConOps); + + Bgp_StartMrtPeersv2(&it, &S.peerIndex); + while ((peer = Bgp_NextMrtPeerv2(&it)) != NULL) { + if (ISPEERINDEXREF(S.peerIndexRefs, idx)) { + Asn asn = MRT_GETPEERADDR(&adr, peer); + char *eptr = Ip_AdrToString(&adr, buf); + + Bufio_Putsn(&sb, buf, eptr - buf); + Bufio_Putc(&sb, ' '); + Bufio_Putu(&sb, beswap32(ASN(asn))); + Bufio_Putc(&sb, '|'); + Bufio_Putc(&sb, ISASN32BIT(asn) ? '1' : '0'); + Bufio_Putc(&sb, '\n'); + } + + idx++; + } + Bufio_Flush(&sb); +} + +static void Peerindex_ProcessMrtDump(const char *filename) +{ + void *hn; + const StmOps *ops; + + S.filename = filename; // NOTE: Only function that manipulates this + + if (strcmp(filename, "-") == 0) { + hn = STM_FILDES(CON_FILDES(STDIN)); + ops = Stm_NcFildesOps; + } else + ops = Peerindex_OpenMrtDump(filename, &hn); + + setjmp(S.dropRecordFrame); // NOTE: The ONLY place where this is set + while (Bgp_ReadMrt(&S.rec, hn, ops) == OK) { + const Mrthdr *hdr = MRT_HDR(&S.rec); + if (hdr->type != MRT_TABLE_DUMPV2) + continue; // don't care for anything else + + if (hdr->subtype == TABLE_DUMPV2_PEER_INDEX_TABLE) { + // Dump peers seen so far, if any + Peerindex_DumpPeerIndexTable(); + // Clear previous PEER_INDEX_TABLE, if any + Bgp_ClearMrt(&S.peerIndex); + // Clear reference table... + memset(S.peerIndexRefs, S.peerIndexClearVal, sizeof(S.peerIndexRefs)); + + //...and update current PEER_INDEX_TABLE + size_t npeers; + + MRT_MOVEREC(&S.peerIndex, &S.rec); + Bgp_GetMrtPeerIndexPeers(&S.peerIndex, &npeers, /*nbytes=*/NULL); + S.npeers = npeers; + S.hasPeerIndex = TRUE; + continue; + } + if (!TABLE_DUMPV2_ISRIB(hdr->subtype)) + continue; // don't care for anything but RIBs + + if (!S.hasPeerIndex) { + Peerindex_Warning("SKIPPING"); + continue; + } + + Peerindex_MarkPeerRefs(); + } + + Peerindex_DumpPeerIndexTable(); // execute the very last dump + + if (ops->Close) + ops->Close(hn); + + S.filename = NULL; +} + +int main(int argc, char **argv) +{ +#ifndef NDEBUG + Sys_SetErrFunc(SYS_ERR_ABORT, NULL); +#else + Sys_SetErrFunc(SYS_ERR_QUIT, NULL); +#endif + Bgp_SetErrFunc(Peerindex_HandleError, NULL); + + Peerindex_SetupCommandLine(argv[0]); + + int optind = Com_ArgParse(argc, argv, options, /*flags=*/0); + if (optind == OPT_HELP) + return EXIT_SUCCESS; // only called to get help... + if (optind < 0) + return EXIT_FAILURE; // can't parse command line + + Peerindex_ApplyProgramOptions(); + + Peerindex_Init(); // initialize according to command line + + // Done with options + argc -= optind; + argv += optind; + + // Move to filtering rules and compile them + if (argc == 0) { + // If no FILES are provided, read from stdin + static const char *const stdinFile[] = { "-" }; + + argv = (char **) stdinFile; + argc = ARRAY_SIZE(stdinFile); + } + + volatile int i = 0; + + setjmp(S.dropFileFrame); // NOTE: The ONLY place where this is set + while (i < argc) { + Peerindex_ProcessMrtDump(argv[i++]); + + // Don't need PEER_INDEX_TABLE anymore + Bgp_ClearMrt(&S.peerIndex); + S.hasPeerIndex = FALSE; + } + + return (S.nerrors > 0) ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/tools/peerindex/peerindex_local.h b/tools/peerindex/peerindex_local.h new file mode 100644 index 0000000..2cf2ea5 --- /dev/null +++ b/tools/peerindex/peerindex_local.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * \file peeridx_local.h + * + * `peerindex` private header. + * + * \copyright The DoubleFourteen Code Forge (C) All Rights Reserved + * \author Lorenzo Cogotti + */ + +#ifndef DF_PEERINDEX_LOCAL_H_ +#define DF_PEERINDEX_LOCAL_H_ + +#include "bgp/mrt.h" + +#include + +#define PEERREFSTABSIZ (0x10000uLL / 64) + +typedef Uint64 PeerRefsTab[PEERREFSTABSIZ]; + +FORCE_INLINE void MARKPEERINDEX(PeerRefsTab tab, Uint16 idx) +{ + tab[idx >> 6] |= (1uLL << (idx & 0x3f)); +} + +FORCE_INLINE Boolean ISPEERINDEXREF(const PeerRefsTab tab, Uint16 idx) +{ + return (tab[idx >> 6] & (1uLL << (idx & 0x3f))) != 0; +} + +typedef struct { + const char *filename; // current file being processed + + // Miscellaneous global flags and data + Boolean8 hasPeerIndex; + Uint8 peerIndexClearVal; + Uint16 npeers; + PeerRefsTab peerIndexRefs; + Mrtrecord peerIndex; + Mrtrecord rec; + + // Error tracking and management + jmp_buf dropRecordFrame; // used by `Peerindex_DropRecord()` + jmp_buf dropFileFrame; // used by `Peerindex_DropFile()` + int nerrors; // for `exit()` status +} PeerindexState; + +#endif