summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPascal Mathis <mail@pascalmathis.com>2018-05-27 17:52:19 +0200
committerPascal Mathis <mail@pascalmathis.com>2018-05-28 19:20:46 +0200
commit9d4f56237a22ff8f9f21912f1632e0279f4d7ec8 (patch)
treeb3ddb289dc737f1870dea57757d9d33b93baae01
parentbgpd: Fix group overrides for inverted AF flags (diff)
downloadfrr-9d4f56237a22ff8f9f21912f1632e0279f4d7ec8.tar.xz
frr-9d4f56237a22ff8f9f21912f1632e0279f4d7ec8.zip
tests: Add tests for overriding BGP peer attrs
This commit introduces unit tests for BGP peer attributes and checks all three involved components, which are: - CLI Configuration Input: The appropriate commands to configure the attribute on either a peer or peer-group are being executed the same way an end user would do it. - CLI Configuration Output: The output of 'show running-config' is being checked for presence/absence of expected configuration strings. - Internal Data Structures: The internal data structures for maintaining flag/filter states (value + override + invert) are being checked after each operation to ensure the override has been implemented properly. All attributes to be tested must be defined within the 'peer_attrs' structure, which contains all peer attributes as of today and checks them with both IPv4 Unicast and IPv6 Unicast. More address families are supposed to be introduced at a later point in time. Each attribute is being checked in its own 'clean' BGP environment, so everything gets reset after each attribute to avoid any weird edge cases. The 'correct' BGP startup and shutdown routine was taken from 'bgp_main.c' to ensure that we are not leaking any memory or acting different than the real 'bgpd' would do. Signed-off-by: Pascal Mathis <mail@pascalmathis.com>
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile.am4
-rw-r--r--tests/bgpd/test_peer_attr.c1144
-rw-r--r--tests/bgpd/test_peer_attr.py94
4 files changed, 1243 insertions, 0 deletions
diff --git a/tests/.gitignore b/tests/.gitignore
index 1708a4b7b..d136cae48 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -25,6 +25,7 @@ __pycache__
/bgpd/test_mp_attr
/bgpd/test_mpath
/bgpd/test_packet
+/bgpd/test_peer_attr
/isisd/test_fuzz_isis_tlv
/isisd/test_fuzz_isis_tlv_tests.h
/isisd/test_isis_vertex_queue
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 6a1932592..aefe0d06a 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -18,6 +18,7 @@ TESTS_BGPD = \
bgpd/test_aspath \
bgpd/test_capability \
bgpd/test_packet \
+ bgpd/test_peer_attr \
bgpd/test_ecommunity \
bgpd/test_mp_attr \
bgpd/test_mpath
@@ -140,6 +141,7 @@ lib_cli_test_commands_SOURCES = lib/cli/test_commands_defun.c \
bgpd_test_aspath_SOURCES = bgpd/test_aspath.c
bgpd_test_capability_SOURCES = bgpd/test_capability.c
bgpd_test_packet_SOURCES = bgpd/test_packet.c
+bgpd_test_peer_attr_SOURCES = bgpd/test_peer_attr.c
bgpd_test_ecommunity_SOURCES = bgpd/test_ecommunity.c
bgpd_test_mp_attr_SOURCES = bgpd/test_mp_attr.c
bgpd_test_mpath_SOURCES = bgpd/test_mpath.c
@@ -179,6 +181,7 @@ lib_cli_test_commands_LDADD = $(ALL_TESTS_LDADD)
bgpd_test_aspath_LDADD = $(BGP_TEST_LDADD)
bgpd_test_capability_LDADD = $(BGP_TEST_LDADD)
bgpd_test_packet_LDADD = $(BGP_TEST_LDADD)
+bgpd_test_peer_attr_LDADD = $(BGP_TEST_LDADD)
bgpd_test_ecommunity_LDADD = $(BGP_TEST_LDADD)
bgpd_test_mp_attr_LDADD = $(BGP_TEST_LDADD)
bgpd_test_mpath_LDADD = $(BGP_TEST_LDADD)
@@ -193,6 +196,7 @@ EXTRA_DIST = \
bgpd/test_ecommunity.py \
bgpd/test_mp_attr.py \
bgpd/test_mpath.py \
+ bgpd/test_peer_attr.py \
helpers/python/frrsix.py \
helpers/python/frrtest.py \
isisd/test_fuzz_isis_tlv.py \
diff --git a/tests/bgpd/test_peer_attr.c b/tests/bgpd/test_peer_attr.c
new file mode 100644
index 000000000..bb965a334
--- /dev/null
+++ b/tests/bgpd/test_peer_attr.c
@@ -0,0 +1,1144 @@
+/*
+ * BGP Peer Attribute Unit Tests
+ * Copyright (C) 2018 Pascal Mathis
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <zebra.h>
+
+#include "memory.h"
+#include "plist.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"
+
+#ifdef ENABLE_BGP_VNC
+#include "bgpd/rfapi/rfapi_backend.h"
+#endif
+
+/* Required variables to link in libbgp */
+struct zebra_privs_t bgpd_privs = {0};
+struct thread_master *master = NULL;
+
+enum test_state {
+ TEST_SUCCESS,
+ TEST_COMMAND_ERROR,
+ TEST_CONFIG_ERROR,
+ TEST_ASSERT_ERROR,
+ TEST_INTERNAL_ERROR,
+};
+
+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 test_config {
+ int local_asn;
+ int peer_asn;
+ const char *peer_address;
+ 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 { PEER_AT_AF_FLAG = 0,
+ PEER_AT_AF_FILTER = 1,
+ } type;
+ union {
+ uint32_t flag;
+ struct {
+ uint32_t flag;
+ size_t direct;
+ } filter;
+ } u;
+ struct {
+ bool invert;
+ bool use_ibgp;
+ } o;
+
+ afi_t afi;
+ safi_t safi;
+ struct test_peer_family families[AFI_MAX * SAFI_MAX];
+};
+
+#define OUT_SYMBOL_INFO "\u25ba"
+#define OUT_SYMBOL_OK "\u2714"
+#define OUT_SYMBOL_NOK "\u2716"
+
+/* clang-format off */
+#define TEST_ASSERT(T, C) \
+ do { \
+ if ((T)->state != TEST_SUCCESS || (C)) \
+ break; \
+ \
+ (T)->state = TEST_ASSERT_ERROR; \
+ (T)->error = str_printf("assertion failed: %s", (#C)); \
+ } while (0)
+
+#define TEST_AF_FLAGS(T, P, A, V, O) \
+ do { \
+ if ((T)->state != TEST_SUCCESS) \
+ break; \
+ \
+ TEST_ASSERT((T), !!CHECK_FLAG((P)->af_flags[(A)->afi][(A)->safi], (A)->u.flag) == ((V) ^ (A)->o.invert)); \
+ TEST_ASSERT((T), !!CHECK_FLAG((P)->af_flags_override[(A)->afi][(A)->safi], (A)->u.flag) == (O)); \
+ TEST_ASSERT((T), !!CHECK_FLAG((P)->af_flags_invert[(A)->afi][(A)->safi], (A)->u.flag) == (A)->o.invert); \
+ } while (0)
+
+#define TEST_AF_FILTER(T, P, A, S, O) \
+ do { \
+ if ((T)->state != TEST_SUCCESS) \
+ break; \
+ \
+ TEST_ASSERT((T), !!CHECK_FLAG((P)->filter_override[(A)->afi][(A)->safi][(A)->u.filter.direct], (A)->u.filter.flag) == (O)); \
+ switch ((A)->u.filter.flag) { \
+ case PEER_FT_DISTRIBUTE_LIST: \
+ TEST_ASSERT((T), !!((P)->filter[(A)->afi][(A)->safi].dlist[(A)->u.filter.direct].name) == (S)); \
+ break; \
+ case PEER_FT_FILTER_LIST: \
+ TEST_ASSERT((T), !!((P)->filter[(A)->afi][(A)->safi].aslist[(A)->u.filter.direct].name) == (S)); \
+ break; \
+ case PEER_FT_PREFIX_LIST: \
+ TEST_ASSERT((T), !!((P)->filter[(A)->afi][(A)->safi].plist[(A)->u.filter.direct].name) == (S)); \
+ break; \
+ case PEER_FT_ROUTE_MAP: \
+ TEST_ASSERT((T), !!((P)->filter[(A)->afi][(A)->safi].map[(A)->u.filter.direct].name) == (S)); \
+ break; \
+ case PEER_FT_UNSUPPRESS_MAP: \
+ TEST_ASSERT((T), !!((P)->filter[(A)->afi][(A)->safi].usmap.name) == (S)); \
+ break; \
+ } \
+ } while (0)
+/* clang-format on */
+
+static struct test_config cfg = {
+ .local_asn = 100,
+ .peer_asn = 200,
+ .peer_address = "1.1.1.1",
+ .peer_group = "PG-TEST",
+};
+
+/* clang-format off */
+static struct test_peer_attr test_peer_attrs[] = {
+ {
+ .cmd = "addpath-tx-all-paths",
+ .u.flag = PEER_FLAG_ADDPATH_TX_ALL_PATHS,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "addpath-tx-bestpath-per-AS",
+ .u.flag = PEER_FLAG_ADDPATH_TX_BESTPATH_PER_AS,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "allowas-in",
+ .peer_cmd = "allowas-in 1",
+ .group_cmd = "allowas-in 2",
+ .u.flag = PEER_FLAG_ALLOWAS_IN,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "allowas-in origin",
+ .u.flag = PEER_FLAG_ALLOWAS_IN_ORIGIN,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "as-override",
+ .u.flag = PEER_FLAG_AS_OVERRIDE,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "attribute-unchanged as-path",
+ .u.flag = PEER_FLAG_AS_PATH_UNCHANGED,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "attribute-unchanged next-hop",
+ .u.flag = PEER_FLAG_NEXTHOP_UNCHANGED,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "attribute-unchanged med",
+ .u.flag = PEER_FLAG_MED_UNCHANGED,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "attribute-unchanged as-path next-hop",
+ .u.flag = PEER_FLAG_AS_PATH_UNCHANGED
+ | PEER_FLAG_NEXTHOP_UNCHANGED,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "attribute-unchanged as-path med",
+ .u.flag = PEER_FLAG_AS_PATH_UNCHANGED
+ | PEER_FLAG_MED_UNCHANGED,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "attribute-unchanged as-path next-hop med",
+ .u.flag = PEER_FLAG_AS_PATH_UNCHANGED
+ | PEER_FLAG_NEXTHOP_UNCHANGED
+ | PEER_FLAG_MED_UNCHANGED,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "capability orf prefix-list send",
+ .u.flag = PEER_FLAG_ORF_PREFIX_SM,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "capability orf prefix-list receive",
+ .u.flag = PEER_FLAG_ORF_PREFIX_RM,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "capability orf prefix-list both",
+ .u.flag = PEER_FLAG_ORF_PREFIX_SM | PEER_FLAG_ORF_PREFIX_RM,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "default-originate",
+ .u.flag = PEER_FLAG_DEFAULT_ORIGINATE,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "maximum-prefix",
+ .peer_cmd = "maximum-prefix 10",
+ .group_cmd = "maximum-prefix 20",
+ .u.flag = PEER_FLAG_MAX_PREFIX,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "maximum-prefix",
+ .peer_cmd = "maximum-prefix 10 restart 100",
+ .group_cmd = "maximum-prefix 20 restart 200",
+ .u.flag = PEER_FLAG_MAX_PREFIX,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "next-hop-self",
+ .u.flag = PEER_FLAG_NEXTHOP_SELF,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "next-hop-self force",
+ .u.flag = PEER_FLAG_FORCE_NEXTHOP_SELF,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "remove-private-AS",
+ .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "remove-private-AS all",
+ .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS
+ | PEER_FLAG_REMOVE_PRIVATE_AS_ALL,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "remove-private-AS replace-AS",
+ .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS
+ | PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "remove-private-AS all replace-AS",
+ .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "route-reflector-client",
+ .u.flag = PEER_FLAG_REFLECTOR_CLIENT,
+ .o.use_ibgp = true,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "route-server-client",
+ .u.flag = PEER_FLAG_RSERVER_CLIENT,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "send-community",
+ .u.flag = PEER_FLAG_SEND_COMMUNITY,
+ .o.invert = true,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "send-community extended",
+ .u.flag = PEER_FLAG_SEND_EXT_COMMUNITY,
+ .o.invert = true,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "send-community large",
+ .u.flag = PEER_FLAG_SEND_LARGE_COMMUNITY,
+ .o.invert = true,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "soft-reconfiguration inbound",
+ .u.flag = PEER_FLAG_SOFT_RECONFIG,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .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,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {
+ .cmd = "weight",
+ .peer_cmd = "weight 100",
+ .group_cmd = "weight 200",
+ .u.flag = PEER_FLAG_WEIGHT,
+ .families = {
+ { .afi = AFI_IP, .safi = SAFI_UNICAST },
+ { .afi = AFI_IP6, .safi = SAFI_UNICAST },
+ }
+ },
+ {NULL}
+};
+/* clang-format on */
+
+static char *str_vprintf(const char *fmt, va_list ap)
+{
+ int ret;
+ int buf_size = 0;
+ char *buf = NULL;
+ va_list apc;
+
+ while (1) {
+ va_copy(apc, ap);
+ ret = vsnprintf(buf, buf_size, fmt, apc);
+ va_end(apc);
+
+ if (ret >= 0 && ret < buf_size)
+ break;
+
+ if (ret >= 0)
+ buf_size = ret + 1;
+ else
+ buf_size *= 2;
+
+ buf = XREALLOC(MTYPE_TMP, buf, buf_size);
+ }
+
+ return buf;
+}
+
+static char *str_printf(const char *fmt, ...)
+{
+ char *buf;
+ va_list ap;
+
+ va_start(ap, fmt);
+ buf = str_vprintf(fmt, ap);
+ va_end(ap);
+
+ return buf;
+}
+
+static const char *str_from_afi(afi_t afi)
+{
+ switch (afi) {
+ case AFI_IP:
+ return "ipv4";
+ case AFI_IP6:
+ return "ipv6";
+ default:
+ return "<unknown AFI>";
+ }
+}
+
+static const char *str_from_safi(safi_t safi)
+{
+ switch (safi) {
+ case SAFI_UNICAST:
+ return "unicast";
+ case SAFI_MULTICAST:
+ return "multicast";
+ case SAFI_MPLS_VPN:
+ return "labeled-unicast";
+ case SAFI_FLOWSPEC:
+ return "flowspec";
+ default:
+ return "<unknown SAFI>";
+ }
+}
+
+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 = str_vprintf(fmt, ap);
+ va_end(ap);
+ if (!cmd) {
+ test->state = TEST_INTERNAL_ERROR;
+ test->error =
+ str_printf("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 = str_printf(
+ "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 = str_printf(
+ "execution of command [%s] has failed with code [%d]",
+ cmd, ret);
+ }
+
+ /* Free memory and return. */
+ cmd_free_strvec(vline);
+ XFREE(MTYPE_TMP, cmd);
+ return;
+}
+
+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 = str_vprintf(fmt, apc);
+ va_end(apc);
+ if (!matcher) {
+ test->state = TEST_INTERNAL_ERROR;
+ test->error =
+ str_printf("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 = str_printf("expected config [%s] to be present",
+ matcher);
+ } else if (matched && invert) {
+ test->state = TEST_CONFIG_ERROR;
+ test->error = str_printf("expected config [%s] to be absent",
+ matcher);
+ }
+
+ /* Free memory and return. */
+ XFREE(MTYPE_TMP, matcher);
+ XFREE(MTYPE_TMP, config);
+ return;
+}
+
+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);
+}
+
+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 struct test *test_new(const char *desc, bool use_ibgp)
+{
+ struct test *test;
+ union sockunion su;
+
+ test = XCALLOC(MTYPE_TMP, sizeof(struct test));
+ test->state = TEST_SUCCESS;
+ test->desc = XSTRDUP(MTYPE_TMP, desc);
+ test->log = list_new();
+
+ test->vty = vty_new();
+ test->vty->type = VTY_TERM;
+ test->vty->node = CONFIG_NODE;
+
+ /* 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);
+ test_execute(test, "neighbor %s remote-as %d", cfg.peer_address,
+ use_ibgp ? cfg.local_asn : cfg.peer_asn);
+ if (test->state != TEST_SUCCESS)
+ return test;
+
+ /* Fetch default BGP instance. */
+ test->bgp = bgp_get_default();
+ if (!test->bgp) {
+ test->state = TEST_INTERNAL_ERROR;
+ test->error =
+ str_printf("could not retrieve default bgp instance");
+ return test;
+ }
+
+ /* Fetch peer instance. */
+ str2sockunion(cfg.peer_address, &su);
+ test->peer = peer_lookup(test->bgp, &su);
+ if (!test->peer) {
+ test->state = TEST_INTERNAL_ERROR;
+ test->error = str_printf(
+ "could not retrieve instance of bgp peer [%s]",
+ cfg.peer_address);
+ return test;
+ }
+
+ /* Fetch peer-group instance. */
+ test->group = peer_group_lookup(test->bgp, cfg.peer_group);
+ if (!test->group) {
+ test->state = TEST_INTERNAL_ERROR;
+ test->error = str_printf(
+ "could not retrieve instance of bgp peer-group [%s]",
+ cfg.peer_group);
+ return test;
+ }
+
+ return test;
+};
+
+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, str_vprintf(fmt, ap));
+ va_end(ap);
+}
+
+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_and_null(&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_attr(struct test *test, struct test_peer_attr *pa)
+{
+ int tc = 1;
+ const char *ec = pa->o.invert ? "no " : "";
+ const char *dc = pa->o.invert ? "" : "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;
+
+ /* Test Case: Switch active address-family. */
+ if (pa->type == PEER_AT_AF_FLAG || pa->type == PEER_AT_AF_FILTER) {
+ test_log(test, "prepare: switch address-family to [%s]",
+ afi_safi_print(pa->afi, pa->safi));
+ test_execute(test, "address-family %s %s",
+ str_from_afi(pa->afi), str_from_safi(pa->safi));
+ }
+
+ /* Test Case: Set flag on BGP peer. */
+ test_log(test, "case %02d: set af-flag [%s] on [%s]", tc++, peer_cmd,
+ p->host);
+ test_execute(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_absent(test, "neighbor %s %s", g->name, pa->cmd);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, true, true);
+ TEST_AF_FLAGS(test, g->conf, pa, false, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, true, true);
+ TEST_AF_FILTER(test, g->conf, pa, false, 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 peer-group %s", p->host,
+ g->name);
+ test_config_present(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_absent(test, "neighbor %s %s", g->name, pa->cmd);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, true, true);
+ TEST_AF_FLAGS(test, g->conf, pa, false, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, true, true);
+ TEST_AF_FILTER(test, g->conf, pa, false, 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 peer-group %s", p->host,
+ g->name);
+ test_config_present(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_absent(test, "neighbor %s %s", g->name, pa->cmd);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, true, true);
+ TEST_AF_FLAGS(test, g->conf, pa, false, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, true, true);
+ TEST_AF_FILTER(test, g->conf, pa, false, false);
+ }
+
+ /* Test Case: Set flag on BGP peer-group. */
+ test_log(test, "case %02d: set af-flag [%s] on [%s]", tc++, group_cmd,
+ g->name);
+ test_execute(test, "%sneighbor %s %s", ec, g->name, group_cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, g->name, group_cmd);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, true, true);
+ TEST_AF_FLAGS(test, g->conf, pa, true, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, true, true);
+ TEST_AF_FILTER(test, g->conf, pa, true, false);
+ }
+
+ /* Test Case: Unset flag on BGP peer-group. */
+ test_log(test, "case %02d: unset af-flag [%s] on [%s]", tc++, group_cmd,
+ g->name);
+ test_execute(test, "%sneighbor %s %s", dc, g->name, group_cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_absent(test, "neighbor %s %s", g->name, pa->cmd);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, true, true);
+ TEST_AF_FLAGS(test, g->conf, pa, false, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, true, true);
+ TEST_AF_FILTER(test, g->conf, pa, false, false);
+ }
+
+ /* Test Case: Set flag on BGP peer-group. */
+ test_log(test, "case %02d: set af-flag [%s] on [%s]", tc++, group_cmd,
+ g->name);
+ test_execute(test, "%sneighbor %s %s", ec, g->name, group_cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, g->name, group_cmd);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, true, true);
+ TEST_AF_FLAGS(test, g->conf, pa, true, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, true, true);
+ TEST_AF_FILTER(test, g->conf, pa, true, false);
+ }
+
+ /* Test Case: Re-set flag on BGP peer. */
+ test_log(test, "case %02d: re-set af-flag [%s] on [%s]", tc++, peer_cmd,
+ p->host);
+ test_execute(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, g->name, group_cmd);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, true, true);
+ TEST_AF_FLAGS(test, g->conf, pa, true, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, true, true);
+ TEST_AF_FILTER(test, g->conf, pa, true, false);
+ }
+
+ /* Test Case: Unset flag on BGP peer. */
+ test_log(test, "case %02d: unset af-flag [%s] on [%s]", tc++, peer_cmd,
+ p->host);
+ test_execute(test, "%sneighbor %s %s", dc, p->host, peer_cmd);
+ test_config_absent(test, "neighbor %s %s", p->host, pa->cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, g->name, group_cmd);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, true, false);
+ TEST_AF_FLAGS(test, g->conf, pa, true, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, true, false);
+ TEST_AF_FILTER(test, g->conf, pa, true, false);
+ }
+
+ /* Test Case: Unset flag on BGP peer-group. */
+ test_log(test, "case %02d: unset af-flag [%s] on [%s]", tc++, group_cmd,
+ g->name);
+ test_execute(test, "%sneighbor %s %s", dc, 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);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, false, false);
+ TEST_AF_FLAGS(test, g->conf, pa, false, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, false, false);
+ TEST_AF_FILTER(test, g->conf, pa, false, false);
+ }
+
+ /* Test Case: Set flag on BGP peer. */
+ test_log(test, "case %02d: set af-flag [%s] on [%s]", tc++, peer_cmd,
+ p->host);
+ test_execute(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_present(test, "%sneighbor %s %s", ec, p->host, peer_cmd);
+ test_config_absent(test, "neighbor %s %s", g->name, pa->cmd);
+ if (pa->type == PEER_AT_AF_FLAG) {
+ TEST_AF_FLAGS(test, p, pa, true, true);
+ TEST_AF_FLAGS(test, g->conf, pa, false, false);
+ } else if (pa->type == PEER_AT_AF_FILTER) {
+ TEST_AF_FILTER(test, p, pa, true, true);
+ TEST_AF_FILTER(test, g->conf, pa, false, false);
+ }
+}
+
+static void bgp_startup()
+{
+ cmd_init(1);
+ openzlog("testbgpd", "NONE", 0, LOG_CONS | LOG_NDELAY | LOG_PID,
+ LOG_DAEMON);
+ zprivs_preinit(&bgpd_privs);
+ zprivs_init(&bgpd_privs);
+
+ master = thread_master_create(NULL);
+ bgp_master_init(master);
+ bgp_option_set(BGP_OPT_NO_LISTEN);
+ vrf_init(NULL, NULL, NULL, NULL);
+ bgp_init();
+ bgp_pthreads_run();
+}
+
+static void bgp_shutdown()
+{
+ 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_and_null(&bm->bgp);
+ memset(bm, 0, sizeof(*bm));
+
+ vty_terminate();
+ cmd_terminate();
+ zprivs_terminate(&bgpd_privs);
+ thread_master_free(master);
+ master = NULL;
+ closezlog();
+}
+
+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++];
+
+ if (pa->families[0].afi && pa->families[0].safi) {
+ 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++;
+ }
+ } else {
+ pac = XMALLOC(MTYPE_TMP, sizeof(struct test_peer_attr));
+ memcpy(pac, pa, sizeof(struct test_peer_attr));
+ listnode_add(pa_list, pac);
+ }
+ }
+
+ for (ALL_LIST_ELEMENTS(pa_list, node, nnode, pa)) {
+ char *desc;
+ struct test *test;
+
+ /* Build test description string. */
+ if (pa->afi && pa->safi)
+ desc = str_printf("peer\\%s-%s\\%s",
+ str_from_afi(pa->afi),
+ str_from_safi(pa->safi), pa->cmd);
+ else
+ desc = str_printf("peer\\%s", pa->cmd);
+
+ /* Initialize new test instance. */
+ test = test_new(desc, pa->o.use_ibgp);
+ 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_and_null(&pa_list);
+ bgp_shutdown();
+
+ return 0;
+}
diff --git a/tests/bgpd/test_peer_attr.py b/tests/bgpd/test_peer_attr.py
new file mode 100644
index 000000000..17c659838
--- /dev/null
+++ b/tests/bgpd/test_peer_attr.py
@@ -0,0 +1,94 @@
+import frrtest
+
+class TestFlag(frrtest.TestMultiOut):
+ program = './test_peer_attr'
+
+# List of tests can be generated by executing:
+# $> ./test_peer_attr 2>&1 | sed -n 's/\\/\\\\/g; s/\S\+ \[test\] \(.\+\)/TestFlag.okfail(\x27\1\x27)/pg'
+#
+TestFlag.okfail('peer\\ipv4-unicast\\addpath-tx-all-paths')
+TestFlag.okfail('peer\\ipv6-unicast\\addpath-tx-all-paths')
+TestFlag.okfail('peer\\ipv4-unicast\\addpath-tx-bestpath-per-AS')
+TestFlag.okfail('peer\\ipv6-unicast\\addpath-tx-bestpath-per-AS')
+TestFlag.okfail('peer\\ipv4-unicast\\allowas-in')
+TestFlag.okfail('peer\\ipv6-unicast\\allowas-in')
+TestFlag.okfail('peer\\ipv4-unicast\\allowas-in origin')
+TestFlag.okfail('peer\\ipv6-unicast\\allowas-in origin')
+TestFlag.okfail('peer\\ipv4-unicast\\as-override')
+TestFlag.okfail('peer\\ipv6-unicast\\as-override')
+TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged as-path')
+TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged as-path')
+TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged next-hop')
+TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged next-hop')
+TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged med')
+TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged med')
+TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged as-path next-hop')
+TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged as-path next-hop')
+TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged as-path med')
+TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged as-path med')
+TestFlag.okfail('peer\\ipv4-unicast\\attribute-unchanged as-path next-hop med')
+TestFlag.okfail('peer\\ipv6-unicast\\attribute-unchanged as-path next-hop med')
+TestFlag.okfail('peer\\ipv4-unicast\\capability orf prefix-list send')
+TestFlag.okfail('peer\\ipv6-unicast\\capability orf prefix-list send')
+TestFlag.okfail('peer\\ipv4-unicast\\capability orf prefix-list receive')
+TestFlag.okfail('peer\\ipv6-unicast\\capability orf prefix-list receive')
+TestFlag.okfail('peer\\ipv4-unicast\\capability orf prefix-list both')
+TestFlag.okfail('peer\\ipv6-unicast\\capability orf prefix-list both')
+TestFlag.okfail('peer\\ipv4-unicast\\default-originate')
+TestFlag.okfail('peer\\ipv6-unicast\\default-originate')
+TestFlag.okfail('peer\\ipv4-unicast\\default-originate route-map')
+TestFlag.okfail('peer\\ipv6-unicast\\default-originate route-map')
+TestFlag.okfail('peer\\ipv4-unicast\\distribute-list')
+TestFlag.okfail('peer\\ipv6-unicast\\distribute-list')
+TestFlag.okfail('peer\\ipv4-unicast\\distribute-list')
+TestFlag.okfail('peer\\ipv6-unicast\\distribute-list')
+TestFlag.okfail('peer\\ipv4-unicast\\filter-list')
+TestFlag.okfail('peer\\ipv6-unicast\\filter-list')
+TestFlag.okfail('peer\\ipv4-unicast\\filter-list')
+TestFlag.okfail('peer\\ipv6-unicast\\filter-list')
+TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv4-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv6-unicast\\maximum-prefix')
+TestFlag.okfail('peer\\ipv4-unicast\\next-hop-self')
+TestFlag.okfail('peer\\ipv6-unicast\\next-hop-self')
+TestFlag.okfail('peer\\ipv4-unicast\\next-hop-self force')
+TestFlag.okfail('peer\\ipv6-unicast\\next-hop-self force')
+TestFlag.okfail('peer\\ipv4-unicast\\prefix-list')
+TestFlag.okfail('peer\\ipv6-unicast\\prefix-list')
+TestFlag.okfail('peer\\ipv4-unicast\\prefix-list')
+TestFlag.okfail('peer\\ipv6-unicast\\prefix-list')
+TestFlag.okfail('peer\\ipv4-unicast\\remove-private-AS')
+TestFlag.okfail('peer\\ipv6-unicast\\remove-private-AS')
+TestFlag.okfail('peer\\ipv4-unicast\\remove-private-AS all')
+TestFlag.okfail('peer\\ipv6-unicast\\remove-private-AS all')
+TestFlag.okfail('peer\\ipv4-unicast\\remove-private-AS replace-AS')
+TestFlag.okfail('peer\\ipv6-unicast\\remove-private-AS replace-AS')
+TestFlag.okfail('peer\\ipv4-unicast\\remove-private-AS all replace-AS')
+TestFlag.okfail('peer\\ipv6-unicast\\remove-private-AS all replace-AS')
+TestFlag.okfail('peer\\ipv4-unicast\\route-map')
+TestFlag.okfail('peer\\ipv6-unicast\\route-map')
+TestFlag.okfail('peer\\ipv4-unicast\\route-map')
+TestFlag.okfail('peer\\ipv6-unicast\\route-map')
+TestFlag.okfail('peer\\ipv4-unicast\\route-reflector-client')
+TestFlag.okfail('peer\\ipv6-unicast\\route-reflector-client')
+TestFlag.okfail('peer\\ipv4-unicast\\route-server-client')
+TestFlag.okfail('peer\\ipv6-unicast\\route-server-client')
+TestFlag.okfail('peer\\ipv4-unicast\\send-community')
+TestFlag.okfail('peer\\ipv6-unicast\\send-community')
+TestFlag.okfail('peer\\ipv4-unicast\\send-community extended')
+TestFlag.okfail('peer\\ipv6-unicast\\send-community extended')
+TestFlag.okfail('peer\\ipv4-unicast\\send-community large')
+TestFlag.okfail('peer\\ipv6-unicast\\send-community large')
+TestFlag.okfail('peer\\ipv4-unicast\\soft-reconfiguration inbound')
+TestFlag.okfail('peer\\ipv6-unicast\\soft-reconfiguration inbound')
+TestFlag.okfail('peer\\ipv4-unicast\\unsuppress-map')
+TestFlag.okfail('peer\\ipv6-unicast\\unsuppress-map')
+TestFlag.okfail('peer\\ipv4-unicast\\weight')
+TestFlag.okfail('peer\\ipv6-unicast\\weight')