]> git.puffer.fish Git - matthieu/frr.git/commitdiff
bgpd: Add RFC9234 implementation
authorEugene Bogomazov <eb@qrator.net>
Fri, 17 Jun 2022 10:14:46 +0000 (13:14 +0300)
committerEugene Bogomazov <eb@qrator.net>
Fri, 17 Jun 2022 10:14:46 +0000 (13:14 +0300)
RFC9234 is a way to establish correct connection roles (Customer/
Provider, Peer or with RS) between bgp speakers. This patch:
- Add a new configuration/terminal option to set the appropriate local
role;
- Add a mechanism for checking used roles, implemented by exchanging
the corresponding capabilities in OPEN messages;
- Add strict mode to force other party to use this feature;
- Add basic support for a new transitive optional bgp attribute - OTC
(Only to Customer);
- Add logic for default setting OTC attribute and filtering routes with
this attribute by the edge speakers, if the appropriate conditions are
met;
- Add two test stands to check role negotiation and route filtering
during role usage.

Signed-off-by: Eugene Bogomazov <eb@qrator.net>
50 files changed:
bgpd/bgp_attr.c
bgpd/bgp_attr.h
bgpd/bgp_debug.c
bgpd/bgp_fsm.c
bgpd/bgp_open.c
bgpd/bgp_open.h
bgpd/bgp_route.c
bgpd/bgp_updgrp.c
bgpd/bgp_vty.c
bgpd/bgpd.c
bgpd/bgpd.h
doc/user/bgp.rst
doc/user/overview.rst
lib/command.h
tests/bgpd/test_capability.c
tests/bgpd/test_capability.py
tests/topotests/bgp_roles_capability/__init__.py [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r2/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r2/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r3/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r3/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r4/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r4/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r5/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/r5/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_capability/roles_capability_stand.dot [new file with mode: 0644]
tests/topotests/bgp_roles_capability/roles_capability_stand.jpg [new file with mode: 0644]
tests/topotests/bgp_roles_capability/test_bgp_roles_capability.py [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/__init__.py [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r1/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r1/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r10/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r10/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r2/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r2/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r3/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r3/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r4/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r4/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r5/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r5/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r6/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r6/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r7/bgpd.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/r7/zebra.conf [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/roles_filtering_stand.dot [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/roles_filtering_stand.jpg [new file with mode: 0644]
tests/topotests/bgp_roles_filtering/test_bgp_roles_filtering.py [new file with mode: 0644]

index 6784e632062c389dac1429130262c108b1f695a3..454e134c8e92877366e9d8e7fe38ed313975fafc 100644 (file)
@@ -76,6 +76,7 @@ static const struct message attr_str[] = {
        {BGP_ATTR_AS_PATHLIMIT, "AS_PATHLIMIT"},
        {BGP_ATTR_PMSI_TUNNEL, "PMSI_TUNNEL_ATTRIBUTE"},
        {BGP_ATTR_ENCAP, "ENCAP"},
+       {BGP_ATTR_OTC, "OTC"},
 #ifdef ENABLE_BGP_VNC_ATTR
        {BGP_ATTR_VNC, "VNC"},
 #endif
@@ -700,6 +701,7 @@ unsigned int attrhash_key_make(const void *p)
        MIX(attr->rmap_table_id);
        MIX(attr->nh_type);
        MIX(attr->bh_type);
+       MIX(attr->otc);
 
        return key;
 }
@@ -762,7 +764,8 @@ bool attrhash_cmp(const void *p1, const void *p2)
                    && srv6_vpn_same(attr1->srv6_vpn, attr2->srv6_vpn)
                    && attr1->srte_color == attr2->srte_color
                    && attr1->nh_type == attr2->nh_type
-                   && attr1->bh_type == attr2->bh_type)
+                   && attr1->bh_type == attr2->bh_type
+                   && attr1->otc == attr2->otc)
                        return true;
        }
 
@@ -1381,6 +1384,7 @@ const uint8_t attr_flags_values[] = {
        [BGP_ATTR_PMSI_TUNNEL] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS,
        [BGP_ATTR_LARGE_COMMUNITIES] =
                BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS,
+       [BGP_ATTR_OTC] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS,
        [BGP_ATTR_PREFIX_SID] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS,
        [BGP_ATTR_IPV6_EXT_COMMUNITIES] =
                BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS,
@@ -3027,6 +3031,28 @@ bgp_attr_pmsi_tunnel(struct bgp_attr_parser_args *args)
        return BGP_ATTR_PARSE_PROCEED;
 }
 
+/* OTC attribute. */
+static enum bgp_attr_parse_ret bgp_attr_otc(struct bgp_attr_parser_args *args)
+{
+       struct peer *const peer = args->peer;
+       struct attr *const attr = args->attr;
+       const bgp_size_t length = args->length;
+
+       /* Length check. */
+       if (length != 4) {
+               flog_err(EC_BGP_ATTR_LEN, "OTC attribute length isn't 4 [%u]",
+                        length);
+               return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR,
+                                         args->total);
+       }
+
+       attr->otc = stream_getl(peer->curr);
+
+       attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_OTC);
+
+       return BGP_ATTR_PARSE_PROCEED;
+}
+
 /* BGP unknown attribute treatment. */
 static enum bgp_attr_parse_ret
 bgp_attr_unknown(struct bgp_attr_parser_args *args)
@@ -3375,6 +3401,9 @@ enum bgp_attr_parse_ret bgp_attr_parse(struct peer *peer, struct attr *attr,
                case BGP_ATTR_IPV6_EXT_COMMUNITIES:
                        ret = bgp_attr_ipv6_ext_communities(&attr_args);
                        break;
+               case BGP_ATTR_OTC:
+                       ret = bgp_attr_otc(&attr_args);
+                       break;
                default:
                        ret = bgp_attr_unknown(&attr_args);
                        break;
@@ -4393,6 +4422,14 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer,
                // Unicast tunnel endpoint IP address
        }
 
+       /* OTC */
+       if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) {
+               stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS);
+               stream_putc(s, BGP_ATTR_OTC);
+               stream_putc(s, 4);
+               stream_putl(s, attr->otc);
+       }
+
        /* Unknown transit attribute. */
        struct transit *transit = bgp_attr_get_transit(attr);
 
@@ -4640,6 +4677,14 @@ void bgp_dump_routes_attr(struct stream *s, struct attr *attr,
                }
        }
 
+       /* OTC */
+       if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) {
+               stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS);
+               stream_putc(s, BGP_ATTR_OTC);
+               stream_putc(s, 4);
+               stream_putl(s, attr->otc);
+       }
+
        /* Return total size of attribute. */
        len = stream_get_endp(s) - cp - 2;
        stream_putw_at(s, cp, len);
index 06f350b36f26e07b3292f5eae7079a3878f77cc3..5007fafc297415a585dd60df0c8a924ed09e1dca 100644 (file)
@@ -334,6 +334,9 @@ struct attr {
 
        /* If NEXTHOP_TYPE_BLACKHOLE, then blackhole type */
        enum blackhole_type bh_type;
+
+       /* OTC value if set */
+       uint32_t otc;
 };
 
 /* rmap_change_flags definition */
