/* bgpparser.c 1.6.3-20071123 * Copyright (C) 2002-2007 Stephane Thiell * Some parts like aspath_make_str_count() taken from GNU Zebra. * * COMPILING * + gcc -Wall -O3 -o bgpparser bgpparser.c -lm * + mwcc -w all -O4,p -o mwbgpparser bgpparser.c * + Add -DNDEBUG to compile without any assert() nor debug (not cool) * * CHANGELOG * + bgpparser.c 1.6.3-20071123 * + check dump file type and subtype to skip AFI_IP6 entries * + bgpparser.c 1.6.2-20020814 * + lasted five years, not bad * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ /* =========== Configuration section =========== */ #define DUMP_COUNTER 1 #define DUMP_PREFIX_IPNUM_START 1 #define DUMP_PREFIX_ENDNUM_END 1 #define DUMP_PREFIX_IP 1 #define DUMP_PREFIX_LENGTH 1 #define DUMP_PEER_IP 1 #define DUMP_AS_NUMBER 1 #define DUMP_AS_PATH 1 #define DUMP_ORIGIN 1 #define DUMP_STATUS 1 #define DUMP_UPTIME 1 #define DUMP_NEXTHOP_IP 0 #define DUMP_MED_ATTR 1 #define DUMP_LOCAL_PREF 1 #define DUMP_AGGREGATOR_AS 0 #define DUMP_AGGREGATOR_IP 0 #define DUMP_COMMUNITY 0 /* Use stdin for input file stream? */ #define USE_STDIN 0 #define COUNTER_START 1 /* ======== End of configuration section ======= */ #include #include #include #include #include #include #include #include #include #include #include static const char *LF = "\n"; /* Origin strings. */ char *bgp_origin_str[] = {"i","e","?"}; /* char *bgp_origin_long_str[] = {"IGP","EGP","incomplete"}; */ #define SIZE_L 4 #define SIZE_W 2 #define SIZE_C 1 #define SIZE_IN_ADDR 4 /* IPv4 */ /* From bgpd/Zebra */ #define BGP_ATTR_ORIGIN 1 #define BGP_ATTR_AS_PATH 2 #define BGP_ATTR_MULTI_EXIT_DISC 4 #define BGP_ATTR_LOCAL_PREF 5 #define BGP_ATTR_AGGREGATOR 7 #define BGP_ATTR_COMMUNITIES 8 #define BGP_ATTR_FLAG_EXTLEN 0x10 /* Extended length flag. */ #define BGP_ATTR_FLAG_PARTIAL 0x20 /* Attribute is partial. */ #define BGP_ATTR_FLAG_TRANS 0x40 /* Attribute is transitive. */ #define BGP_ATTR_FLAG_OPTIONAL 0x80 /* Attribute is optional. */ #define MSG_TABLE_DUMP 12 /* Address family numbers from RFC1700. */ #define AFI_IP 1 #define AFI_IP6 2 #define AFI_MAX 3 #define RETIF(a, b, r) if ((a) != (b)) return (r); #ifdef __MWERKS__ #define alloca __alloca #pragma options align=packed #define PACK_STRUCT #else #ifndef __GNUC__ #warning Compiler not supported, trying as GCC... #endif #define PACK_STRUCT __attribute__((packed)) #endif struct packtablebuf { struct in_addr prefix_addr; unsigned char prefixlen; unsigned char status; unsigned long originated; struct in_addr peer_addr; unsigned short as_number; } PACK_STRUCT; #ifdef __MWERKS__ #pragma options align=reset #endif /* Community attribute. */ struct community { unsigned long refcnt; int size; unsigned long *val; }; static char *aspath_make_str_count(char *aspath, int aslength); #if DUMP_COMMUNITY static const char *community_print(struct community *com); #endif static char my_readbuf[2048]; static char *my_readpos = my_readbuf; static ssize_t my_readbuflen = 0; static int my_eof = 0; /* my_* is an read/seek optimisation for this software */ static void my_init() { my_readbuflen = 0; my_eof = 0; } static ssize_t my_read(int d, void *buf, size_t nbytes) { ssize_t len; #if 0 assert(nbytes > 0); #endif if (my_readbuflen == 0) { len = read(d, my_readbuf, sizeof(my_readbuf)); my_readpos = my_readbuf; my_readbuflen = len; if (len < sizeof(my_readbuf)) my_eof = 1; } if (my_readbuflen >= nbytes) { /* 1 byte optimization */ if (nbytes == 1) *((char *)buf) = *my_readpos; else memmove(buf, my_readpos, nbytes); my_readpos += nbytes; my_readbuflen -= nbytes; len = (ssize_t) nbytes; } else { len = my_readbuflen; if (len > 0) { memmove(buf, my_readpos, (size_t)len); nbytes -= len; my_readbuflen = 0; } if (!my_eof) len = len + my_read(d, (char *)buf + len, nbytes); } return len; } /* my_lseek only supports SEEK_CUR. */ static off_t my_lseek(int fildes, off_t offset, int whence) { #if 0 assert (whence == SEEK_CUR); assert (my_readbuflen >= 0); assert (offset >= 0); #endif if (whence == SEEK_CUR) { if (my_readbuflen > offset) { my_readpos += offset; my_readbuflen -= offset; } else { ssize_t len; ssize_t curoffset = (ssize_t) offset; do { curoffset -= my_readbuflen; len = read(fildes, my_readbuf, sizeof(my_readbuf)); my_readpos = my_readbuf; my_readbuflen = len; if (len < sizeof(my_readbuf)) { my_eof = 1; break; } } while (my_readbuflen <= curoffset); my_readpos += curoffset; if (my_readbuflen > curoffset) my_readbuflen -= curoffset; else my_readbuflen = 0; } } return lseek(fildes, 0, SEEK_CUR) - my_readbuflen; } /* returns: 0: alright * 1: unwanted EOF * 2: allowed EOF */ static int bgpparse_routes_entry(int fd, int counter) { #ifndef NDEBUG ssize_t len1, len2; #endif ssize_t result, info_pos; struct packtablebuf tablebuf; struct in_addr prefix_addr, peer_addr, nexthop_addr; struct in_addr aggregator_addr = { 0 }; char *aspathstr; char prefixIP[16], peerIP[16]; #if DUMP_NEXTHOP_IP char nexthopIP[16]; #endif #if DUMP_AGGREGATOR_IP char aggregatorIP[16]=""; #endif #if DUMP_COMMUNITY struct community com; #endif unsigned short dump_hdr_type, dump_hdr_subtype; unsigned long totallen, uptime, med, local_pref; unsigned short as_num, aggregator_as=0; /* u_int16_t */ unsigned short attrlen; unsigned char prefixlen, status, attr, asflags, origin; unsigned short aslen; /* bgp_dump_header: now, type, subtype, len */ my_lseek(fd, SIZE_L, SEEK_CUR); /* now */ /* type */ result = my_read(fd, &dump_hdr_type, sizeof(dump_hdr_type)); RETIF(result, sizeof(dump_hdr_type), 2); dump_hdr_type = htons(dump_hdr_type); if (dump_hdr_type != MSG_TABLE_DUMP) { fprintf(stderr, "bgpparser: Fatal error - please provide a MRT BGP dump file\n"); return 1; } /* subtype */ result = my_read(fd, &dump_hdr_subtype, sizeof(dump_hdr_subtype)); RETIF(result, sizeof(dump_hdr_subtype), 1); dump_hdr_subtype = htons(dump_hdr_subtype); if (dump_hdr_subtype == AFI_IP6) { /* skip ipv6 */ result = my_read(fd, &totallen, sizeof(totallen)); RETIF(result, sizeof(totallen), 1); totallen = htonl(totallen); my_lseek(fd, totallen, SEEK_CUR); return 0; } else if (dump_hdr_subtype != AFI_IP) { fprintf(stderr, "bgpparser: Fatal error - unsupported address family AFI %d != AFI_IP\n", dump_hdr_subtype); return 1; } /* length */ result = my_read(fd, &totallen, sizeof(totallen)); RETIF(result, sizeof(totallen), 1); totallen = htonl(totallen); #ifndef NDEBUG len1 = (ssize_t) my_lseek(fd, 0, SEEK_CUR); #endif /* bgp_dump_routes_entry (header): view #, sequence number */ my_lseek(fd, SIZE_W+SIZE_W, SEEK_CUR); /* FAST TABLE DUMP */ result = my_read(fd, &tablebuf, sizeof(tablebuf)); RETIF(result, sizeof(tablebuf), 1); prefix_addr = tablebuf.prefix_addr; prefixlen = tablebuf.prefixlen; status = tablebuf.status; uptime = htonl(tablebuf.originated); /* convert from big endian */ peer_addr = tablebuf.peer_addr; as_num = htons(tablebuf.as_number); /* convert from big endian */ /* ************** bgp_dump_routes_attr ************** */ result = my_read(fd, &attrlen, sizeof(attrlen)); RETIF(result, sizeof(attrlen), 1); attrlen = htons(attrlen); info_pos = (ssize_t) my_lseek(fd, 0, SEEK_CUR); /* origin attribute */ #if DUMP_ORIGIN my_lseek(fd, SIZE_C+SIZE_C+SIZE_C, SEEK_CUR); result = my_read(fd, &origin, sizeof(origin)); RETIF(result, sizeof(origin), 1); assert(origin < 3); #else my_lseek(fd, SIZE_C+SIZE_C+SIZE_C+SIZE_C, SEEK_CUR); #endif /* read AS path flag */ result = my_read(fd, &asflags, sizeof(asflags)); RETIF(result, sizeof(asflags), 1); result = my_read(fd, &attr, sizeof(attr)); RETIF(result, sizeof(attr), 1); /* Sanity check */ if (attr != BGP_ATTR_AS_PATH) { if (attr == BGP_ATTR_ORIGIN) { fprintf(stderr, "bgpparser: WARNING: BGP_ATTR_ORIGIN found, skipping!\n"); } else { fprintf(stderr, "bgpparser: WARNING: entry with attr %d != BGP_ATTR_AS_PATH, skipping!\n", attr); } my_lseek(fd, attrlen - (my_lseek(fd, 0, SEEK_CUR)-info_pos), SEEK_CUR); return 0; /* ignore */ } if (asflags & BGP_ATTR_FLAG_EXTLEN) { result = my_read(fd, &aslen, sizeof(aslen)); RETIF(result, sizeof(aslen), 1); } else { unsigned char aslen1byte; result = my_read(fd, &aslen1byte, sizeof(aslen1byte)); RETIF(result, sizeof(aslen1byte), 1); aslen = (unsigned short)aslen1byte; } { char *aspath = (char *) alloca(aslen); result = my_read(fd, aspath, aslen); RETIF(result, aslen, 1); aspathstr = aspath_make_str_count(aspath, aslen); } my_lseek(fd, SIZE_C+SIZE_C+SIZE_C, SEEK_CUR); /* Nexthop attribute address */ result = my_read(fd, &nexthop_addr, sizeof(nexthop_addr)); RETIF(result, sizeof(nexthop_addr), 1); /* MED attribute? */ while (attrlen - (my_lseek(fd, 0, SEEK_CUR)-info_pos) > 0) { unsigned char attrflag; result = my_read(fd, &attrflag, sizeof(attrflag)); RETIF(result, sizeof(attrflag), 1); result = my_read(fd, &attr, sizeof(attr)); RETIF(result, sizeof(attr), 1); switch (attr) { case BGP_ATTR_MULTI_EXIT_DISC: my_lseek(fd, SIZE_C, SEEK_CUR); result = my_read(fd, &med, sizeof(med)); RETIF(result, sizeof(med), 1); med = htonl(med); break; case BGP_ATTR_LOCAL_PREF: my_lseek(fd, SIZE_C, SEEK_CUR); result = my_read(fd, &local_pref, sizeof(local_pref)); RETIF(result, sizeof(local_pref), 1); local_pref = htonl(local_pref); break; case BGP_ATTR_AGGREGATOR: my_lseek(fd, SIZE_C, SEEK_CUR); result = my_read(fd, &aggregator_as, sizeof(aggregator_as)); RETIF(result, sizeof(aggregator_as), 1); aggregator_as = htons(aggregator_as); result = my_read(fd, &aggregator_addr, sizeof(aggregator_addr)); RETIF(result, sizeof(aggregator_addr), 1); break; case BGP_ATTR_COMMUNITIES: #if DUMP_COMMUNITY { if (attrflag & BGP_ATTR_FLAG_EXTLEN) { unsigned short community_size2; result = my_read(fd, &community_size2, sizeof(community_size2)); RETIF(result, sizeof(community_size2), 1); com.size = htons(community_size2); } else { unsigned char community_size1; result = my_read(fd, &community_size1, sizeof(community_size1)); RETIF(result, sizeof(community_size1), 1); com.size = community_size1; } com.val = (unsigned long *)alloca(com.size); result = my_read(fd, com.val, com.size); RETIF(result, com.size, 1); } #endif break; default: goto NoMoreAttr; } } NoMoreAttr: /* skip rest of attr */ my_lseek(fd, attrlen - (my_lseek(fd, 0, SEEK_CUR)-info_pos), SEEK_CUR); #ifndef NDEBUG len2 = (ssize_t) my_lseek(fd, 0, SEEK_CUR); /* Do we own the World ? */ assert (totallen == len2 - len1); #endif /***OUTPUT***/ #if DUMP_PREFIX_IP snprintf(prefixIP, sizeof(prefixIP), "%s", inet_ntoa(prefix_addr)); #endif #if DUMP_PEER_IP snprintf(peerIP, sizeof(peerIP), "%s", inet_ntoa(peer_addr)); #endif #if DUMP_NEXTHOP_IP snprintf(nexthopIP, sizeof(nexthopIP), "%s", inet_ntoa(nexthop_addr)); #endif #if DUMP_AGGREGATOR_IP if (aggregator_addr.s_addr != 0) snprintf(aggregatorIP, sizeof(aggregatorIP), "%s", inet_ntoa(aggregator_addr)); #endif printf( #if DUMP_COUNTER "%d:" #endif #if DUMP_PREFIX_IPNUM_START "%ld:" #endif #if DUMP_PREFIX_ENDNUM_END "%ld:" #endif #if DUMP_PREFIX_IP "%s:" #endif #if DUMP_PREFIX_LENGTH "%d:" #endif #if DUMP_PEER_IP "%s:" #endif #if DUMP_AS_NUMBER "%u:" #endif #if DUMP_AS_PATH "%s:" #endif #if DUMP_ORIGIN "%s:" #endif #if DUMP_STATUS "%u:" #endif #if DUMP_UPTIME "%lu:" #endif #if DUMP_NEXTHOP_IP "%s:" #endif #if DUMP_MED_ATTR "%lu:" #endif #if DUMP_LOCAL_PREF "%lu:" #endif #if DUMP_AGGREGATOR_AS "%u:" #endif #if DUMP_AGGREGATOR_IP "%s:" #endif #if DUMP_COMMUNITY "%s:" #endif "%s" /* dummy_args */ , /* */ #if DUMP_COUNTER counter, #endif #if DUMP_PREFIX_IPNUM_START (long)htonl((long)prefix_addr.s_addr), /* signed here */ #endif #if DUMP_PREFIX_ENDNUM_END (long)(htonl(prefix_addr.s_addr) + (unsigned long)pow(2.0, (32-prefixlen)) - 1U), #endif #if DUMP_PREFIX_IP prefixIP, #endif #if DUMP_PREFIX_LENGTH prefixlen, #endif #if DUMP_PEER_IP peerIP, #endif #if DUMP_AS_NUMBER as_num, #endif #if DUMP_AS_PATH aspathstr, #endif #if DUMP_ORIGIN bgp_origin_str[origin], #endif #if DUMP_STATUS status, #endif #if DUMP_UPTIME uptime, #endif #if DUMP_NEXTHOP_IP nexthopIP, #endif #if DUMP_MED_ATTR med, #endif #if DUMP_LOCAL_PREF local_pref, #endif #if DUMP_AGGREGATOR_AS aggregator_as, #endif #if DUMP_AGGREGATOR_IP aggregatorIP, #endif #if DUMP_COMMUNITY community_print(&com), #endif LF ); if (aspathstr) free(aspathstr); return 0; } static void bgpparse(int fd) { int result = 0, counter; lseek(fd, 0, SEEK_SET); /* Just to be sure. */ assert(sizeof(struct packtablebuf) == 16); for (counter = COUNTER_START; result == 0; counter++) result = bgpparse_routes_entry(fd, counter); if (result == 1) fprintf(stderr, "bgpparser: warning: unexpected EOF\n"); } static void usage() { fprintf(stderr, "Usage: bgpparser \n"); exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { int fd; #if !USE_STDIN if (argc <= 1 || !strcmp(argv[1], "-h")) usage(); #endif #if USE_STDIN fd = fileno(stdin); #else fd = open(argv[1], O_RDONLY); #endif if (fd < 0) { fprintf(stderr, "Error: can't open file\n"); exit(EXIT_FAILURE); } my_init(); bgpparse(fd); close(fd); return 0; } /****************************************************************************/ /* GNU Zebra import (modified) */ typedef u_int16_t as_t; /* To fetch and store as segment value. */ struct assegment { u_char type; u_char length; as_t asval[1]; }; /* Return the start or end delimiters for a particular Segment type */ #define AS_SEG_START 0 #define AS_SEG_END 1 #define AS_SET 1 #define AS_SEQUENCE 2 #define AS_CONFED_SEQUENCE 3 #define AS_CONFED_SET 4 static char aspath_delimiter_char (u_char type, u_char which) { int i; struct { int type; char start; char end; } aspath_delim_char [] = { { AS_SET, '{', '}' }, { AS_SEQUENCE, ' ', ' ' }, { AS_CONFED_SET, '[', ']' }, { AS_CONFED_SEQUENCE, '(', ')' }, { 0 } }; for (i = 0; aspath_delim_char[i].type != 0; i++) { if (aspath_delim_char[i].type == type) { if (which == AS_SEG_START) return aspath_delim_char[i].start; else if (which == AS_SEG_END) return aspath_delim_char[i].end; } } return ' '; } /* Convert aspath structure to string expression. */ #define ASPATH_STR_DEFAULT_LEN 32 #define AS_VALUE_SIZE sizeof (as_t) #define AS_HEADER_SIZE 2 static char * aspath_make_str_count (char *aspath, int aslength) { int space; u_char type; caddr_t pnt; caddr_t end; struct assegment *assegment; int str_size = ASPATH_STR_DEFAULT_LEN; int str_pnt; u_char *str_buf; int count = 0; /* Empty aspath. */ if (aslength == 0) { str_buf = (u_char *)malloc(1U); str_buf[0] = '\0'; return (char *)str_buf; } /* Set default value. */ space = 0; type = AS_SEQUENCE; /* Set initial pointer. */ pnt = aspath; end = pnt + aslength; str_buf = (u_char *)malloc((unsigned long)str_size); str_pnt = 0; assegment = (struct assegment *) pnt; while (pnt < end) { int i; int estimate_len; /* For fetch value. */ assegment = (struct assegment *) pnt; /* Check AS type validity. */ if ((assegment->type != AS_SET) && (assegment->type != AS_SEQUENCE) && (assegment->type != AS_CONFED_SET) && (assegment->type != AS_CONFED_SEQUENCE)) { free(str_buf); return NULL; } /* Check AS length. */ if ((pnt + (assegment->length * AS_VALUE_SIZE) + AS_HEADER_SIZE) > end) { free(str_buf); return NULL; } /* Buffer length check. */ estimate_len = ((assegment->length * 6) + 4); /* String length check. */ while (str_pnt + estimate_len >= str_size) { str_size *= 2; str_buf = (u_char *)realloc(str_buf, (unsigned long) str_size); } /* If assegment type is changed, print previous type's end character. */ if (type != AS_SEQUENCE) str_buf[str_pnt++] = (u_char) aspath_delimiter_char (type, AS_SEG_END); if (space) str_buf[str_pnt++] = ' '; if (assegment->type != AS_SEQUENCE) str_buf[str_pnt++] = (u_char) aspath_delimiter_char (assegment->type, AS_SEG_START); space = 0; /* Increment count - ignoring CONFED SETS/SEQUENCES */ if (assegment->type != AS_CONFED_SEQUENCE && assegment->type != AS_CONFED_SET) { if (assegment->type == AS_SEQUENCE) count += assegment->length; else if (assegment->type == AS_SET) count++; } for (i = 0; i < assegment->length; i++) { int len; if (space) { if (assegment->type == AS_SET || assegment->type == AS_CONFED_SET) str_buf[str_pnt++] = ','; else str_buf[str_pnt++] = ' '; } else space = 1; len = sprintf ((char *)(str_buf + str_pnt), "%d", ntohs (assegment->asval[i])); str_pnt += len; } type = assegment->type; pnt += (assegment->length * AS_VALUE_SIZE) + AS_HEADER_SIZE; } if (assegment->type != AS_SEQUENCE) str_buf[str_pnt++] = (u_char) aspath_delimiter_char (assegment->type, AS_SEG_END); str_buf[str_pnt] = '\0'; /* count */ return (char *)str_buf; } #if DUMP_COMMUNITY /* Pretty printing of community. For debug and logging purpose. */ #define COMMUNITY_NO_EXPORT 0xFFFFFF01 #define COMMUNITY_NO_ADVERTISE 0xFFFFFF02 #define COMMUNITY_LOCAL_AS 0xFFFFFF03 #define com_nthval(X,n) ((X)->val + (n)) const char * community_print(struct community *com) { /* XXX non-re-entrant warning */ static char buf[BUFSIZ]; int i; u_int32_t comval; u_int16_t as; u_int16_t val; memset (buf, 0, BUFSIZ); for (i = 0; i < com->size; i++) { memcpy (&comval, com_nthval (com, i), sizeof (u_int32_t)); comval = ntohl (comval); switch (comval) { case COMMUNITY_NO_EXPORT: strlcat (buf, " no-export", BUFSIZ); break; case COMMUNITY_NO_ADVERTISE: strlcat (buf, " no-advertise", BUFSIZ); break; case COMMUNITY_LOCAL_AS: strlcat (buf, " local-AS", BUFSIZ); break; default: as = (comval >> 16) & 0xFFFF; val = comval & 0xFFFF; snprintf (buf + strlen (buf), BUFSIZ - strlen (buf), " %d:%d", as, val); break; } } return buf; } #endif