diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index 6784e63206..454e134c8e 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -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); diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h index 06f350b36f..5007fafc29 100644 --- a/bgpd/bgp_attr.h +++ b/bgpd/bgp_attr.h @@ -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 */ diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c index 8976b3b674..fbe967d8b2 100644 --- a/bgpd/bgp_debug.c +++ b/bgpd/bgp_debug.c @@ -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[] = { diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index fe4ab272ac..044e72cc1e 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -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; diff --git a/bgpd/bgp_open.c b/bgpd/bgp_open.c index fa3fa3fcee..d8dd71788b 100644 --- a/bgpd/bgp_open.c +++ b/bgpd/bgp_open.c @@ -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]) { diff --git a/bgpd/bgp_open.h b/bgpd/bgp_open.h index 1727b66041..19ddd9bd25 100644 --- a/bgpd/bgp_open.h +++ b/bgpd/bgp_open.h @@ -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. */ diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index f9d01913a8..90e3f88058 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -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))) { diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c index c5d049f363..f113eccd39 100644 --- a/bgpd/bgp_updgrp.c +++ b/bgpd/bgp_updgrp.c @@ -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) diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index bb88adbfc2..03b66c7d45 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -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 local-role ", + 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 local-role 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 local-role [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); diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 7d284f28b3..4bf89a0375 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -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) { diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 98e59bcc85..b30a3059b9 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -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); diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index d4abf2c34d..76af844b37 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -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: +. + +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``: + . + + 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 diff --git a/doc/user/overview.rst b/doc/user/overview.rst index 9bcd2af346..bf401825e0 100644 --- a/doc/user/overview.rst +++ b/doc/user/overview.rst @@ -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 ---- diff --git a/lib/command.h b/lib/command.h index 047e550532..7363ed84c8 100644 --- a/lib/command.h +++ b/lib/command.h @@ -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); diff --git a/tests/bgpd/test_capability.c b/tests/bgpd/test_capability.c index 44d15d6014..51792825da 100644 --- a/tests/bgpd/test_capability.c +++ b/tests/bgpd/test_capability.c @@ -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 */ diff --git a/tests/bgpd/test_capability.py b/tests/bgpd/test_capability.py index e275195537..da9245bf04 100644 --- a/tests/bgpd/test_capability.py +++ b/tests/bgpd/test_capability.py @@ -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 index 0000000000..e69de29bb2 diff --git a/tests/topotests/bgp_roles_capability/r1/bgpd.conf b/tests/topotests/bgp_roles_capability/r1/bgpd.conf new file mode 100644 index 0000000000..4f152de960 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r1/bgpd.conf @@ -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 index 0000000000..3e90a261c1 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r1/zebra.conf @@ -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 index 0000000000..efca633daa --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r2/bgpd.conf @@ -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 index 0000000000..86a97846dc --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r2/zebra.conf @@ -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 index 0000000000..201c0afb2b --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r3/bgpd.conf @@ -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 index 0000000000..9df578e1b0 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r3/zebra.conf @@ -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 index 0000000000..30b97bb3a4 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r4/bgpd.conf @@ -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 index 0000000000..03632ee1ac --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r4/zebra.conf @@ -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 index 0000000000..b4bf73fa3d --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r5/bgpd.conf @@ -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 index 0000000000..2e95d7777f --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r5/zebra.conf @@ -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 index 0000000000..c0b5c81364 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/roles_capability_stand.dot @@ -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 index 0000000000..b8dea2f0b3 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 index 0000000000..55fc0972c1 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/test_bgp_roles_capability.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +# +# test_bgp_roles_capability.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2022 by Eugene Bogomazov +# 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 index 0000000000..e69de29bb2 diff --git a/tests/topotests/bgp_roles_filtering/r1/bgpd.conf b/tests/topotests/bgp_roles_filtering/r1/bgpd.conf new file mode 100644 index 0000000000..99f6211ac7 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r1/bgpd.conf @@ -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 index 0000000000..acf120b200 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r1/zebra.conf @@ -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 index 0000000000..f60bc6e38b --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r10/bgpd.conf @@ -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 index 0000000000..f2733fe1aa --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r10/zebra.conf @@ -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 index 0000000000..b6db8c1414 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r2/bgpd.conf @@ -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 index 0000000000..f785ea1ab4 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r2/zebra.conf @@ -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 index 0000000000..70f10b192c --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r3/bgpd.conf @@ -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 index 0000000000..b347257de4 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r3/zebra.conf @@ -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 index 0000000000..11e324e605 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r4/bgpd.conf @@ -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 index 0000000000..3543c08d55 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r4/zebra.conf @@ -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 index 0000000000..39d2a8dcce --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r5/bgpd.conf @@ -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 index 0000000000..4a1c2736b9 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r5/zebra.conf @@ -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 index 0000000000..25e5cd8660 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r6/bgpd.conf @@ -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 index 0000000000..3644a69908 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r6/zebra.conf @@ -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 index 0000000000..5f5f25790f --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r7/bgpd.conf @@ -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 index 0000000000..0407a488be --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r7/zebra.conf @@ -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 index 0000000000..df0f6853e4 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/roles_filtering_stand.dot @@ -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 index 0000000000..dfedcf854d 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 index 0000000000..77116f474b --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/test_bgp_roles_filtering.py @@ -0,0 +1,141 @@ +#!/usr/bin/python +# +# test_bgp_roles_filtering.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2022 by Eugene Bogomazov +# 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))