index 8976b3b674eb00960d4e4fbaf1235920fba3a8f5..fbe967d8b2537812dbedecbccdc9fa5cb71e6c38 100644 (file)
@@ -141,6 +141,7 @@ static const struct message bgp_notify_open_msg[] = {
        {BGP_NOTIFY_OPEN_AUTH_FAILURE, "/Authentication Failure"},
        {BGP_NOTIFY_OPEN_UNACEP_HOLDTIME, "/Unacceptable Hold Time"},
        {BGP_NOTIFY_OPEN_UNSUP_CAPBL, "/Unsupported Capability"},
+       {BGP_NOTIFY_OPEN_ROLE_MISMATCH, "/Role Mismatch"},
        {0}};
 
 static const struct message bgp_notify_update_msg[] = {
index fe4ab272ac7048c43b5a1a7e368407165028c2b1..044e72cc1e017be570a918e745c7957285639e03 100644 (file)
@@ -243,6 +243,7 @@ static struct peer *peer_xfer_conn(struct peer *from_peer)
        peer->v_delayopen = from_peer->v_delayopen;
        peer->v_gr_restart = from_peer->v_gr_restart;
        peer->cap = from_peer->cap;
+       peer->neighbor_role = from_peer->neighbor_role;
        status = peer->status;
        pstatus = peer->ostatus;
        last_evt = peer->last_event;
@@ -1526,6 +1527,9 @@ int bgp_stop(struct peer *peer)
        /* Reset capabilities. */
        peer->cap = 0;
 
+       /* Resetting neighbor role to the default value */
+       peer->neighbor_role = ROLE_UNDEFINE;
+
        FOREACH_AFI_SAFI (afi, safi) {
                /* Reset all negotiated variables */
                peer->afc_nego[afi][safi] = 0;
index fa3fa3fcee35655f531a24c337414cd6c69bec41..d8dd71788be4998bf6b39242765325c1f1e0439b 100644 (file)
@@ -58,6 +58,7 @@ static const struct message capcode_str[] = {
        {CAPABILITY_CODE_ENHANCED_RR, "Enhanced Route Refresh"},
        {CAPABILITY_CODE_EXT_MESSAGE, "BGP Extended Message"},
        {CAPABILITY_CODE_LLGR, "Long-lived BGP Graceful Restart"},
+       {CAPABILITY_CODE_ROLE, "Role"},
        {0}};
 
 /* Minimum sizes for length field of each cap (so not inc. the header) */
@@ -77,6 +78,7 @@ static const size_t cap_minsizes[] = {
                [CAPABILITY_CODE_ENHANCED_RR] = CAPABILITY_CODE_ENHANCED_LEN,
                [CAPABILITY_CODE_EXT_MESSAGE] = CAPABILITY_CODE_EXT_MESSAGE_LEN,
                [CAPABILITY_CODE_LLGR] = CAPABILITY_CODE_LLGR_LEN,
+               [CAPABILITY_CODE_ROLE] = CAPABILITY_CODE_ROLE_LEN,
 };
 
 /* value the capability must be a multiple of.
@@ -100,6 +102,7 @@ static const size_t cap_modsizes[] = {
                [CAPABILITY_CODE_ENHANCED_RR] = 1,
                [CAPABILITY_CODE_EXT_MESSAGE] = 1,
                [CAPABILITY_CODE_LLGR] = 1,
+               [CAPABILITY_CODE_ROLE] = 1,
 };
 
 /* BGP-4 Multiprotocol Extentions lead us to the complex world. We can
@@ -887,6 +890,20 @@ static int bgp_capability_hostname(struct peer *peer,
        return 0;
 }
 
+static int bgp_capability_role(struct peer *peer, struct capability_header *hdr)
+{
+       SET_FLAG(peer->cap, PEER_CAP_ROLE_RCV);
+       if (hdr->length != CAPABILITY_CODE_ROLE_LEN) {
+               flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH,
+                         "Role: Received invalid length %d", hdr->length);
+               return -1;
+       }
+       uint8_t role = stream_getc(BGP_INPUT(peer));
+
+       peer->neighbor_role = role;
+       return 0;
+}
+
 /**
  * Parse given capability.
  * XXX: This is reading into a stream, but not using stream API
@@ -954,6 +971,7 @@ static int bgp_capability_parse(struct peer *peer, size_t length,
                case CAPABILITY_CODE_FQDN:
                case CAPABILITY_CODE_ENHANCED_RR:
                case CAPABILITY_CODE_EXT_MESSAGE:
+               case CAPABILITY_CODE_ROLE:
                        /* Check length. */
                        if (caphdr.length < cap_minsizes[caphdr.code]) {
                                zlog_info(
@@ -1051,6 +1069,9 @@ static int bgp_capability_parse(struct peer *peer, size_t length,
                case CAPABILITY_CODE_FQDN:
                        ret = bgp_capability_hostname(peer, &caphdr);
                        break;
+               case CAPABILITY_CODE_ROLE:
+                       ret = bgp_capability_role(peer, &caphdr);
+                       break;
                default:
                        if (caphdr.code > 128) {
                                /* We don't send Notification for unknown vendor
@@ -1113,6 +1134,35 @@ static bool strict_capability_same(struct peer *peer)
        return true;
 }
 
+
+static bool bgp_role_violation(struct peer *peer)
+{
+       uint8_t local_role = peer->local_role;
+       uint8_t neigh_role = peer->neighbor_role;
+
+       if (local_role != ROLE_UNDEFINE && neigh_role != ROLE_UNDEFINE &&
+           !((local_role == ROLE_PEER && neigh_role == ROLE_PEER) ||
+             (local_role == ROLE_PROVIDER && neigh_role == ROLE_CUSTOMER) ||
+             (local_role == ROLE_CUSTOMER && neigh_role == ROLE_PROVIDER) ||
+             (local_role == ROLE_RS_SERVER && neigh_role == ROLE_RS_CLIENT) ||
+             (local_role == ROLE_RS_CLIENT && neigh_role == ROLE_RS_SERVER))) {
+               bgp_notify_send(peer, BGP_NOTIFY_OPEN_ERR,
+                               BGP_NOTIFY_OPEN_ROLE_MISMATCH);
+               return true;
+       }
+       if (neigh_role == ROLE_UNDEFINE &&
+           CHECK_FLAG(peer->flags, PEER_FLAG_STRICT_MODE)) {
+               const char *err_msg =
+                       "Strict mode. Please set the role on your side.";
+               bgp_notify_send_with_data(peer, BGP_NOTIFY_OPEN_ERR,
+                                         BGP_NOTIFY_OPEN_ROLE_MISMATCH,
+                                         (uint8_t *)err_msg, strlen(err_msg));
+               return true;
+       }
+       return false;
+}
+
+
 /* peek into option, stores ASN to *as4 if the AS4 capability was found.
  * Returns  0 if no as4 found, as4cap value otherwise.
  */
@@ -1297,6 +1347,10 @@ int bgp_open_option_parse(struct peer *peer, uint16_t length,
                        ? BGP_EXTENDED_MESSAGE_MAX_PACKET_SIZE
                        : BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE;
 
+       /* Check that roles are corresponding to each other */
+       if (bgp_role_violation(peer))
+               return -1;
+
        /* Check there are no common AFI/SAFIs and send Unsupported Capability
           error. */
        if (*mp_capability
@@ -1674,6 +1728,16 @@ uint16_t bgp_open_capability(struct stream *s, struct peer *peer,
        stream_putc(s, CAPABILITY_CODE_EXT_MESSAGE);
        stream_putc(s, CAPABILITY_CODE_EXT_MESSAGE_LEN);
 
+       /* Role*/
+       if (peer->local_role != ROLE_UNDEFINE) {
+               SET_FLAG(peer->cap, PEER_CAP_ROLE_ADV);
+               stream_putc(s, BGP_OPEN_OPT_CAP);
+               stream_putc(s, CAPABILITY_CODE_ROLE_LEN + 2);
+               stream_putc(s, CAPABILITY_CODE_ROLE);
+               stream_putc(s, CAPABILITY_CODE_ROLE_LEN);
+               stream_putc(s, peer->local_role);
+       }
+
        /* AddPath */
        FOREACH_AFI_SAFI (afi, safi) {
                if (peer->afc[afi][safi]) {
index 1727b6604174bc3a2b21d9bfa3e16e22e868834b..19ddd9bd25afed6a9019ae1df3edb3e83cc8be69 100644 (file)
@@ -56,6 +56,7 @@ struct graceful_restart_af {
 #define CAPABILITY_CODE_REFRESH_OLD   128 /* Route Refresh Capability(cisco) */
 #define CAPABILITY_CODE_ORF_OLD       130 /* Cooperative Route Filtering Capability(cisco) */
 #define CAPABILITY_CODE_EXT_MESSAGE     6 /* Extended Message Support */
+#define CAPABILITY_CODE_ROLE            9 /* Role Capability */
 
 /* Capability Length */
 #define CAPABILITY_CODE_MP_LEN          4
@@ -70,6 +71,7 @@ struct graceful_restart_af {
 #define CAPABILITY_CODE_LLGR_LEN        0
 #define CAPABILITY_CODE_ORF_LEN         5
 #define CAPABILITY_CODE_EXT_MESSAGE_LEN 0 /* Extended Message Support */
+#define CAPABILITY_CODE_ROLE_LEN        1
 
 /* Cooperative Route Filtering Capability.  */
 
index f9d01913a8b82ff8dc6a92b13f835a8ed010f8c3..90e3f880587b95f84af02912943829fe27cc4bd7 100644 (file)
@@ -1561,6 +1561,43 @@ static bool bgp_cluster_filter(struct peer *peer, struct attr *attr)
        return false;
 }
 
+static bool bgp_otc_filter(struct peer *peer, struct attr *attr)
+{
+       if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) {
+               if (peer->local_role == ROLE_PROVIDER ||
+                   peer->local_role == ROLE_RS_SERVER)
+                       return true;
+               if (peer->local_role == ROLE_PEER && attr->otc != peer->as)
+                       return true;
+               return false;
+       }
+       if (peer->local_role == ROLE_CUSTOMER ||
+           peer->local_role == ROLE_PEER ||
+           peer->local_role == ROLE_RS_CLIENT) {
+               attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_OTC);
+               attr->otc = peer->as;
+       }
+       return false;
+}
+
+static bool bgp_otc_egress(struct peer *peer, struct attr *attr)
+{
+       if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) {
+               if (peer->local_role == ROLE_CUSTOMER ||
+                   peer->local_role == ROLE_RS_CLIENT ||
+                   peer->local_role == ROLE_PEER)
+                       return true;
+               return false;
+       }
+       if (peer->local_role == ROLE_PROVIDER ||
+           peer->local_role == ROLE_PEER ||
+           peer->local_role == ROLE_RS_SERVER) {
+               attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_OTC);
+               attr->otc = peer->bgp->as;
+       }
+       return false;
+}
+
 static int bgp_input_modifier(struct peer *peer, const struct prefix *p,
                              struct attr *attr, afi_t afi, safi_t safi,
                              const char *rmap_name, mpls_label_t *label,
@@ -2165,6 +2202,9 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi,
                        memset(&attr->mp_nexthop_local, 0, IPV6_MAX_BYTELEN);
        }
 
+       if (bgp_otc_egress(peer, attr))
+               return false;
+
        bgp_peer_remove_private_as(bgp, afi, safi, peer, attr);
        bgp_peer_as_override(bgp, afi, safi, peer, attr);
 
@@ -3961,6 +4001,12 @@ int bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
                goto filtered;
        }
 
+       if (bgp_otc_filter(peer, &new_attr)) {
+               reason = "failing otc validation";
+               bgp_attr_flush(&new_attr);
+               goto filtered;
+       }
+
        /* The flag BGP_NODE_FIB_INSTALL_PENDING is for the following
         * condition :
         * Suppress fib is enabled
@@ -10447,6 +10493,13 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn,
                        vty_out(vty, ", atomic-aggregate");
        }
 
+       if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) {
+               if (json_paths)
+                       json_object_int_add(json_path, "otc", attr->otc);
+               else
+                       vty_out(vty, ", otc %u", attr->otc);
+       }
+
        if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH)
            || (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)
                && bgp_path_info_mpath_count(path))) {
index c5d049f36316b3882cc27190291baf322dd344c9..f113eccd39e3bb8d891b433777ccc45c2749c8d1 100644 (file)
@@ -159,6 +159,7 @@ static void conf_copy(struct peer *dst, struct peer *src, afi_t afi,
        dst->local_as = src->local_as;
        dst->change_local_as = src->change_local_as;
        dst->shared_network = src->shared_network;
+       dst->local_role = src->local_role;
        memcpy(&(dst->nexthop), &(src->nexthop), sizeof(struct bgp_nexthop));
 
        dst->group = src->group;
@@ -308,6 +309,7 @@ static void *updgrp_hash_alloc(void *p)
  *       15. If peer is configured to be a lonesoul, peer ip address
  *       16. Local-as should match, if configured.
  *       17. maximum-prefix-out
+ *       18. Local-role should also match, if configured.
  *      )
  */
 static unsigned int updgrp_hash_key_make(const void *p)
@@ -411,6 +413,11 @@ static unsigned int updgrp_hash_key_make(const void *p)
            || CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_OUT))
                key = jhash_1word(jhash(peer->host, strlen(peer->host), SEED2),
                                  key);
+       /*
+        * Multiple sessions with the same neighbor should get their own
+        * update-group if they have different roles.
+        */
+       key = jhash_1word(peer->local_role, key);
 
        if (bgp_debug_neighbor_events(peer)) {
                zlog_debug(
@@ -532,6 +539,10 @@ static bool updgrp_hash_cmp(const void *p1, const void *p2)
        if (pe1->group != pe2->group)
                return false;
 
+       /* Roles can affect filtering */
+       if (pe1->local_role != pe2->local_role)
+               return false;
+
        /* route-map names should be the same */
        if ((fl1->map[RMAP_OUT].name && !fl2->map[RMAP_OUT].name)
            || (!fl1->map[RMAP_OUT].name && fl2->map[RMAP_OUT].name)
index bb88adbfc2513c004f26a106f1fd47d62e187ff8..03b66c7d45d5a5f39e872a1a72f6a528e7884513 100644 (file)
@@ -923,6 +923,12 @@ int bgp_vty_return(struct vty *vty, enum bgp_create_error_code ret)
        case BGP_ERR_INVALID_AS:
                str = "Confederation AS specified is the same AS as our AS.";
                break;
+       case BGP_ERR_INVALID_ROLE_NAME:
+               str = "Invalid role name";
+               break;
+       case BGP_ERR_INVALID_INTERNAL_ROLE:
+               str = "Extrenal roles can be set only on eBGP session";
+               break;
        }
        if (str) {
                vty_out(vty, "%% %s\n", str);
@@ -6405,6 +6411,91 @@ DEFUN (no_neighbor_ebgp_multihop,
        return peer_ebgp_multihop_unset_vty(vty, argv[idx_peer]->arg);
 }
 
+static uint8_t get_role_by_name(const char *role_str)
+{
+       if (strncmp(role_str, "peer", 2) == 0)
+               return ROLE_PEER;
+       if (strncmp(role_str, "provider", 2) == 0)
+               return ROLE_PROVIDER;
+       if (strncmp(role_str, "customer", 2) == 0)
+               return ROLE_CUSTOMER;
+       if (strncmp(role_str, "rs-server", 4) == 0)
+               return ROLE_RS_SERVER;
+       if (strncmp(role_str, "rs-client", 4) == 0)
+               return ROLE_RS_CLIENT;
+       return ROLE_UNDEFINE;
+}
+
+static int peer_role_set_vty(struct vty *vty, const char *ip_str,
+                            const char *role_str, int strict_mode)
+{
+       struct peer *peer;
+
+       peer = peer_lookup_vty(vty, ip_str);
+       if (!peer)
+               return CMD_WARNING_CONFIG_FAILED;
+       uint8_t role = get_role_by_name(role_str);
+
+       if (role == ROLE_UNDEFINE)
+               return bgp_vty_return(vty, BGP_ERR_INVALID_ROLE_NAME);
+       return bgp_vty_return(vty, peer_role_set(peer, role, strict_mode));
+}
+
+static int peer_role_unset_vty(struct vty *vty, const char *ip_str)
+{
+       struct peer *peer;
+
+       peer = peer_lookup_vty(vty, ip_str);
+       if (!peer)
+               return CMD_WARNING_CONFIG_FAILED;
+       return bgp_vty_return(vty, peer_role_unset(peer));
+}
+
+DEFUN(neighbor_role,
+      neighbor_role_cmd,
+      "neighbor <A.B.C.D|X:X::X:X|WORD> local-role <provider|rs-server|rs-client|customer|peer>",
+      NEIGHBOR_STR
+      NEIGHBOR_ADDR_STR2
+      "Set session role\n"
+      ROLE_STR)
+{
+       int idx_peer = 1;
+       int idx_role = 3;
+
+       return peer_role_set_vty(vty, argv[idx_peer]->arg, argv[idx_role]->arg,
+                                0);
+}
+
+DEFUN(neighbor_role_strict,
+      neighbor_role_strict_cmd,
+      "neighbor <A.B.C.D|X:X::X:X|WORD> local-role <provider|rs-server|rs-client|customer|peer> strict-mode",
+      NEIGHBOR_STR
+      NEIGHBOR_ADDR_STR2
+      "Set session role\n"
+      ROLE_STR
+      "Use additional restriction on peer\n")
+{
+       int idx_peer = 1;
+       int idx_role = 3;
+
+       return peer_role_set_vty(vty, argv[idx_peer]->arg, argv[idx_role]->arg,
+                                1);
+}
+
+DEFUN(no_neighbor_role,
+      no_neighbor_role_cmd,
+      "no neighbor <A.B.C.D|X:X::X:X|WORD> local-role <provider|rs-server|rs-client|customer|peer> [strict-mode]",
+      NO_STR
+      NEIGHBOR_STR
+      NEIGHBOR_ADDR_STR2
+      "Unset session role\n"
+      ROLE_STR
+      "Was used additional restriction on peer\n")
+{
+       int idx_peer = 2;
+
+       return peer_role_unset_vty(vty, argv[idx_peer]->arg);
+}
 
 /* disable-connected-check */
 DEFUN (neighbor_disable_connected_check,
@@ -12477,6 +12568,20 @@ static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json,
                        vty_out(vty, "unspecified link\n");
        }
 
+       /* Roles */
+       if (use_json) {
+               json_object_string_add(json_neigh, "localRole",
+                                      get_name_by_role(p->local_role));
+               json_object_string_add(json_neigh, "neighRole",
+                                      get_name_by_role(p->neighbor_role));
+       } else {
+               vty_out(vty, "  Local Role: %s\n",
+                       get_name_by_role(p->local_role));
+               vty_out(vty, "  Neighbor Role: %s\n",
+                       get_name_by_role(p->neighbor_role));
+       }
+
+
        /* Description. */
        if (p->desc) {
                if (use_json)
@@ -12941,6 +13046,22 @@ static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json,
                                                               "received");
                        }
 
+                       /* Role */
+                       if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV) ||
+                           CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV)) {
+                               if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV) &&
+                                   CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV))
+                                       json_object_string_add(
+                                               json_cap, "role",
+                                               "advertisedAndReceived");
+                               else if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV))
+                                       json_object_string_add(json_cap, "role",
+                                                              "advertised");
+                               else if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV))
+                                       json_object_string_add(json_cap, "role",
+                                                              "received");
+                       }
+
                        /* Extended nexthop */
                        if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV) ||
                            CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) {
@@ -13379,6 +13500,21 @@ static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json,
                                vty_out(vty, "\n");
                        }
 
