Fix a segfault when printing a malformed BGP AS_PATH update due to ASN extraction.
Better AS size extraction from AS paths: better heuristics (see bgp_attr_get_as_size). Also fixes output support for 4-byte ASNs. For example; (AS_PATH[T] {500.500 513.65211}) becomes: (AS_PATH[T] {500 500} 65211) Collapses bgp_attr_print switch 2-byte and 4-byte path cases. bgp_attr_get_as_size function copied from tcpdump Git repo master. This brings behavior in-line with newer versions on Linux. Finally, minor output tweak: add a space after BGP update attribute ORIGINATOR_ID's flags to make consistent with other attributes. Index: print-bgp.c =================================================================== RCS file: /cvs/src/usr.sbin/tcpdump/print-bgp.c,v retrieving revision 1.17 diff -u -p -r1.17 print-bgp.c --- print-bgp.c 16 Jan 2015 06:40:21 -0000 1.17 +++ print-bgp.c 14 Oct 2015 00:15:13 -0000 @@ -140,6 +140,9 @@ struct bgp_attr { #define BGP_CONFED_AS_SEQUENCE 3 /* draft-ietf-idr-rfc3065bis-01 */ #define BGP_CONFED_AS_SET 4 /* draft-ietf-idr-rfc3065bis-01 */ +#define BGP_AS_SEG_TYPE_MIN BGP_AS_SET +#define BGP_AS_SEG_TYPE_MAX BGP_CONFED_AS_SET + static struct tok bgp_as_path_segment_open_values[] = { { BGP_AS_SET, " {" }, { BGP_AS_SEQUENCE, " " }, @@ -400,6 +403,63 @@ trunc: } #endif +/* + * bgp_attr_get_as_size + * + * Try to find the size of the ASs encoded in an as-path. It is not obvious, as + * both Old speakers that do not support 4 byte AS, and the new speakers that do + * support, exchange AS-Path with the same path-attribute type value 0x02. + */ +static int +bgp_attr_get_as_size (u_int8_t bgpa_type, const u_char *pptr, int len) +{ + const u_char *tptr = pptr; + + /* + * If the path attribute is the optional AS4 path type, then we already + * know, that ASs must be encoded in 4 byte format. + */ + if (bgpa_type == BGPTYPE_AS4_PATH) { + return 4; + } + + /* + * Let us assume that ASs are of 2 bytes in size, and check if the AS-Path + * TLV is good. If not, ask the caller to try with AS encoded as 4 bytes + * each. + */ + while (tptr < pptr + len) { + TCHECK(tptr[0]); + + /* + * If we do not find a valid segment type, our guess might be wrong. + */ + if (tptr[0] < BGP_AS_SEG_TYPE_MIN || tptr[0] > BGP_AS_SEG_TYPE_MAX) { + goto trunc; + } + TCHECK(tptr[1]); + tptr += 2 + tptr[1] * 2; + } + + /* + * If we correctly reached end of the AS path attribute data content, + * then most likely ASs were indeed encoded as 2 bytes. + */ + if (tptr == pptr + len) { + return 2; + } + +trunc: + + /* + * We can come here, either we did not have enough data, or if we + * try to decode 4 byte ASs in 2 byte format. Either way, return 4, + * so that calller can try to decode each AS as of 4 bytes. If indeed + * there was not enough data, it will crib and end the parse anyways. + */ + return 4; +} + static int bgp_attr_print(const struct bgp_attr *attr, const u_char *dat, int len) { @@ -413,7 +473,6 @@ bgp_attr_print(const struct bgp_attr *at p = dat; tlen = len; - asn_bytes = 0; switch (attr->bgpa_type) { case BGPTYPE_ORIGIN: @@ -425,17 +484,8 @@ bgp_attr_print(const struct bgp_attr *at } break; case BGPTYPE_AS4_PATH: - asn_bytes = 4; /* FALLTHROUGH */ case BGPTYPE_AS_PATH: - /* - * 2-byte speakers will receive AS4_PATH as well AS_PATH (2-byte). - * 4-byte speakers will only receive AS_PATH but it will be 4-byte. - * To identify which is the case, compare the length of the path - * segment value in bytes, with the path segment length from the - * message (counted in # of AS) - */ - if (len % 2) { printf(" invalid len"); break; @@ -444,11 +494,19 @@ bgp_attr_print(const struct bgp_attr *at printf(" empty"); break; } + + /* + * BGP updates exchanged between New speakers that support 4 + * byte AS, ASs are always encoded in 4 bytes. There is no + * definitive way to find this, just by the packet's + * contents. So, check for packet's TLV's sanity assuming + * 2 bytes first, and it does not pass, assume that ASs are + * encoded in 4 bytes format and move on. + */ + asn_bytes = bgp_attr_get_as_size(attr->bgpa_type, dat, len); + while (p < dat + len) { TCHECK(p[0]); - if (asn_bytes == 0) { - asn_bytes = (len-2)/p[1]; - } printf("%s", tok2str(bgp_as_path_segment_open_values, "?", p[0])); @@ -456,13 +514,10 @@ bgp_attr_print(const struct bgp_attr *at for (i = 0; i < p[1] * asn_bytes; i += asn_bytes) { TCHECK2(p[2 + i], asn_bytes); printf("%s", i == 0 ? "" : " "); - if (asn_bytes == 2 || EXTRACT_16BITS(&p[2 + i])) - printf("%u%s", - EXTRACT_16BITS(&p[2 + i]), - asn_bytes == 4 ? "." : ""); - if (asn_bytes == 4) - printf("%u", - EXTRACT_16BITS(&p[2 + i + 2])); + printf("%u", + asn_bytes == 2 ? + EXTRACT_16BITS(&p[2 + i]) : + EXTRACT_32BITS(&p[2 + i])); } TCHECK(p[0]); printf("%s", @@ -549,7 +604,7 @@ bgp_attr_print(const struct bgp_attr *at break; } TCHECK2(p[0], 4); - printf("%s",getname(p)); + printf(" %s", getname(p)); break; case BGPTYPE_CLUSTER_LIST: if (len % 4) {