diff --git a/CHANGELOG.md b/CHANGELOG.md index 08cd5c4..d14d132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ # Change Log +- 0.0.8 + - Added 4-byte ASN support (RFC 6793) + - Added COMMUNITY path attribute parsing (RFC 1997) + - Added LARGE_COMMUNITY path attribute parsing (RFC 8092) + - Added timestamp to log messages + - Fixed header include guard issue causing build failures in CI + - 0.0.7 - Added automatic reconnection with exponential backoff (`-R` flag) - Added output queue with dedicated writer thread to prevent keepalive stalls on slow stdout diff --git a/README.md b/README.md index d3d2435..22049df 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ BGPSee is a multi-threaded BGP client for the CLI. Its goal is to allow you to q # Version -Current version is **0.0.7** +Current version is **0.0.8** -Major changes from **0.0.6** to **0.0.7**: -- Automatic reconnection with exponential backoff (`-R` flag) -- Output queue for reliable keepalive handling on slow connections -- Hold time negotiation per RFC 4271 -- FSM race condition fixes +Major changes from **0.0.7** to **0.0.8**: +- 4-byte ASN support (RFC 6793) +- COMMUNITY path attribute parsing (RFC 1997) +- LARGE_COMMUNITY path attribute parsing (RFC 8092) +- Timestamp added to log messages See the [CHANGELOG](CHANGELOG.md) for further information. diff --git a/src/bgp.c b/src/bgp.c index 98b648b..c9974d4 100644 --- a/src/bgp.c +++ b/src/bgp.c @@ -26,7 +26,7 @@ struct bgp_instance { uint8_t version; - uint16_t local_asn; + uint32_t local_asn; uint32_t local_rid; int n_peers; struct bgp_peer *peers[MAX_BGP_PEERS]; @@ -162,7 +162,7 @@ int setup_reconnect_timer(struct bgp_peer *peer) { */ -struct bgp_instance *create_bgp_instance(uint16_t local_asn, uint32_t local_rid, uint8_t version) { +struct bgp_instance *create_bgp_instance(uint32_t local_asn, uint32_t local_rid, uint8_t version) { struct bgp_instance *i; log_print(LOG_DEBUG, "Creating new peer (ASN %d, RID: %d, Version: %d)\n", local_asn, local_rid, version); @@ -239,7 +239,7 @@ struct bgp_peer *get_peer_from_instance(struct bgp_instance *i, unsigned int id) //TODO: Need to fix unsigned here, as we need to be able to signal failure of the function -unsigned int create_bgp_peer(struct bgp_instance *i, const char *peer_ip, const uint16_t peer_asn, const char *peer_name) { +unsigned int create_bgp_peer(struct bgp_instance *i, const char *peer_ip, const uint32_t peer_asn, const char *peer_name) { struct bgp_peer *peer; unsigned int new_id; @@ -309,6 +309,7 @@ unsigned int create_bgp_peer(struct bgp_instance *i, const char *peer_ip, const peer->socket.fd = -1; peer->peer_asn = peer_asn; + peer->four_octet_asn = 0; //Init the stdout lock and the output function pthread_mutex_init(&peer->stdout_lock, NULL); @@ -625,7 +626,7 @@ void *bgp_peer_thread(void *param) { //Did we receive messages? if (peer->socket.fd >= 0 && FD_ISSET(peer->socket.fd, set)) { log_print(LOG_DEBUG, "Calling recv_msg() on socket\n"); - message = recv_msg(peer->socket.fd); + message = recv_msg(peer); if (!message) { log_print(LOG_ERROR, "recv_msg() errored\n"); @@ -789,7 +790,7 @@ int fsm_state_idle(struct bgp_peer *peer, fd_set *set) { //Start the ConnectRetryTimer start_timer(peer->local_timers, ConnectRetryTimer); - log_print(LOG_INFO, "Opening connection to %s,%d (%s)\n", peer->peer_ip, peer->peer_asn, peer->name); + log_print(LOG_INFO, "Opening connection to %s,%u (%s)\n", peer->peer_ip, peer->peer_asn, peer->name); peer->socket.fd = tcp_connect(peer->peer_ip, "179", peer->source_ip); if (peer->socket.fd < 0) { @@ -814,6 +815,7 @@ int fsm_state_idle(struct bgp_peer *peer, fd_set *set) { int fsm_state_connect(struct bgp_peer *peer) { struct bgp_capabilities *caps; + uint16_t open_asn; log_print(LOG_DEBUG, "Peer %s FSM state: CONNECT\n", peer->name); @@ -823,12 +825,14 @@ int fsm_state_connect(struct bgp_peer *peer) { bgp_capabilities_add_route_refresh(caps); bgp_capabilities_add_mp_ext(caps, BGP_AFI_IPV4, BGP_SAFI_UNICAST); bgp_capabilities_add_mp_ext(caps, BGP_AFI_IPV6, BGP_SAFI_UNICAST); + bgp_capabilities_add_four_octet_asn(caps, *peer->local_asn); } - //TODO: fix hold timer + /* RFC 6793: OPEN ASN field is 2 bytes. Use AS_TRANS (23456) if local ASN > 65535 */ + open_asn = (*peer->local_asn > 65535) ? 23456 : (uint16_t)*peer->local_asn; + log_print(LOG_DEBUG, "Sending OPEN to peer %s\n", peer->name); - /* Note: caps ownership transfers to the queued message, freed by free_msg() */ - queue_and_send_open(peer, *peer->version, *peer->local_asn, 30, *peer->local_rid, caps); + queue_and_send_open(peer, *peer->version, open_asn, 30, *peer->local_rid, caps); start_timer(peer->local_timers, HoldTimer); peer->fsm_state = OPENSENT; @@ -855,10 +859,30 @@ int fsm_state_opensent(struct bgp_peer *peer, struct bgp_msg *msg, fd_set *set) if (message->type == OPEN) { log_print(LOG_DEBUG, "Checking OPEN for correctness\n"); + /* Check 4-byte ASN capability (RFC 6793) */ + uint32_t peer_real_asn = message->open.asn; + uint32_t peer_cap_asn = 0; + int peer_has_4byte = 0; + + if (message->open.capabilities) { + peer_has_4byte = bgp_capabilities_has_four_octet_asn( + message->open.capabilities, &peer_cap_asn); + } + + if (peer_has_4byte) { + /* Peer supports 4-byte ASN - use capability value as real ASN */ + peer_real_asn = peer_cap_asn; + peer->four_octet_asn = 1; + log_print(LOG_INFO, "Peer %s supports 4-byte ASN (ASN %u)\n", + peer->name, peer_real_asn); + } else { + peer->four_octet_asn = 0; + } + //Check peer ASN matches configured ASN - if (message->open.asn != peer->peer_asn) { - log_print(LOG_WARN, "Peer %s ASN mismatch: expected %d, got %d\n", - peer->name, peer->peer_asn, message->open.asn); + if (peer_real_asn != peer->peer_asn) { + log_print(LOG_WARN, "Peer %s ASN mismatch: expected %u, got %u\n", + peer->name, peer->peer_asn, peer_real_asn); queue_and_send_notification(peer, BGP_ERR_OPEN, BGP_ERR_OPEN_PEER_AS); bgp_close_socket(peer); peer->fsm_state = IDLE; diff --git a/src/bgp.h b/src/bgp.h index 01ea1a7..993d0e4 100644 --- a/src/bgp.h +++ b/src/bgp.h @@ -13,10 +13,10 @@ struct bgp_peer; struct bgp_instance; struct bgp_local; -struct bgp_instance *create_bgp_instance(uint16_t, uint32_t, uint8_t); +struct bgp_instance *create_bgp_instance(uint32_t, uint32_t, uint8_t); void free_bgp_instance(struct bgp_instance *); -unsigned int create_bgp_peer(struct bgp_instance *, const char *, const uint16_t, const char *); +unsigned int create_bgp_peer(struct bgp_instance *, const char *, const uint32_t, const char *); unsigned int bgp_peer_source(struct bgp_instance *, unsigned int, const char *); int set_bgp_output(struct bgp_instance *, unsigned int, enum bgp_output); diff --git a/src/bgp_capability.c b/src/bgp_capability.c index cec4a2a..b5c696e 100644 --- a/src/bgp_capability.c +++ b/src/bgp_capability.c @@ -238,3 +238,24 @@ const char *bgp_capability_name(uint8_t code) { default: return "Unknown"; } } + +int bgp_capabilities_has_four_octet_asn(const struct bgp_capabilities *caps, uint32_t *asn) { + struct list_head *pos; + struct bgp_capability *cap; + + if (!caps) { + return 0; + } + + list_for_each(pos, &caps->caps) { + cap = list_entry(pos, struct bgp_capability, list); + if (cap->code == BGP_CAP_FOUR_OCTET_ASN && cap->length == 4 && cap->value) { + if (asn) { + *asn = uchar_be_to_uint32(cap->value); + } + return 1; + } + } + + return 0; +} diff --git a/src/bgp_capability.h b/src/bgp_capability.h index 1768826..ba9cba6 100644 --- a/src/bgp_capability.h +++ b/src/bgp_capability.h @@ -126,3 +126,9 @@ struct bgp_capabilities *bgp_capabilities_parse(const unsigned char *opt_params, * Returns a static string describing the capability */ const char *bgp_capability_name(uint8_t code); + +/* + * Check if 4-octet ASN capability is present and extract the ASN + * Returns 1 if present (and sets *asn to the 4-byte ASN), 0 if not present + */ +int bgp_capabilities_has_four_octet_asn(const struct bgp_capabilities *caps, uint32_t *asn); diff --git a/src/bgp_message.c b/src/bgp_message.c index ebf12a5..5c96ed6 100644 --- a/src/bgp_message.c +++ b/src/bgp_message.c @@ -7,6 +7,7 @@ #include "debug.h" #include "bgp_message.h" #include "bgp_capability.h" +#include "bgp_peer.h" #include "byte_conv.h" #include "list.h" #include "log.h" @@ -56,7 +57,7 @@ struct bgp_msg *alloc_bgp_msg(const uint16_t length, enum bgp_msg_type type); int parse_open(struct bgp_msg *, unsigned char *); -int parse_update(struct bgp_msg *, unsigned char *); +int parse_update(struct bgp_msg *, unsigned char *, int four_octet_asn); int parse_keepalive(struct bgp_msg *); int parse_notification(struct bgp_msg *, unsigned char *); int parse_route_refresh(struct bgp_msg *); @@ -78,9 +79,11 @@ int parse_route_refresh(struct bgp_msg *); #define MSG_PAD 256 -struct bgp_msg *recv_msg(int socket_fd) { +struct bgp_msg *recv_msg(struct bgp_peer *peer) { struct bgp_msg *message; unsigned char *message_body = NULL; + int socket_fd = peer->socket.fd; + int four_octet_asn = peer->four_octet_asn; unsigned char header[BGP_HEADER_LEN]; ssize_t ret; @@ -130,7 +133,7 @@ struct bgp_msg *recv_msg(int socket_fd) { free(message); free(message_body); return NULL; - } + } } switch (message->type) { @@ -138,7 +141,7 @@ struct bgp_msg *recv_msg(int socket_fd) { ret = parse_open(message, message_body); break; case UPDATE: - ret = parse_update(message, message_body); + ret = parse_update(message, message_body, four_octet_asn); break; case NOTIFICATION: ret = parse_notification(message, message_body); @@ -193,6 +196,8 @@ int free_update(struct bgp_update *); int free_path_attributes(struct bgp_update *); int free_as_path(struct bgp_path_attribute *); int free_aggregator(struct bgp_path_attribute *); +int free_community(struct bgp_path_attribute *); +int free_large_community(struct bgp_path_attribute *); int free_mp_reach(struct bgp_path_attribute *); int free_mp_unreach(struct bgp_path_attribute *); @@ -242,8 +247,10 @@ int free_path_attributes(struct bgp_update *update) { pa_free_dispatch[AS_PATH] = &free_as_path; pa_free_dispatch[AGGREGATOR] = &free_aggregator; + pa_free_dispatch[COMMUNITY] = &free_community; pa_free_dispatch[MP_REACH_NLRI] = &free_mp_reach; pa_free_dispatch[MP_UNREACH_NLRI] = &free_mp_unreach; + pa_free_dispatch[LARGE_COMMUNITY] = &free_large_community; //Note the <= for (int attr = ORIGIN; attr <= MAX_ATTRIBUTE; attr++) { @@ -287,6 +294,24 @@ int free_aggregator(struct bgp_path_attribute *attribute) { return 0; } +int free_community(struct bgp_path_attribute *attribute) { + if (attribute->community) { + free(attribute->community->communities); + free(attribute->community); + } + + return 0; +} + +int free_large_community(struct bgp_path_attribute *attribute) { + if (attribute->large_community) { + free(attribute->large_community->communities); + free(attribute->large_community); + } + + return 0; +} + int free_mp_reach(struct bgp_path_attribute *attribute) { struct list_head *i, *tmp; @@ -450,18 +475,18 @@ ssize_t send_open(int fd, uint8_t version, uint16_t asn, uint16_t hold_time, //freed in free_as_path() #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak" -struct as_path *parse_update_as_path(unsigned char **body, uint16_t attr_length) { +struct as_path *parse_update_as_path(unsigned char **body, uint16_t attr_length, int four_octet_asn) { struct as_path *path; struct path_segment *seg; unsigned char **pos; uint16_t length; int n; + int as_size = four_octet_asn ? 4 : 2; // Bytes per AS number pos = body; - //Remove two bytes to account or type and flags of the attrbute length = attr_length; - path = calloc(1, sizeof(*path)); + path = calloc(1, sizeof(*path)); if (!path) { return NULL; @@ -490,8 +515,8 @@ struct as_path *parse_update_as_path(unsigned char **body, uint16_t attr_length) goto error; } - //Check we have enough data for all AS numbers (2 bytes each) - uint16_t as_bytes_needed = (uint16_t)seg->n_as * 2; + //Check we have enough data for all AS numbers + uint16_t as_bytes_needed = (uint16_t)(seg->n_as * as_size); if (as_bytes_needed > length) { goto error; } @@ -504,7 +529,11 @@ struct as_path *parse_update_as_path(unsigned char **body, uint16_t attr_length) } for (n = 0; n < seg->n_as; n++) { - seg->as[n] = uchar_be_to_uint16_inc(pos); + if (four_octet_asn) { + seg->as[n] = uchar_be_to_uint32_inc(pos); + } else { + seg->as[n] = uchar_be_to_uint16_inc(pos); + } } length -= as_bytes_needed; } @@ -526,7 +555,7 @@ struct as_path *parse_update_as_path(unsigned char **body, uint16_t attr_length) } #pragma GCC diagnostic pop -struct aggregator *parse_update_aggregator(unsigned char **body) { +struct aggregator *parse_update_aggregator(unsigned char **body, int four_octet_asn) { struct aggregator *agg; unsigned char **pos; @@ -538,17 +567,73 @@ struct aggregator *parse_update_aggregator(unsigned char **body) { return NULL; } - agg->asn = uchar_be_to_uint16_inc(pos); + if (four_octet_asn) { + agg->asn = uchar_be_to_uint32_inc(pos); + } else { + agg->asn = uchar_be_to_uint16_inc(pos); + } agg->ip = uchar_be_to_uint32_inc(pos); return agg; } +struct community *parse_update_community(unsigned char **body, uint16_t attr_length) { + struct community *comm; + unsigned char **pos = body; + uint16_t count = attr_length / 4; + + comm = calloc(1, sizeof(*comm)); + if (!comm) { + return NULL; + } + + comm->n_communities = count; + if (count > 0) { + comm->communities = malloc(count * sizeof(*comm->communities)); + if (!comm->communities) { + free(comm); + return NULL; + } + for (uint16_t i = 0; i < count; i++) { + comm->communities[i] = uchar_be_to_uint32_inc(pos); + } + } + + return comm; +} + +struct large_community *parse_update_large_community(unsigned char **body, uint16_t attr_length) { + struct large_community *lcomm; + unsigned char **pos = body; + uint16_t count = attr_length / 12; + + lcomm = calloc(1, sizeof(*lcomm)); + if (!lcomm) { + return NULL; + } + + lcomm->n_communities = count; + if (count > 0) { + lcomm->communities = malloc(count * sizeof(*lcomm->communities)); + if (!lcomm->communities) { + free(lcomm); + return NULL; + } + for (uint16_t i = 0; i < count; i++) { + lcomm->communities[i].global_admin = uchar_be_to_uint32_inc(pos); + lcomm->communities[i].local_data_1 = uchar_be_to_uint32_inc(pos); + lcomm->communities[i].local_data_2 = uchar_be_to_uint32_inc(pos); + } + } + + return lcomm; +} + /* Forward declarations for MP_REACH/UNREACH parsing */ struct mp_reach_nlri *parse_mp_reach_nlri(unsigned char **body, uint16_t attr_length); struct mp_unreach_nlri *parse_mp_unreach_nlri(unsigned char **body, uint16_t attr_length); -struct bgp_path_attribute *parse_update_attr(unsigned char **body) { +struct bgp_path_attribute *parse_update_attr(unsigned char **body, int four_octet_asn) { unsigned char **pos = body; struct bgp_path_attribute *attr; @@ -561,7 +646,7 @@ struct bgp_path_attribute *parse_update_attr(unsigned char **body) { attr->flags = uchar_to_uint8_inc(pos); attr->type = uchar_to_uint8_inc(pos); - + //One or two octet length? if (attr->flags & 0x16) { attr->length = uchar_be_to_uint16_inc(pos); @@ -575,7 +660,7 @@ struct bgp_path_attribute *parse_update_attr(unsigned char **body) { break; case AS_PATH: if (attr->length > 0) { - attr->as_path = parse_update_as_path(pos, attr->length); + attr->as_path = parse_update_as_path(pos, attr->length, four_octet_asn); } break; case NEXT_HOP: @@ -591,7 +676,17 @@ struct bgp_path_attribute *parse_update_attr(unsigned char **body) { //Length of zero, type is enough to define this break; case AGGREGATOR: - attr->aggregator = parse_update_aggregator(pos); + attr->aggregator = parse_update_aggregator(pos, four_octet_asn); + break; + case COMMUNITY: + if (attr->length > 0) { + attr->community = parse_update_community(pos, attr->length); + } + break; + case LARGE_COMMUNITY: + if (attr->length > 0) { + attr->large_community = parse_update_large_community(pos, attr->length); + } break; case MP_REACH_NLRI: if (attr->length > 0) { @@ -864,7 +959,7 @@ struct mp_unreach_nlri *parse_mp_unreach_nlri(unsigned char **body, uint16_t att } -int parse_update(struct bgp_msg *message, unsigned char *body) { +int parse_update(struct bgp_msg *message, unsigned char *body, int four_octet_asn) { unsigned char *pos = body; struct ipv4_nlri *nlri; @@ -928,7 +1023,7 @@ int parse_update(struct bgp_msg *message, unsigned char *body) { int n_attr = 0; while(pos < (pa_start + message->update->path_attr_length)) { - attr = parse_update_attr(&pos); + attr = parse_update_attr(&pos, four_octet_asn); if (!attr) { free(message->update); diff --git a/src/bgp_message.h b/src/bgp_message.h index 82b0126..76ec5c6 100644 --- a/src/bgp_message.h +++ b/src/bgp_message.h @@ -40,7 +40,7 @@ struct bgp_open { struct path_segment { uint8_t type; uint8_t n_as; - uint16_t *as; + uint32_t *as; // 4-byte ASNs (RFC 6793) struct list_head list; }; @@ -51,10 +51,26 @@ struct as_path { }; struct aggregator { - uint16_t asn; + uint32_t asn; // 4-byte ASN (RFC 6793) uint32_t ip; }; +struct community { + uint16_t n_communities; + uint32_t *communities; +}; + +struct large_community_value { + uint32_t global_admin; + uint32_t local_data_1; + uint32_t local_data_2; +}; + +struct large_community { + uint16_t n_communities; + struct large_community_value *communities; +}; + struct bgp_path_attribute { uint8_t flags; uint8_t type; @@ -68,6 +84,8 @@ struct bgp_path_attribute { uint32_t local_pref; //Atomic aggregate is length zero, defined only by the type struct aggregator *aggregator; + struct community *community; + struct large_community *large_community; struct mp_reach_nlri *mp_reach; struct mp_unreach_nlri *mp_unreach; }; @@ -134,9 +152,12 @@ enum bgp_update_attrs { LOCAL_PREF, ATOMIC_AGGREGATE, AGGREGATOR, + COMMUNITY, /* ... gap ... */ MP_REACH_NLRI = 14, - MP_UNREACH_NLRI = 15 + MP_UNREACH_NLRI = 15, + /* ... gap ... */ + LARGE_COMMUNITY = 32 }; #define MAX_ATTRIBUTE 255 @@ -223,7 +244,12 @@ struct bgp_msg { -struct bgp_msg *recv_msg(int socket_fd); +/* + * Receive and parse a BGP message from the socket. + * Uses peer->socket.fd for the socket and peer->four_octet_asn for AS_PATH parsing. + */ +struct bgp_peer; +struct bgp_msg *recv_msg(struct bgp_peer *peer); struct bgp_msg *alloc_sent_msg(void); int free_msg(struct bgp_msg *); ssize_t send_open(int fd, uint8_t version, uint16_t asn, uint16_t hold_time, diff --git a/src/bgp_peer.h b/src/bgp_peer.h index f227701..c9e5c53 100644 --- a/src/bgp_peer.h +++ b/src/bgp_peer.h @@ -58,11 +58,14 @@ struct bgp_peer { sds name; unsigned int id; uint8_t *version; - uint16_t *local_asn; - uint16_t peer_asn; + uint32_t *local_asn; + uint32_t peer_asn; uint32_t *local_rid; uint32_t peer_rid; + // 4-byte ASN support (RFC 6793) + int four_octet_asn; // 1 if both peers support 4-byte ASN + sds peer_ip; sds source_ip; diff --git a/src/bgp_print.c b/src/bgp_print.c index a775326..feeb321 100644 --- a/src/bgp_print.c +++ b/src/bgp_print.c @@ -302,8 +302,10 @@ json_t *construct_json_med(struct bgp_path_attribute *); json_t *construct_json_local_pref(struct bgp_path_attribute *); json_t *construct_json_atomic_aggregate(struct bgp_path_attribute *); json_t *construct_json_aggregator(struct bgp_path_attribute *); +json_t *construct_json_community(struct bgp_path_attribute *); json_t *construct_json_mp_reach(struct bgp_path_attribute *); json_t *construct_json_mp_unreach(struct bgp_path_attribute *); +json_t *construct_json_large_community(struct bgp_path_attribute *); json_t *construct_json_update(struct bgp_msg *msg) { struct list_head *i; @@ -317,12 +319,13 @@ json_t *construct_json_update(struct bgp_msg *msg) { "MULTI_EXIT_DISC", "LOCAL_PREF", "ATOMIC_AGGREGATE", - "AGGREGATOR" + "AGGREGATOR", + "COMMUNITY" }; //+1 to account for 0 at the start - json_t *(*path_attr_dispatch[AGGREGATOR + 1]) (struct bgp_path_attribute *) = { + json_t *(*path_attr_dispatch[COMMUNITY + 1]) (struct bgp_path_attribute *) = { NULL, &construct_json_pa_origin, &construct_json_pa_as_path, @@ -330,7 +333,8 @@ json_t *construct_json_update(struct bgp_msg *msg) { &construct_json_med, &construct_json_local_pref, &construct_json_atomic_aggregate, - &construct_json_aggregator + &construct_json_aggregator, + &construct_json_community }; json_t *leaf = json_object(); @@ -348,7 +352,7 @@ json_t *construct_json_update(struct bgp_msg *msg) { //Path attributes json_object_set_new( leaf, "path_attribute_length", json_integer(msg->update->path_attr_length) ); json_t *path_attributes = json_object(); - for (int x = 0; x <= AGGREGATOR; x++) { + for (int x = 0; x <= COMMUNITY; x++) { if (!msg->update->path_attrs[x] || !path_attr_dispatch[x]) { continue; } @@ -378,6 +382,15 @@ json_t *construct_json_update(struct bgp_msg *msg) { ); } + /* Handle LARGE_COMMUNITY (type 32) */ + if (msg->update->path_attrs[LARGE_COMMUNITY]) { + json_object_set_new( + path_attributes, + "LARGE_COMMUNITY", + construct_json_large_community(msg->update->path_attrs[LARGE_COMMUNITY]) + ); + } + json_object_set_new( leaf, "path_attributes", path_attributes ); //NLRI @@ -486,6 +499,53 @@ json_t *construct_json_aggregator(struct bgp_path_attribute *attr) { return aggregator; } +json_t *construct_json_community(struct bgp_path_attribute *attr) { + json_t *communities = json_array(); + + if (!attr->community) { + return communities; + } + + for (uint16_t i = 0; i < attr->community->n_communities; i++) { + uint32_t val = attr->community->communities[i]; + char buf[32]; + + if (val == 0xFFFFFF01) { + json_array_append_new(communities, json_string("NO_EXPORT")); + } else if (val == 0xFFFFFF02) { + json_array_append_new(communities, json_string("NO_ADVERTISE")); + } else if (val == 0xFFFFFF03) { + json_array_append_new(communities, json_string("NO_EXPORT_SUBCONFED")); + } else { + uint16_t high = (uint16_t)(val >> 16); + uint16_t low = (uint16_t)(val & 0xFFFF); + snprintf(buf, sizeof(buf), "%u:%u", high, low); + json_array_append_new(communities, json_string(buf)); + } + } + + return communities; +} + +json_t *construct_json_large_community(struct bgp_path_attribute *attr) { + json_t *communities = json_array(); + + if (!attr->large_community) { + return communities; + } + + for (uint16_t i = 0; i < attr->large_community->n_communities; i++) { + char buf[48]; + snprintf(buf, sizeof(buf), "%u:%u:%u", + attr->large_community->communities[i].global_admin, + attr->large_community->communities[i].local_data_1, + attr->large_community->communities[i].local_data_2); + json_array_append_new(communities, json_string(buf)); + } + + return communities; +} + json_t *construct_json_mp_reach(struct bgp_path_attribute *attr) { json_t *mp = json_object(); struct list_head *i; diff --git a/src/bgp_print.h b/src/bgp_print.h index 2687e5b..8bde7a4 100644 --- a/src/bgp_print.h +++ b/src/bgp_print.h @@ -1,3 +1,6 @@ +#ifndef BGP_PRINT_H +#define BGP_PRINT_H + //Types of output enum bgp_output { BGP_OUT_JSON, @@ -15,3 +18,4 @@ int _set_bgp_output(struct bgp_peer *, enum bgp_output); char *format_msg_json(struct bgp_msg *); char *format_msg_jsonl(struct bgp_msg *); +#endif /* BGP_PRINT_H */ diff --git a/src/log.c b/src/log.c index 5403bb3..7816b7c 100644 --- a/src/log.c +++ b/src/log.c @@ -1,5 +1,6 @@ #include #include +#include #include "sds.h" #include "log.h" @@ -52,29 +53,36 @@ void log_print(enum LOG_LEVEL level, const char *format, ... ) { sds log_prefix(enum LOG_LEVEL level) { sds prefix; + time_t now; + struct tm *tm_info; + char timestamp[32]; + + time(&now); + tm_info = localtime(&now); + strftime(timestamp, sizeof(timestamp), "[%Y-%m-%d %H:%M:%S] ", tm_info); switch (level) { case LOG_NONE: - prefix = sdsempty(); + prefix = sdsnew(timestamp); break; case LOG_ERROR: //Bold Red - prefix = sdsnew("\033[1;31m- "); + prefix = sdscatprintf(sdsempty(), "%s\033[1;31m- ", timestamp); break; case LOG_WARN: //Bold Yellow - prefix = sdsnew("\033[1;33m- "); + prefix = sdscatprintf(sdsempty(), "%s\033[1;33m- ", timestamp); break; case LOG_INFO: //Bold green then reset - prefix = sdsnew("\033[1;32m- \033[0;m"); + prefix = sdscatprintf(sdsempty(), "%s\033[1;32m- \033[0;m", timestamp); break; case LOG_DEBUG: //Bold Blue then reset - prefix = sdsnew("\033[1;36m+ \033[0;m"); + prefix = sdscatprintf(sdsempty(), "%s\033[1;36m+ \033[0;m", timestamp); break; default: - prefix = sdsempty(); + prefix = sdsnew(timestamp); break; } diff --git a/src/main.c b/src/main.c index 198aa98..ce9be1a 100644 --- a/src/main.c +++ b/src/main.c @@ -22,8 +22,8 @@ struct cmdline_opts { char *peer; char *name; sds source_ip; - uint16_t peer_asn; - uint16_t local_asn; + uint32_t peer_asn; + uint32_t local_asn; uint32_t local_rid; enum bgp_output format; int reconnect_enabled; @@ -60,7 +60,7 @@ int main(int argc, char **argv) { //Parse the peers for(int x = optind; x < argc; x++) { - uint16_t asn; + uint32_t asn; int bgp_peer_id; //Split the peer into IP:ASN sds *tokens; @@ -84,7 +84,7 @@ int main(int argc, char **argv) { if (n_tokens == 3) { peer_name = sdsdup(tokens[2]); } - asn = (uint16_t) strtol(tokens[1], NULL, 10); + asn = (uint32_t) strtoul(tokens[1], NULL, 10); //Create the peer and keep track of the used ID bgp_peer_id = create_bgp_peer( @@ -198,7 +198,7 @@ struct cmdline_opts parse_cmdline(int argc, char **argv) { option_return.source_ip = sdsnew(optarg); break; case 'a': - option_return.local_asn = (uint16_t) strtol(optarg, NULL, 10); + option_return.local_asn = (uint32_t) strtoul(optarg, NULL, 10); break; case 'r': option_return.local_rid = (uint16_t) strtol(optarg, NULL, 10); diff --git a/tests/test_bgp_message.c b/tests/test_bgp_message.c index b9ee69e..a9ec548 100644 --- a/tests/test_bgp_message.c +++ b/tests/test_bgp_message.c @@ -15,8 +15,26 @@ #include "testhelp.h" #include "../src/bgp_message.h" #include "../src/bgp_capability.h" +#include "../src/bgp_peer.h" #include "../src/list.h" +/* + * Helper to create a minimal peer struct for testing. + * Sets socket.fd and four_octet_asn, caller must free. + */ +static struct bgp_peer *create_test_peer(int fd, int four_octet_asn) { + struct bgp_peer *peer = calloc(1, sizeof(*peer)); + if (peer) { + peer->socket.fd = fd; + peer->four_octet_asn = four_octet_asn; + } + return peer; +} + +static void free_test_peer(struct bgp_peer *peer) { + free(peer); +} + /* * Helper function to create a socket pair and write test data * Returns the read end of the socket pair, or -1 on error @@ -66,8 +84,10 @@ void test_keepalive_message(void) { test_cond("Created test socket for KEEPALIVE", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns non-NULL for valid KEEPALIVE", parsed != NULL); @@ -108,8 +128,10 @@ void test_open_message(void) { test_cond("Created test socket for OPEN", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns non-NULL for valid OPEN", parsed != NULL); @@ -147,8 +169,10 @@ void test_notification_message(void) { test_cond("Created test socket for NOTIFICATION", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns non-NULL for valid NOTIFICATION", parsed != NULL); @@ -182,8 +206,10 @@ void test_update_empty(void) { test_cond("Created test socket for empty UPDATE", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns non-NULL for empty UPDATE", parsed != NULL); @@ -257,8 +283,10 @@ void test_update_with_nlri(void) { test_cond("Created test socket for UPDATE with NLRI", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns non-NULL for UPDATE with NLRI", parsed != NULL); @@ -337,8 +365,10 @@ void test_update_with_withdrawn(void) { test_cond("Created test socket for UPDATE with withdrawn", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns non-NULL for UPDATE with withdrawn", parsed != NULL); @@ -379,8 +409,10 @@ void test_invalid_header_marker(void) { test_cond("Created test socket for invalid marker", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns NULL for invalid marker", parsed == NULL); @@ -398,8 +430,10 @@ void test_invalid_message_type(void) { test_cond("Created test socket for invalid type 0", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns NULL for type 0", parsed == NULL); @@ -412,8 +446,10 @@ void test_invalid_message_type(void) { test_cond("Created test socket for invalid type 6", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns NULL for type 6", parsed == NULL); @@ -439,8 +475,10 @@ void test_update_invalid_withdrawn_length(void) { test_cond("Created test socket for invalid withdrawn length", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns NULL for invalid withdrawn length", parsed == NULL); @@ -556,8 +594,10 @@ void test_notification_round_trip(void) { close(fds[1]); // Close write end so recv_msg gets EOF after data // Parse it with recv_msg - struct bgp_msg *parsed = recv_msg(fds[0]); + struct bgp_peer *test_peer = create_test_peer(fds[0], 0); + struct bgp_msg *parsed = recv_msg(test_peer); close(fds[0]); + free_test_peer(test_peer); test_cond("recv_msg parses sent NOTIFICATION", parsed != NULL); @@ -595,8 +635,10 @@ void test_nlri_invalid_prefix_length(void) { test_cond("Created test socket for invalid NLRI prefix length", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); // The UPDATE should parse but skip the invalid NLRI // (our fix returns NULL from parse_ipv4_nlri, which breaks the loop) @@ -866,8 +908,10 @@ void test_send_open_round_trip(void) { send_open(fds[1], 4, 65001, 90, 0xC0A80001, caps); close(fds[1]); - struct bgp_msg *parsed = recv_msg(fds[0]); + struct bgp_peer *test_peer = create_test_peer(fds[0], 0); + struct bgp_msg *parsed = recv_msg(test_peer); close(fds[0]); + free_test_peer(test_peer); test_cond("recv_msg parses OPEN with caps", parsed != NULL); @@ -953,8 +997,10 @@ void test_update_mp_reach_ipv6(void) { test_cond("Created test socket for MP_REACH_NLRI IPv6", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns non-NULL", parsed != NULL); @@ -1035,8 +1081,10 @@ void test_update_mp_unreach_ipv6(void) { test_cond("Created test socket for MP_UNREACH_NLRI IPv6", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns non-NULL", parsed != NULL); @@ -1143,8 +1191,10 @@ void test_update_mp_reach_dual_nexthop(void) { test_cond("Created test socket for dual next hop", fd >= 0); if (fd >= 0) { - struct bgp_msg *parsed = recv_msg(fd); + struct bgp_peer *peer = create_test_peer(fd, 0); + struct bgp_msg *parsed = recv_msg(peer); close(fd); + free_test_peer(peer); test_cond("recv_msg returns non-NULL", parsed != NULL);