// SPDX-License-Identifier: GPL-2.0-or-later /* * BGP Peer Attribute Unit Tests * Copyright (C) 2018 Pascal Mathis */ #include #include "memory.h" #include "plist.h" #include "printfrr.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_attr.h" #include "bgpd/bgp_regex.h" #include "bgpd/bgp_clist.h" #include "bgpd/bgp_dump.h" #include "bgpd/bgp_filter.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_network.h" #ifdef ENABLE_BGP_VNC #include "bgpd/rfapi/rfapi_backend.h" #endif #define OUT_SYMBOL_INFO "\u25ba" #define OUT_SYMBOL_OK "\u2714" #define OUT_SYMBOL_NOK "\u2716" #define TEST_ASSERT(T, C) \ do { \ if ((T)->state != TEST_SUCCESS || (C)) \ break; \ (T)->state = TEST_ASSERT_ERROR; \ (T)->error = \ asprintfrr(MTYPE_TMP, "assertion failed: %s (%s:%d)", \ (#C), __FILE__, __LINE__); \ } while (0) #define TEST_ASSERT_EQ(T, A, B) \ do { \ if ((T)->state != TEST_SUCCESS || ((A) == (B))) \ break; \ (T)->state = TEST_ASSERT_ERROR; \ (T)->error = asprintfrr( \ MTYPE_TMP, \ "assertion failed: %s[%lld] == [%lld]%s (%s:%d)", \ (#A), (long long)(A), (long long)(B), (#B), __FILE__, \ __LINE__); \ } while (0) #define TEST_HANDLER_MAX 5 #define TEST_HANDLER(name) _test_handler_##name #define TEST_HANDLER_DECL(name) \ static void _test_handler_##name( \ struct test *test, struct test_peer_attr *pa, \ struct peer *peer, struct peer *group, bool peer_set, \ bool group_set) #define TEST_ATTR_HANDLER_DECL(name, attr, pval, gval) \ TEST_HANDLER_DECL(name) \ { \ if (peer_set) \ TEST_ASSERT_EQ(test, peer->attr, (pval)); \ else if (peer_group_active(peer) && group_set) \ TEST_ASSERT_EQ(test, peer->attr, (gval)); \ if (group_set) \ TEST_ASSERT_EQ(test, group->attr, (gval)); \ } \ TEST_HANDLER_DECL(name) #define TEST_STR_ATTR_HANDLER_DECL(name, attr, pval, gval) \ TEST_HANDLER_DECL(name) \ { \ if (peer_set) { \ TEST_ASSERT(test, peer->attr != NULL); \ TEST_ASSERT(test, !strcmp(peer->attr, (pval))); \ } else if (peer_group_active(peer) && group_set) { \ TEST_ASSERT(test, peer->attr != NULL); \ TEST_ASSERT(test, !strcmp(peer->attr, (gval))); \ } \ if (group_set) { \ TEST_ASSERT(test, group->attr != NULL); \ TEST_ASSERT(test, !strcmp(group->attr, (gval))); \ } \ } \ TEST_HANDLER_DECL(name) #define TEST_SU_ATTR_HANDLER_DECL(name, attr, pval, gval) \ TEST_HANDLER_DECL(name) \ { \ union sockunion su; \ if (peer_set) { \ str2sockunion(pval, &su); \ TEST_ASSERT(test, !sockunion_cmp(peer->attr, &su)); \ } else if (peer_group_active(peer) && group_set) { \ str2sockunion(gval, &su); \ TEST_ASSERT(test, !sockunion_cmp(group->attr, &su)); \ } \ if (group_set) { \ str2sockunion(gval, &su); \ TEST_ASSERT(test, !sockunion_cmp(group->attr, &su)); \ } \ } \ TEST_HANDLER_DECL(name) /* Required variables to link in libbgp */ struct zebra_privs_t bgpd_privs = {0}; struct event_loop *master; enum test_state { TEST_SUCCESS, TEST_SKIPPING, TEST_COMMAND_ERROR, TEST_CONFIG_ERROR, TEST_ASSERT_ERROR, TEST_CUSTOM_ERROR, TEST_INTERNAL_ERROR, }; enum test_peer_attr_type { PEER_AT_AF_FLAG = 0, PEER_AT_AF_FILTER = 1, PEER_AT_AF_CUSTOM = 2, PEER_AT_GLOBAL_FLAG = 3, PEER_AT_GLOBAL_CUSTOM = 4 }; struct test { enum test_state state; char *desc; char *error; struct list *log; struct vty *vty; struct bgp *bgp; struct peer *peer; struct peer_group *group; struct { bool use_ibgp; bool use_iface_peer; } o; }; struct test_config { int local_asn; int peer_asn; const char *peer_address; const char *peer_interface; const char *peer_group; }; struct test_peer_family { afi_t afi; safi_t safi; }; struct test_peer_attr { const char *cmd; const char *peer_cmd; const char *group_cmd; enum test_peer_attr_type type; union { uint64_t flag; struct { uint64_t flag; size_t direct; } filter; } u; struct { bool invert_peer; bool invert_group; bool use_ibgp; bool use_iface_peer; bool skip_xfer_cases; } o; afi_t afi; safi_t safi; struct test_peer_family families[AFI_MAX * SAFI_MAX]; void (*handlers[TEST_HANDLER_MAX])(struct test *test, struct test_peer_attr *pa, struct peer *peer, struct peer *group, bool peer_set, bool group_set); }; static struct test_config cfg = { .local_asn = 100, .peer_asn = 200, .peer_address = "1.1.1.1", .peer_interface = "IP-TEST", .peer_group = "PG-TEST", }; static struct test_peer_family test_default_families[] = { {.afi = AFI_IP, .safi = SAFI_UNICAST}, {.afi = AFI_IP, .safi = SAFI_MULTICAST}, {.afi = AFI_IP6, .safi = SAFI_UNICAST}, {.afi = AFI_IP6, .safi = SAFI_MULTICAST}, }; TEST_ATTR_HANDLER_DECL(advertisement_interval, v_routeadv, 10, 20); TEST_STR_ATTR_HANDLER_DECL(password, password, "FRR-Peer", "FRR-Group"); TEST_ATTR_HANDLER_DECL(local_as, change_local_as, 1, 2); TEST_ATTR_HANDLER_DECL(timers_1, keepalive, 10, 20); TEST_ATTR_HANDLER_DECL(timers_2, holdtime, 30, 60); TEST_ATTR_HANDLER_DECL(addpath_types, addpath_type[pa->afi][pa->safi], BGP_ADDPATH_ALL, BGP_ADDPATH_BEST_PER_AS); TEST_SU_ATTR_HANDLER_DECL(update_source_su, update_source, "255.255.255.1", "255.255.255.2"); TEST_STR_ATTR_HANDLER_DECL(update_source_if, update_if, "IF-PEER", "IF-GROUP"); TEST_ATTR_HANDLER_DECL(allowas_in, allowas_in[pa->afi][pa->safi], 1, 2); TEST_STR_ATTR_HANDLER_DECL(default_originate_route_map, default_rmap[pa->afi][pa->safi].name, "RM-PEER", "RM-GROUP"); TEST_STR_ATTR_HANDLER_DECL( distribute_list, filter[pa->afi][pa->safi].dlist[pa->u.filter.direct].name, "DL-PEER", "DL-GROUP"); TEST_STR_ATTR_HANDLER_DECL( filter_list, filter[pa->afi][pa->safi].aslist[pa->u.filter.direct].name, "FL-PEER", "FL-GROUP"); TEST_ATTR_HANDLER_DECL(maximum_prefix, pmax[pa->afi][pa->safi], 10, 20); TEST_ATTR_HANDLER_DECL(maximum_prefix_threshold, pmax_threshold[pa->afi][pa->safi], 1, 2); TEST_ATTR_HANDLER_DECL(maximum_prefix_restart, pmax_restart[pa->afi][pa->safi], 100, 200); TEST_STR_ATTR_HANDLER_DECL( prefix_list, filter[pa->afi][pa->safi].plist[pa->u.filter.direct].name, "PL-PEER", "PL-GROUP"); TEST_STR_ATTR_HANDLER_DECL( route_map, filter[pa->afi][pa->safi].map[pa->u.filter.direct].name, "RM-PEER", "RM-GROUP"); TEST_STR_ATTR_HANDLER_DECL(unsuppress_map, filter[pa->afi][pa->safi].usmap.name, "UM-PEER", "UM-GROUP"); TEST_ATTR_HANDLER_DECL(weight, weight[pa->afi][pa->safi], 100, 200); /* clang-format off */ static struct test_peer_attr test_peer_attrs[] = { /* Peer Attributes */ { .cmd = "advertisement-interval", .peer_cmd = "advertisement-interval 10", .group_cmd = "advertisement-interval 20", .u.flag = PEER_FLAG_ROUTEADV, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(advertisement_interval), }, { .cmd = "capability dynamic", .u.flag = PEER_FLAG_DYNAMIC_CAPABILITY, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "capability extended-nexthop", .u.flag = PEER_FLAG_CAPABILITY_ENHE, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "capability extended-nexthop", .u.flag = PEER_FLAG_CAPABILITY_ENHE, .type = PEER_AT_GLOBAL_FLAG, .o.invert_peer = true, .o.use_iface_peer = true, }, { .cmd = "capability software-version", .u.flag = PEER_FLAG_CAPABILITY_SOFT_VERSION, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "capability software-version", .u.flag = PEER_FLAG_CAPABILITY_SOFT_VERSION, .type = PEER_AT_GLOBAL_FLAG, .o.invert_peer = true, .o.use_iface_peer = true, }, { .cmd = "description", .peer_cmd = "description FRR Peer", .group_cmd = "description FRR Group", .type = PEER_AT_GLOBAL_CUSTOM, }, { .cmd = "disable-connected-check", .u.flag = PEER_FLAG_DISABLE_CONNECTED_CHECK, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "dont-capability-negotiate", .u.flag = PEER_FLAG_DONT_CAPABILITY, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "enforce-first-as", .u.flag = PEER_FLAG_ENFORCE_FIRST_AS, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "local-as", .peer_cmd = "local-as 1", .group_cmd = "local-as 2", .u.flag = PEER_FLAG_LOCAL_AS, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(local_as), }, { .cmd = "local-as 1 no-prepend", .u.flag = PEER_FLAG_LOCAL_AS | PEER_FLAG_LOCAL_AS_NO_PREPEND, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "local-as 1 no-prepend replace-as", .u.flag = PEER_FLAG_LOCAL_AS | PEER_FLAG_LOCAL_AS_REPLACE_AS, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "override-capability", .u.flag = PEER_FLAG_OVERRIDE_CAPABILITY, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "passive", .u.flag = PEER_FLAG_PASSIVE, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "password", .peer_cmd = "password FRR-Peer", .group_cmd = "password FRR-Group", .u.flag = PEER_FLAG_PASSWORD, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(password), }, { .cmd = "shutdown", .u.flag = PEER_FLAG_SHUTDOWN, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "strict-capability-match", .u.flag = PEER_FLAG_STRICT_CAP_MATCH, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "timers", .peer_cmd = "timers 10 30", .group_cmd = "timers 20 60", .u.flag = PEER_FLAG_TIMER, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(timers_1), .handlers[1] = TEST_HANDLER(timers_2), }, { .cmd = "timers connect", .peer_cmd = "timers connect 10", .group_cmd = "timers connect 20", .u.flag = PEER_FLAG_TIMER_CONNECT, .type = PEER_AT_GLOBAL_FLAG, }, { .cmd = "update-source", .peer_cmd = "update-source 255.255.255.1", .group_cmd = "update-source 255.255.255.2", .u.flag = PEER_FLAG_UPDATE_SOURCE, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(update_source_su), }, { .cmd = "update-source", .peer_cmd = "update-source IF-PEER", .group_cmd = "update-source IF-GROUP", .u.flag = PEER_FLAG_UPDATE_SOURCE, .type = PEER_AT_GLOBAL_FLAG, .handlers[0] = TEST_HANDLER(update_source_if), }, /* Address Family Attributes */ { .cmd = "addpath", .peer_cmd = "addpath-tx-all-paths", .group_cmd = "addpath-tx-bestpath-per-AS", .type = PEER_AT_AF_CUSTOM, .handlers[0] = TEST_HANDLER(addpath_types), }, { .cmd = "allowas-in", .peer_cmd = "allowas-in 1", .group_cmd = "allowas-in 2", .u.flag = PEER_FLAG_ALLOWAS_IN, .handlers[0] = TEST_HANDLER(allowas_in), }, { .cmd = "allowas-in origin", .u.flag = PEER_FLAG_ALLOWAS_IN_ORIGIN, }, { .cmd = "as-override", .u.flag = PEER_FLAG_AS_OVERRIDE, }, { .cmd = "attribute-unchanged as-path", .u.flag = PEER_FLAG_AS_PATH_UNCHANGED, }, { .cmd = "attribute-unchanged next-hop", .u.flag = PEER_FLAG_NEXTHOP_UNCHANGED, }, { .cmd = "attribute-unchanged med", .u.flag = PEER_FLAG_MED_UNCHANGED, }, { .cmd = "attribute-unchanged as-path next-hop", .u.flag = PEER_FLAG_AS_PATH_UNCHANGED | PEER_FLAG_NEXTHOP_UNCHANGED, }, { .cmd = "attribute-unchanged as-path med", .u.flag = PEER_FLAG_AS_PATH_UNCHANGED | PEER_FLAG_MED_UNCHANGED, }, { .cmd = "attribute-unchanged as-path next-hop med", .u.flag = PEER_FLAG_AS_PATH_UNCHANGED | PEER_FLAG_NEXTHOP_UNCHANGED | PEER_FLAG_MED_UNCHANGED, }, { .cmd = "capability orf prefix-list send", .u.flag = PEER_FLAG_ORF_PREFIX_SM, }, { .cmd = "capability orf prefix-list receive", .u.flag = PEER_FLAG_ORF_PREFIX_RM, }, { .cmd = "capability orf prefix-list both", .u.flag = PEER_FLAG_ORF_PREFIX_SM | PEER_FLAG_ORF_PREFIX_RM, }, { .cmd = "default-originate", .u.flag = PEER_FLAG_DEFAULT_ORIGINATE, }, { .cmd = "default-originate route-map", .peer_cmd = "default-originate route-map RM-PEER", .group_cmd = "default-originate route-map RM-GROUP", .u.flag = PEER_FLAG_DEFAULT_ORIGINATE, .handlers[0] = TEST_HANDLER(default_originate_route_map), }, { .cmd = "distribute-list", .peer_cmd = "distribute-list DL-PEER in", .group_cmd = "distribute-list DL-GROUP in", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_DISTRIBUTE_LIST, .u.filter.direct = FILTER_IN, .handlers[0] = TEST_HANDLER(distribute_list), }, { .cmd = "distribute-list", .peer_cmd = "distribute-list DL-PEER out", .group_cmd = "distribute-list DL-GROUP out", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_DISTRIBUTE_LIST, .u.filter.direct = FILTER_OUT, .handlers[0] = TEST_HANDLER(distribute_list), }, { .cmd = "filter-list", .peer_cmd = "filter-list FL-PEER in", .group_cmd = "filter-list FL-GROUP in", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_FILTER_LIST, .u.filter.direct = FILTER_IN, .handlers[0] = TEST_HANDLER(filter_list), }, { .cmd = "filter-list", .peer_cmd = "filter-list FL-PEER out", .group_cmd = "filter-list FL-GROUP out", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_FILTER_LIST, .u.filter.direct = FILTER_OUT, .handlers[0] = TEST_HANDLER(filter_list), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10", .group_cmd = "maximum-prefix 20", .u.flag = PEER_FLAG_MAX_PREFIX, .handlers[0] = TEST_HANDLER(maximum_prefix), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10 restart 100", .group_cmd = "maximum-prefix 20 restart 200", .u.flag = PEER_FLAG_MAX_PREFIX, .handlers[0] = TEST_HANDLER(maximum_prefix), .handlers[1] = TEST_HANDLER(maximum_prefix_restart), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10 1 restart 100", .group_cmd = "maximum-prefix 20 2 restart 200", .u.flag = PEER_FLAG_MAX_PREFIX, .handlers[0] = TEST_HANDLER(maximum_prefix), .handlers[1] = TEST_HANDLER(maximum_prefix_threshold), .handlers[2] = TEST_HANDLER(maximum_prefix_restart), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10 warning-only", .group_cmd = "maximum-prefix 20 warning-only", .u.flag = PEER_FLAG_MAX_PREFIX | PEER_FLAG_MAX_PREFIX_WARNING, .handlers[0] = TEST_HANDLER(maximum_prefix), }, { .cmd = "maximum-prefix", .peer_cmd = "maximum-prefix 10 1 warning-only", .group_cmd = "maximum-prefix 20 2 warning-only", .u.flag = PEER_FLAG_MAX_PREFIX | PEER_FLAG_MAX_PREFIX_WARNING, .handlers[0] = TEST_HANDLER(maximum_prefix), .handlers[1] = TEST_HANDLER(maximum_prefix_threshold), }, { .cmd = "next-hop-self", .u.flag = PEER_FLAG_NEXTHOP_SELF, }, { .cmd = "next-hop-self force", .u.flag = PEER_FLAG_FORCE_NEXTHOP_SELF, }, { .cmd = "prefix-list", .peer_cmd = "prefix-list PL-PEER in", .group_cmd = "prefix-list PL-GROUP in", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_PREFIX_LIST, .u.filter.direct = FILTER_IN, .handlers[0] = TEST_HANDLER(prefix_list), }, { .cmd = "prefix-list", .peer_cmd = "prefix-list PL-PEER out", .group_cmd = "prefix-list PL-GROUP out", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_PREFIX_LIST, .u.filter.direct = FILTER_OUT, .handlers[0] = TEST_HANDLER(prefix_list), }, { .cmd = "remove-private-AS", .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS, }, { .cmd = "remove-private-AS all", .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS | PEER_FLAG_REMOVE_PRIVATE_AS_ALL, }, { .cmd = "remove-private-AS replace-AS", .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS | PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE, }, { .cmd = "remove-private-AS all replace-AS", .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE, }, { .cmd = "route-map", .peer_cmd = "route-map RM-PEER in", .group_cmd = "route-map RM-GROUP in", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_ROUTE_MAP, .u.filter.direct = FILTER_IN, .handlers[0] = TEST_HANDLER(route_map), }, { .cmd = "route-map", .peer_cmd = "route-map RM-PEER out", .group_cmd = "route-map RM-GROUP out", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_ROUTE_MAP, .u.filter.direct = FILTER_OUT, .handlers[0] = TEST_HANDLER(route_map), }, { .cmd = "route-reflector-client", .u.flag = PEER_FLAG_REFLECTOR_CLIENT, .o.use_ibgp = true, .o.skip_xfer_cases = true, }, { .cmd = "route-server-client", .u.flag = PEER_FLAG_RSERVER_CLIENT, }, { .cmd = "send-community", .u.flag = PEER_FLAG_SEND_COMMUNITY, .o.invert_peer = true, .o.invert_group = true, }, { .cmd = "send-community extended", .u.flag = PEER_FLAG_SEND_EXT_COMMUNITY, .o.invert_peer = true, .o.invert_group = true, }, { .cmd = "send-community large", .u.flag = PEER_FLAG_SEND_LARGE_COMMUNITY, .o.invert_peer = true, .o.invert_group = true, }, { .cmd = "soft-reconfiguration inbound", .u.flag = PEER_FLAG_SOFT_RECONFIG, }, { .cmd = "unsuppress-map", .peer_cmd = "unsuppress-map UM-PEER", .group_cmd = "unsuppress-map UM-GROUP", .type = PEER_AT_AF_FILTER, .u.filter.flag = PEER_FT_UNSUPPRESS_MAP, .u.filter.direct = 0, .handlers[0] = TEST_HANDLER(unsuppress_map), }, { .cmd = "weight", .peer_cmd = "weight 100", .group_cmd = "weight 200", .u.flag = PEER_FLAG_WEIGHT, .handlers[0] = TEST_HANDLER(weight), }, { .cmd = "accept-own", .peer_cmd = "accept-own", .group_cmd = "accept-own", .families[0] = {.afi = AFI_IP, .safi = SAFI_MPLS_VPN}, .families[1] = {.afi = AFI_IP6, .safi = SAFI_MPLS_VPN}, .u.flag = PEER_FLAG_ACCEPT_OWN, }, {NULL} }; /* clang-format on */ static const char *str_from_afi(afi_t afi) { switch (afi) { case AFI_IP: return "ipv4"; case AFI_IP6: return "ipv6"; case AFI_L2VPN: return "l2vpn"; case AFI_MAX: case AFI_UNSPEC: return "bad-value"; } assert(!"Reached end of function we should never reach"); } static const char *str_from_attr_type(enum test_peer_attr_type at) { switch (at) { case PEER_AT_GLOBAL_FLAG: return "peer-flag"; case PEER_AT_AF_FLAG: return "af-flag"; case PEER_AT_AF_FILTER: return "af-filter"; case PEER_AT_GLOBAL_CUSTOM: case PEER_AT_AF_CUSTOM: return "custom"; default: return NULL; } } static bool is_attr_type_global(enum test_peer_attr_type at) { return at == PEER_AT_GLOBAL_FLAG || at == PEER_AT_GLOBAL_CUSTOM; } PRINTFRR(2, 3) static void test_log(struct test *test, const char *fmt, ...) { va_list ap; /* Skip logging if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Store formatted log message. */ va_start(ap, fmt); listnode_add(test->log, vasprintfrr(MTYPE_TMP, fmt, ap)); va_end(ap); } PRINTFRR(2, 3) static void test_execute(struct test *test, const char *fmt, ...) { int ret; char *cmd; va_list ap; vector vline; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Format command string with variadic arguments. */ va_start(ap, fmt); cmd = vasprintfrr(MTYPE_TMP, fmt, ap); va_end(ap); if (!cmd) { test->state = TEST_INTERNAL_ERROR; test->error = asprintfrr( MTYPE_TMP, "could not format command string [%s]", fmt); return; } /* Tokenize formatted command string. */ vline = cmd_make_strvec(cmd); if (vline == NULL) { test->state = TEST_INTERNAL_ERROR; test->error = asprintfrr( MTYPE_TMP, "tokenizing command string [%s] returned empty result", cmd); XFREE(MTYPE_TMP, cmd); return; } /* Execute command (non-strict). */ ret = cmd_execute_command(vline, test->vty, NULL, 0); if (ret != CMD_SUCCESS) { test->state = TEST_COMMAND_ERROR; test->error = asprintfrr( MTYPE_TMP, "execution of command [%s] has failed with code [%d]", cmd, ret); } /* Free memory. */ cmd_free_strvec(vline); XFREE(MTYPE_TMP, cmd); } PRINTFRR(2, 0) static void test_config(struct test *test, const char *fmt, bool invert, va_list ap) { char *matcher; char *config; bool matched; va_list apc; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Format matcher string with variadic arguments. */ va_copy(apc, ap); matcher = vasprintfrr(MTYPE_TMP, fmt, apc); va_end(apc); if (!matcher) { test->state = TEST_INTERNAL_ERROR; test->error = asprintfrr( MTYPE_TMP, "could not format matcher string [%s]", fmt); return; } /* Fetch BGP configuration into buffer. */ bgp_config_write(test->vty); config = buffer_getstr(test->vty->obuf); buffer_reset(test->vty->obuf); /* Match config against matcher. */ matched = !!strstr(config, matcher); if (!matched && !invert) { test->state = TEST_CONFIG_ERROR; test->error = asprintfrr(MTYPE_TMP, "expected config [%s] to be present", matcher); } else if (matched && invert) { test->state = TEST_CONFIG_ERROR; test->error = asprintfrr(MTYPE_TMP, "expected config [%s] to be absent", matcher); } /* Free memory and return. */ XFREE(MTYPE_TMP, matcher); XFREE(MTYPE_TMP, config); } PRINTFRR(2, 3) static void test_config_present(struct test *test, const char *fmt, ...) { va_list ap; va_start(ap, fmt); test_config(test, fmt, false, ap); va_end(ap); } PRINTFRR(2, 3) static void test_config_absent(struct test *test, const char *fmt, ...) { va_list ap; va_start(ap, fmt); test_config(test, fmt, true, ap); va_end(ap); } static void test_initialize(struct test *test) { union sockunion su; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Log message about (re)-initialization */ test_log(test, "prepare: %sinitialize bgp test environment", test->bgp ? "re-" : ""); /* Attempt gracefully to purge previous BGP configuration. */ test_execute(test, "no router bgp"); test->state = TEST_SUCCESS; /* Initialize BGP test environment. */ test_execute(test, "router bgp %d", cfg.local_asn); test_execute(test, "no bgp default ipv4-unicast"); test_execute(test, "neighbor %s peer-group", cfg.peer_group); if (test->o.use_iface_peer) { test_execute(test, "neighbor %s interface", cfg.peer_interface); test_execute(test, "neighbor %s remote-as %d", cfg.peer_interface, test->o.use_ibgp ? cfg.local_asn : cfg.peer_asn); } else { test_execute(test, "neighbor %s remote-as %d", cfg.peer_address, test->o.use_ibgp ? cfg.local_asn : cfg.peer_asn); } if (test->state != TEST_SUCCESS) return; /* Fetch default BGP instance. */ test->bgp = bgp_get_default(); if (!test->bgp) { test->state = TEST_INTERNAL_ERROR; test->error = asprintfrr( MTYPE_TMP, "could not retrieve default bgp instance"); return; } /* Fetch peer instance. */ if (test->o.use_iface_peer) { test->peer = peer_lookup_by_conf_if(test->bgp, cfg.peer_interface); } else { str2sockunion(cfg.peer_address, &su); test->peer = peer_lookup(test->bgp, &su); } if (!test->peer) { test->state = TEST_INTERNAL_ERROR; test->error = asprintfrr( MTYPE_TMP, "could not retrieve instance of bgp peer [%s]", cfg.peer_address); return; } /* Fetch peer-group instance. */ test->group = peer_group_lookup(test->bgp, cfg.peer_group); if (!test->group) { test->state = TEST_INTERNAL_ERROR; test->error = asprintfrr( MTYPE_TMP, "could not retrieve instance of bgp peer-group [%s]", cfg.peer_group); return; } } static struct test *test_new(const char *desc, bool use_ibgp, bool use_iface_peer) { struct test *test; test = XCALLOC(MTYPE_TMP, sizeof(struct test)); test->state = TEST_SUCCESS; test->desc = XSTRDUP(MTYPE_TMP, desc); test->log = list_new(); test->o.use_ibgp = use_ibgp; test->o.use_iface_peer = use_iface_peer; test->vty = vty_new(); test->vty->type = VTY_TERM; test->vty->node = CONFIG_NODE; test_initialize(test); return test; }; static void test_finish(struct test *test) { char *msg; struct listnode *node, *nnode; /* Print test output header. */ printf("%s [test] %s\n", (test->state == TEST_SUCCESS) ? OUT_SYMBOL_OK : OUT_SYMBOL_NOK, test->desc); /* Print test log messages. */ for (ALL_LIST_ELEMENTS(test->log, node, nnode, msg)) { printf("%s %s\n", OUT_SYMBOL_INFO, msg); XFREE(MTYPE_TMP, msg); } /* Print test error message if available. */ if (test->state != TEST_SUCCESS && test->error) printf("%s error: %s\n", OUT_SYMBOL_INFO, test->error); /* Print machine-readable result of test. */ printf("%s\n", test->state == TEST_SUCCESS ? "OK" : "failed"); /* Cleanup allocated memory. */ if (test->vty) { vty_close(test->vty); test->vty = NULL; } if (test->log) list_delete(&test->log); if (test->desc) XFREE(MTYPE_TMP, test->desc); if (test->error) XFREE(MTYPE_TMP, test->error); XFREE(MTYPE_TMP, test); } static void test_peer_flags(struct test *test, struct test_peer_attr *pa, struct peer *peer, bool exp_val, bool exp_ovrd) { bool exp_inv, cur_val, cur_ovrd, cur_inv; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Detect if flag is meant to be inverted. */ if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) exp_inv = pa->o.invert_group; else exp_inv = pa->o.invert_peer; /* Flip expected value if flag is inverted. */ exp_val ^= exp_inv; /* Fetch current state of value, override and invert flags. */ if (pa->type == PEER_AT_GLOBAL_FLAG) { cur_val = !!CHECK_FLAG(peer->flags, pa->u.flag); cur_ovrd = !!CHECK_FLAG(peer->flags_override, pa->u.flag); cur_inv = !!CHECK_FLAG(peer->flags_invert, pa->u.flag); } else /* if (pa->type == PEER_AT_AF_FLAG) */ { cur_val = !!CHECK_FLAG(peer->af_flags[pa->afi][pa->safi], pa->u.flag); cur_ovrd = !!CHECK_FLAG( peer->af_flags_override[pa->afi][pa->safi], pa->u.flag); cur_inv = !!CHECK_FLAG(peer->af_flags_invert[pa->afi][pa->safi], pa->u.flag); } /* Assert expected flag states. */ TEST_ASSERT_EQ(test, cur_val, exp_val); TEST_ASSERT_EQ(test, cur_ovrd, exp_ovrd); TEST_ASSERT_EQ(test, cur_inv, exp_inv); } static void test_af_filter(struct test *test, struct test_peer_attr *pa, struct peer *peer, bool exp_state, bool exp_ovrd) { bool cur_ovrd; struct bgp_filter *filter; /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Fetch and assert current state of override flag. */ cur_ovrd = !!CHECK_FLAG( peer->filter_override[pa->afi][pa->safi][pa->u.filter.direct], pa->u.filter.flag); TEST_ASSERT_EQ(test, cur_ovrd, exp_ovrd); /* Assert that map/list matches expected state (set/unset). */ filter = &peer->filter[pa->afi][pa->safi]; switch (pa->u.filter.flag) { case PEER_FT_DISTRIBUTE_LIST: TEST_ASSERT_EQ(test, !!(filter->dlist[pa->u.filter.direct].name), exp_state); break; case PEER_FT_FILTER_LIST: TEST_ASSERT_EQ(test, !!(filter->aslist[pa->u.filter.direct].name), exp_state); break; case PEER_FT_PREFIX_LIST: TEST_ASSERT_EQ(test, !!(filter->plist[pa->u.filter.direct].name), exp_state); break; case PEER_FT_ROUTE_MAP: TEST_ASSERT_EQ(test, !!(filter->map[pa->u.filter.direct].name), exp_state); break; case PEER_FT_UNSUPPRESS_MAP: TEST_ASSERT_EQ(test, !!(filter->usmap.name), exp_state); break; } } static void test_custom(struct test *test, struct test_peer_attr *pa, struct peer *peer, struct peer *group, bool peer_set, bool group_set) { int i; char *handler_error; for (i = 0; i < TEST_HANDLER_MAX; i++) { /* Skip execution if test instance has previously failed. */ if (test->state != TEST_SUCCESS) return; /* Skip further execution if handler is undefined. */ if (!pa->handlers[i]) return; /* Execute custom handler. */ pa->handlers[i](test, pa, peer, group, peer_set, group_set); if (test->state != TEST_SUCCESS) { test->state = TEST_CUSTOM_ERROR; handler_error = test->error; test->error = asprintfrr(MTYPE_TMP, "custom handler failed: %s", handler_error); XFREE(MTYPE_TMP, handler_error); } } } static void test_process(struct test *test, struct test_peer_attr *pa, struct peer *peer, struct peer *group, bool peer_set, bool group_set) { switch (pa->type) { case PEER_AT_GLOBAL_FLAG: case PEER_AT_AF_FLAG: test_peer_flags( test, pa, peer, peer_set || (peer_group_active(peer) && group_set), peer_set); test_peer_flags(test, pa, group, group_set, false); break; case PEER_AT_AF_FILTER: test_af_filter( test, pa, peer, peer_set || (peer_group_active(peer) && group_set), peer_set); test_af_filter(test, pa, group, group_set, false); break; case PEER_AT_GLOBAL_CUSTOM: case PEER_AT_AF_CUSTOM: /* * Do nothing here - a custom handler can be executed, but this * is not required. This will allow defining peer attributes * which shall not be checked for flag/filter/other internal * states. */ break; default: test->state = TEST_INTERNAL_ERROR; test->error = asprintfrr( MTYPE_TMP, "invalid attribute type: %d", pa->type); break; } /* Attempt to call a custom handler if set for further processing. */ test_custom(test, pa, peer, group, peer_set, group_set); } static void test_peer_attr(struct test *test, struct test_peer_attr *pa) { int tc = 1; const char *type; const char *ecp = pa->o.invert_peer ? "no " : ""; const char *dcp = pa->o.invert_peer ? "" : "no "; const char *ecg = pa->o.invert_group ? "no " : ""; const char *dcg = pa->o.invert_group ? "" : "no "; const char *peer_cmd = pa->peer_cmd ?: pa->cmd; const char *group_cmd = pa->group_cmd ?: pa->cmd; struct peer *p = test->peer; struct peer_group *g = test->group; /* Determine type and if test is address-family relevant */ type = str_from_attr_type(pa->type); if (!type) { test->state = TEST_INTERNAL_ERROR; test->error = asprintfrr( MTYPE_TMP, "invalid attribute type: %d", pa->type); return; } /* * ===================================================================== * Test Case Suite 1: Config persistence after adding peer to group * * Example: If a peer attribute has value [1] and a group attribute has * value [2], the peer attribute value should be persisted when the peer * gets added to the peer-group. * * This test suite is meant to test the group2peer functions which can * be found inside bgpd/bgpd.c, which are related to initial peer-group * inheritance. * ===================================================================== */ /* Test Preparation: Switch and activate address-family. */ if (!is_attr_type_global(pa->type)) { test_log(test, "prepare: switch address-family to [%s]", get_afi_safi_str(pa->afi, pa->safi, false)); test_execute(test, "address-family %s %s", str_from_afi(pa->afi), safi2str(pa->safi)); test_execute(test, "neighbor %s activate", g->name); test_execute(test, "neighbor %s activate", p->host); } /* Skip peer-group to peer transfer test cases if requested. */ if (pa->o.skip_xfer_cases && test->state == TEST_SUCCESS) test->state = TEST_SKIPPING; /* Test Case: Set flag on BGP peer. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Set flag on BGP peer-group. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Add BGP peer to peer-group. */ test_log(test, "case %02d: add peer [%s] to group [%s]", tc++, p->host, g->name); test_execute(test, "neighbor %s peer-group %s", p->host, g->name); test_config_present(test, "neighbor %s %speer-group %s", p->host, p->conf_if ? "interface " : "", g->name); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Unset flag on BGP peer-group. */ test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", dcg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* * ===================================================================== * Test Case Suite 2: Config inheritance after adding peer to group * * Example: If a peer attribute has not been set and a group attribute * has a value of [2], the group attribute should be inherited to the * peer without flagging the newly set value as overridden. * * This test suite is meant to test the group2peer functions which can * be found inside bgpd/bgpd.c, which are related to initial peer-group * inheritance. * ===================================================================== */ /* Test Preparation: Re-initialize test environment. */ test_initialize(test); p = test->peer; g = test->group; /* Test Preparation: Switch and activate address-family. */ if (!is_attr_type_global(pa->type)) { test_log(test, "prepare: switch address-family to [%s]", get_afi_safi_str(pa->afi, pa->safi, false)); test_execute(test, "address-family %s %s", str_from_afi(pa->afi), safi2str(pa->safi)); test_execute(test, "neighbor %s activate", g->name); test_execute(test, "neighbor %s activate", p->host); } /* Test Case: Set flag on BGP peer-group. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, false, true); /* Test Case: Add BGP peer to peer-group. */ test_log(test, "case %02d: add peer [%s] to group [%s]", tc++, p->host, g->name); test_execute(test, "neighbor %s peer-group %s", p->host, g->name); test_config_present(test, "neighbor %s %speer-group %s", p->host, p->conf_if ? "interface " : "", g->name); test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, false, true); /* Stop skipping test cases if previously enabled. */ if (pa->o.skip_xfer_cases && test->state == TEST_SKIPPING) test->state = TEST_SUCCESS; /* * ===================================================================== * Test Case Suite 3: Miscellaneous flag checks * * This test suite does not focus on initial peer-group inheritance and * instead executes various different commands to set/unset attributes * on both peer- and group-level. These checks should always be executed * and must pass. * ===================================================================== */ /* Test Preparation: Re-initialize test environment. */ test_initialize(test); p = test->peer; g = test->group; /* Test Preparation: Switch and activate address-family. */ if (!is_attr_type_global(pa->type)) { test_log(test, "prepare: switch address-family to [%s]", get_afi_safi_str(pa->afi, pa->safi, false)); test_execute(test, "address-family %s %s", str_from_afi(pa->afi), safi2str(pa->safi)); test_execute(test, "neighbor %s activate", g->name); test_execute(test, "neighbor %s activate", p->host); } /* Test Case: Set flag on BGP peer. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Add BGP peer to peer-group. */ test_log(test, "case %02d: add peer [%s] to group [%s]", tc++, p->host, g->name); test_execute(test, "neighbor %s peer-group %s", p->host, g->name); test_config_present(test, "neighbor %s %speer-group %s", p->host, p->conf_if ? "interface " : "", g->name); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Re-add BGP peer to peer-group. */ test_log(test, "case %02d: re-add peer [%s] to group [%s]", tc++, p->host, g->name); test_execute(test, "neighbor %s peer-group %s", p->host, g->name); test_config_present(test, "neighbor %s %speer-group %s", p->host, p->conf_if ? "interface " : "", g->name); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Set flag on BGP peer-group. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Unset flag on BGP peer-group. */ test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", dcg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); /* Test Case: Set flag on BGP peer-group. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Re-set flag on BGP peer. */ test_log(test, "case %02d: re-set %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, true, true); /* Test Case: Unset flag on BGP peer. */ test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", dcp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); test_process(test, pa, p, g->conf, false, true); /* Test Case: Unset flag on BGP peer-group. */ test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, group_cmd, g->name); test_execute(test, "%sneighbor %s %s", dcg, g->name, group_cmd); test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, false, false); /* Test Case: Set flag on BGP peer. */ test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, peer_cmd, p->host); test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); test_process(test, pa, p, g->conf, true, false); } static void bgp_startup(void) { cmd_init(1); zlog_aux_init("NONE: ", LOG_DEBUG); zprivs_preinit(&bgpd_privs); zprivs_init(&bgpd_privs); master = event_master_create(NULL); nb_init(master, NULL, 0, false); bgp_master_init(master, BGP_SOCKET_SNDBUF_SIZE, list_new()); bgp_option_set(BGP_OPT_NO_LISTEN); vrf_init(NULL, NULL, NULL, NULL); frr_pthread_init(); bgp_init(0); bgp_pthreads_run(); } static void bgp_shutdown(void) { struct bgp *bgp; struct listnode *node, *nnode; bgp_terminate(); bgp_close(); for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) bgp_delete(bgp); bgp_dump_finish(); bgp_route_finish(); bgp_route_map_terminate(); bgp_attr_finish(); bgp_pthreads_finish(); access_list_add_hook(NULL); access_list_delete_hook(NULL); access_list_reset(); as_list_add_hook(NULL); as_list_delete_hook(NULL); bgp_filter_reset(); prefix_list_add_hook(NULL); prefix_list_delete_hook(NULL); prefix_list_reset(); community_list_terminate(bgp_clist); vrf_terminate(); #ifdef ENABLE_BGP_VNC vnc_zebra_destroy(); #endif bgp_zebra_destroy(); bf_free(bm->rd_idspace); list_delete(&bm->bgp); memset(bm, 0, sizeof(*bm)); vty_terminate(); cmd_terminate(); nb_terminate(); yang_terminate(); zprivs_terminate(&bgpd_privs); event_master_free(master); master = NULL; } int main(void) { int i, ii; struct list *pa_list; struct test_peer_attr *pa, *pac; struct listnode *node, *nnode; bgp_startup(); pa_list = list_new(); i = 0; while (test_peer_attrs[i].cmd) { pa = &test_peer_attrs[i++]; /* Just copy the peer attribute structure for global flags. */ if (is_attr_type_global(pa->type)) { pac = XMALLOC(MTYPE_TMP, sizeof(struct test_peer_attr)); memcpy(pac, pa, sizeof(struct test_peer_attr)); listnode_add(pa_list, pac); continue; } /* Fallback to default families if not specified. */ if (!pa->families[0].afi && !pa->families[0].safi) memcpy(&pa->families, test_default_families, sizeof(test_default_families)); /* Add peer attribute definition for each address family. */ ii = 0; while (pa->families[ii].afi && pa->families[ii].safi) { pac = XMALLOC(MTYPE_TMP, sizeof(struct test_peer_attr)); memcpy(pac, pa, sizeof(struct test_peer_attr)); pac->afi = pa->families[ii].afi; pac->safi = pa->families[ii].safi; listnode_add(pa_list, pac); ii++; } } for (ALL_LIST_ELEMENTS(pa_list, node, nnode, pa)) { char *desc; struct test *test; /* Build test description string. */ if (pa->afi && pa->safi) desc = asprintfrr(MTYPE_TMP, "peer\\%s-%s\\%s", str_from_afi(pa->afi), safi2str(pa->safi), pa->cmd); else desc = asprintfrr(MTYPE_TMP, "peer\\%s", pa->cmd); /* Initialize new test instance. */ test = test_new(desc, pa->o.use_ibgp, pa->o.use_iface_peer); XFREE(MTYPE_TMP, desc); /* Execute tests and finish test instance. */ test_peer_attr(test, pa); test_finish(test); /* Print empty line as spacer. */ printf("\n"); /* Free memory used for peer-attr declaration. */ XFREE(MTYPE_TMP, pa); } list_delete(&pa_list); bgp_shutdown(); return 0; }