diff options
Diffstat (limited to 'bgpd/bgp_aspath.c')
-rw-r--r-- | bgpd/bgp_aspath.c | 2433 |
1 files changed, 2433 insertions, 0 deletions
diff --git a/bgpd/bgp_aspath.c b/bgpd/bgp_aspath.c new file mode 100644 index 0000000..0e70de9 --- /dev/null +++ b/bgpd/bgp_aspath.c @@ -0,0 +1,2433 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* AS path management routines. + * Copyright (C) 1996, 97, 98, 99 Kunihiro Ishiguro + * Copyright (C) 2005 Sun Microsystems, Inc. + */ + +#include <zebra.h> + +#include "hash.h" +#include "memory.h" +#include "vector.h" +#include "log.h" +#include "stream.h" +#include "command.h" +#include "jhash.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_filter.h" + +/* Attr. Flags and Attr. Type Code. */ +#define AS_HEADER_SIZE 2 + +/* Now FOUR octets are used for AS value. */ +#define AS_VALUE_SIZE sizeof(as_t) +/* This is the old one */ +#define AS16_VALUE_SIZE sizeof(as16_t) + +/* Maximum protocol segment length value */ +#define AS_SEGMENT_MAX 255 + +/* The following length and size macros relate specifically to Quagga's + * internal representation of AS-Segments, not per se to the on-wire + * sizes and lengths. At present (200508) they sort of match, however + * the ONLY functions which should now about the on-wire syntax are + * aspath_put, assegment_put and assegment_parse. + * + * aspath_put returns bytes written, the only definitive record of + * size of wire-format attribute.. + */ + +/* Calculated size in bytes of ASN segment data to hold N ASN's */ +#define ASSEGMENT_DATA_SIZE(N, S) \ + ((N) * ((S) ? AS_VALUE_SIZE : AS16_VALUE_SIZE)) + +/* Calculated size of segment struct to hold N ASN's */ +#define ASSEGMENT_SIZE(N,S) (AS_HEADER_SIZE + ASSEGMENT_DATA_SIZE (N,S)) + +/* AS segment octet length. */ +#define ASSEGMENT_LEN(X,S) ASSEGMENT_SIZE((X)->length,S) + +/* AS_SEQUENCE segments can be packed together */ +/* Can the types of X and Y be considered for packing? */ +#define ASSEGMENT_TYPES_PACKABLE(X, Y) \ + (((X)->type == (Y)->type) && ((X)->type == AS_SEQUENCE)) +/* Types and length of X,Y suitable for packing? */ +#define ASSEGMENTS_PACKABLE(X, Y) \ + (ASSEGMENT_TYPES_PACKABLE((X), (Y)) \ + && (((X)->length + (Y)->length) <= AS_SEGMENT_MAX)) + +/* As segment header - the on-wire representation + * NOT the internal representation! + */ +struct assegment_header { + uint8_t type; + uint8_t length; +}; + +/* Hash for aspath. This is the top level structure of AS path. */ +static struct hash *ashash; + +/* Stream for SNMP. See aspath_snmp_pathseg */ +static struct stream *snmp_stream; + +/* Callers are required to initialize the memory */ +static as_t *assegment_data_new(int num) +{ + return (XMALLOC(MTYPE_AS_SEG_DATA, ASSEGMENT_DATA_SIZE(num, 1))); +} + +static void assegment_data_free(as_t *asdata) +{ + XFREE(MTYPE_AS_SEG_DATA, asdata); +} + +const char *const aspath_segment_type_str[] = { + "as-invalid", "as-set", "as-sequence", "as-confed-sequence", + "as-confed-set" +}; + +/* Get a new segment. Note that 0 is an allowed length, + * and will result in a segment with no allocated data segment. + * the caller should immediately assign data to the segment, as the segment + * otherwise is not generally valid + */ +static struct assegment *assegment_new(uint8_t type, unsigned short length) +{ + struct assegment *new; + + new = XCALLOC(MTYPE_AS_SEG, sizeof(struct assegment)); + + if (length) + new->as = assegment_data_new(length); + + new->length = length; + new->type = type; + + return new; +} + +static void assegment_free(struct assegment *seg) +{ + if (!seg) + return; + + assegment_data_free(seg->as); + memset(seg, 0xfe, sizeof(struct assegment)); + XFREE(MTYPE_AS_SEG, seg); + + return; +} + +/* free entire chain of segments */ +static void assegment_free_all(struct assegment *seg) +{ + struct assegment *prev; + + while (seg) { + prev = seg; + seg = seg->next; + assegment_free(prev); + } +} + +/* Duplicate just the given assegment and its data */ +static struct assegment *assegment_dup(struct assegment *seg) +{ + struct assegment *new; + + new = assegment_new(seg->type, seg->length); + memcpy(new->as, seg->as, ASSEGMENT_DATA_SIZE(new->length, 1)); + + return new; +} + +/* Duplicate entire chain of assegments, return the head */ +static struct assegment *assegment_dup_all(struct assegment *seg) +{ + struct assegment *new = NULL; + struct assegment *head = NULL; + + while (seg) { + if (head) { + new->next = assegment_dup(seg); + new = new->next; + } else + head = new = assegment_dup(seg); + + seg = seg->next; + } + return head; +} + +/* prepend the as number to given segment, given num of times */ +static struct assegment *assegment_prepend_asns(struct assegment *seg, + as_t asnum, int num) +{ + as_t *newas; + int i; + + if (!num) + return seg; + + if (num >= AS_SEGMENT_MAX) + return seg; /* we don't do huge prepends */ + + newas = assegment_data_new(seg->length + num); + if (newas == NULL) + return seg; + + for (i = 0; i < num; i++) + newas[i] = asnum; + + memcpy(newas + num, seg->as, ASSEGMENT_DATA_SIZE(seg->length, 1)); + assegment_data_free(seg->as); + seg->as = newas; + seg->length += num; + + return seg; +} + +/* append given array of as numbers to the segment */ +static struct assegment *assegment_append_asns(struct assegment *seg, + as_t *asnos, int num) +{ + as_t *newas; + + if (!seg) + return seg; + + newas = XREALLOC(MTYPE_AS_SEG_DATA, seg->as, + ASSEGMENT_DATA_SIZE(seg->length + num, 1)); + + seg->as = newas; + memcpy(seg->as + seg->length, asnos, + ASSEGMENT_DATA_SIZE(num, 1)); + seg->length += num; + return seg; +} + +static int int_cmp(const void *p1, const void *p2) +{ + const as_t *as1 = p1; + const as_t *as2 = p2; + + return (*as1 == *as2) ? 0 : ((*as1 > *as2) ? 1 : -1); +} + +/* normalise the segment. + * In particular, merge runs of AS_SEQUENCEs into one segment + * Internally, we do not care about the wire segment length limit, and + * we want each distinct AS_PATHs to have the exact same internal + * representation - eg, so that our hashing actually works.. + */ +static struct assegment *assegment_normalise(struct assegment *head) +{ + struct assegment *seg = head, *pin; + struct assegment *tmp; + + if (!head) + return head; + + while (seg) { + pin = seg; + + /* Sort values SET segments, for determinism in paths to aid + * creation of hash values / path comparisons + * and because it helps other lesser implementations ;) + */ + if (seg->type == AS_SET || seg->type == AS_CONFED_SET) { + int tail = 0; + int i; + + qsort(seg->as, seg->length, sizeof(as_t), int_cmp); + + /* weed out dupes */ + for (i = 1; i < seg->length; i++) { + if (seg->as[tail] == seg->as[i]) + continue; + + tail++; + if (tail < i) + seg->as[tail] = seg->as[i]; + } + /* seg->length can be 0.. */ + if (seg->length) + seg->length = tail + 1; + } + + /* read ahead from the current, pinned segment while the + * segments + * are packable/mergeable. Append all following packable + * segments + * to the segment we have pinned and remove these appended + * segments. + */ + while (pin->next && ASSEGMENT_TYPES_PACKABLE(pin, pin->next)) { + tmp = pin->next; + seg = pin->next; + + /* append the next sequence to the pinned sequence */ + pin = assegment_append_asns(pin, seg->as, seg->length); + + /* bypass the next sequence */ + pin->next = seg->next; + + /* get rid of the now referenceless segment */ + assegment_free(tmp); + } + + seg = pin->next; + } + return head; +} + +static struct aspath *aspath_new(enum asnotation_mode asnotation) +{ + struct aspath *as; + + as = XCALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); + as->asnotation = asnotation; + return as; +} + +/* Free AS path structure. */ +void aspath_free(struct aspath *aspath) +{ + if (!aspath) + return; + if (aspath->segments) + assegment_free_all(aspath->segments); + XFREE(MTYPE_AS_STR, aspath->str); + + if (aspath->json) { + json_object_free(aspath->json); + aspath->json = NULL; + } + + XFREE(MTYPE_AS_PATH, aspath); +} + +/* Unintern aspath from AS path bucket. */ +void aspath_unintern(struct aspath **aspath) +{ + struct aspath *ret; + struct aspath *asp; + + if (!*aspath) + return; + + asp = *aspath; + + if (asp->refcnt) + asp->refcnt--; + + if (asp->refcnt == 0) { + /* This aspath must exist in aspath hash table. */ + ret = hash_release(ashash, asp); + assert(ret != NULL); + aspath_free(asp); + *aspath = NULL; + } +} + +/* Return the start or end delimiters for a particular Segment type */ +#define AS_SEG_START 0 +#define AS_SEG_END 1 +static char aspath_delimiter_char(uint8_t type, uint8_t which) +{ + int i; + struct { + int type; + char start; + char end; + } aspath_delim_char[] = {{AS_SET, '{', '}'}, + {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 ' '; +} + +/* countup asns from this segment and index onward */ +static int assegment_count_asns(struct assegment *seg, int from) +{ + int count = 0; + while (seg) { + if (!from) + count += seg->length; + else { + count += (seg->length - from); + from = 0; + } + seg = seg->next; + } + return count; +} + +unsigned int aspath_count_confeds(struct aspath *aspath) +{ + int count = 0; + struct assegment *seg = aspath->segments; + + while (seg) { + if (seg->type == AS_CONFED_SEQUENCE) + count += seg->length; + else if (seg->type == AS_CONFED_SET) + count++; + + seg = seg->next; + } + return count; +} + +unsigned int aspath_count_hops(const struct aspath *aspath) +{ + int count = 0; + struct assegment *seg = aspath->segments; + + while (seg) { + if (seg->type == AS_SEQUENCE) + count += seg->length; + else if (seg->type == AS_SET) + count++; + + seg = seg->next; + } + return count; +} + +/* Check if aspath has AS_SET or AS_CONFED_SET */ +bool aspath_check_as_sets(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + + while (seg) { + if (seg->type == AS_SET || seg->type == AS_CONFED_SET) + return true; + seg = seg->next; + } + return false; +} + +/* Check if aspath has BGP_AS_ZERO */ +bool aspath_check_as_zero(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + unsigned int i; + + while (seg) { + for (i = 0; i < seg->length; i++) + if (seg->as[i] == BGP_AS_ZERO) + return true; + seg = seg->next; + } + + return false; +} + +/* Estimate size aspath /might/ take if encoded into an + * ASPATH attribute. + * + * This is a quick estimate, not definitive! aspath_put() + * may return a different number!! + */ +unsigned int aspath_size(struct aspath *aspath) +{ + int size = 0; + struct assegment *seg = aspath->segments; + + while (seg) { + size += ASSEGMENT_SIZE(seg->length, 1); + seg = seg->next; + } + return size; +} + +/* Return highest public ASN in path */ +as_t aspath_highest(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + as_t highest = 0; + unsigned int i; + + while (seg) { + for (i = 0; i < seg->length; i++) + if (seg->as[i] > highest + && !BGP_AS_IS_PRIVATE(seg->as[i])) + highest = seg->as[i]; + seg = seg->next; + } + return highest; +} + +/* Return the left-most ASN in path */ +as_t aspath_leftmost(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + as_t leftmost = 0; + + if (seg && seg->length && seg->type == AS_SEQUENCE) + leftmost = seg->as[0]; + + return leftmost; +} + +/* Return 1 if there are any 4-byte ASes in the path */ +bool aspath_has_as4(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + unsigned int i; + + while (seg) { + for (i = 0; i < seg->length; i++) + if (seg->as[i] > BGP_AS_MAX) + return true; + seg = seg->next; + } + return false; +} + +/* Convert aspath structure to string expression. */ +static void aspath_make_str_count(struct aspath *as, bool make_json) +{ + struct assegment *seg; + int str_size; + int len = 0; + char *str_buf; + json_object *jaspath_segments = NULL; + json_object *jseg = NULL; + json_object *jseg_list = NULL; + + if (make_json) { + as->json = json_object_new_object(); + jaspath_segments = json_object_new_array(); + } + + /* Empty aspath. */ + if (!as->segments) { + if (make_json) { + json_object_string_add(as->json, "string", "Local"); + json_object_object_add(as->json, "segments", + jaspath_segments); + json_object_int_add(as->json, "length", 0); + } + as->str = XMALLOC(MTYPE_AS_STR, 1); + as->str[0] = '\0'; + as->str_len = 0; + return; + } + + seg = as->segments; + +/* ASN takes 5 to 10 chars plus separator, see below. + * If there is one differing segment type, we need an additional + * 2 chars for segment delimiters, and the final '\0'. + * Hopefully this is large enough to avoid hitting the realloc + * code below for most common sequences. + * + * This was changed to 10 after the well-known BGP assertion, which + * had hit some parts of the Internet in May of 2009. + * plain format : '4294967295 ' : 10 + 1 + * astod format : '65535.65535 ': 11 + 1 + */ +#define ASN_STR_LEN (11 + 1) + str_size = MAX(assegment_count_asns(seg, 0) * ASN_STR_LEN + 2 + 1, + ASPATH_STR_DEFAULT_LEN); + str_buf = XMALLOC(MTYPE_AS_STR, str_size); + + while (seg) { + int i; + char separator; + + /* Check AS type validity. Set separator for segment */ + switch (seg->type) { + case AS_SET: + case AS_CONFED_SET: + separator = ','; + break; + case AS_SEQUENCE: + case AS_CONFED_SEQUENCE: + separator = ' '; + break; + default: + XFREE(MTYPE_AS_STR, str_buf); + as->str = NULL; + as->str_len = 0; + json_object_free(as->json); + as->json = NULL; + + return; + } + +/* We might need to increase str_buf, particularly if path has + * differing segments types, our initial guesstimate above will + * have been wrong. Need 11 chars for ASN, a separator each and + * potentially two segment delimiters, plus a space between each + * segment and trailing zero. + * + * This definitely didn't work with the value of 5 bytes and + * 32-bit ASNs. + */ +#define SEGMENT_STR_LEN(X) (((X)->length * ASN_STR_LEN) + 2 + 1 + 1) + if ((len + SEGMENT_STR_LEN(seg)) > str_size) { + str_size = len + SEGMENT_STR_LEN(seg); + str_buf = XREALLOC(MTYPE_AS_STR, str_buf, str_size); + } +#undef ASN_STR_LEN +#undef SEGMENT_STR_LEN + + if (seg->type != AS_SEQUENCE) + len += snprintf( + str_buf + len, str_size - len, "%c", + aspath_delimiter_char(seg->type, AS_SEG_START)); + + if (make_json) + jseg_list = json_object_new_array(); + + /* write out the ASNs, with their separators, bar the last one*/ + for (i = 0; i < seg->length; i++) { + if (make_json) + asn_asn2json_array(jseg_list, seg->as[i], + as->asnotation); + len += snprintfrr(str_buf + len, str_size - len, + ASN_FORMAT(as->asnotation), + &seg->as[i]); + + if (i < (seg->length - 1)) + len += snprintf(str_buf + len, str_size - len, + "%c", separator); + } + + if (make_json) { + jseg = json_object_new_object(); + json_object_string_add( + jseg, "type", + aspath_segment_type_str[seg->type]); + json_object_object_add(jseg, "list", jseg_list); + json_object_array_add(jaspath_segments, jseg); + } + + if (seg->type != AS_SEQUENCE) + len += snprintf( + str_buf + len, str_size - len, "%c", + aspath_delimiter_char(seg->type, AS_SEG_END)); + if (seg->next) + len += snprintf(str_buf + len, str_size - len, " "); + + seg = seg->next; + } + + assert(len < str_size); + + str_buf[len] = '\0'; + as->str = str_buf; + as->str_len = len; + + if (make_json) { + json_object_string_add(as->json, "string", str_buf); + json_object_object_add(as->json, "segments", jaspath_segments); + json_object_int_add(as->json, "length", aspath_count_hops(as)); + } + + return; +} + +void aspath_str_update(struct aspath *as, bool make_json) +{ + XFREE(MTYPE_AS_STR, as->str); + + if (as->json) { + json_object_free(as->json); + as->json = NULL; + } + + aspath_make_str_count(as, make_json); +} + +/* Intern allocated AS path. */ +struct aspath *aspath_intern(struct aspath *aspath) +{ + struct aspath *find; + + /* Assert this AS path structure is not interned and has the string + representation built. */ + assert(aspath->refcnt == 0); + assert(aspath->str); + + /* Check AS path hash. */ + find = hash_get(ashash, aspath, hash_alloc_intern); + if (find != aspath) + aspath_free(aspath); + + find->refcnt++; + + return find; +} + +/* Duplicate aspath structure. Created same aspath structure but + reference count and AS path string is cleared. */ +struct aspath *aspath_dup(struct aspath *aspath) +{ + unsigned short buflen = aspath->str_len + 1; + struct aspath *new; + + new = XCALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); + new->json = NULL; + + if (aspath->segments) + new->segments = assegment_dup_all(aspath->segments); + + if (!aspath->str) + return new; + + new->str = XMALLOC(MTYPE_AS_STR, buflen); + new->str_len = aspath->str_len; + new->asnotation = aspath->asnotation; + + /* copy the string data */ + if (aspath->str_len > 0) + memcpy(new->str, aspath->str, buflen); + else + new->str[0] = '\0'; + + return new; +} + +static void *aspath_hash_alloc(void *arg) +{ + const struct aspath *aspath = arg; + struct aspath *new; + + /* Malformed AS path value. */ + assert(aspath->str); + + /* New aspath structure is needed. */ + new = XMALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); + + /* Reuse segments and string representation */ + new->refcnt = 0; + new->segments = aspath->segments; + new->str = aspath->str; + new->str_len = aspath->str_len; + new->json = aspath->json; + new->asnotation = aspath->asnotation; + + return new; +} + +/* parse as-segment byte stream in struct assegment */ +static int assegments_parse(struct stream *s, size_t length, + struct assegment **result, int use32bit) +{ + struct assegment_header segh; + struct assegment *seg, *prev = NULL, *head = NULL; + size_t bytes = 0; + + /* empty aspath (ie iBGP or somesuch) */ + if (length == 0) + return 0; + + if (BGP_DEBUG(as4, AS4_SEGMENT)) + zlog_debug( + "[AS4SEG] Parse aspath segment: got total byte length %lu", + (unsigned long)length); + /* basic checks */ + if ((STREAM_READABLE(s) < length) + || (STREAM_READABLE(s) < AS_HEADER_SIZE) + || (length % AS16_VALUE_SIZE)) + return -1; + + while (bytes < length) { + int i; + size_t seg_size; + + if ((length - bytes) <= AS_HEADER_SIZE) { + if (head) + assegment_free_all(head); + return -1; + } + + /* softly softly, get the header first on its own */ + segh.type = stream_getc(s); + segh.length = stream_getc(s); + + seg_size = ASSEGMENT_SIZE(segh.length, use32bit); + + if (BGP_DEBUG(as4, AS4_SEGMENT)) + zlog_debug( + "[AS4SEG] Parse aspath segment: got type %d, length %d", + segh.type, segh.length); + + /* check it.. */ + if (((bytes + seg_size) > length) + /* 1771bis 4.3b: seg length contains one or more */ + || (segh.length == 0) + /* Paranoia in case someone changes type of segment length. + * Shift both values by 0x10 to make the comparison operate + * on more, than 8 bits (otherwise it's a warning, bug + * #564). + */ + || ((sizeof(segh.length) > 1) + && (0x10 + segh.length > 0x10 + AS_SEGMENT_MAX))) { + if (head) + assegment_free_all(head); + return -1; + } + + switch (segh.type) { + case AS_SEQUENCE: + case AS_SET: + case AS_CONFED_SEQUENCE: + case AS_CONFED_SET: + break; + default: + if (head) + assegment_free_all(head); + return -1; + } + + /* now its safe to trust lengths */ + seg = assegment_new(segh.type, segh.length); + + if (head) + prev->next = seg; + else /* it's the first segment */ + head = seg; + + for (i = 0; i < segh.length; i++) + seg->as[i] = + (use32bit) ? stream_getl(s) : stream_getw(s); + + bytes += seg_size; + + if (BGP_DEBUG(as4, AS4_SEGMENT)) + zlog_debug( + "[AS4SEG] Parse aspath segment: Bytes now: %lu", + (unsigned long)bytes); + + prev = seg; + } + + *result = assegment_normalise(head); + return 0; +} + +/* AS path parse function. pnt is a pointer to byte stream and length + is length of byte stream. If there is same AS path in the the AS + path hash then return it else make new AS path structure. + + On error NULL is returned. + */ +struct aspath *aspath_parse(struct stream *s, size_t length, int use32bit, + enum asnotation_mode asnotation) +{ + struct aspath as; + struct aspath *find; + + /* If length is odd it's malformed AS path. */ + /* Nit-picking: if (use32bit == 0) it is malformed if odd, + * otherwise its malformed when length is larger than 2 and (length-2) + * is not dividable by 4. + * But... this time we're lazy + */ + if (length % AS16_VALUE_SIZE) + return NULL; + + memset(&as, 0, sizeof(as)); + as.asnotation = asnotation; + if (assegments_parse(s, length, &as.segments, use32bit) < 0) + return NULL; + + /* If already same aspath exist then return it. */ + find = hash_get(ashash, &as, aspath_hash_alloc); + + /* if the aspath was already hashed free temporary memory. */ + if (find->refcnt) { + assegment_free_all(as.segments); + /* aspath_key_make() always updates the string */ + XFREE(MTYPE_AS_STR, as.str); + if (as.json) { + json_object_free(as.json); + as.json = NULL; + } + } + + find->refcnt++; + + return find; +} + +static void assegment_data_put(struct stream *s, as_t *as, int num, + int use32bit) +{ + int i; + assert(num <= AS_SEGMENT_MAX); + + for (i = 0; i < num; i++) + if (use32bit) + stream_putl(s, as[i]); + else { + if (as[i] <= BGP_AS_MAX) + stream_putw(s, as[i]); + else + stream_putw(s, BGP_AS_TRANS); + } +} + +static size_t assegment_header_put(struct stream *s, uint8_t type, int length) +{ + size_t lenp; + assert(length <= AS_SEGMENT_MAX); + stream_putc(s, type); + lenp = stream_get_endp(s); + stream_putc(s, length); + return lenp; +} + +/* write aspath data to stream */ +size_t aspath_put(struct stream *s, struct aspath *as, int use32bit) +{ + struct assegment *seg = as->segments; + size_t bytes = 0; + + if (!seg || seg->length == 0) + return 0; + + /* + * Hey, what do we do when we have > STREAM_WRITABLE(s) here? + * At the moment, we would write out a partial aspath, and our + * peer + * will complain and drop the session :-/ + * + * The general assumption here is that many things tested will + * never happen. And, in real live, up to now, they have not. + */ + while (seg && (ASSEGMENT_LEN(seg, use32bit) <= STREAM_WRITEABLE(s))) { + struct assegment *next = seg->next; + int written = 0; + int asns_packed = 0; + size_t lenp; + + /* Overlength segments have to be split up */ + while ((seg->length - written) > AS_SEGMENT_MAX) { + assegment_header_put(s, seg->type, AS_SEGMENT_MAX); + assegment_data_put(s, (seg->as + written), + AS_SEGMENT_MAX, use32bit); + written += AS_SEGMENT_MAX; + bytes += ASSEGMENT_SIZE(AS_SEGMENT_MAX, use32bit); + } + + /* write the final segment, probably is also the first + */ + lenp = assegment_header_put(s, seg->type, + seg->length - written); + assegment_data_put(s, (seg->as + written), + seg->length - written, use32bit); + + /* Sequence-type segments can be 'packed' together + * Case of a segment which was overlength and split up + * will be missed here, but that doesn't matter. + */ + while (next && ASSEGMENTS_PACKABLE(seg, next)) { + /* NB: We should never normally get here given + * we + * normalise aspath data when parse them. + * However, better + * safe than sorry. We potentially could call + * assegment_normalise here instead, but it's + * cheaper and + * easier to do it on the fly here rather than + * go through + * the segment list twice every time we write + * out + * aspath's. + */ + + /* Next segment's data can fit in this one */ + assegment_data_put(s, next->as, next->length, use32bit); + + /* update the length of the segment header */ + stream_putc_at(s, lenp, + seg->length - written + next->length); + asns_packed += next->length; + + next = next->next; + } + + bytes += ASSEGMENT_SIZE(seg->length - written + asns_packed, + use32bit); + seg = next; + } + return bytes; +} + +/* This is for SNMP BGP4PATHATTRASPATHSEGMENT + * We have no way to manage the storage, so we use a static stream + * wrapper around aspath_put. + */ +uint8_t *aspath_snmp_pathseg(struct aspath *as, size_t *varlen) +{ +#define SNMP_PATHSEG_MAX 1024 + + if (!snmp_stream) + snmp_stream = stream_new(SNMP_PATHSEG_MAX); + else + stream_reset(snmp_stream); + + if (!as) { + *varlen = 0; + return NULL; + } + aspath_put(snmp_stream, as, 0); /* use 16 bit for now here */ + + *varlen = stream_get_endp(snmp_stream); + return stream_pnt(snmp_stream); +} + +static struct assegment *aspath_aggregate_as_set_add(struct aspath *aspath, + struct assegment *asset, + as_t as) +{ + int i; + + /* If this is first AS set member, create new as-set segment. */ + if (asset == NULL) { + asset = assegment_new(AS_SET, 1); + if (!aspath->segments) + aspath->segments = asset; + else { + struct assegment *seg = aspath->segments; + while (seg->next) + seg = seg->next; + seg->next = asset; + } + asset->as[0] = as; + } else { + /* Check this AS value already exists or not. */ + for (i = 0; i < asset->length; i++) + if (asset->as[i] == as) + return asset; + + asset->length++; + asset->as = XREALLOC(MTYPE_AS_SEG_DATA, asset->as, + asset->length * AS_VALUE_SIZE); + asset->as[asset->length - 1] = as; + } + + + return asset; +} + +/* Modify as1 using as2 for aggregation. */ +struct aspath *aspath_aggregate(struct aspath *as1, struct aspath *as2) +{ + int i; + int minlen = 0; + int match = 0; + int from; + struct assegment *seg1 = as1->segments; + struct assegment *seg2 = as2->segments; + struct aspath *aspath = NULL; + struct assegment *asset = NULL; + struct assegment *prevseg = NULL; + + /* First of all check common leading sequence. */ + while (seg1 && seg2) { + /* Check segment type. */ + if (seg1->type != seg2->type) + break; + + /* Minimum segment length. */ + minlen = MIN(seg1->length, seg2->length); + + for (match = 0; match < minlen; match++) + if (seg1->as[match] != seg2->as[match]) + break; + + if (match) { + struct assegment *seg = assegment_new(seg1->type, 0); + + seg = assegment_append_asns(seg, seg1->as, match); + + if (!aspath) { + aspath = aspath_new(as1->asnotation); + aspath->segments = seg; + } else + prevseg->next = seg; + + prevseg = seg; + } + + if (match != minlen || match != seg1->length + || seg1->length != seg2->length) + break; + /* We are moving on to the next segment to reset match */ + else + match = 0; + + seg1 = seg1->next; + seg2 = seg2->next; + } + + if (!aspath) + aspath = aspath_new(as1->asnotation); + + /* Make as-set using rest of all information. */ + from = match; + while (seg1) { + for (i = from; i < seg1->length; i++) + asset = aspath_aggregate_as_set_add(aspath, asset, + seg1->as[i]); + + from = 0; + seg1 = seg1->next; + } + + from = match; + while (seg2) { + for (i = from; i < seg2->length; i++) + asset = aspath_aggregate_as_set_add(aspath, asset, + seg2->as[i]); + + from = 0; + seg2 = seg2->next; + } + + assegment_normalise(aspath->segments); + aspath_str_update(aspath, false); + return aspath; +} + +/* When a BGP router receives an UPDATE with an MP_REACH_NLRI + attribute, check the leftmost AS number in the AS_PATH attribute is + or not the peer's AS number. */ +bool aspath_firstas_check(struct aspath *aspath, as_t asno) +{ + if ((aspath == NULL) || (aspath->segments == NULL)) + return false; + + if (aspath->segments && (aspath->segments->type == AS_SEQUENCE) + && (aspath->segments->as[0] == asno)) + return true; + + return false; +} + +unsigned int aspath_get_first_as(struct aspath *aspath) +{ + if (aspath == NULL || aspath->segments == NULL) + return 0; + + return aspath->segments->as[0]; +} + +unsigned int aspath_get_last_as(struct aspath *aspath) +{ + int i; + unsigned int last_as = 0; + const struct assegment *seg; + + if (aspath == NULL || aspath->segments == NULL) + return last_as; + + seg = aspath->segments; + + while (seg) { + if (seg->type == AS_SEQUENCE || seg->type == AS_CONFED_SEQUENCE) + for (i = 0; i < seg->length; i++) + last_as = seg->as[i]; + seg = seg->next; + } + + return last_as; +} + +/* AS path loop check. If aspath contains asno then return >= 1. */ +int aspath_loop_check(struct aspath *aspath, as_t asno) +{ + struct assegment *seg; + int count = 0; + + if ((aspath == NULL) || (aspath->segments == NULL)) + return 0; + + seg = aspath->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) + if (seg->as[i] == asno) + count++; + + seg = seg->next; + } + return count; +} + +/* AS path loop check. If aspath contains asno + * that is a confed id then return >= 1. + */ +int aspath_loop_check_confed(struct aspath *aspath, as_t asno) +{ + struct assegment *seg; + int count = 0; + + if (aspath == NULL || aspath->segments == NULL) + return 0; + + seg = aspath->segments; + + while (seg) { + unsigned int i; + + for (i = 0; i < seg->length; i++) + if (seg->type != AS_CONFED_SEQUENCE && + seg->type != AS_CONFED_SET && seg->as[i] == asno) + count++; + + seg = seg->next; + } + return count; +} + + +/* When all of AS path is private AS return 1. */ +bool aspath_private_as_check(struct aspath *aspath) +{ + struct assegment *seg; + + if (!(aspath && aspath->segments)) + return false; + + seg = aspath->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) { + if (!BGP_AS_IS_PRIVATE(seg->as[i])) + return false; + } + seg = seg->next; + } + return true; +} + +/* Replace all ASN instances of the regex rule with our own ASN */ +struct aspath *aspath_replace_regex_asn(struct aspath *aspath, + struct as_list *acl_list, as_t our_asn) +{ + struct aspath *new; + struct assegment *cur_seg; + struct as_list *cur_as_list; + struct as_filter *cur_as_filter; + char str_buf[ASPATH_STR_DEFAULT_LEN]; + uint32_t i; + + new = aspath_dup(aspath); + cur_seg = new->segments; + + while (cur_seg) { + cur_as_list = acl_list; + while (cur_as_list) { + cur_as_filter = cur_as_list->head; + while (cur_as_filter) { + for (i = 0; i < cur_seg->length; i++) { + snprintfrr(str_buf, + ASPATH_STR_DEFAULT_LEN, + ASN_FORMAT(new->asnotation), + &cur_seg->as[i]); + if (!regexec(cur_as_filter->reg, + str_buf, 0, NULL, 0)) + cur_seg->as[i] = our_asn; + } + cur_as_filter = cur_as_filter->next; + } + cur_as_list = cur_as_list->next; + } + cur_seg = cur_seg->next; + } + + aspath_str_update(new, false); + return new; +} + + +/* Replace all instances of the target ASN with our own ASN */ +struct aspath *aspath_replace_specific_asn(struct aspath *aspath, + as_t target_asn, as_t our_asn) +{ + struct aspath *new; + struct assegment *seg; + + new = aspath_dup(aspath); + seg = new->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) { + if (seg->as[i] == target_asn) + seg->as[i] = our_asn; + } + seg = seg->next; + } + + aspath_str_update(new, false); + return new; +} + +/* Replace all ASNs with our own ASN */ +struct aspath *aspath_replace_all_asn(struct aspath *aspath, as_t our_asn) +{ + struct aspath *new; + struct assegment *seg; + + new = aspath_dup(aspath); + seg = new->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) + seg->as[i] = our_asn; + + seg = seg->next; + } + + aspath_str_update(new, false); + return new; +} + +/* Replace all private ASNs with our own ASN */ +struct aspath *aspath_replace_private_asns(struct aspath *aspath, as_t asn, + as_t peer_asn) +{ + struct aspath *new; + struct assegment *seg; + + new = aspath_dup(aspath); + seg = new->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) { + /* Don't replace if public ASN or peer's ASN */ + if (BGP_AS_IS_PRIVATE(seg->as[i]) + && (seg->as[i] != peer_asn)) + seg->as[i] = asn; + } + seg = seg->next; + } + + aspath_str_update(new, false); + return new; +} + +/* Remove all private ASNs */ +struct aspath *aspath_remove_private_asns(struct aspath *aspath, as_t peer_asn) +{ + struct aspath *new; + struct assegment *seg; + struct assegment *new_seg; + struct assegment *last_new_seg; + int i; + int j; + int public = 0; + int peer = 0; + + new = XCALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); + + new->json = NULL; + new_seg = NULL; + last_new_seg = NULL; + seg = aspath->segments; + while (seg) { + public = 0; + peer = 0; + for (i = 0; i < seg->length; i++) { + // ASN is public + if (!BGP_AS_IS_PRIVATE(seg->as[i])) + public++; + /* ASN matches peer's. + * Don't double-count if peer_asn is public. + */ + else if (seg->as[i] == peer_asn) + peer++; + } + + // The entire segment is public so copy it + if (public == seg->length) + new_seg = assegment_dup(seg); + + // The segment is a mix of public and private ASNs. Copy as many + // spots as + // there are public ASNs then come back and fill in only the + // public ASNs. + else { + /* length needs to account for all retained ASNs + * (public or peer_asn), not just public + */ + new_seg = assegment_new(seg->type, (public + peer)); + j = 0; + for (i = 0; i < seg->length; i++) { + // keep ASN if public or matches peer's ASN + if (!BGP_AS_IS_PRIVATE(seg->as[i]) + || (seg->as[i] == peer_asn)) { + new_seg->as[j] = seg->as[i]; + j++; + } + } + } + + // This is the first segment so set the aspath segments pointer + // to this one + if (!last_new_seg) + new->segments = new_seg; + else + last_new_seg->next = new_seg; + + last_new_seg = new_seg; + seg = seg->next; + } + + aspath_str_update(new, false); + return new; +} + +/* AS path confed check. If aspath contains confed set or sequence then return + * 1. */ +bool aspath_confed_check(struct aspath *aspath) +{ + struct assegment *seg; + + if (!(aspath && aspath->segments)) + return false; + + seg = aspath->segments; + + while (seg) { + if (seg->type == AS_CONFED_SET + || seg->type == AS_CONFED_SEQUENCE) + return true; + seg = seg->next; + } + return false; +} + +/* Leftmost AS path segment confed check. If leftmost AS segment is of type + AS_CONFED_SEQUENCE or AS_CONFED_SET then return 1. */ +bool aspath_left_confed_check(struct aspath *aspath) +{ + + if (!(aspath && aspath->segments)) + return false; + + if ((aspath->segments->type == AS_CONFED_SEQUENCE) + || (aspath->segments->type == AS_CONFED_SET)) + return true; + + return false; +} + +/* Merge as1 to as2. as2 should be uninterned aspath. */ +static struct aspath *aspath_merge(struct aspath *as1, struct aspath *as2) +{ + struct assegment *last, *new; + + if (!as1 || !as2) + return NULL; + + last = new = assegment_dup_all(as1->segments); + + /* find the last valid segment */ + while (last && last->next) + last = last->next; + + if (last) + last->next = as2->segments; + as2->segments = new; + aspath_str_update(as2, false); + return as2; +} + +/* Prepend as1 to as2. as2 should be uninterned aspath. */ +struct aspath *aspath_prepend(struct aspath *as1, struct aspath *as2) +{ + struct assegment *as1segtail; + struct assegment *as2segtail; + struct assegment *as2seghead; + + if (!as1 || !as2) + return NULL; + + /* If as2 is empty, only need to dupe as1's chain onto as2 */ + if (as2->segments == NULL) { + as2->segments = assegment_dup_all(as1->segments); + aspath_str_update(as2, false); + return as2; + } + + /* If as1 is empty AS, no prepending to do. */ + if (as1->segments == NULL) + return as2; + + /* find the tail as1's segment chain. */ + as1segtail = as1->segments; + while (as1segtail && as1segtail->next) + as1segtail = as1segtail->next; + + /* Delete any AS_CONFED_SEQUENCE segment from as2. */ + if (as1segtail->type == AS_SEQUENCE + && as2->segments->type == AS_CONFED_SEQUENCE) + as2 = aspath_delete_confed_seq(as2); + + if (!as2->segments) { + as2->segments = assegment_dup_all(as1->segments); + aspath_str_update(as2, false); + return as2; + } + + /* Compare last segment type of as1 and first segment type of as2. */ + if (as1segtail->type != as2->segments->type) + return aspath_merge(as1, as2); + + if (as1segtail->type == AS_SEQUENCE) { + /* We have two chains of segments, as1->segments and seg2, + * and we have to attach them together, merging the attaching + * segments together into one. + * + * 1. dupe as1->segments onto head of as2 + * 2. merge seg2's asns onto last segment of this new chain + * 3. attach chain after seg2 + */ + + /* save as2 head */ + as2seghead = as2->segments; + + /* dupe as1 onto as2's head */ + as2segtail = as2->segments = assegment_dup_all(as1->segments); + + /* refind the tail of as2 */ + while (as2segtail && as2segtail->next) + as2segtail = as2segtail->next; + + /* merge the old head, seg2, into tail, seg1 */ + assegment_append_asns(as2segtail, as2seghead->as, + as2seghead->length); + + /* + * bypass the merged seg2, and attach any chain after it + * to chain descending from as2's head + */ + if (as2segtail) + as2segtail->next = as2seghead->next; + + /* as2->segments is now referenceless and useless */ + assegment_free(as2seghead); + + /* we've now prepended as1's segment chain to as2, merging + * the inbetween AS_SEQUENCE of seg2 in the process + */ + aspath_str_update(as2, false); + return as2; + } else { + /* AS_SET merge code is needed at here. */ + return aspath_merge(as1, as2); + } + /* XXX: Ermmm, what if as1 has multiple segments?? */ + + /* Not reached */ +} + +/* Iterate over AS_PATH segments and wipe all occurrences of the + * listed AS numbers. Hence some segments may lose some or even + * all data on the way, the operation is implemented as a smarter + * version of aspath_dup(), which allocates memory to hold the new + * data, not the original. The new AS path is returned. + */ +struct aspath *aspath_filter_exclude(struct aspath *source, + struct aspath *exclude_list) +{ + struct assegment *srcseg, *exclseg, *lastseg; + struct aspath *newpath; + + newpath = aspath_new(source->asnotation); + lastseg = NULL; + + for (srcseg = source->segments; srcseg; srcseg = srcseg->next) { + unsigned i, y, newlen = 0, done = 0, skip_as; + struct assegment *newseg; + + /* Find out, how much ASns are we going to pick from this + * segment. + * We can't perform filtering right inline, because the size of + * the new segment isn't known at the moment yet. + */ + for (i = 0; i < srcseg->length; i++) { + skip_as = 0; + for (exclseg = exclude_list->segments; + exclseg && !skip_as; exclseg = exclseg->next) + for (y = 0; y < exclseg->length; y++) + if (srcseg->as[i] == exclseg->as[y]) { + skip_as = 1; + // There's no sense in testing + // the rest of exclusion list, + // bail out. + break; + } + if (!skip_as) + newlen++; + } + /* newlen is now the number of ASns to copy */ + if (!newlen) + continue; + + /* Actual copying. Allocate memory and iterate once more, + * performing filtering. */ + newseg = assegment_new(srcseg->type, newlen); + for (i = 0; i < srcseg->length; i++) { + skip_as = 0; + for (exclseg = exclude_list->segments; + exclseg && !skip_as; exclseg = exclseg->next) + for (y = 0; y < exclseg->length; y++) + if (srcseg->as[i] == exclseg->as[y]) { + skip_as = 1; + break; + } + if (skip_as) + continue; + newseg->as[done++] = srcseg->as[i]; + } + /* At his point newlen must be equal to done, and both must be + * positive. Append + * the filtered segment to the gross result. */ + if (!lastseg) + newpath->segments = newseg; + else + lastseg->next = newseg; + lastseg = newseg; + } + aspath_str_update(newpath, false); + /* We are happy returning even an empty AS_PATH, because the + * administrator + * might expect this very behaviour. There's a mean to avoid this, if + * necessary, + * by having a match rule against certain AS_PATH regexps in the + * route-map index. + */ + aspath_free(source); + return newpath; +} + +struct aspath *aspath_filter_exclude_all(struct aspath *source) +{ + struct aspath *newpath; + + newpath = aspath_new(source->asnotation); + + aspath_str_update(newpath, false); + /* We are happy returning even an empty AS_PATH, because the + * administrator + * might expect this very behaviour. There's a mean to avoid this, if + * necessary, + * by having a match rule against certain AS_PATH regexps in the + * route-map index. + */ + aspath_free(source); + return newpath; +} + +struct aspath *aspath_filter_exclude_acl(struct aspath *source, + struct as_list *acl_list) +{ + struct assegment *cur_seg, *new_seg, *prev_seg, *next_seg; + struct as_list *cur_as_list; + struct as_filter *cur_as_filter; + char str_buf[ASPATH_STR_DEFAULT_LEN]; + uint32_t nb_as_del; + uint32_t i, j; + + cur_seg = source->segments; + prev_seg = NULL; + /* segments from source aspath */ + while (cur_seg) { + next_seg = cur_seg->next; + cur_as_list = acl_list; + nb_as_del = 0; + /* aspath filter list from acl_list */ + while (cur_as_list) { + cur_as_filter = cur_as_list->head; + while (cur_as_filter) { + for (i = 0; i < cur_seg->length; i++) { + if (cur_seg->as[i] == 0) + continue; + + snprintfrr(str_buf, + ASPATH_STR_DEFAULT_LEN, + ASN_FORMAT(source->asnotation), + &cur_seg->as[i]); + if (!regexec(cur_as_filter->reg, + str_buf, 0, NULL, 0)) { + cur_seg->as[i] = 0; + nb_as_del++; + } + } + + cur_as_filter = cur_as_filter->next; + } + + cur_as_list = cur_as_list->next; + } + /* full segment is excluded remove it */ + if (nb_as_del == cur_seg->length) { + if (cur_seg == source->segments) + /* first segment */ + source->segments = cur_seg->next; + else if (prev_seg) + prev_seg->next = cur_seg->next; + assegment_free(cur_seg); + } + /* change in segment size -> new allocation and replace segment*/ + else if (nb_as_del) { + new_seg = assegment_new(cur_seg->type, + cur_seg->length - nb_as_del); + j = 0; + for (i = 0; i < cur_seg->length; i++) { + if (cur_seg->as[i] == 0) + continue; + new_seg->as[j] = cur_seg->as[i]; + j++; + } + new_seg->next = next_seg; + if (cur_seg == source->segments) + /* first segment */ + source->segments = new_seg; + else if (prev_seg) + prev_seg->next = new_seg; + assegment_free(cur_seg); + prev_seg = new_seg; + } else + prev_seg = cur_seg; + cur_seg = next_seg; + } + + + aspath_str_update(source, false); + /* We are happy returning even an empty AS_PATH, because the + * administrator + * might expect this very behaviour. There's a mean to avoid this, if + * necessary, + * by having a match rule against certain AS_PATH regexps in the + * route-map index. + */ + return source; +} + + +/* Add specified AS to the leftmost of aspath. */ +static struct aspath *aspath_add_asns(struct aspath *aspath, as_t asno, + uint8_t type, unsigned num) +{ + struct assegment *assegment = aspath->segments; + unsigned i; + + if (assegment && assegment->type == type) { + /* extend existing segment */ + aspath->segments = + assegment_prepend_asns(aspath->segments, asno, num); + } else { + /* prepend with new segment */ + struct assegment *newsegment = assegment_new(type, num); + for (i = 0; i < num; i++) + newsegment->as[i] = asno; + + /* insert potentially replacing empty segment */ + if (assegment && assegment->length == 0) { + newsegment->next = assegment->next; + assegment_free(assegment); + } else + newsegment->next = assegment; + aspath->segments = newsegment; + } + + aspath_str_update(aspath, false); + return aspath; +} + +/* Add specified AS to the leftmost of aspath num times. */ +struct aspath *aspath_add_seq_n(struct aspath *aspath, as_t asno, unsigned num) +{ + return aspath_add_asns(aspath, asno, AS_SEQUENCE, num); +} + +/* Add specified AS to the leftmost of aspath. */ +struct aspath *aspath_add_seq(struct aspath *aspath, as_t asno) +{ + return aspath_add_asns(aspath, asno, AS_SEQUENCE, 1); +} + +/* Compare leftmost AS value for MED check. If as1's leftmost AS and + as2's leftmost AS is same return 1. */ +bool aspath_cmp_left(const struct aspath *aspath1, const struct aspath *aspath2) +{ + const struct assegment *seg1; + const struct assegment *seg2; + + if (!(aspath1 && aspath2)) + return false; + + seg1 = aspath1->segments; + seg2 = aspath2->segments; + + /* If both paths are originated in this AS then we do want to compare + * MED */ + if (!seg1 && !seg2) + return true; + + /* find first non-confed segments for each */ + while (seg1 && ((seg1->type == AS_CONFED_SEQUENCE) + || (seg1->type == AS_CONFED_SET))) + seg1 = seg1->next; + + while (seg2 && ((seg2->type == AS_CONFED_SEQUENCE) + || (seg2->type == AS_CONFED_SET))) + seg2 = seg2->next; + + /* Check as1's */ + if (!(seg1 && seg2 && (seg1->type == AS_SEQUENCE) + && (seg2->type == AS_SEQUENCE))) + return false; + + if (seg1->as[0] == seg2->as[0]) + return true; + + return false; +} + +/* Truncate an aspath after a number of hops, and put the hops remaining + * at the front of another aspath. Needed for AS4 compat. + * + * Returned aspath is a /new/ aspath, which should either by free'd or + * interned by the caller, as desired. + */ +struct aspath *aspath_reconcile_as4(struct aspath *aspath, + struct aspath *as4path) +{ + struct assegment *seg, *newseg, *prevseg = NULL; + struct aspath *newpath = NULL, *mergedpath; + int hops, cpasns = 0; + + if (!aspath || !as4path) + return NULL; + + seg = aspath->segments; + + /* CONFEDs should get reconciled too.. */ + hops = (aspath_count_hops(aspath) + aspath_count_confeds(aspath)) + - aspath_count_hops(as4path); + + if (hops < 0) { + if (BGP_DEBUG(as4, AS4)) + flog_warn( + EC_BGP_ASPATH_FEWER_HOPS, + "[AS4] Fewer hops in AS_PATH than NEW_AS_PATH"); + /* Something's gone wrong. The RFC says we should now ignore + * AS4_PATH, + * which is daft behaviour - it contains vital loop-detection + * information which must have been removed from AS_PATH. + */ + hops = aspath_count_hops(aspath); + } + + if (!hops) { + newpath = aspath_dup(as4path); + aspath_str_update(newpath, false); + return newpath; + } + + if (BGP_DEBUG(as4, AS4)) + zlog_debug( + "[AS4] got AS_PATH %s and AS4_PATH %s synthesizing now", + aspath->str, as4path->str); + + while (seg && hops > 0) { + switch (seg->type) { + case AS_SET: + case AS_CONFED_SET: + hops--; + cpasns = seg->length; + break; + case AS_CONFED_SEQUENCE: + /* Should never split a confed-sequence, if hop-count + * suggests we must then something's gone wrong + * somewhere. + * + * Most important goal is to preserve AS_PATHs prime + * function + * as loop-detector, so we fudge the numbers so that the + * entire + * confed-sequence is merged in. + */ + if (hops < seg->length) { + if (BGP_DEBUG(as4, AS4)) + zlog_debug( + "[AS4] AS4PATHmangle: AS_CONFED_SEQUENCE falls across 2/4 ASN boundary somewhere, broken.."); + hops = seg->length; + } + /* fallthru */ + case AS_SEQUENCE: + cpasns = MIN(seg->length, hops); + hops -= seg->length; + } + + assert(cpasns <= seg->length); + + newseg = assegment_new(seg->type, 0); + newseg = assegment_append_asns(newseg, seg->as, cpasns); + + if (!newpath) { + newpath = aspath_new(aspath->asnotation); + newpath->segments = newseg; + } else + prevseg->next = newseg; + + prevseg = newseg; + seg = seg->next; + } + + /* We may be able to join some segments here, and we must + * do this because... we want normalised aspaths in out hash + * and we do not want to stumble in aspath_put. + */ + mergedpath = aspath_merge(newpath, aspath_dup(as4path)); + aspath_free(newpath); + mergedpath->segments = assegment_normalise(mergedpath->segments); + aspath_str_update(mergedpath, false); + + if (BGP_DEBUG(as4, AS4)) + zlog_debug("[AS4] result of synthesizing is %s", + mergedpath->str); + + return mergedpath; +} + +/* Compare leftmost AS value for MED check. If as1's leftmost AS and + as2's leftmost AS is same return 1. (confederation as-path + only). */ +bool aspath_cmp_left_confed(const struct aspath *aspath1, + const struct aspath *aspath2) +{ + if (!(aspath1 && aspath2)) + return false; + + if (!(aspath1->segments && aspath2->segments)) + return false; + + if ((aspath1->segments->type != AS_CONFED_SEQUENCE) + || (aspath2->segments->type != AS_CONFED_SEQUENCE)) + return false; + + if (aspath1->segments->as[0] == aspath2->segments->as[0]) + return true; + + return false; +} + +/* Delete all AS_CONFED_SEQUENCE/SET segments from aspath. + * RFC 5065 section 4.1.c.1 + * + * 1) if any path segments of the AS_PATH are of the type + * AS_CONFED_SEQUENCE or AS_CONFED_SET, those segments MUST be + * removed from the AS_PATH attribute, leaving the sanitized + * AS_PATH attribute to be operated on by steps 2, 3 or 4. + */ +struct aspath *aspath_delete_confed_seq(struct aspath *aspath) +{ + struct assegment *seg, *prev, *next; + char removed_confed_segment; + + if (!(aspath && aspath->segments)) + return aspath; + + seg = aspath->segments; + removed_confed_segment = 0; + next = NULL; + prev = NULL; + + while (seg) { + next = seg->next; + + if (seg->type == AS_CONFED_SEQUENCE + || seg->type == AS_CONFED_SET) { + /* This is the first segment in the aspath */ + if (aspath->segments == seg) + aspath->segments = seg->next; + else + prev->next = seg->next; + + assegment_free(seg); + removed_confed_segment = 1; + } else + prev = seg; + + seg = next; + } + + if (removed_confed_segment) + aspath_str_update(aspath, false); + + return aspath; +} + +/* Add new AS number to the leftmost part of the aspath as + AS_CONFED_SEQUENCE. */ +struct aspath *aspath_add_confed_seq(struct aspath *aspath, as_t asno) +{ + return aspath_add_asns(aspath, asno, AS_CONFED_SEQUENCE, 1); +} + +/* Add new as value to as path structure. */ +static void aspath_as_add(struct aspath *as, as_t asno) +{ + struct assegment *seg = as->segments; + + if (!seg) + return; + + /* Last segment search procedure. */ + while (seg->next) + seg = seg->next; + + assegment_append_asns(seg, &asno, 1); +} + +/* Add new as segment to the as path. */ +static void aspath_segment_add(struct aspath *as, int type) +{ + struct assegment *seg = as->segments; + struct assegment *new = assegment_new(type, 0); + + if (seg) { + while (seg->next) + seg = seg->next; + seg->next = new; + } else + as->segments = new; +} + +struct aspath *aspath_empty(enum asnotation_mode asnotation) +{ + return aspath_parse(NULL, 0, 1, asnotation); /* 32Bit ;-) */ +} + +struct aspath *aspath_empty_get(void) +{ + struct aspath *aspath; + + aspath = aspath_new(bgp_get_asnotation(NULL)); + aspath_make_str_count(aspath, false); + return aspath; +} + +unsigned long aspath_count(void) +{ + return ashash->count; +} + +/* + Theoretically, one as path can have: + + One BGP packet size should be less than 4096. + One BGP attribute size should be less than 4096 - BGP header size. + One BGP aspath size should be less than 4096 - BGP header size - + BGP mandantry attribute size. +*/ + +/* AS path string lexical token enum. */ +enum as_token { + as_token_asval, + as_token_set_start, + as_token_set_end, + as_token_confed_seq_start, + as_token_confed_seq_end, + as_token_confed_set_start, + as_token_confed_set_end, + as_token_unknown +}; + +/* Return next token and point for string parse. */ +static const char *aspath_gettoken(const char *buf, enum as_token *token, + unsigned long *asno) +{ + const char *p = buf; + as_t asval; + bool found = false; + + /* Skip separators (space for sequences, ',' for sets). */ + while (isspace((unsigned char)*p) || *p == ',') + p++; + + /* Check the end of the string and type specify characters + (e.g. {}()). */ + switch (*p) { + case '\0': + return NULL; + case '{': + *token = as_token_set_start; + p++; + return p; + case '}': + *token = as_token_set_end; + p++; + return p; + case '(': + *token = as_token_confed_seq_start; + p++; + return p; + case ')': + *token = as_token_confed_seq_end; + p++; + return p; + case '[': + *token = as_token_confed_set_start; + p++; + return p; + case ']': + *token = as_token_confed_set_end; + p++; + return p; + } + + asval = 0; + p = asn_str2asn_parse(p, &asval, &found); + if (found) { + *asno = asval; + *token = as_token_asval; + } else + *token = as_token_unknown; + return p; +} + +struct aspath *aspath_str2aspath(const char *str, + enum asnotation_mode asnotation) +{ + enum as_token token = as_token_unknown; + unsigned short as_type; + unsigned long asno = 0; + struct aspath *aspath; + int needtype; + + aspath = aspath_new(asnotation); + + /* We start default type as AS_SEQUENCE. */ + as_type = AS_SEQUENCE; + needtype = 1; + + while ((str = aspath_gettoken(str, &token, &asno)) != NULL) { + switch (token) { + case as_token_asval: + if (needtype) { + aspath_segment_add(aspath, as_type); + needtype = 0; + } + aspath_as_add(aspath, asno); + break; + case as_token_set_start: + as_type = AS_SET; + aspath_segment_add(aspath, as_type); + needtype = 0; + break; + case as_token_set_end: + as_type = AS_SEQUENCE; + needtype = 1; + break; + case as_token_confed_seq_start: + as_type = AS_CONFED_SEQUENCE; + aspath_segment_add(aspath, as_type); + needtype = 0; + break; + case as_token_confed_seq_end: + as_type = AS_SEQUENCE; + needtype = 1; + break; + case as_token_confed_set_start: + as_type = AS_CONFED_SET; + aspath_segment_add(aspath, as_type); + needtype = 0; + break; + case as_token_confed_set_end: + as_type = AS_SEQUENCE; + needtype = 1; + break; + case as_token_unknown: + default: + aspath_free(aspath); + return NULL; + } + } + + aspath_make_str_count(aspath, false); + + return aspath; +} + +/* Make hash value by raw aspath data. */ +unsigned int aspath_key_make(const void *p) +{ + const struct aspath *aspath = p; + unsigned int key = 0; + + if (!aspath->str) + aspath_str_update((struct aspath *)aspath, false); + + key = jhash(aspath->str, aspath->str_len, 2334325); + + return key; +} + +/* If two aspath have same value then return 1 else return 0 */ +bool aspath_cmp(const void *arg1, const void *arg2) +{ + const struct assegment *seg1 = ((const struct aspath *)arg1)->segments; + const struct assegment *seg2 = ((const struct aspath *)arg2)->segments; + + if (((const struct aspath *)arg1)->asnotation != + ((const struct aspath *)arg2)->asnotation) + return false; + + while (seg1 || seg2) { + int i; + if ((!seg1 && seg2) || (seg1 && !seg2)) + return false; + if (seg1->type != seg2->type) + return false; + if (seg1->length != seg2->length) + return false; + for (i = 0; i < seg1->length; i++) + if (seg1->as[i] != seg2->as[i]) + return false; + seg1 = seg1->next; + seg2 = seg2->next; + } + return true; +} + +/* AS path hash initialize. */ +void aspath_init(void) +{ + ashash = hash_create_size(32768, aspath_key_make, aspath_cmp, + "BGP AS Path"); +} + +void aspath_finish(void) +{ + hash_clean_and_free(&ashash, (void (*)(void *))aspath_free); + + if (snmp_stream) + stream_free(snmp_stream); +} + +/* return and as path value */ +const char *aspath_print(struct aspath *as) +{ + return (as ? as->str : NULL); +} + +/* Printing functions */ +/* Feed the AS_PATH to the vty; the space suffix follows it only in case + * AS_PATH wasn't empty. + */ +void aspath_print_vty(struct vty *vty, struct aspath *as) +{ + vty_out(vty, "%s%s", as->str, as->str_len ? " " : ""); +} + +static void aspath_show_all_iterator(struct hash_bucket *bucket, + struct vty *vty) +{ + struct aspath *as; + + as = (struct aspath *)bucket->data; + + vty_out(vty, "[%p:%u] (%ld) ", (void *)bucket, bucket->key, as->refcnt); + vty_out(vty, "%s\n", as->str); +} + +/* Print all aspath and hash information. This function is used from + `show [ip] bgp paths' command. */ +void aspath_print_all_vty(struct vty *vty) +{ + hash_iterate(ashash, (void (*)(struct hash_bucket *, + void *))aspath_show_all_iterator, + vty); +} + +static struct aspath *bgp_aggr_aspath_lookup(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + return hash_lookup(aggregate->aspath_hash, aspath); +} + +static void *bgp_aggr_aspath_hash_alloc(void *p) +{ + struct aspath *ref = (struct aspath *)p; + struct aspath *aspath = NULL; + + aspath = aspath_dup(ref); + return aspath; +} + +static void bgp_aggr_aspath_prepare(struct hash_bucket *hb, void *arg) +{ + struct aspath *hb_aspath = hb->data; + struct aspath **aggr_aspath = arg; + struct aspath *aspath = NULL; + + if (*aggr_aspath) { + aspath = aspath_aggregate(*aggr_aspath, hb_aspath); + aspath_free(*aggr_aspath); + *aggr_aspath = aspath; + } else { + *aggr_aspath = aspath_dup(hb_aspath); + } +} + +void bgp_aggr_aspath_remove(void *arg) +{ + struct aspath *aspath = arg; + + aspath_free(aspath); +} + +void bgp_compute_aggregate_aspath(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + bgp_compute_aggregate_aspath_hash(aggregate, aspath); + + bgp_compute_aggregate_aspath_val(aggregate); + +} + +void bgp_compute_aggregate_aspath_hash(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + struct aspath *aggr_aspath = NULL; + + if ((aggregate == NULL) || (aspath == NULL)) + return; + + /* Create hash if not already created. + */ + if (aggregate->aspath_hash == NULL) + aggregate->aspath_hash = hash_create( + aspath_key_make, aspath_cmp, + "BGP Aggregator as-path hash"); + + aggr_aspath = bgp_aggr_aspath_lookup(aggregate, aspath); + if (aggr_aspath == NULL) { + /* Insert as-path into hash. + */ + aggr_aspath = hash_get(aggregate->aspath_hash, aspath, + bgp_aggr_aspath_hash_alloc); + } + + /* Increment reference counter. + */ + aggr_aspath->refcnt++; +} + +void bgp_compute_aggregate_aspath_val(struct bgp_aggregate *aggregate) +{ + if (aggregate == NULL) + return; + /* Re-compute aggregate's as-path. + */ + if (aggregate->aspath) { + aspath_free(aggregate->aspath); + aggregate->aspath = NULL; + } + if (aggregate->aspath_hash + && aggregate->aspath_hash->count) { + hash_iterate(aggregate->aspath_hash, + bgp_aggr_aspath_prepare, + &aggregate->aspath); + } +} + +void bgp_remove_aspath_from_aggregate(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + struct aspath *aggr_aspath = NULL; + struct aspath *ret_aspath = NULL; + + if ((!aggregate) + || (!aggregate->aspath_hash) + || (!aspath)) + return; + + /* Look-up the aspath in the hash. + */ + aggr_aspath = bgp_aggr_aspath_lookup(aggregate, aspath); + if (aggr_aspath) { + aggr_aspath->refcnt--; + + if (aggr_aspath->refcnt == 0) { + ret_aspath = hash_release(aggregate->aspath_hash, + aggr_aspath); + aspath_free(ret_aspath); + ret_aspath = NULL; + + /* Remove aggregate's old as-path. + */ + aspath_free(aggregate->aspath); + aggregate->aspath = NULL; + + bgp_compute_aggregate_aspath_val(aggregate); + } + } +} + +void bgp_remove_aspath_from_aggregate_hash(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + struct aspath *aggr_aspath = NULL; + struct aspath *ret_aspath = NULL; + + if ((!aggregate) + || (!aggregate->aspath_hash) + || (!aspath)) + return; + + /* Look-up the aspath in the hash. + */ + aggr_aspath = bgp_aggr_aspath_lookup(aggregate, aspath); + if (aggr_aspath) { + aggr_aspath->refcnt--; + + if (aggr_aspath->refcnt == 0) { + ret_aspath = hash_release(aggregate->aspath_hash, + aggr_aspath); + aspath_free(ret_aspath); + ret_aspath = NULL; + } + } +} + |