+                       /* Role */
+                       if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV) ||
+                           CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV)) {
+                               vty_out(vty, "    Role:");
+                               if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV))
+                                       vty_out(vty, " advertised");
+                               if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV))
+                                       vty_out(vty, " %sreceived",
+                                               CHECK_FLAG(p->cap,
+                                                          PEER_CAP_ROLE_ADV)
+                                                       ? "and "
+                                                       : "");
+                               vty_out(vty, "\n");
+                       }
+
                        /* Extended nexthop */
                        if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV) ||
                            CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) {
@@ -16657,6 +16793,15 @@ static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp,
                }
        }
 
+       /* role */
+       if (peer->local_role != ROLE_UNDEFINE) {
+               vty_out(vty, " neighbor %s local-role %s%s\n", addr,
+                       get_name_by_role(peer->local_role),
+                       CHECK_FLAG(peer->flags, PEER_FLAG_STRICT_MODE)
+                               ? " strict-mode"
+                               : "");
+       }
+
        /* ttl-security hops */
        if (peer->gtsm_hops != BGP_GTSM_HOPS_DISABLED) {
                if (!peer_group_active(peer)
@@ -17927,6 +18072,11 @@ void bgp_vty_init(void)
        install_element(BGP_NODE, &bgp_maxmed_onstartup_cmd);
        install_element(BGP_NODE, &no_bgp_maxmed_onstartup_cmd);
 
+       /* "neighbor role" commands. */
+       install_element(BGP_NODE, &neighbor_role_cmd);
+       install_element(BGP_NODE, &neighbor_role_strict_cmd);
+       install_element(BGP_NODE, &no_neighbor_role_cmd);
+
        /* bgp disable-ebgp-connected-nh-check */
        install_element(BGP_NODE, &bgp_disable_connected_route_check_cmd);
        install_element(BGP_NODE, &no_bgp_disable_connected_route_check_cmd);
index 7d284f28b38b22c248db2076759a0c7e0fdc8c88..4bf89a0375922c88948ce8db6856481afa25d114 100644 (file)
@@ -1374,6 +1374,8 @@ struct peer *peer_new(struct bgp *bgp)
        peer->cur_event = peer->last_event = peer->last_major_event = 0;
        peer->bgp = bgp_lock(bgp);
        peer = peer_lock(peer); /* initial reference */
+       peer->local_role = ROLE_UNDEFINE;
+       peer->neighbor_role = ROLE_UNDEFINE;
        peer->password = NULL;
        peer->max_packet_size = BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE;
 
@@ -1470,6 +1472,7 @@ void peer_xfer_config(struct peer *peer_dst, struct peer *peer_src)
        peer_dst->tcp_mss = peer_src->tcp_mss;
        (void)peer_sort(peer_dst);
        peer_dst->rmap_type = peer_src->rmap_type;
+       peer_dst->local_role = peer_src->local_role;
 
        peer_dst->max_packet_size = peer_src->max_packet_size;
 
@@ -1996,6 +1999,17 @@ int peer_remote_as(struct bgp *bgp, union sockunion *su, const char *conf_if,
        return 0;
 }
 
+const char *get_name_by_role(uint8_t role)
+{
+       static const char *const bgp_role_names[] = {
+               "provider", "rs-server", "rs-client", "customer", "peer"};
+       if (role == ROLE_UNDEFINE)
+               return "undefine";
+       if (role <= 5)
+               return bgp_role_names[role];
+       return "unknown";
+}
+
 static void peer_group2peer_config_copy_af(struct peer_group *group,
                                           struct peer *peer, afi_t afi,
                                           safi_t safi)
@@ -4902,6 +4916,43 @@ int peer_ebgp_multihop_unset(struct peer *peer)
        return 0;
 }
 
+/* Set Open Policy Role and check its correctness */
+int peer_role_set(struct peer *peer, uint8_t role, int strict_mode)
+{
+       if (peer->local_role == role) {
+               if (CHECK_FLAG(peer->flags, PEER_FLAG_STRICT_MODE) &&
+                   !strict_mode)
+                       /* TODO: Is session restart needed if it was down? */
+                       UNSET_FLAG(peer->flags, PEER_FLAG_STRICT_MODE);
+               if (!CHECK_FLAG(peer->flags, PEER_FLAG_STRICT_MODE) &&
+                   strict_mode) {
+                       SET_FLAG(peer->flags, PEER_FLAG_STRICT_MODE);
+                       /* Restart session to throw Role Mismatch Notification
+                        */
+                       if (peer->neighbor_role == ROLE_UNDEFINE)
+                               bgp_session_reset(peer);
+               }
+       } else {
+               if (peer->sort == BGP_PEER_IBGP &&
+                   (role == ROLE_CUSTOMER || role == ROLE_PROVIDER ||
+                    role == ROLE_PEER || role == ROLE_RS_SERVER ||
+                    role == ROLE_RS_CLIENT))
+                       return BGP_ERR_INVALID_INTERNAL_ROLE;
+               peer->local_role = role;
+               if (strict_mode)
+                       SET_FLAG(peer->flags, PEER_FLAG_STRICT_MODE);
+               else
+                       UNSET_FLAG(peer->flags, PEER_FLAG_STRICT_MODE);
+               bgp_session_reset(peer);
+       }
+       return 0;
+}
+
+int peer_role_unset(struct peer *peer)
+{
+       return peer_role_set(peer, ROLE_UNDEFINE, 0);
+}
+
 /* Neighbor description. */
 void peer_description_set(struct peer *peer, const char *desc)
 {
index 98e59bcc85755e35c30745b480bd852496287ae8..b30a3059b99d73fa5a429fef4b1893dd45801031 100644 (file)
@@ -1167,6 +1167,18 @@ struct peer {
        int shared_network;      /* Is this peer shared same network. */
        struct bgp_nexthop nexthop; /* Nexthop */
 
+       /* Roles in bgp session */
+       uint8_t local_role;
+       uint8_t neighbor_role;
+#define ROLE_PROVIDER                       0
+#define ROLE_RS_SERVER                      1
+#define ROLE_RS_CLIENT                      2
+#define ROLE_CUSTOMER                       3
+#define ROLE_PEER                           4
+#define ROLE_UNDEFINE                     255
+
+#define ROLE_NAME_MAX_LEN                  20
+
        /* Peer address family configuration. */
        uint8_t afc[AFI_MAX][SAFI_MAX];
        uint8_t afc_nego[AFI_MAX][SAFI_MAX];
@@ -1204,6 +1216,8 @@ struct peer {
 #define PEER_CAP_GRACEFUL_RESTART_N_BIT_ADV (1U << 23)
 /* received graceful-restart notification (N) bit */
 #define PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV (1U << 24)
+#define PEER_CAP_ROLE_ADV                   (1U << 25) /* role advertised */
+#define PEER_CAP_ROLE_RCV                   (1U << 26) /* role received */
 
        /* Capability flags (reset in bgp_stop) */
        uint32_t af_cap[AFI_MAX][SAFI_MAX];
@@ -1320,6 +1334,11 @@ struct peer {
 /* force the extended format for Optional Parameters in OPEN message */
 #define PEER_FLAG_EXTENDED_OPT_PARAMS (1U << 30)
 
+       /* BGP Open Policy flags.
+        * Enforce using roles on both sides
+        */
+#define PEER_FLAG_STRICT_MODE               (1U << 31)
+
        /*
         *GR-Disabled mode means unset PEER_FLAG_GRACEFUL_RESTART
         *& PEER_FLAG_GRACEFUL_RESTART_HELPER
@@ -1803,6 +1822,7 @@ struct bgp_nlri {
 #define BGP_ATTR_ENCAP                          23
 #define BGP_ATTR_IPV6_EXT_COMMUNITIES           25
 #define BGP_ATTR_LARGE_COMMUNITIES              32
+#define BGP_ATTR_OTC                            35
 #define BGP_ATTR_PREFIX_SID                     40
 #define BGP_ATTR_SRTE_COLOR                     51
 #ifdef ENABLE_BGP_VNC_ATTR
@@ -1846,6 +1866,7 @@ struct bgp_nlri {
 #define BGP_NOTIFY_OPEN_AUTH_FAILURE             5
 #define BGP_NOTIFY_OPEN_UNACEP_HOLDTIME          6
 #define BGP_NOTIFY_OPEN_UNSUP_CAPBL              7
+#define BGP_NOTIFY_OPEN_ROLE_MISMATCH           11
 
 /* BGP_NOTIFY_UPDATE_ERR sub codes.  */
 #define BGP_NOTIFY_UPDATE_MAL_ATTR               1
@@ -1984,6 +2005,10 @@ enum bgp_create_error_code {
        BGP_ERR_GR_INVALID_CMD = -32,
        BGP_ERR_GR_OPERATION_FAILED = -33,
        BGP_GR_NO_OPERATION = -34,
+
+       /*BGP Open Policy ERRORS */
+       BGP_ERR_INVALID_ROLE_NAME = -35,
+       BGP_ERR_INVALID_INTERNAL_ROLE = -36
 };
 
 /*
@@ -2154,6 +2179,9 @@ extern int peer_ebgp_multihop_set(struct peer *, int);
 extern int peer_ebgp_multihop_unset(struct peer *);
 extern int is_ebgp_multihop_configured(struct peer *peer);
 
+extern int peer_role_set(struct peer *peer, uint8_t role, int strict_mode);
+extern int peer_role_unset(struct peer *peer);
+
 extern void peer_description_set(struct peer *, const char *);
 extern void peer_description_unset(struct peer *);
 
@@ -2253,6 +2281,8 @@ extern void peer_tx_shutdown_message_set(struct peer *, const char *msg);
 extern void peer_tx_shutdown_message_unset(struct peer *);
 
 extern void bgp_route_map_update_timer(struct thread *thread);
+extern const char *get_name_by_role(uint8_t role);
+
 extern void bgp_route_map_terminate(void);
 
 extern int peer_cmp(struct peer *p1, struct peer *p2);
index d4abf2c34d6addf38072a4b570440def961c43de..76af844b37a1ad058c65619123a4d2fe1cecfa37 100644 (file)
@@ -2643,6 +2643,65 @@ Large Communities in Route Map
 Note that the large expanded community is only used for `match` rule, not for
 `set` actions.
 
+.. _bgp-roles-and-only-to-customers:
+
+BGP Roles and Only to Customers
+-------------------------------
+
+BGP roles are defined in :rfc:`9234` and provide an easy way to route leaks
+prevention, detection and mitigation.
+
+To enable its mechanics, you must set your local role to reflect your type of
+peering relationship with your neighbor. Possible values of ``LOCAL-ROLE`` are:
+<provider|rs-server|rs-client|customer|peer>.
+
+The local Role value is negotiated with the new BGP Role capability with a
+built-in check of the corresponding value. In case of mismatch the new OPEN
+Roles Mismatch Notification <2, 11> would be sent.
+
+The correct Role pairs are:
+
+* Provider - Customer
+* Peer - Peer
+* RS-Server - RS-Client
+
+.. code-block:: shell
+
+   ~# vtysh -c 'show bgp neighbor' | grep 'Role'
+     Local Role: customer
+     Neighbor Role: provider
+       Role: advertised and received
+
+If strict-mode is set BGP session won't become established until BGP neighbor
+set local Role on its side. This configuratoin parameter is defined in
+:rfc:`9234` and used to enforce corresponding configuration at your
+conter-part side. Default value - disabled.
+
+Routes that sent from provider, rs-server, or peer local-role (or if received
+by customer, rs-clinet, or peer local-role) will be marked with a new
+Only to Customer (OTC) attribute.
+
+Routes with this attribute can only be sent to your neighbor if your
+local-role is provider or rs-server. Routes with this attribute can be
+received only if your local-role is customer or rs-client.
+
+In case of peer-peer relaitonship routes can be received only if
+OTC value is equal to your neighbor AS number.
+
+All these rules with OTC help to detect and mitigate route leaks and
+happened automatically if local-role is set.
+
+.. clicmd:: neighbor PEER local-role LOCAL-ROLE [strict-mode]
+
+   This command set your local-role to ``LOCAL-ROLE``:
+   <provider|rs-server|rs-client|customer|peer>.
+
+   This role help to detect and prevent route leaks.
+
+   If ``strict-mode`` is set, your neighbor must send you Capability with the
+   value of his role (by setting local-role on his side). Otherwise, a Role
+   Mismatch Notification will be sent.
+
 .. _bgp-l3vpn-vrfs:
 
 L3VPN VRFs
index 9bcd2af346fb3b67c42128e15672641a0d377829..bf401825e05c73ee5256444e59a6a6112db00a59 100644 (file)
@@ -383,6 +383,8 @@ BGP
   :t:`Extended BGP Administrative Shutdown Communication. J. Snijders, J. Heitz, J. Scudder, A. Azimov. January 2021`
 - :rfc:`9072`
   :t:`Extended Optional Parameters Length for BGP OPEN Message. E. Chen, J. Scudder. July 2021`
+- :rfc:`9234`
+  :t:`Route Leak Prevention and Detection Using Roles in UPDATE and OPEN Messages. A. Azimov, E. Bogomazov, R. Bush, K. Patel, K. Sriram. May 2022`
 
 OSPF
 ----
index 047e55053280d7d99a31667eda8bc7362af78d48..7363ed84c888c29d95a0a77c5d4c15156415d846 100644 (file)
@@ -508,6 +508,9 @@ struct cmd_node {
        EVPN_TYPE_4_HELP_STR EVPN_TYPE_4_HELP_STR                              \
        EVPN_TYPE_5_HELP_STR EVPN_TYPE_5_HELP_STR
 
+/* Describing roles */
+#define ROLE_STR                                                               \
+       "Providing transit\nRoute server\nRS client\nUsing transit\nPublic/private peering\n"
 
 /* Prototypes. */
 extern void install_node(struct cmd_node *node);
index 44d15d6014c3ed6f829e4589ee7ad2667525a0e4..51792825da94d4bce8a268d201aab5af04798c27 100644 (file)
@@ -649,6 +649,35 @@ static struct test_segment misc_segments[] =
                        2,
                        SHOULD_PARSE,
                },
+               {
+                       "Role",
+                       "Role capability",
+                       {
+                               /* hdr */ 0x9, 0x1,
+                               0x1,
+                       },
+                       3,
+                       SHOULD_PARSE,
+               },
+               {
+                       "Role-long",
+                       "Role capability, but too long",
+                       {
+                               /* hdr */ 0x9, 0x4,
+                               0x0, 0x0, 0x0, 0x1,
+                       },
+                       6,
+                       SHOULD_ERR,
+               },
+               {
+                       "Role-empty",
+                       "Role capability, but empty.",
+                       {
+                               /* hdr */ 0x9, 0x0,
+                       },
+                       2,
+                       SHOULD_ERR,
+               },
                {NULL, NULL, {0}, 0, 0}};
 
 /* DYNAMIC message */
index e2751955371a6fb2f3c3023a6612e6dfb7ac6a20..da9245bf04e2b93a0cce2753d58bbf5901a3683a 100644 (file)
@@ -36,6 +36,9 @@ TestCapability.okfail("ORF-empty: ORF capability, but empty.")
 TestCapability.okfail("AS4-empty: AS4 capability, but empty.")
 TestCapability.okfail("dyn-empty: Dynamic capability, but empty.")
 TestCapability.okfail("dyn-old: Dynamic capability (deprecated version)")
+TestCapability.okfail("Role: Role capability")
+TestCapability.okfail("Role-long: Role capability, but too long")
+TestCapability.okfail("Role-empty: Role capability, but empty.")
 TestCapability.okfail("Cap-singlets: One capability per Optional-Param")
 TestCapability.okfail("Cap-series: Series of capability, one Optional-Param")
 TestCapability.okfail("AS4more: AS4 capability after other caps (singlets)")
diff --git a/tests/topotests/bgp_roles_capability/__init__.py b/tests/topotests/bgp_roles_capability/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/bgp_roles_capability/r1/bgpd.conf b/tests/topotests/bgp_roles_capability/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..4f152de
--- /dev/null
@@ -0,0 +1,15 @@
+router bgp 64501
+  bgp router-id 192.168.2.1
+  network 192.0.2.0/24
+! Correct role pair
+  neighbor 192.168.2.2 remote-as 64502
+  neighbor 192.168.2.2 local-role provider
+! Incorrect role pair
+  neighbor 192.168.3.2 remote-as 64503
+  neighbor 192.168.3.2 local-role provider
+! Missed neighbor role
+  neighbor 192.168.4.2 remote-as 64504
+  neighbor 192.168.4.2 local-role provider
+! Missed neighbor role with strict-mode
+  neighbor 192.168.5.2 remote-as 64505
+  neighbor 192.168.5.2 local-role provider strict-mode
diff --git a/tests/topotests/bgp_roles_capability/r1/zebra.conf b/tests/topotests/bgp_roles_capability/r1/zebra.conf
new file mode 100644 (file)
index 0000000..3e90a26
--- /dev/null
@@ -0,0 +1,15 @@
+!
+interface r1-eth0
+ ip address 192.168.2.1/24
+!
+interface r1-eth1
+ ip address 192.168.3.1/24
+!
+interface r1-eth2
+ ip address 192.168.4.1/24
+!
+interface r1-eth3
+ ip address 192.168.5.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_capability/r2/bgpd.conf b/tests/topotests/bgp_roles_capability/r2/bgpd.conf
new file mode 100644 (file)
index 0000000..efca633
--- /dev/null
@@ -0,0 +1,4 @@
+router bgp 64502
+  bgp router-id 192.168.2.2
+  neighbor 192.168.2.1 remote-as 64501
+  neighbor 192.168.2.1 local-role customer
diff --git a/tests/topotests/bgp_roles_capability/r2/zebra.conf b/tests/topotests/bgp_roles_capability/r2/zebra.conf
new file mode 100644 (file)
index 0000000..86a9784
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r2-eth0
+ ip address 192.168.2.2/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_capability/r3/bgpd.conf b/tests/topotests/bgp_roles_capability/r3/bgpd.conf
new file mode 100644 (file)
index 0000000..201c0af
--- /dev/null
@@ -0,0 +1,3 @@
+router bgp 64503
+  neighbor 192.168.3.1 remote-as 64501
+  neighbor 192.168.3.1 local-role peer
diff --git a/tests/topotests/bgp_roles_capability/r3/zebra.conf b/tests/topotests/bgp_roles_capability/r3/zebra.conf
new file mode 100644 (file)
index 0000000..9df578e
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r3-eth0
+ ip address 192.168.3.2/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_capability/r4/bgpd.conf b/tests/topotests/bgp_roles_capability/r4/bgpd.conf
new file mode 100644 (file)
index 0000000..30b97bb
--- /dev/null
@@ -0,0 +1,2 @@
+router bgp 64504
+  neighbor 192.168.4.1 remote-as 64501
diff --git a/tests/topotests/bgp_roles_capability/r4/zebra.conf b/tests/topotests/bgp_roles_capability/r4/zebra.conf
new file mode 100644 (file)
index 0000000..03632ee
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r4-eth0
+ ip address 192.168.4.2/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_capability/r5/bgpd.conf b/tests/topotests/bgp_roles_capability/r5/bgpd.conf
new file mode 100644 (file)
index 0000000..b4bf73f
--- /dev/null
@@ -0,0 +1,2 @@
+router bgp 64505
+  neighbor 192.168.5.1 remote-as 64501
diff --git a/tests/topotests/bgp_roles_capability/r5/zebra.conf b/tests/topotests/bgp_roles_capability/r5/zebra.conf
new file mode 100644 (file)
index 0000000..2e95d77
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r5-eth0
+ ip address 192.168.5.2/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_capability/roles_capability_stand.dot b/tests/topotests/bgp_roles_capability/roles_capability_stand.dot
new file mode 100644 (file)
index 0000000..c0b5c81
--- /dev/null
@@ -0,0 +1,15 @@
+graph roles_filtering_stand {
+    layout="circo"
+    label="roles capability stand"
+    fontsize="20"
+
+    r1  [label="r1 provider"];
+    r2  [label="r2"];
+    r3  [label="r3"];
+    r4  [label="r4"];
+    r5  [label="r5"];
+    r1 -- r2 [headlabel="customer", taillabel="provider"];
+    r1 -- r3 [headlabel="peer", taillabel="provider"];
+    r1 -- r4 [headlabel="?", taillabel="provider"];
+    r1 -- r5 [headlabel="?", taillabel="provider", label="strict-mode"];
+}
diff --git a/tests/topotests/bgp_roles_capability/roles_capability_stand.jpg b/tests/topotests/bgp_roles_capability/roles_capability_stand.jpg
new file mode 100644 (file)
index 0000000..b8dea2f
Binary files /dev/null and b/tests/topotests/bgp_roles_capability/roles_capability_stand.jpg differ
diff --git a/tests/topotests/bgp_roles_capability/test_bgp_roles_capability.py b/tests/topotests/bgp_roles_capability/test_bgp_roles_capability.py
new file mode 100644 (file)
index 0000000..55fc097
--- /dev/null
@@ -0,0 +1,136 @@
+#!/usr/bin/python
+#
+# test_bgp_roles_capability.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2022 by Eugene Bogomazov <eb@qrator.net>
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# Permission to use, copy, modify, and/or distribute this software
+# for any purpose with or without fee is hereby granted, provided
+# that the above copyright notice and this permission notice appear
+# in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+#
+
+"""
+test_bgp_roles_capability: test bgp roles negotiation
+"""
+
+import json
+import os
+import sys
+import functools
+import pytest
+import time
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+pytestmark = [pytest.mark.bgpd]
+
+
+topodef = {f"s{i}": ("r1", f"r{i}") for i in range(2, 6)}
+
+
+@pytest.fixture(scope="module")
+def tgen(request):
+    tgen = Topogen(topodef, request.module.__name__)
+    tgen.start_topology()
+    router_list = tgen.routers()
+    for rname, router in router_list.items():
+        router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf")
+        router.load_config(TopoRouter.RD_BGP, "bgpd.conf")
+    tgen.start_router()
+    time.sleep(1)
+    yield tgen
+    tgen.stop_topology()
+
+
+@pytest.fixture(autouse=True)
+def skip_on_failure(tgen):
+    if tgen.routers_have_failure():
+        pytest.skip("skipped because of previous test failure")
+
+
+def is_role_mismatch(neighbor_status):
+    return (
+        neighbor_status["bgpState"] != "Established"
+        and neighbor_status.get("lastErrorCodeSubcode") == "020B"  # <2, 11>
+        and "Role Mismatch" in neighbor_status.get("lastNotificationReason", "")
+    )
+
+
+def test_correct_pair(tgen):
+    # provider-customer pair
+    neighbor_ip = "192.168.2.2"
+    neighbor_status = json.loads(
+        tgen.gears["r1"].vtysh_cmd(f"show bgp neighbors {neighbor_ip} json")
+    )[neighbor_ip]
+    assert neighbor_status["localRole"] == "provider"
+    assert neighbor_status["neighRole"] == "customer"
+    assert neighbor_status["bgpState"] == "Established"
+    assert (
+        neighbor_status["neighborCapabilities"].get("role") == "advertisedAndReceived"
+    )
+
+
+def test_role_pair_mismatch(tgen):
+    # provider-peer mistmatch
+    neighbor_ip = "192.168.3.2"
+    neighbor_status = json.loads(
+        tgen.gears["r1"].vtysh_cmd(f"show bgp neighbors {neighbor_ip} json")
+    )[neighbor_ip]
+    assert is_role_mismatch(neighbor_status)
+
+
+def test_single_role_advertising(tgen):
+    # provider-undefine pair; we set role
+    neighbor_ip = "192.168.4.2"
+    neighbor_status = json.loads(
+        tgen.gears["r1"].vtysh_cmd(f"show bgp neighbors {neighbor_ip} json")
+    )[neighbor_ip]
+    assert neighbor_status["localRole"] == "provider"
+    assert neighbor_status["neighRole"] == "undefine"
+    assert neighbor_status["bgpState"] == "Established"
+    assert neighbor_status["neighborCapabilities"].get("role") == "advertised"
+
+
+def test_single_role_receiving(tgen):
+    # provider-undefine pair; we receive role
+    neighbor_ip = "192.168.4.1"
+    neighbor_status = json.loads(
+        tgen.gears["r4"].vtysh_cmd(f"show bgp neighbors {neighbor_ip} json")
+    )[neighbor_ip]
+    assert neighbor_status["localRole"] == "undefine"
+    assert neighbor_status["neighRole"] == "provider"
+    assert neighbor_status["bgpState"] == "Established"
+    assert neighbor_status["neighborCapabilities"].get("role") == "received"
+
+
+def test_role_strict_mode(tgen):
+    # provider-undefine pair bur strict-mode was set
+    neighbor_ip = "192.168.5.2"
+    neighbor_status = json.loads(
+        tgen.gears["r1"].vtysh_cmd(f"show bgp neighbors {neighbor_ip} json")
+    )
+    assert is_role_mismatch(neighbor_status[neighbor_ip])
+
+
+if __name__ == "__main__":
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_roles_filtering/__init__.py b/tests/topotests/bgp_roles_filtering/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/topotests/bgp_roles_filtering/r1/bgpd.conf b/tests/topotests/bgp_roles_filtering/r1/bgpd.conf
new file mode 100644 (file)
index 0000000..99f6211
--- /dev/null
@@ -0,0 +1,12 @@
+! Provider on this side
+router bgp 64501
+  bgp router-id 192.168.1.1
+  no bgp network import-check
+  network 192.0.2.1/32
+  neighbor 192.168.1.2 remote-as 64510
+  neighbor 192.168.1.2 local-role provider
+  neighbor 192.168.1.2 route-map ALLOW_ALL out
+  neighbor 192.168.1.2 route-map ALLOW_ALL in
+  neighbor 192.168.1.2 timers 3 10
+
+route-map ALLOW_ALL permit 999
diff --git a/tests/topotests/bgp_roles_filtering/r1/zebra.conf b/tests/topotests/bgp_roles_filtering/r1/zebra.conf
new file mode 100644 (file)
index 0000000..acf120b
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r1-eth0
+ ip address 192.168.1.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_filtering/r10/bgpd.conf b/tests/topotests/bgp_roles_filtering/r10/bgpd.conf
new file mode 100644 (file)
index 0000000..f60bc6e
--- /dev/null
@@ -0,0 +1,21 @@
+! Customer on other side
+router bgp 64510
+  bgp router-id 192.168.10.1
+  no bgp ebgp-requires-policy
+  neighbor 192.168.1.1 remote-as 64501
+  neighbor 192.168.1.1 timers 3 10
+  neighbor 192.168.2.1 remote-as 64502
+  neighbor 192.168.2.1 timers 3 10
+  neighbor 192.168.3.1 remote-as 64503
+  neighbor 192.168.3.1 timers 3 10
+  neighbor 192.168.4.1 remote-as 64504
+  neighbor 192.168.4.1 timers 3 10
+  neighbor 192.168.5.1 remote-as 64505
+  neighbor 192.168.5.1 local-role provider
+  neighbor 192.168.5.1 timers 3 10
+  neighbor 192.168.6.1 remote-as 64506
+  neighbor 192.168.6.1 local-role peer
+  neighbor 192.168.6.1 timers 3 10
+  neighbor 192.168.7.1 remote-as 64507
+  neighbor 192.168.7.1 local-role customer
+  neighbor 192.168.7.1 timers 3 10
diff --git a/tests/topotests/bgp_roles_filtering/r10/zebra.conf b/tests/topotests/bgp_roles_filtering/r10/zebra.conf
new file mode 100644 (file)
index 0000000..f2733fe
--- /dev/null
@@ -0,0 +1,24 @@
+!
+interface r10-eth0
+ ip address 192.168.1.2/24
+!
+interface r10-eth1
+ ip address 192.168.2.2/24
+!
+interface r10-eth2
+ ip address 192.168.3.2/24
+!
+interface r10-eth3
+ ip address 192.168.4.2/24
+!
+interface r10-eth4
+ ip address 192.168.5.2/24
+!
+interface r10-eth5
+ ip address 192.168.6.2/24
+!
+interface r10-eth6
+ ip address 192.168.7.2/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_filtering/r2/bgpd.conf b/tests/topotests/bgp_roles_filtering/r2/bgpd.conf
new file mode 100644 (file)
index 0000000..b6db8c1
--- /dev/null
@@ -0,0 +1,12 @@
+! With peer on this side
+router bgp 64502
+  bgp router-id 192.168.2.1
+  no bgp network import-check
+  network 192.0.2.2/32
+  neighbor 192.168.2.2 remote-as 64510
+  neighbor 192.168.2.2 local-role peer
+  neighbor 192.168.2.2 route-map ALLOW_ALL out
+  neighbor 192.168.2.2 route-map ALLOW_ALL in
+  neighbor 192.168.2.2 timers 3 10
+
+route-map ALLOW_ALL permit 999
diff --git a/tests/topotests/bgp_roles_filtering/r2/zebra.conf b/tests/topotests/bgp_roles_filtering/r2/zebra.conf
new file mode 100644 (file)
index 0000000..f785ea1
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r2-eth0
+ ip address 192.168.2.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_filtering/r3/bgpd.conf b/tests/topotests/bgp_roles_filtering/r3/bgpd.conf
new file mode 100644 (file)
index 0000000..70f10b1
--- /dev/null
@@ -0,0 +1,12 @@
+! Customer role on this side
+router bgp 64503
+  bgp router-id 192.168.3.1
+  no bgp network import-check
+  network 192.0.2.3/32
+  neighbor 192.168.3.2 remote-as 64510
+  neighbor 192.168.3.2 local-role customer
+  neighbor 192.168.3.2 route-map ALLOW_ALL out
+  neighbor 192.168.3.2 route-map ALLOW_ALL in
+  neighbor 192.168.3.2 timers 3 10
+
+route-map ALLOW_ALL permit 999
diff --git a/tests/topotests/bgp_roles_filtering/r3/zebra.conf b/tests/topotests/bgp_roles_filtering/r3/zebra.conf
new file mode 100644 (file)
index 0000000..b347257
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r3-eth0
+ ip address 192.168.3.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_filtering/r4/bgpd.conf b/tests/topotests/bgp_roles_filtering/r4/bgpd.conf
new file mode 100644 (file)
index 0000000..11e324e
--- /dev/null
@@ -0,0 +1,11 @@
+! Without role on this side
+router bgp 64504
+  bgp router-id 192.168.4.1
+  no bgp network import-check
+  network 192.0.2.4/32
+  neighbor 192.168.4.2 remote-as 64510
+  neighbor 192.168.4.2 route-map ALLOW_ALL out
+  neighbor 192.168.4.2 route-map ALLOW_ALL in
+  neighbor 192.168.4.2 timers 3 10
+
+route-map ALLOW_ALL permit 999
diff --git a/tests/topotests/bgp_roles_filtering/r4/zebra.conf b/tests/topotests/bgp_roles_filtering/r4/zebra.conf
new file mode 100644 (file)
index 0000000..3543c08
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r4-eth0
+ ip address 192.168.4.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_filtering/r5/bgpd.conf b/tests/topotests/bgp_roles_filtering/r5/bgpd.conf
new file mode 100644 (file)
index 0000000..39d2a8d
--- /dev/null
@@ -0,0 +1,11 @@
+! Provider on other side
+router bgp 64505
+  bgp router-id 192.168.5.1
+  no bgp network import-check
+  network 192.0.2.5/32
+  neighbor 192.168.5.2 remote-as 64510
+  neighbor 192.168.5.2 route-map ALLOW_ALL out
+  neighbor 192.168.5.2 route-map ALLOW_ALL in
+  neighbor 192.168.5.2 timers 3 10
+
+route-map ALLOW_ALL permit 999
diff --git a/tests/topotests/bgp_roles_filtering/r5/zebra.conf b/tests/topotests/bgp_roles_filtering/r5/zebra.conf
new file mode 100644 (file)
index 0000000..4a1c273
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r5-eth0
+ ip address 192.168.5.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_filtering/r6/bgpd.conf b/tests/topotests/bgp_roles_filtering/r6/bgpd.conf
new file mode 100644 (file)
index 0000000..25e5cd8
--- /dev/null
@@ -0,0 +1,11 @@
+! Peer on other side
+router bgp 64506
+  bgp router-id 192.168.6.1
+  no bgp network import-check
+  network 192.0.2.6/32
+  neighbor 192.168.6.2 remote-as 64510
+  neighbor 192.168.6.2 route-map ALLOW_ALL out
+  neighbor 192.168.6.2 route-map ALLOW_ALL in
+  neighbor 192.168.6.2 timers 3 10
+
+route-map ALLOW_ALL permit 999
diff --git a/tests/topotests/bgp_roles_filtering/r6/zebra.conf b/tests/topotests/bgp_roles_filtering/r6/zebra.conf
new file mode 100644 (file)
index 0000000..3644a69
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r6-eth0
+ ip address 192.168.6.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_filtering/r7/bgpd.conf b/tests/topotests/bgp_roles_filtering/r7/bgpd.conf
new file mode 100644 (file)
index 0000000..5f5f257
--- /dev/null
@@ -0,0 +1,11 @@
+! Customer on other side
+router bgp 64507
+  bgp router-id 192.168.7.1
+  no bgp network import-check
+  network 192.0.2.7/32
+  neighbor 192.168.7.2 remote-as 64510
+  neighbor 192.168.7.2 route-map ALLOW_ALL out
+  neighbor 192.168.7.2 route-map ALLOW_ALL in
+  neighbor 192.168.7.2 timers 3 10
+
+route-map ALLOW_ALL permit 999
diff --git a/tests/topotests/bgp_roles_filtering/r7/zebra.conf b/tests/topotests/bgp_roles_filtering/r7/zebra.conf
new file mode 100644 (file)
index 0000000..0407a48
--- /dev/null
@@ -0,0 +1,6 @@
+!
+interface r7-eth0
+ ip address 192.168.7.1/24
+!
+ip forwarding
+!
diff --git a/tests/topotests/bgp_roles_filtering/roles_filtering_stand.dot b/tests/topotests/bgp_roles_filtering/roles_filtering_stand.dot
new file mode 100644 (file)
index 0000000..df0f685
--- /dev/null
@@ -0,0 +1,21 @@
+graph roles_filtering_stand {
+    layout="circo"
+    label="roles filtering stand"
+    fontsize="20"
+
+    r1  [label="r1 192.0.2.1/32"];
+    r2  [label="r2 192.0.2.2/32"];
+    r3  [label="r3 192.0.2.3/32"];
+    r4  [label="r4 192.0.2.4/32"];
+    r5  [label="r5 192.0.2.5/32"];
+    r6  [label="r6 192.0.2.6/32"];
+    r7  [label="r7 192.0.2.7/32"];
+    r10 [label="r10 intermediate"];
+    r10 -- r1 [headlabel="provider", taillabel="?"];
+    r10 -- r2 [headlabel="peer", taillabel="?"];
+    r10 -- r3 [headlabel="customer", taillabel="?"];
+    r10 -- r4 [headlabel="?", taillabel="?"];
+    r10 -- r5 [headlabel="?", taillabel="provider"];
+    r10 -- r6 [headlabel="?", taillabel="peer"];
+    r10 -- r7 [headlabel="?", taillabel="customer"];
+}
diff --git a/tests/topotests/bgp_roles_filtering/roles_filtering_stand.jpg b/tests/topotests/bgp_roles_filtering/roles_filtering_stand.jpg
new file mode 100644 (file)
index 0000000..dfedcf8
Binary files /dev/null and b/tests/topotests/bgp_roles_filtering/roles_filtering_stand.jpg differ
diff --git a/tests/topotests/bgp_roles_filtering/test_bgp_roles_filtering.py b/tests/topotests/bgp_roles_filtering/test_bgp_roles_filtering.py
new file mode 100644 (file)
index 0000000..77116f4
--- /dev/null
@@ -0,0 +1,141 @@
+#!/usr/bin/python
+#
+# test_bgp_roles_filtering.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2022 by Eugene Bogomazov <eb@qrator.net>
+# Copyright (c) 2017 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+# Permission to use, copy, modify, and/or distribute this software
+# for any purpose with or without fee is hereby granted, provided
+# that the above copyright notice and this permission notice appear
+# in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+#
+
+"""
+test_bgp_roles_filtering: test leaks prevention and mitigation with roles
+"""
+
+import json
+import os
+import sys
+import functools
+import pytest
+import time
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.bgp import verify_bgp_convergence_from_running_config
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+pytestmark = [pytest.mark.bgpd]
+
+
+topodef = {f"s{i}": (f"r{i}", "r10") for i in range(1, 8)}
+
+
+@pytest.fixture(scope="module")
+def tgen(request):
+    tgen = Topogen(topodef, request.module.__name__)
+    tgen.start_topology()
+    router_list = tgen.routers()
+    for rname, router in router_list.items():
+        router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf")
+        router.load_config(TopoRouter.RD_BGP, "bgpd.conf")
+    tgen.start_router()
+    BGP_CONVERGENCE = verify_bgp_convergence_from_running_config(tgen)
+    assert BGP_CONVERGENCE, f"setup_module :Failed \n Error: {BGP_CONVERGENCE}"
+    # Todo: What is the indented way to wait for convergence without json?!
+    time.sleep(3)
+    yield tgen
+    tgen.stop_topology()
+
+
+@pytest.fixture(autouse=True)
+def skip_on_failure(tgen):
+    if tgen.routers_have_failure():
+        pytest.skip("skipped because of previous test failure")
+
+
+def test_r10_routes(tgen):
+    # provider-undefine pair bur strict-mode was set
+    routes = json.loads(tgen.gears["r10"].vtysh_cmd("show bgp ipv4 json"))["routes"]
+    route_list = sorted(routes.keys())
+    assert route_list == [
+        "192.0.2.1/32",
+        "192.0.2.2/32",
+        "192.0.2.3/32",
+        "192.0.2.4/32",
+        "192.0.2.5/32",
+        "192.0.2.6/32",
+        "192.0.2.7/32",
+    ]
+    routes_with_otc = list()
+    for number in range(1, 8):
+        prefix = f"192.0.2.{number}/32"
+        route_details = json.loads(
+            tgen.gears["r10"].vtysh_cmd(f"show bgp ipv4 {prefix} json")
+        )
+        if route_details["paths"][0].get("otc") is not None:
+            routes_with_otc.append(prefix)
+    assert routes_with_otc == [
+        "192.0.2.1/32",
+        "192.0.2.2/32",
+        "192.0.2.6/32",
+        "192.0.2.7/32",
+    ]
+
+
+def test_r1_routes(tgen):
+    routes = json.loads(tgen.gears["r1"].vtysh_cmd("show bgp ipv4 json"))["routes"]
+    routes_list = sorted(routes.keys())
+    assert routes_list == [
+        "192.0.2.1/32",  # own
+        "192.0.2.3/32",
+        "192.0.2.4/32",
+        "192.0.2.5/32",
+    ]
+
+
+def test_r6_routes(tgen):
+    routes = json.loads(tgen.gears["r6"].vtysh_cmd("show bgp ipv4 json"))["routes"]
+    routes_list = sorted(routes.keys())
+    assert routes_list == [
+        "192.0.2.3/32",
+        "192.0.2.4/32",
+        "192.0.2.5/32",
+        "192.0.2.6/32",  # own
+    ]
+
+
+def test_r4_routes(tgen):
+    routes = json.loads(tgen.gears["r4"].vtysh_cmd("show bgp ipv4 json"))["routes"]
+    routes_list = sorted(routes.keys())
+    assert routes_list == [
+        "192.0.2.1/32",
+        "192.0.2.2/32",
+        "192.0.2.3/32",
+        "192.0.2.4/32",
+        "192.0.2.5/32",
+        "192.0.2.6/32",
+        "192.0.2.7/32",
+    ]
+
+
+if __name__ == "__main__":
+    args = ["-s"] + sys.argv[1:]
+    sys.exit(pytest.main(args))