On Sat, Oct 24, 2015 at 12:37:44PM -0600, Kevin Reay wrote:
> Adopt an updated version of the tcpdump.org ASN size calculation for
> BGP UPDATE message AS_PATHs. This corrects some bad behaviour due to
> incorrect ASN size calculations.
>
> I believe that the current way of calculating the ASN size for an
> UPDATE AS_PATH attribute is flawed.
>
> Currently, the ASN length (2 or 4 bytes) is calculated by dividing the
> total length of the AS_PATH attribute by the number of ASNs (p[1]) in
> the *first set encountered*:
>
> asn_bytes = (len-2)/p[1];
>
> The assumption that this first segment length describes the entire
> attribute is incorrect; there could be multiple path segment lengths
> in the attribute. The current method only uses the first encountered
> segment length (while using the entire attribute's byte length).
>
> This very often works fine, and when the calculation is incorrect the
> printf code only prints the first 2 bytes due to the way the if
> statements are structured, so it usually appears to work.
>
> (Sometimes the calculated ASN length isn't even 2 or 4 bytes, often
> causing 2 separate ASNs to be printed together as one ASN in ASDOT
> notation.)
>
> Example: here's the original OpenBSD output for a 2-byte encoded
> AS_PATH:
> (AS_PATH[T] 30)
> here's tcpdump.org 4.5.1 on Linux:
> AS Path (2), length: 10, Flags [T]: 30 { 10 20 }
> and here's OpenBSD with the changes in this patch:
> (AS_PATH[T] 30 {10 20})
>
> A comment in the original code claims:
> ...
> * 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)
>
> This incorrectly describes the calculation being performed; the length
> being compared isn't just the length of the "path segment value" but of
> the entire attribute.
>
> I've attached an updated version of the upstream's heuristics function
> for calculating the ASN size: bgp_attr_get_as_size(). The updated
> version contains style(9) changes, simplifications, an additional
> validity check (non-zero segment lengths), and comment re-writes.
>
> I'm interested in hearing any feedback. I'm also interested if anyone
> with experience using tcpdump with BGP packets can report seeing the
> incorrect behaviour described here in the past.
>
> Index: print-bgp.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/tcpdump/print-bgp.c,v
> retrieving revision 1.18
> diff -u -p -r1.18 print-bgp.c
> --- print-bgp.c 20 Oct 2015 11:29:07 -0000 1.18
> +++ print-bgp.c 24 Oct 2015 18:21:55 -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,55 @@ trunc:
> }
> #endif
>
> +/*
> + * Try to determine the size of the ASs encoded in an AS-path. It is
> + * not obvious as both speaker types exchange AS-paths with the same
> + * path-attribute type.
> + */
> +static int
> +bgp_attr_get_as_size(u_int8_t bgpa_type, const u_char *dat, int len)
> +{
> + const u_char *p;
> +
> + p = dat;
> +
> + /* AS4 path types are always encoded in 4-byte format */
> + if (bgpa_type == BGPTYPE_AS4_PATH) {
> + return 4;
> + }
> +
> + /*
> + * Start by assuming 2-byte ASs. Iterate through the path data using the
> + * segment length values. Switch to 4-bytes if we encounter an invalid
> + * field value/if the AS-Path length is invalid for the assumed size.
> + */
I wonder if it would not be smarter to check the other way around. As in
start with 4 byte and see if we overflow. I would guess that this would
happen in most cases on the first segment for 2 byte AS pathes.
> + while (p < dat + len) {
> + TCHECK(p[0]);
> +
> + /* check segment type: invalid value means wrong size */
> + if (p[0] < BGP_AS_SEG_TYPE_MIN || p[0] > BGP_AS_SEG_TYPE_MAX)
> + goto trunc;
> +
> + TCHECK(p[1]);
> +
> + /* check segment length: invalid indicates wrong size */
> + if (p[1] == 0)
> + goto trunc;
> +
> + p += 2 + p[1] * 2;
> + }
> +
> + /* matching length: it's very likely the ASs were encoded as 2-bytes */
> + if (p == dat + len)
> + return 2;
> +trunc:
> + /*
> + * Either there was not enough data or we tried to decode 4-byte ASs
> + * with an incorrect size of 2-bytes.
> + */
> + return 4;
> +}
> +
> static int
> bgp_attr_print(const struct bgp_attr *attr, const u_char *dat, int len)
> {
> @@ -425,17 +477,7 @@ 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;
> @@ -445,15 +487,13 @@ bgp_attr_print(const struct bgp_attr *at
> printf(" empty");
> break;
> }
> + asn_bytes = bgp_attr_get_as_size(attr->bgpa_type, dat, len);
> while (p < dat + len) {
> TCHECK2(p[0], 2);
> - if (asn_bytes == 0) {
> - if (p[1] == 0) {
> - /* invalid: segment contains one or more AS */
> - printf(" malformed");
> - break;
> - }
> - asn_bytes = (len-2)/p[1];
> + if (p[1] == 0) {
> + /* invalid: segment contains no AS */
> + printf(" malformed");
> + break;
> }
> printf("%s",
> tok2str(bgp_as_path_segment_open_values,
--
:wq Claudio