From 1cb1fe71f889eb37b5c958b4d980ae73d7ea23db Mon Sep 17 00:00:00 2001 From: Eugene Bogomazov Date: Tue, 28 Jun 2022 11:57:35 +0300 Subject: [PATCH] [PATCH] Add RFC9234 implementation for bird 2.0.10 --- doc/bird.sgml | 26 +++++++++++++++++++ proto/bgp/attrs.c | 62 +++++++++++++++++++++++++++++++++++++++++++++ proto/bgp/bgp.c | 25 ++++++++++++++++++ proto/bgp/bgp.h | 18 +++++++++++++ proto/bgp/config.Y | 17 +++++++++++-- proto/bgp/packets.c | 43 +++++++++++++++++++++++++++++++ 6 files changed, 189 insertions(+), 2 deletions(-) diff --git a/doc/bird.sgml b/doc/bird.sgml index 89b1541c..fa079b60 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -2377,6 +2377,7 @@ avoid routing loops. - BGP Administrative Shutdown Communication - Default EBGP Route Propagation Behavior without Policies - Revised Validation Procedure for BGP Flow Specifications + - Route Leak Prevention and Detection Using Roles Route selection rules @@ -2817,6 +2818,26 @@ using the following configuration parameters: protocol itself (for example, if a route is received through eBGP and therefore does not have such attribute). Default: 100 (0 in pre-1.2.0 versions of BIRD). + + + This attribute is defined in . OTC is a flag that marks + routes that should be sent only to customers. If is configured it set automatically. Example diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c index 9dd9fb1a..2fa9b0a2 100644 --- a/proto/bgp/attrs.c +++ b/proto/bgp/attrs.c @@ -406,6 +406,15 @@ bgp_format_origin(const eattr *a, byte *buf, uint size UNUSED) bsprintf(buf, (a->u.data <= 2) ? bgp_origin_names[a->u.data] : "?"); } +static void +bgp_decode_otc(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data UNUSED, uint len, ea_list **to) +{ + if (len != 4) + WITHDRAW(BAD_LENGTH, "OTC", len); + + u32 val = get_u32(data); + bgp_set_attr_u32(to, s->pool, BA_ONLY_TO_CUSTOMER, flags, val); +} static inline int bgp_as_path_first_as_equal(const byte *data, uint len, u32 asn) @@ -1117,6 +1126,13 @@ static const struct bgp_attr_desc bgp_attr_table[] = { .decode = bgp_decode_mpls_label_stack, .format = bgp_format_mpls_label_stack, }, + [BA_ONLY_TO_CUSTOMER] = { + .name = "OTC", + .type = EAF_TYPE_INT, + .flags = BAF_OPTIONAL | BAF_TRANSITIVE, + .encode = bgp_encode_u32, + .decode = bgp_decode_otc, + }, }; static inline int @@ -1445,6 +1461,35 @@ bgp_finish_attrs(struct bgp_parse_state *s, rta *a) REPORT("Discarding AIGP attribute received on non-AIGP session"); bgp_unset_attr(&a->eattrs, s->pool, BA_AIGP); } + + if (bgp_channel_is_role_applicable(s->channel)) + { + /* Reject routes from down neighbors if they are leaked */ + struct bgp_proto *p = s->proto; + eattr *e = bgp_find_attr(a->eattrs, BA_ONLY_TO_CUSTOMER); + if (e && + (p->cf->local_role == BGP_ROLE_PROVIDER || + p->cf->local_role == BGP_ROLE_RS_SERVER)) + goto withdraw; + + if (e && e->u.data != p->cf->remote_as && p->cf->local_role == BGP_ROLE_PEER) + goto withdraw; + + /* Mark routes from up neighbors if it doesn't happened before */ + if (!BIT32_TEST(s->attrs_seen, BA_ONLY_TO_CUSTOMER) && + (p->cf->local_role == BGP_ROLE_CUSTOMER || + p->cf->local_role == BGP_ROLE_PEER || + p->cf->local_role == BGP_ROLE_RS_CLIENT)) + bgp_set_attr_u32(&a->eattrs, s->pool, BA_ONLY_TO_CUSTOMER, 0, p->cf->remote_as); + } + return; + +withdraw: + if (!s->ip_reach_len && !s->mp_reach_len) + bgp_parse_error(s, 1); + + s->err_withdraw = 1; + return; } @@ -1725,6 +1770,14 @@ bgp_preexport(struct channel *C, rte **new, struct linpool *pool UNUSED) return -1; } + /* Do not export routes which are marked with OTC */ + c = ea_find(e->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_ONLY_TO_CUSTOMER)); + if (bgp_channel_is_role_applicable((struct bgp_channel *)C) && c && + (p->cf->local_role==BGP_ROLE_CUSTOMER || + p->cf->local_role==BGP_ROLE_PEER || + p->cf->local_role==BGP_ROLE_RS_CLIENT)) + return -1; + return 0; } @@ -1834,6 +1887,15 @@ bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *at } } + /* Mark routes with OTC to the down directions */ + a = bgp_find_attr(attrs, BA_ONLY_TO_CUSTOMER); + if (bgp_channel_is_role_applicable(c) && ! a) { + if (p->cf->local_role == BGP_ROLE_PROVIDER || + p->cf->local_role == BGP_ROLE_PEER || + p->cf->local_role == BGP_ROLE_RS_SERVER) + bgp_set_attr_u32(&attrs, pool, BA_ONLY_TO_CUSTOMER, 0, p->local_as); + } + /* * Presence of mandatory attributes ORIGIN and AS_PATH is ensured by above * conditions. Presence and validity of quasi-mandatory NEXT_HOP attribute diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index 89507f1a..a59b5e0a 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -1983,6 +1983,17 @@ bgp_postconfig(struct proto_config *CF) if (internal && cf->rs_client) cf_error("Only external neighbor can be RS client"); + if (internal && + (cf->local_role==BGP_ROLE_PEER || + cf->local_role==BGP_ROLE_CUSTOMER || + cf->local_role==BGP_ROLE_PROVIDER || + cf->local_role==BGP_ROLE_RS_CLIENT || + cf->local_role==BGP_ROLE_RS_SERVER)) + cf_error("External roles can be set only on external connection"); + + if (cf->strict_mode && cf->local_role==BGP_ROLE_UNDEFINED) + cf_error("Role must be set when using strict mode"); + if (!cf->confederation && cf->confederation_member) cf_error("Confederation ID must be set for member sessions"); @@ -2345,6 +2356,17 @@ bgp_show_afis(int code, char *s, u32 *afis, uint count) cli_msg(code, b.start); } + +static const char * +bgp_format_role_name(u8 role) +{ + static const char *bgp_role_names[] = { "provider", "rs_server", "rs_client", "customer", "peer" }; + if (role == BGP_ROLE_UNDEFINED) return "undefine"; + if (role <= 5) return bgp_role_names[role]; + return "?"; +} + + static void bgp_show_capabilities(struct bgp_proto *p UNUSED, struct bgp_caps *caps) { @@ -2473,6 +2495,9 @@ bgp_show_capabilities(struct bgp_proto *p UNUSED, struct bgp_caps *caps) if (caps->hostname) cli_msg(-1006, " Hostname: %s", caps->hostname); + + if (caps->role != BGP_ROLE_UNDEFINED) + cli_msg(-1006, " Role %s", bgp_format_role_name(caps->role)); } static void diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index 7cd1c27d..0cf3347b 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -114,6 +114,8 @@ struct bgp_config { int gr_mode; /* Graceful restart mode (BGP_GR_*) */ int llgr_mode; /* Long-lived graceful restart mode (BGP_LLGR_*) */ int setkey; /* Set MD5 password to system SA/SP database */ + u8 local_role; /* Set peering role with neighbor */ + int strict_mode; /* Is setting role on both sides is mandatory */ /* Times below are in seconds */ unsigned gr_time; /* Graceful restart timeout */ unsigned llgr_time; /* Long-lived graceful restart stale time */ @@ -167,6 +169,13 @@ struct bgp_channel_config { #define BGP_PT_INTERNAL 1 #define BGP_PT_EXTERNAL 2 +#define BGP_ROLE_UNDEFINED 255 +#define BGP_ROLE_PROVIDER 0 +#define BGP_ROLE_RS_SERVER 1 +#define BGP_ROLE_RS_CLIENT 2 +#define BGP_ROLE_CUSTOMER 3 +#define BGP_ROLE_PEER 4 + #define NH_NO 0 #define NH_ALL 1 #define NH_IBGP 2 @@ -223,6 +232,7 @@ struct bgp_caps { u8 ext_messages; /* Extended message length, RFC draft */ u8 route_refresh; /* Route refresh capability, RFC 2918 */ u8 enhanced_refresh; /* Enhanced route refresh, RFC 7313 */ + u8 role; /* BGP role capability, RFC 9234 */ u8 gr_aware; /* Graceful restart capability, RFC 4724 */ u8 gr_flags; /* Graceful restart flags */ @@ -278,6 +288,7 @@ struct bgp_conn { u8 last_channel_count; /* Number of times the last channel was used in succession */ int notify_code, notify_subcode, notify_size; byte *notify_data; + u8 neighbor_role; uint hold_time, keepalive_time; /* Times calculated from my and neighbor's requirements */ }; @@ -485,6 +496,12 @@ static inline int bgp_cc_is_ipv4(struct bgp_channel_config *c) static inline int bgp_cc_is_ipv6(struct bgp_channel_config *c) { return BGP_AFI(c->afi) == BGP_AFI_IPV6; } +static inline int bgp_channel_is_role_applicable(struct bgp_channel *c) +{ return (c->afi == BGP_AF_IPV4 || c->afi == BGP_AF_IPV6); } + +static inline int bgp_cc_is_role_applicable(struct bgp_channel_config *c) +{ return (c->afi == BGP_AF_IPV4 || c->afi == BGP_AF_IPV6); } + static inline uint bgp_max_packet_length(struct bgp_conn *conn) { return conn->ext_messages ? BGP_MAX_EXT_MSG_LENGTH : BGP_MAX_MESSAGE_LENGTH; } @@ -660,6 +677,7 @@ void bgp_update_next_hop(struct bgp_export_state *s, eattr *a, ea_list **to); #define BA_AS4_AGGREGATOR 0x12 /* RFC 6793 */ #define BA_AIGP 0x1a /* RFC 7311 */ #define BA_LARGE_COMMUNITY 0x20 /* RFC 8092 */ +#define BA_ONLY_TO_CUSTOMER 0x23 /* RFC 9234 */ /* Bird's private internal BGP attributes */ #define BA_MPLS_LABEL_STACK 0xfe /* MPLS label stack transfer attribute */ diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index 241aa7c2..8a8152c8 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -31,7 +31,8 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE, STRICT, BIND, CONFEDERATION, MEMBER, MULTICAST, FLOW4, FLOW6, LONG, LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS, DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE, - FIRST, FREE, VALIDATE, BASE) + FIRST, FREE, VALIDATE, BASE, LOCAL, ROLE, PEER, PROVIDER, CUSTOMER, + RS_SERVER, RS_CLIENT, STRICT, MODE) %type bgp_nh %type bgp_afi @@ -40,7 +41,7 @@ CF_KEYWORDS(CEASE, PREFIX, LIMIT, HIT, ADMINISTRATIVE, SHUTDOWN, RESET, PEER, CONFIGURATION, CHANGE, DECONFIGURED, CONNECTION, REJECTED, COLLISION, OUT, OF, RESOURCES) -%type bgp_cease_mask bgp_cease_list bgp_cease_flag +%type bgp_cease_mask bgp_cease_list bgp_cease_flag bgp_role_name CF_GRAMMAR @@ -74,6 +75,8 @@ bgp_proto_start: proto_start BGP { BGP_CFG->setkey = 1; BGP_CFG->dynamic_name = "dynbgp"; BGP_CFG->check_link = -1; + BGP_CFG->local_role = BGP_ROLE_UNDEFINED; + BGP_CFG->strict_mode = 0; } ; @@ -114,6 +117,14 @@ bgp_cease_flag: | OUT OF RESOURCES { $$ = 1 << 8; } ; +bgp_role_name: + PEER { $$ = BGP_ROLE_PEER; } + | PROVIDER { $$ = BGP_ROLE_PROVIDER; } + | CUSTOMER { $$ = BGP_ROLE_CUSTOMER; } + | RS_SERVER { $$ = BGP_ROLE_RS_SERVER; } + | RS_CLIENT { $$ = BGP_ROLE_RS_CLIENT; } + ; + bgp_proto: bgp_proto_start proto_name '{' | bgp_proto proto_item ';' @@ -197,6 +208,8 @@ bgp_proto: | bgp_proto BFD GRACEFUL ';' { init_bfd_opts(&BGP_CFG->bfd); BGP_CFG->bfd->mode = BGP_BFD_GRACEFUL; } | bgp_proto BFD { open_bfd_opts(&BGP_CFG->bfd); } bfd_opts { close_bfd_opts(); } ';' | bgp_proto ENFORCE FIRST AS bool ';' { BGP_CFG->enforce_first_as = $5; } + | bgp_proto LOCAL ROLE bgp_role_name ';' { BGP_CFG->local_role = $4; } + | bgp_proto STRICT MODE bool ';' { BGP_CFG->strict_mode = $4; } ; bgp_afi: diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c index f13625e2..3f5e1e8b 100644 --- a/proto/bgp/packets.c +++ b/proto/bgp/packets.c @@ -238,6 +238,7 @@ bgp_prepare_capabilities(struct bgp_conn *conn) caps->ext_messages = p->cf->enable_extended_messages; caps->route_refresh = p->cf->enable_refresh; caps->enhanced_refresh = p->cf->enable_refresh; + caps->role = p->cf->local_role; if (caps->as4_support) caps->as4_number = p->public_as; @@ -350,6 +351,14 @@ bgp_write_capabilities(struct bgp_conn *conn, byte *buf) *buf++ = 0; /* Capability data length */ } + if (caps->role != BGP_ROLE_UNDEFINED) + { + *buf++ = 9; /* Capability 9: Announce choosen BGP role */ + *buf++ = 1; /* Capability data length */ + buf[0] = caps->role; + buf += 1; + } + if (caps->gr_aware) { *buf++ = 64; /* Capability 64: Support for graceful restart */ @@ -460,6 +469,7 @@ bgp_read_capabilities(struct bgp_conn *conn, byte *pos, int len) conn->remote_caps = NULL; } + caps->role = BGP_ROLE_UNDEFINED; caps->length += len; while (len > 0) @@ -513,6 +523,20 @@ bgp_read_capabilities(struct bgp_conn *conn, byte *pos, int len) caps->ext_messages = 1; break; + case 9: /* Roles capability, RFC 9234 */ + if (cl != 1) + goto err; + + caps->role = pos[2]; + if ((conn->neighbor_role != BGP_ROLE_UNDEFINED) && (conn->neighbor_role != caps->role)) + { + mb_free(caps); + bgp_error(conn, 2, 11, NULL, 0); + return -1; + } + conn->neighbor_role = caps->role; + break; + case 64: /* Graceful restart capability, RFC 4724 */ if (cl % 4 != 2) goto err; @@ -704,6 +728,8 @@ bgp_read_options(struct bgp_conn *conn, byte *pos, uint len, uint rest) /* Length of option parameter header */ uint hlen = ext ? 3 : 2; + conn->neighbor_role = BGP_ROLE_UNDEFINED; + while (len > 0) { if (len < hlen) @@ -854,6 +880,22 @@ bgp_rx_open(struct bgp_conn *conn, byte *pkt, uint len) conn->received_as = asn; } + u8 neigh_role = conn->neighbor_role; + u8 local_role = p->cf->local_role; + + if ((neigh_role != BGP_ROLE_UNDEFINED) && + (local_role != BGP_ROLE_UNDEFINED) && !( + (local_role == BGP_ROLE_PEER && neigh_role == BGP_ROLE_PEER) || + (local_role == BGP_ROLE_CUSTOMER && neigh_role == BGP_ROLE_PROVIDER) || + (local_role == BGP_ROLE_PROVIDER && neigh_role == BGP_ROLE_CUSTOMER) || + (local_role == BGP_ROLE_RS_CLIENT && neigh_role == BGP_ROLE_RS_SERVER) || + (local_role == BGP_ROLE_RS_SERVER && neigh_role == BGP_ROLE_RS_CLIENT))) + + { bgp_error(conn, 2, 11, NULL, 0); return; } + + if ((p->cf->strict_mode) && (neigh_role == BGP_ROLE_UNDEFINED)) + { bgp_error(conn, 2, 11, NULL, 0); return; } + /* Check the other connection */ other = (conn == &p->outgoing_conn) ? &p->incoming_conn : &p->outgoing_conn; switch (other->state) @@ -2984,6 +3026,7 @@ static struct { { 2, 6, "Unacceptable hold time" }, { 2, 7, "Required capability missing" }, /* [RFC5492] */ { 2, 8, "No supported AFI/SAFI" }, /* This error msg is nonstandard */ + { 2,11, "Role mismatch" }, /* From Open Policy, RFC 9234 */ { 3, 0, "Invalid UPDATE message" }, { 3, 1, "Malformed attribute list" }, { 3, 2, "Unrecognized well-known attribute" }, -- 2.25.1