From 0c21cc2916b44ceac3611fcf358a6e3a3d59d658 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Tue, 10 Sep 2024 18:36:07 +0200 Subject: [PATCH 1/6] bgpd, lib: generate aspath json string on demand When using the 'show bgp ipv4 json detail' against a full route, the BGP memory consumed increases a lot, and is never restored. Before the show: > root@dut-orwell-nianticvf:~# ps -aux | grep bgpd > root 12263 10.8 10.1 1213920 1033452 ? Ssl 10:43 1:42 /usr/bin/bgpd -A 127.0.0.1 -M snmp -M rpki -M bmp After the show: > root@dut-orwell-nianticvf:~# ps -aux | grep bgpd > root 11772 4.9 19.3 2150668 1970548 ? Ssl 08:59 2:09 /usr/bin/bgpd -A 127.0.0.1 -M snmp -M rpki -M bmp This is not a memory leak, since applying multiple times the command does not increase the memory. What happens is that some internal structures of BGP store a json structure: AsPath, Communities, Large Communities. This series of commits intends to do the following for the following objects: ASPaths, communities and large communities: - replace the usage of the internal json structure by a dynamically built json object. - remove the no more used json object referenced in the BGP internals. That first commit addresses the case with ASpaths: instead of relying on the internal json structure, the json aspath objects are allocated and freed at usage. The implementation relies on the json_object_set_serializer() utility. The function overrides the json buffer build at display time. Do fallback to the original code when this utility is not present in json-c library (aka < 0.13). Signed-off-by: Philippe Guibert --- bgpd/bgp_aspath.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++- bgpd/bgp_aspath.h | 1 + bgpd/bgp_route.c | 8 +++--- lib/asn.c | 35 ++++++++++++++++++-------- lib/asn.h | 3 ++- 5 files changed, 93 insertions(+), 17 deletions(-) diff --git a/bgpd/bgp_aspath.c b/bgpd/bgp_aspath.c index 4c1615a5c625..bf50186bf2cc 100644 --- a/bgpd/bgp_aspath.c +++ b/bgpd/bgp_aspath.c @@ -606,7 +606,7 @@ static void aspath_make_str_count(struct aspath *as, bool make_json) for (i = 0; i < seg->length; i++) { if (make_json) asn_asn2json_array(jseg_list, seg->as[i], - as->asnotation); + as->asnotation, NULL, false); len += snprintfrr(str_buf + len, str_size - len, ASN_FORMAT(as->asnotation), &seg->as[i]); @@ -662,6 +662,67 @@ void aspath_str_update(struct aspath *as, bool make_json) aspath_make_str_count(as, make_json); } +#if JSON_C_MINOR_VERSION >= 13 && JSON_C_MAJOR_VERSION >= 0 +static int aspath_json_string(struct json_object *jso, struct printbuf *pb, + int level, int flags) +{ + struct aspath *as; + struct assegment *seg; + int count_seg = 0, i; + char num_str[12]; + const char *seg_type; + + + as = json_object_get_userdata(jso); + assert(as); + seg = as->segments; + if (seg == NULL) + printbuf_strappend(pb, "{\"string\":\"Local\",\"segments\":["); + else { + printbuf_strappend(pb, "{\"string\":\""); + printbuf_memappend(pb, as->str, strlen(as->str)); + printbuf_strappend(pb, "\",\"segments\":["); + } + while (seg) { + if (count_seg) + printbuf_strappend(pb, ","); + printbuf_strappend(pb, "{\"type\":\""); + seg_type = aspath_segment_type_str[seg->type]; + printbuf_memappend(pb, seg_type, strlen(seg_type)); + printbuf_strappend(pb, "\",\"list\":["); + + for (i = 0; i < seg->length; i++) { + asn_asn2json_array(NULL, seg->as[i], as->asnotation, pb, + true); + if (i < (seg->length - 1)) + printbuf_strappend(pb, ","); + } + printbuf_strappend(pb, "]}"); + seg = seg->next; + count_seg++; + } + printbuf_strappend(pb, "],\"length\":"); + snprintf(num_str, sizeof(num_str), "%d}", aspath_count_hops(as)); + return printbuf_memappend(pb, num_str, strlen(num_str)); +} +#endif + +json_object *aspath_get_json(struct aspath *aspath) +{ + json_object *json_aspath = NULL; + +#if JSON_C_MINOR_VERSION >= 13 && JSON_C_MAJOR_VERSION >= 0 + json_aspath = json_object_new_object(); + json_object_set_serializer(json_aspath, aspath_json_string, aspath, + NULL); +#else + if (!aspath->json) + aspath_str_update(aspath, true); + json_aspath = json_object_lock(aspath->json); +#endif + return json_aspath; +} + /* Intern allocated AS path. */ struct aspath *aspath_intern(struct aspath *aspath) { diff --git a/bgpd/bgp_aspath.h b/bgpd/bgp_aspath.h index f7e57fd66dda..cf2b66cfb3e8 100644 --- a/bgpd/bgp_aspath.h +++ b/bgpd/bgp_aspath.h @@ -112,6 +112,7 @@ extern struct aspath *aspath_str2aspath(const char *str, extern void aspath_str_update(struct aspath *as, bool make_json); extern void aspath_free(struct aspath *aspath); extern struct aspath *aspath_intern(struct aspath *aspath); +extern json_object *aspath_get_json(struct aspath *aspath); extern void aspath_unintern(struct aspath **aspath); extern const char *aspath_print(struct aspath *aspath); extern void aspath_print_vty(struct vty *vty, struct aspath *aspath); diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index f28c9adda218..83b537aa3e90 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -10548,6 +10548,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, json_object *json_peer = NULL; json_object *json_string = NULL; json_object *json_adv_to = NULL; + json_object *json_aspath = NULL; int first = 0; struct listnode *node, *nnode; struct peer *peer; @@ -10687,11 +10688,8 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, /* Line1 display AS-path, Aggregator */ if (attr->aspath) { if (json_paths) { - if (!attr->aspath->json) - aspath_str_update(attr->aspath, true); - json_object_lock(attr->aspath->json); - json_object_object_add(json_path, "aspath", - attr->aspath->json); + json_aspath = aspath_get_json(attr->aspath); + json_object_object_add(json_path, "aspath", json_aspath); } else { if (attr->aspath->segments) vty_out(vty, " %s", attr->aspath->str); diff --git a/lib/asn.c b/lib/asn.c index 4042f5846cc1..ad1b78885727 100644 --- a/lib/asn.c +++ b/lib/asn.c @@ -128,13 +128,17 @@ static bool asn_str2asn_internal(const char *asstring, as_t *asn, return ret; } -static void asn_asn2asdot(as_t asn, char *asstring, size_t len) +static void asn_asn2asdot(as_t asn, char *asstring, size_t len, + bool quotation_mark) { uint16_t low, high; high = (asn >> 16) & 0xffff; low = asn & 0xffff; - snprintf(asstring, len, "%hu.%hu", high, low); + if (quotation_mark) + snprintf(asstring, len, "\"%hu.%hu\"", high, low); + else + snprintf(asstring, len, "%hu.%hu", high, low); } bool asn_str2asn(const char *asstring, as_t *asn) @@ -202,23 +206,34 @@ void asn_asn2json(json_object *json, const char *attr, ((asnotation == ASNOTATION_DOT) && asn < UINT16_MAX)) json_object_int_add(json, attr, asn); else { - asn_asn2asdot(asn, as_str, sizeof(as_str)); + asn_asn2asdot(asn, as_str, sizeof(as_str), false); json_object_string_add(json, attr, as_str); } } void asn_asn2json_array(json_object *jseg_list, as_t asn, - enum asnotation_mode asnotation) + enum asnotation_mode asnotation, struct printbuf *pb, + bool incremental_print) { - static char as_str[ASN_STRING_MAX_SIZE]; + static char as_str[ASN_STRING_MAX_SIZE + 2]; + const char *const_str = as_str; if ((asnotation == ASNOTATION_PLAIN) || ((asnotation == ASNOTATION_DOT) && asn < UINT16_MAX)) - json_object_array_add(jseg_list, - json_object_new_int64(asn)); + if (incremental_print) { + snprintf(as_str, sizeof(as_str), "%u", asn); + printbuf_memappend(pb, const_str, strlen(const_str)); + } else + json_object_array_add(jseg_list, + json_object_new_int64(asn)); else { - asn_asn2asdot(asn, as_str, sizeof(as_str)); - json_array_string_add(jseg_list, as_str); + if (incremental_print) { + asn_asn2asdot(asn, as_str, sizeof(as_str), true); + printbuf_memappend(pb, const_str, strlen(const_str)); + } else { + asn_asn2asdot(asn, as_str, sizeof(as_str), false); + json_array_string_add(jseg_list, as_str); + } } } @@ -229,7 +244,7 @@ char *asn_asn2string(const as_t *asn, char *buf, size_t len, ((asnotation == ASNOTATION_DOT) && *asn < UINT16_MAX)) snprintf(buf, len, "%u", *asn); else - asn_asn2asdot(*asn, buf, len); + asn_asn2asdot(*asn, buf, len, false); return buf; } diff --git a/lib/asn.h b/lib/asn.h index a7394fa52bc6..f66d8a9ef60b 100644 --- a/lib/asn.h +++ b/lib/asn.h @@ -50,7 +50,8 @@ extern bool asn_str2asn_notation(const char *asstring, as_t *asn, enum asnotation_mode *asnotation); extern const char *asn_mode2str(enum asnotation_mode asnotation); void asn_asn2json_array(json_object *jseg_list, as_t asn, - enum asnotation_mode asnotation); + enum asnotation_mode asnotation, struct printbuf *pb, + bool incremental_print); void asn_asn2json(json_object *jseg_list, const char *attr, as_t asn, enum asnotation_mode asnotation); extern char *asn_asn2string(const as_t *as, char *buf, size_t len, From c446d3a4f86c3a0b153ec6bbb2be19e8764840d2 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Tue, 3 Sep 2024 15:05:41 +0200 Subject: [PATCH 2/6] bgpd: move community list build in a separate function Create the community_json_list() function, that stores the community list output in a passed buffer, and optionally in the json community list passed array. This commit does not have any functional impact. Signed-off-by: Philippe Guibert --- bgpd/bgp_community.c | 266 +++++++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 125 deletions(-) diff --git a/bgpd/bgp_community.c b/bgpd/bgp_community.c index 602c1437af11..c7015ca699f4 100644 --- a/bgpd/bgp_community.c +++ b/bgpd/bgp_community.c @@ -164,120 +164,22 @@ struct community *community_uniq_sort(struct community *com) return new; } -/* Convert communities attribute to string. - - For Well-known communities value, below keyword is used. - - 0xFFFF0000 "graceful-shutdown" - 0xFFFF0001 "accept-own" - 0xFFFF0002 "route-filter-translated-v4" - 0xFFFF0003 "route-filter-v4" - 0xFFFF0004 "route-filter-translated-v6" - 0xFFFF0005 "route-filter-v6" - 0xFFFF0006 "llgr-stale" - 0xFFFF0007 "no-llgr" - 0xFFFF0008 "accept-own-nexthop" - 0xFFFF029A "blackhole" - 0xFFFFFF01 "no-export" - 0xFFFFFF02 "no-advertise" - 0xFFFFFF03 "local-AS" - 0xFFFFFF04 "no-peer" - - For other values, "AS:VAL" format is used. */ -static void set_community_string(struct community *com, bool make_json, - bool translate_alias) +/* Convert communities attribute to string and optionally add json objects + * into the pased json_community_list string + */ +static void community_json_list(struct community *com, + json_object *json_community_list, char *str, + int len, bool translate_alias) { int i; - char *str; - int len; int first; uint32_t comval; + json_object *json_string = NULL; uint16_t as; uint16_t val; - json_object *json_community_list = NULL; - json_object *json_string = NULL; + char buf[32]; + const char *com2alias; - if (!com) - return; - - if (make_json) { - com->json = json_object_new_object(); - json_community_list = json_object_new_array(); - } - - /* When communities attribute is empty. */ - if (com->size == 0) { - str = XMALLOC(MTYPE_COMMUNITY_STR, 1); - str[0] = '\0'; - - if (make_json) { - json_object_string_add(com->json, "string", ""); - json_object_object_add(com->json, "list", - json_community_list); - } - com->str = str; - return; - } - - /* Memory allocation is time consuming work. So we calculate - required string length first. */ - len = 0; - - for (i = 0; i < com->size; i++) { - memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); - comval = ntohl(comval); - - switch (comval) { - case COMMUNITY_GSHUT: - len += strlen(" graceful-shutdown"); - break; - case COMMUNITY_ACCEPT_OWN: - len += strlen(" accept-own"); - break; - case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: - len += strlen(" route-filter-translated-v4"); - break; - case COMMUNITY_ROUTE_FILTER_v4: - len += strlen(" route-filter-v4"); - break; - case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: - len += strlen(" route-filter-translated-v6"); - break; - case COMMUNITY_ROUTE_FILTER_v6: - len += strlen(" route-filter-v6"); - break; - case COMMUNITY_LLGR_STALE: - len += strlen(" llgr-stale"); - break; - case COMMUNITY_NO_LLGR: - len += strlen(" no-llgr"); - break; - case COMMUNITY_ACCEPT_OWN_NEXTHOP: - len += strlen(" accept-own-nexthop"); - break; - case COMMUNITY_BLACKHOLE: - len += strlen(" blackhole"); - break; - case COMMUNITY_NO_EXPORT: - len += strlen(" no-export"); - break; - case COMMUNITY_NO_ADVERTISE: - len += strlen(" no-advertise"); - break; - case COMMUNITY_LOCAL_AS: - len += strlen(" local-AS"); - break; - case COMMUNITY_NO_PEER: - len += strlen(" no-peer"); - break; - default: - len = BUFSIZ; - break; - } - } - - /* Allocate memory. */ - str = XCALLOC(MTYPE_COMMUNITY_STR, len); first = 1; /* Fill in string. */ @@ -293,7 +195,7 @@ static void set_community_string(struct community *com, bool make_json, switch (comval) { case COMMUNITY_GSHUT: strlcat(str, "graceful-shutdown", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "gracefulShutdown"); json_object_array_add(json_community_list, @@ -302,7 +204,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_ACCEPT_OWN: strlcat(str, "accept-own", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "acceptown"); json_object_array_add(json_community_list, @@ -311,7 +213,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: strlcat(str, "route-filter-translated-v4", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "routeFilterTranslatedV4"); json_object_array_add(json_community_list, @@ -320,7 +222,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_ROUTE_FILTER_v4: strlcat(str, "route-filter-v4", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "routeFilterV4"); json_object_array_add(json_community_list, @@ -329,7 +231,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: strlcat(str, "route-filter-translated-v6", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "routeFilterTranslatedV6"); json_object_array_add(json_community_list, @@ -338,7 +240,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_ROUTE_FILTER_v6: strlcat(str, "route-filter-v6", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "routeFilterV6"); json_object_array_add(json_community_list, @@ -347,7 +249,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_LLGR_STALE: strlcat(str, "llgr-stale", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "llgrStale"); json_object_array_add(json_community_list, @@ -356,7 +258,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_NO_LLGR: strlcat(str, "no-llgr", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "noLlgr"); json_object_array_add(json_community_list, @@ -365,7 +267,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_ACCEPT_OWN_NEXTHOP: strlcat(str, "accept-own-nexthop", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "acceptownnexthop"); json_object_array_add(json_community_list, @@ -374,7 +276,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_BLACKHOLE: strlcat(str, "blackhole", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string( "blackhole"); json_object_array_add(json_community_list, @@ -383,7 +285,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_NO_EXPORT: strlcat(str, "no-export", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string("noExport"); json_object_array_add(json_community_list, @@ -392,7 +294,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_NO_ADVERTISE: strlcat(str, "no-advertise", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string("noAdvertise"); json_object_array_add(json_community_list, @@ -401,7 +303,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_LOCAL_AS: strlcat(str, "local-AS", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string("localAs"); json_object_array_add(json_community_list, json_string); @@ -409,7 +311,7 @@ static void set_community_string(struct community *com, bool make_json, break; case COMMUNITY_NO_PEER: strlcat(str, "no-peer", len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string("noPeer"); json_object_array_add(json_community_list, json_string); @@ -418,13 +320,12 @@ static void set_community_string(struct community *com, bool make_json, default: as = CHECK_FLAG((comval >> 16), 0xFFFF); val = CHECK_FLAG(comval, 0xFFFF); - char buf[32]; snprintf(buf, sizeof(buf), "%u:%d", as, val); - const char *com2alias = - translate_alias ? bgp_community2alias(buf) : buf; + com2alias = translate_alias ? bgp_community2alias(buf) + : buf; strlcat(str, com2alias, len); - if (make_json) { + if (json_community_list) { json_string = json_object_new_string(com2alias); json_object_array_add(json_community_list, json_string); @@ -432,6 +333,121 @@ static void set_community_string(struct community *com, bool make_json, break; } } +} + +/* Convert communities attribute to string. + + For Well-known communities value, below keyword is used. + + 0xFFFF0000 "graceful-shutdown" + 0xFFFF0001 "accept-own" + 0xFFFF0002 "route-filter-translated-v4" + 0xFFFF0003 "route-filter-v4" + 0xFFFF0004 "route-filter-translated-v6" + 0xFFFF0005 "route-filter-v6" + 0xFFFF0006 "llgr-stale" + 0xFFFF0007 "no-llgr" + 0xFFFF0008 "accept-own-nexthop" + 0xFFFF029A "blackhole" + 0xFFFFFF01 "no-export" + 0xFFFFFF02 "no-advertise" + 0xFFFFFF03 "local-AS" + 0xFFFFFF04 "no-peer" + + For other values, "AS:VAL" format is used. */ +static void set_community_string(struct community *com, bool make_json, + bool translate_alias) +{ + int i; + char *str; + int len; + uint32_t comval; + json_object *json_community_list = NULL; + + if (!com) + return; + + if (make_json) { + com->json = json_object_new_object(); + json_community_list = json_object_new_array(); + } + + /* When communities attribute is empty. */ + if (com->size == 0) { + str = XMALLOC(MTYPE_COMMUNITY_STR, 1); + str[0] = '\0'; + + if (make_json) { + json_object_string_add(com->json, "string", ""); + json_object_object_add(com->json, "list", + json_community_list); + } + com->str = str; + return; + } + + /* Memory allocation is time consuming work. So we calculate + required string length first. */ + len = 0; + + for (i = 0; i < com->size; i++) { + memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); + comval = ntohl(comval); + + switch (comval) { + case COMMUNITY_GSHUT: + len += strlen(" graceful-shutdown"); + break; + case COMMUNITY_ACCEPT_OWN: + len += strlen(" accept-own"); + break; + case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: + len += strlen(" route-filter-translated-v4"); + break; + case COMMUNITY_ROUTE_FILTER_v4: + len += strlen(" route-filter-v4"); + break; + case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: + len += strlen(" route-filter-translated-v6"); + break; + case COMMUNITY_ROUTE_FILTER_v6: + len += strlen(" route-filter-v6"); + break; + case COMMUNITY_LLGR_STALE: + len += strlen(" llgr-stale"); + break; + case COMMUNITY_NO_LLGR: + len += strlen(" no-llgr"); + break; + case COMMUNITY_ACCEPT_OWN_NEXTHOP: + len += strlen(" accept-own-nexthop"); + break; + case COMMUNITY_BLACKHOLE: + len += strlen(" blackhole"); + break; + case COMMUNITY_NO_EXPORT: + len += strlen(" no-export"); + break; + case COMMUNITY_NO_ADVERTISE: + len += strlen(" no-advertise"); + break; + case COMMUNITY_LOCAL_AS: + len += strlen(" local-AS"); + break; + case COMMUNITY_NO_PEER: + len += strlen(" no-peer"); + break; + default: + len = BUFSIZ; + break; + } + } + + /* Allocate memory. */ + str = XCALLOC(MTYPE_COMMUNITY_STR, len); + + community_json_list(com, make_json ? json_community_list : NULL, str, + len, translate_alias); if (make_json) { json_object_string_add(com->json, "string", str); From d5ed66fc7d03768673037c1fcfaacfadaad35768 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Wed, 11 Sep 2024 13:54:43 +0200 Subject: [PATCH 3/6] bgpd: add community API to return json object The community_get_json() API is used to return a json object. At display time, this object will use the community_list_json_to_string() function to handle the output. Signed-off-by: Philippe Guibert --- bgpd/bgp_community.c | 156 ++++++++++++++++++++++++++++++++++--------- bgpd/bgp_community.h | 1 + bgpd/bgp_route.c | 12 ++-- 3 files changed, 130 insertions(+), 39 deletions(-) diff --git a/bgpd/bgp_community.c b/bgpd/bgp_community.c index c7015ca699f4..8a5d81155da5 100644 --- a/bgpd/bgp_community.c +++ b/bgpd/bgp_community.c @@ -169,7 +169,8 @@ struct community *community_uniq_sort(struct community *com) */ static void community_json_list(struct community *com, json_object *json_community_list, char *str, - int len, bool translate_alias) + int len, bool translate_alias, + struct printbuf *pb) { int i; int first; @@ -194,8 +195,12 @@ static void community_json_list(struct community *com, switch (comval) { case COMMUNITY_GSHUT: - strlcat(str, "graceful-shutdown", len); - if (json_community_list) { + if (str) + strlcat(str, "graceful-shutdown", len); + if (pb) + sprintbuf(pb, "%s\"gracefulShutdown\"", + i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "gracefulShutdown"); json_object_array_add(json_community_list, @@ -203,8 +208,11 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_ACCEPT_OWN: - strlcat(str, "accept-own", len); - if (json_community_list) { + if (str) + strlcat(str, "accept-own", len); + if (pb) + sprintbuf(pb, "%s\"acceptOwn\"", i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "acceptown"); json_object_array_add(json_community_list, @@ -212,8 +220,12 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: - strlcat(str, "route-filter-translated-v4", len); - if (json_community_list) { + if (str) + strlcat(str, "route-filter-translated-v4", len); + if (pb) + sprintbuf(pb, "%s\"routeFilterTranslateV4\"", + i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "routeFilterTranslatedV4"); json_object_array_add(json_community_list, @@ -221,8 +233,12 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_ROUTE_FILTER_v4: - strlcat(str, "route-filter-v4", len); - if (json_community_list) { + if (str) + strlcat(str, "route-filter-v4", len); + if (pb) + sprintbuf(pb, "%s\"routeFilterV4\"", + i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "routeFilterV4"); json_object_array_add(json_community_list, @@ -230,8 +246,12 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: - strlcat(str, "route-filter-translated-v6", len); - if (json_community_list) { + if (str) + strlcat(str, "route-filter-translated-v6", len); + if (pb) + sprintbuf(pb, "%s\"routeFilterTranslatedV6\"", + i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "routeFilterTranslatedV6"); json_object_array_add(json_community_list, @@ -239,8 +259,12 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_ROUTE_FILTER_v6: - strlcat(str, "route-filter-v6", len); - if (json_community_list) { + if (str) + strlcat(str, "route-filter-v6", len); + if (pb) + sprintbuf(pb, "%s\"routeFilterV6\"", + i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "routeFilterV6"); json_object_array_add(json_community_list, @@ -248,8 +272,11 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_LLGR_STALE: - strlcat(str, "llgr-stale", len); - if (json_community_list) { + if (str) + strlcat(str, "llgr-stale", len); + if (pb) + sprintbuf(pb, "%s\"llgrStale\"", i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "llgrStale"); json_object_array_add(json_community_list, @@ -257,8 +284,11 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_NO_LLGR: - strlcat(str, "no-llgr", len); - if (json_community_list) { + if (str) + strlcat(str, "no-llgr", len); + if (pb) + sprintbuf(pb, "%s\"noLlgr\"", i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "noLlgr"); json_object_array_add(json_community_list, @@ -266,8 +296,12 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_ACCEPT_OWN_NEXTHOP: - strlcat(str, "accept-own-nexthop", len); - if (json_community_list) { + if (str) + strlcat(str, "accept-own-nexthop", len); + if (pb) + sprintbuf(pb, "%s\"acceptownnexthop\"", + i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "acceptownnexthop"); json_object_array_add(json_community_list, @@ -275,8 +309,11 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_BLACKHOLE: - strlcat(str, "blackhole", len); - if (json_community_list) { + if (str) + strlcat(str, "blackhole", len); + if (pb) + sprintbuf(pb, "%s\"blackhole\"", i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string( "blackhole"); json_object_array_add(json_community_list, @@ -284,8 +321,11 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_NO_EXPORT: - strlcat(str, "no-export", len); - if (json_community_list) { + if (str) + strlcat(str, "no-export", len); + if (pb) + sprintbuf(pb, "%s\"noExport\"", i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string("noExport"); json_object_array_add(json_community_list, @@ -293,8 +333,11 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_NO_ADVERTISE: - strlcat(str, "no-advertise", len); - if (json_community_list) { + if (str) + strlcat(str, "no-advertise", len); + if (pb) + sprintbuf(pb, "%s\"noAdvertise\"", i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string("noAdvertise"); json_object_array_add(json_community_list, @@ -302,16 +345,22 @@ static void community_json_list(struct community *com, } break; case COMMUNITY_LOCAL_AS: - strlcat(str, "local-AS", len); - if (json_community_list) { + if (str) + strlcat(str, "local-AS", len); + if (pb) + sprintbuf(pb, "%s\"localAs\"", i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string("localAs"); json_object_array_add(json_community_list, json_string); } break; case COMMUNITY_NO_PEER: - strlcat(str, "no-peer", len); - if (json_community_list) { + if (str) + strlcat(str, "no-peer", len); + if (pb) + sprintbuf(pb, "%s\"noPeer\"", i ? "," : ""); + else if (json_community_list) { json_string = json_object_new_string("noPeer"); json_object_array_add(json_community_list, json_string); @@ -324,8 +373,14 @@ static void community_json_list(struct community *com, com2alias = translate_alias ? bgp_community2alias(buf) : buf; - strlcat(str, com2alias, len); - if (json_community_list) { + if (str) + strlcat(str, com2alias, len); + if (pb) + sprintbuf(pb, "%s\"%s\"", i ? "," : "", + translate_alias + ? bgp_community2alias(buf) + : buf); + else if (json_community_list) { json_string = json_object_new_string(com2alias); json_object_array_add(json_community_list, json_string); @@ -447,7 +502,7 @@ static void set_community_string(struct community *com, bool make_json, str = XCALLOC(MTYPE_COMMUNITY_STR, len); community_json_list(com, make_json ? json_community_list : NULL, str, - len, translate_alias); + len, translate_alias, NULL); if (make_json) { json_object_string_add(com->json, "string", str); @@ -935,6 +990,27 @@ static void bgp_aggr_community_prepare(struct hash_bucket *hb, void *arg) *aggr_community = community_dup(hb_community); } +#if JSON_C_MINOR_VERSION >= 13 && JSON_C_MAJOR_VERSION >= 0 +static int community_list_json_to_string(struct json_object *jso, + struct printbuf *pb, int level, + int flags) +{ + struct community *com; + + com = json_object_get_userdata(jso); + assert(com); + if (!com->str) + return 0; + printbuf_strappend(pb, "{\"string\":\""); + printbuf_memappend(pb, com->str, strlen(com->str)); + printbuf_strappend(pb, "\",\"list\":["); + + community_json_list(com, NULL, NULL, 0, true, pb); + printbuf_strappend(pb, "]}"); + return 0; +} +#endif + void bgp_aggr_community_remove(void *arg) { struct community *community = arg; @@ -1056,3 +1132,19 @@ void bgp_remove_comm_from_aggregate_hash(struct bgp_aggregate *aggregate, } } } + +json_object *community_get_json(struct community *com) +{ + json_object *json_community_list = NULL; + +#if JSON_C_MINOR_VERSION >= 13 && JSON_C_MAJOR_VERSION >= 0 + json_community_list = json_object_new_object(); + json_object_set_serializer(json_community_list, + community_list_json_to_string, com, NULL); +#else + if (!com->json) + community_str(com, true, true); + json_community_list = json_object_lock(com->json); +#endif + return json_community_list; +} diff --git a/bgpd/bgp_community.h b/bgpd/bgp_community.h index 7c7e7af4d29d..85d7b384434e 100644 --- a/bgpd/bgp_community.h +++ b/bgpd/bgp_community.h @@ -60,6 +60,7 @@ extern void community_free(struct community **comm); extern struct community *community_uniq_sort(struct community *com); extern struct community *community_parse(uint32_t *pnt, unsigned short length); extern struct community *community_intern(struct community *com); +extern json_object *community_get_json(struct community *com); extern void community_unintern(struct community **com); extern char *community_str(struct community *com, bool make_json, bool translate_alias); diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 83b537aa3e90..9e39d14dd962 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -10549,6 +10549,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, json_object *json_string = NULL; json_object *json_adv_to = NULL; json_object *json_aspath = NULL; + json_object *json_community = NULL; int first = 0; struct listnode *node, *nnode; struct peer *peer; @@ -11247,13 +11248,10 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, /* Line 4 display Community */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) { if (json_paths) { - if (!bgp_attr_get_community(attr)->json) - community_str(bgp_attr_get_community(attr), - true, true); - json_object_lock(bgp_attr_get_community(attr)->json); - json_object_object_add( - json_path, "community", - bgp_attr_get_community(attr)->json); + json_community = community_get_json( + bgp_attr_get_community(attr)); + json_object_object_add(json_path, "community", + json_community); } else { vty_out(vty, " Community: %s\n", bgp_attr_get_community(attr)->str); From 756d7238f31048868c2550f42896110847e829b1 Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Tue, 3 Sep 2024 16:41:48 +0200 Subject: [PATCH 4/6] bgpd: move large community list build in a separate function Create the lcommunity_json_list() function, that returns the large community list output in a returned buffer, and optionally in the json large community list passed array. This commit does not have any functional impact. Signed-off-by: Philippe Guibert --- bgpd/bgp_lcommunity.c | 77 ++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/bgpd/bgp_lcommunity.c b/bgpd/bgp_lcommunity.c index 1b8c22a5038c..ff1e350f84f2 100644 --- a/bgpd/bgp_lcommunity.c +++ b/bgpd/bgp_lcommunity.c @@ -161,43 +161,25 @@ struct lcommunity *lcommunity_merge(struct lcommunity *lcom1, return lcom1; } -static void set_lcommunity_string(struct lcommunity *lcom, bool make_json, +/* 3 32-bit integers, 2 colons, and a space */ +#define LCOMMUNITY_STRLEN (10 * 3 + 2 + 1) + +/* Convert large communities attribute to string and optionally add json objects + * into the pased json_community_list string + */ +static char *lcommunity_json_list(struct lcommunity *lcom, + json_object *json_lcommunity_list, bool translate_alias) { - int i; - int len; - char *str_buf; + int i, len; const uint8_t *pnt; uint32_t global, local1, local2; - json_object *json_lcommunity_list = NULL; + char *str_buf; + size_t str_buf_sz; json_object *json_string = NULL; - /* 3 32-bit integers, 2 colons, and a space */ -#define LCOMMUNITY_STRLEN (10 * 3 + 2 + 1) - - if (!lcom) - return; - - if (make_json) { - lcom->json = json_object_new_object(); - json_lcommunity_list = json_object_new_array(); - } - - if (lcom->size == 0) { - str_buf = XCALLOC(MTYPE_LCOMMUNITY_STR, 1); - - if (make_json) { - json_object_string_add(lcom->json, "string", ""); - json_object_object_add(lcom->json, "list", - json_lcommunity_list); - } - - lcom->str = str_buf; - return; - } - /* 1 space + lcom->size lcom strings + null terminator */ - size_t str_buf_sz = (LCOMMUNITY_STRLEN * lcom->size) + 2; + str_buf_sz = (LCOMMUNITY_STRLEN * lcom->size) + 2; str_buf = XCALLOC(MTYPE_LCOMMUNITY_STR, str_buf_sz); len = 0; @@ -233,12 +215,45 @@ static void set_lcommunity_string(struct lcommunity *lcom, bool make_json, len = strlcat(str_buf, com2alias, str_buf_sz); - if (make_json) { + if (json_lcommunity_list) { json_string = json_object_new_string(com2alias); json_object_array_add(json_lcommunity_list, json_string); } } + return str_buf; +} + +static void set_lcommunity_string(struct lcommunity *lcom, bool make_json, + bool translate_alias) +{ + char *str_buf; + json_object *json_lcommunity_list = NULL; + + if (!lcom) + return; + + if (make_json) { + lcom->json = json_object_new_object(); + json_lcommunity_list = json_object_new_array(); + } + + if (lcom->size == 0) { + str_buf = XCALLOC(MTYPE_LCOMMUNITY_STR, 1); + + if (make_json) { + json_object_string_add(lcom->json, "string", ""); + json_object_object_add(lcom->json, "list", + json_lcommunity_list); + } + + lcom->str = str_buf; + return; + } + + str_buf = lcommunity_json_list(lcom, + make_json ? json_lcommunity_list : NULL, + translate_alias); if (make_json) { json_object_string_add(lcom->json, "string", str_buf); From 2dd79c0d24f04c10939e9769772079b9810e790e Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Tue, 3 Sep 2024 18:27:51 +0200 Subject: [PATCH 5/6] bgpd: add large community API to return json object The lcommunity_get_json() API is used to return a json object. At display time, this object will use the lcommunity_json_to_string() function to handle the output. Signed-off-by: Philippe Guibert --- bgpd/bgp_lcommunity.c | 70 ++++++++++++++++++++++++++++++++++--------- bgpd/bgp_lcommunity.h | 1 + bgpd/bgp_route.c | 12 ++++---- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/bgpd/bgp_lcommunity.c b/bgpd/bgp_lcommunity.c index ff1e350f84f2..949c8f95e2b6 100644 --- a/bgpd/bgp_lcommunity.c +++ b/bgpd/bgp_lcommunity.c @@ -169,22 +169,25 @@ struct lcommunity *lcommunity_merge(struct lcommunity *lcom1, */ static char *lcommunity_json_list(struct lcommunity *lcom, json_object *json_lcommunity_list, - bool translate_alias) + bool translate_alias, bool return_buffer, + struct printbuf *pb) { int i, len; const uint8_t *pnt; uint32_t global, local1, local2; - char *str_buf; - size_t str_buf_sz; + char *str_buf = NULL; + size_t str_buf_sz = 0; json_object *json_string = NULL; /* 1 space + lcom->size lcom strings + null terminator */ - str_buf_sz = (LCOMMUNITY_STRLEN * lcom->size) + 2; - str_buf = XCALLOC(MTYPE_LCOMMUNITY_STR, str_buf_sz); + if (return_buffer) { + str_buf_sz = (LCOMMUNITY_STRLEN * lcom->size) + 2; + str_buf = XCALLOC(MTYPE_LCOMMUNITY_STR, str_buf_sz); + } len = 0; for (i = 0; i < lcom->size; i++) { - if (i > 0) + if (return_buffer && i > 0) len = strlcat(str_buf, " ", str_buf_sz); pnt = lcom->val + (i * LCOMMUNITY_SIZE); @@ -207,15 +210,18 @@ static char *lcommunity_json_list(struct lcommunity *lcom, const char *com2alias = translate_alias ? bgp_community2alias(lcsb) : lcsb; size_t individual_len = strlen(com2alias); - if (individual_len + len > str_buf_sz) { - str_buf_sz = individual_len + len + 1; - str_buf = XREALLOC(MTYPE_LCOMMUNITY_STR, str_buf, - str_buf_sz); + if (str_buf) { + if (individual_len + len > str_buf_sz) { + str_buf_sz = individual_len + len + 1; + str_buf = XREALLOC(MTYPE_LCOMMUNITY_STR, + str_buf, str_buf_sz); + } + len = strlcat(str_buf, com2alias, str_buf_sz); } - len = strlcat(str_buf, com2alias, str_buf_sz); - - if (json_lcommunity_list) { + if (pb) + sprintbuf(pb, "%s\"%s\"", i ? "," : "", com2alias); + else if (json_lcommunity_list) { json_string = json_object_new_string(com2alias); json_object_array_add(json_lcommunity_list, json_string); @@ -253,7 +259,7 @@ static void set_lcommunity_string(struct lcommunity *lcom, bool make_json, str_buf = lcommunity_json_list(lcom, make_json ? json_lcommunity_list : NULL, - translate_alias); + translate_alias, true, NULL); if (make_json) { json_object_string_add(lcom->json, "string", str_buf); @@ -567,6 +573,26 @@ static void bgp_aggr_lcommunity_prepare(struct hash_bucket *hb, void *arg) *aggr_lcommunity = lcommunity_dup(hb_lcommunity); } +#if JSON_C_MINOR_VERSION >= 13 && JSON_C_MAJOR_VERSION >= 0 +static int lcommunity_json_to_string(struct json_object *jso, + struct printbuf *pb, int level, int flags) +{ + struct lcommunity *lcom; + + lcom = json_object_get_userdata(jso); + assert(lcom); + if (!lcom->str) + return 0; + printbuf_strappend(pb, "{\"string\":\""); + printbuf_memappend(pb, lcom->str, strlen(lcom->str)); + printbuf_strappend(pb, "\",\"list\":["); + + lcommunity_json_list(lcom, NULL, 0, false, pb); + printbuf_strappend(pb, "]}"); + return 0; +} +#endif + void bgp_aggr_lcommunity_remove(void *arg) { struct lcommunity *lcommunity = arg; @@ -687,3 +713,19 @@ void bgp_remove_lcomm_from_aggregate_hash(struct bgp_aggregate *aggregate, } } } + +json_object *lcommunity_get_json(struct lcommunity *lcom) +{ + json_object *json_lcommunity = NULL; + +#if JSON_C_MINOR_VERSION >= 13 && JSON_C_MAJOR_VERSION >= 0 + json_lcommunity = json_object_new_object(); + json_object_set_serializer(json_lcommunity, lcommunity_json_to_string, + lcom, NULL); +#else + if (!lcom->json) + lcommunity_str(lcom, true, true); + json_lcommunity = json_object_lock(lcom->json); +#endif + return json_lcommunity; +} diff --git a/bgpd/bgp_lcommunity.h b/bgpd/bgp_lcommunity.h index 151b79fa63c4..91a04595f40e 100644 --- a/bgpd/bgp_lcommunity.h +++ b/bgpd/bgp_lcommunity.h @@ -48,6 +48,7 @@ extern struct lcommunity *lcommunity_merge(struct lcommunity *, struct lcommunity *); extern struct lcommunity *lcommunity_uniq_sort(struct lcommunity *); extern struct lcommunity *lcommunity_intern(struct lcommunity *); +extern json_object *lcommunity_get_json(struct lcommunity *lcom); extern bool lcommunity_cmp(const void *arg1, const void *arg2); extern void lcommunity_unintern(struct lcommunity **); extern unsigned int lcommunity_hash_make(const void *); diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 9e39d14dd962..98061f2e605e 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -10550,6 +10550,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, json_object *json_adv_to = NULL; json_object *json_aspath = NULL; json_object *json_community = NULL; + json_object *json_lcommunity = NULL; int first = 0; struct listnode *node, *nnode; struct peer *peer; @@ -11291,13 +11292,10 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, /* Line 6 display Large community */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)) { if (json_paths) { - if (!bgp_attr_get_lcommunity(attr)->json) - lcommunity_str(bgp_attr_get_lcommunity(attr), - true, true); - json_object_lock(bgp_attr_get_lcommunity(attr)->json); - json_object_object_add( - json_path, "largeCommunity", - bgp_attr_get_lcommunity(attr)->json); + json_lcommunity = lcommunity_get_json( + bgp_attr_get_lcommunity(attr)); + json_object_object_add(json_path, "largeCommunity", + json_lcommunity); } else { vty_out(vty, " Large Community: %s\n", bgp_attr_get_lcommunity(attr)->str); From de17bbb188298d159e7e48151e1ef40808a7508a Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Thu, 12 Sep 2024 13:40:01 +0200 Subject: [PATCH 6/6] lib: add printbuf.h API from json This API proposes to use the sprintbuf() method to facilitate the forge of buffers in json. Note that this buffer is limited to 128 bytes, so it can not be used everywhere as it without any checks. Signed-off-by: Philippe Guibert --- lib/json.h | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/json.h b/lib/json.h index 4763803acd24..b6b146bc734e 100644 --- a/lib/json.h +++ b/lib/json.h @@ -13,6 +13,7 @@ extern "C" { #include "command.h" #include "printfrr.h" #include +#include /* * FRR style JSON iteration.