From 4b1bc0cbc53289b2a750ce143eafc0ba5422e120 Mon Sep 17 00:00:00 2001 From: claudio Date: Tue, 9 Apr 2024 09:03:18 +0000 Subject: [PATCH] Allow operators to enforce the presence of certain capabilities on sessions. For simple capabilities this just adds enforce to the yes/no option of the announce statement. For multi-protocol capabilities and add-path there is an extra keyword. On top of this for add-path the enforcement requires the neighbor to send a matching capability, e.g 'announce add-path recv enforce' requires the other side to send any 'announce add-path send XYZ' capability. This is mainly to enforce as-4byte and extra multi-protocol capabilities. OK denis@ tb@ --- usr.sbin/bgpd/bgpd.conf.5 | 39 ++++++++++---- usr.sbin/bgpd/bgpd.h | 5 +- usr.sbin/bgpd/parse.y | 47 ++++++++++------ usr.sbin/bgpd/printconf.c | 54 ++++++++++++++----- usr.sbin/bgpd/session.c | 110 +++++++++++++++++++++++++++++++------- 5 files changed, 196 insertions(+), 59 deletions(-) diff --git a/usr.sbin/bgpd/bgpd.conf.5 b/usr.sbin/bgpd/bgpd.conf.5 index a52227b8e55..e637818c043 100644 --- a/usr.sbin/bgpd/bgpd.conf.5 +++ b/usr.sbin/bgpd/bgpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: bgpd.conf.5,v 1.238 2024/03/18 10:16:50 claudio Exp $ +.\" $OpenBSD: bgpd.conf.5,v 1.239 2024/04/09 09:03:18 claudio Exp $ .\" .\" Copyright (c) 2004 Claudio Jeker .\" Copyright (c) 2003, 2004 Henning Brauer @@ -16,7 +16,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: March 18 2024 $ +.Dd $Mdocdate: April 9 2024 $ .Dt BGPD.CONF 5 .Os .Sh NAME @@ -956,6 +956,7 @@ The neighbor properties are as follows: .Ic announce .Pq Ic IPv4 Ns | Ns Ic IPv6 .Pq Ic none Ns | Ns Ic unicast Ns | Ns Ic vpn Ns | Ns Ic flowspec +.Op Ic enforce .Xc For the given address family, control which .Em subsequent address families @@ -979,7 +980,7 @@ for the same address family of the session. .Pp .It Xo .Ic announce add-path recv -.Pq Ic yes Ns | Ns Ic no +.Pq Ic yes Ns | Ns Ic no Ns | Ns Ic enforce .Xc If set to .Ic yes , @@ -991,12 +992,14 @@ The default is .It Xo .Ic announce add-path send .Pq Ic no Ns | Ns Ic all +.Op Ic enforce .Xc .It Xo .Ic announce add-path send -.Pq Ic best Ns | Ns Ic ecmp | Ns Ic as-wide-best +.Pq Ic best Ns | Ns Ic ecmp Ns | Ns Ic as-wide-best .Op Ic plus Ar num .Op Ic max Ar num +.Op Ic enforce .Xc If set to .Ic all , @@ -1047,12 +1050,16 @@ is ignored. .Pp .It Xo .Ic announce as-4byte -.Pq Ic yes Ns | Ns Ic no +.Pq Ic yes Ns | Ns Ic no Ns | Ns Ic enforce .Xc If set to .Ic no , the 4-byte AS capability is not announced and so native 4-byte AS support is disabled. +If +.Ic enforce +is set, the session will only be established if the neighbor also announces +the capability. The default is .Ic yes . .Pp @@ -1069,11 +1076,15 @@ The default is .Pp .It Xo .Ic announce enhanced refresh -.Pq Ic yes Ns | Ns Ic no +.Pq Ic yes Ns | Ns Ic no Ns | Ns Ic enforce .Xc If set to .Ic yes , the enhanced route refresh capability is announced. +If +.Ic enforce +is set, the session will only be established if the neighbor also announces +the capability. The default is .Ic no . .Pp @@ -1088,24 +1099,28 @@ If the role of the neighbor does not correspond to the expected role then the session will be closed. If .Ic enforce -is set the session will only establish if the neighbor also announces -the open policy capability. +is set, the session will only be established if the neighbor also announces +the capability. The default is .Ic no . .Pp .It Xo .Ic announce refresh -.Pq Ic yes Ns | Ns Ic no +.Pq Ic yes Ns | Ns Ic no Ns | Ns Ic enforce .Xc If set to .Ic no , the route refresh capability is not announced. +If +.Ic enforce +is set, the session will only be established if the neighbor also announces +the capability. The default is .Ic yes . .Pp .It Xo .Ic announce restart -.Pq Ic yes Ns | Ns Ic no +.Pq Ic yes Ns | Ns Ic no Ns | Ns Ic enforce .Xc If set to .Ic no , @@ -1113,6 +1128,10 @@ the graceful restart capability is not announced. Currently only the End-of-RIB marker is supported and announced by the .Ic restart capability. +If +.Ic enforce +is set, the session will only be established if the neighbor also announces +the capability. The default is .Ic yes . .Pp diff --git a/usr.sbin/bgpd/bgpd.h b/usr.sbin/bgpd/bgpd.h index 869d6539ea6..8e791d0cec2 100644 --- a/usr.sbin/bgpd/bgpd.h +++ b/usr.sbin/bgpd/bgpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: bgpd.h,v 1.489 2024/03/22 15:41:34 claudio Exp $ */ +/* $OpenBSD: bgpd.h,v 1.490 2024/04/09 09:03:18 claudio Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -432,6 +432,9 @@ enum capa_codes { #define CAPA_AP_RECV 0x01 #define CAPA_AP_SEND 0x02 #define CAPA_AP_BIDIR 0x03 +#define CAPA_AP_MASK 0x0f +#define CAPA_AP_RECV_ENFORCE 0x10 /* internal only */ +#define CAPA_AP_SEND_ENFORCE 0x20 /* internal only */ /* values for RFC 9234 - BGP Open Policy */ #define CAPA_ROLE_PROVIDER 0x00 diff --git a/usr.sbin/bgpd/parse.y b/usr.sbin/bgpd/parse.y index 9a469f0327f..646bf967141 100644 --- a/usr.sbin/bgpd/parse.y +++ b/usr.sbin/bgpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.458 2024/04/03 08:57:26 claudio Exp $ */ +/* $OpenBSD: parse.y,v 1.459 2024/04/09 09:03:18 claudio Exp $ */ /* * Copyright (c) 2002, 2003, 2004 Henning Brauer @@ -273,6 +273,7 @@ typedef struct { %type asnumber as4number as4number_any optnumber %type espah af safi restart origincode nettype %type yesno inout restricted expires enforce +%type yesnoenforce enforce %type validity aspa_validity %type addpathextra addpathmax %type port proto_item tos length flag icmptype @@ -1889,7 +1890,7 @@ peeropts : REMOTEAS as4number { } curpeer->conf.min_holdtime = $3; } - | ANNOUNCE af safi { + | ANNOUNCE af safi enforce { uint8_t aid, safi; uint16_t afi; @@ -1905,42 +1906,48 @@ peeropts : REMOTEAS as4number { yyerror("unknown AFI/SAFI pair"); YYERROR; } - curpeer->conf.capabilities.mp[aid] = 1; + if ($4) + curpeer->conf.capabilities.mp[aid] = 2; + else + curpeer->conf.capabilities.mp[aid] = 1; } } | ANNOUNCE CAPABILITIES yesno { curpeer->conf.announce_capa = $3; } - | ANNOUNCE REFRESH yesno { + | ANNOUNCE REFRESH yesnoenforce { curpeer->conf.capabilities.refresh = $3; } - | ANNOUNCE ENHANCED REFRESH yesno { + | ANNOUNCE ENHANCED REFRESH yesnoenforce { curpeer->conf.capabilities.enhanced_rr = $4; } - | ANNOUNCE RESTART yesno { + | ANNOUNCE RESTART yesnoenforce { curpeer->conf.capabilities.grestart.restart = $3; } - | ANNOUNCE AS4BYTE yesno { + | ANNOUNCE AS4BYTE yesnoenforce { curpeer->conf.capabilities.as4byte = $3; } - | ANNOUNCE ADDPATH RECV yesno { + | ANNOUNCE ADDPATH RECV yesnoenforce { int8_t *ap = curpeer->conf.capabilities.add_path; uint8_t i; - for (i = AID_MIN; i < AID_MAX; i++) - if ($4) + for (i = AID_MIN; i < AID_MAX; i++) { + if ($4) { + if ($4 == 2) + ap[i] |= CAPA_AP_RECV_ENFORCE; ap[i] |= CAPA_AP_RECV; - else + } else ap[i] &= ~CAPA_AP_RECV; + } } - | ANNOUNCE ADDPATH SEND STRING addpathextra addpathmax { + | ANNOUNCE ADDPATH SEND STRING addpathextra addpathmax enforce { int8_t *ap = curpeer->conf.capabilities.add_path; enum addpath_mode mode; u_int8_t i; if (!strcmp($4, "no")) { free($4); - if ($5 != 0 || $6 != 0) { + if ($5 != 0 || $6 != 0 || $7 != 0) { yyerror("no additional option allowed " "for 'add-path send no'"); YYERROR; @@ -1970,16 +1977,18 @@ peeropts : REMOTEAS as4number { YYERROR; } for (i = AID_MIN; i < AID_MAX; i++) { - if (mode != ADDPATH_EVAL_NONE) + if (mode != ADDPATH_EVAL_NONE) { + if ($7) + ap[i] |= CAPA_AP_SEND_ENFORCE; ap[i] |= CAPA_AP_SEND; - else + } else ap[i] &= ~CAPA_AP_SEND; } curpeer->conf.eval.mode = mode; curpeer->conf.eval.extrapaths = $5; curpeer->conf.eval.maxpaths = $6; } - | ANNOUNCE POLICY enforce { + | ANNOUNCE POLICY yesnoenforce { curpeer->conf.capabilities.policy = $3; } | ROLE STRING { @@ -3083,7 +3092,11 @@ delete : /* empty */ { $$ = 0; } | DELETE { $$ = 1; } ; -enforce : yesno { $$ = $1; } +enforce : /* empty */ { $$ = 0; } + | ENFORCE { $$ = 2; } + ; + +yesnoenforce : yesno { $$ = $1; } | ENFORCE { $$ = 2; } ; diff --git a/usr.sbin/bgpd/printconf.c b/usr.sbin/bgpd/printconf.c index 74095ddb49c..f1a4efa3d5e 100644 --- a/usr.sbin/bgpd/printconf.c +++ b/usr.sbin/bgpd/printconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: printconf.c,v 1.170 2024/03/20 09:35:46 claudio Exp $ */ +/* $OpenBSD: printconf.c,v 1.171 2024/04/09 09:03:18 claudio Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -916,37 +916,67 @@ void print_announce(struct peer_config *p, const char *c) { uint8_t aid; + int match = 0; if (p->announce_capa == 0) printf("%s\tannounce capabilities no\n", c); for (aid = AID_MIN; aid < AID_MAX; aid++) - if (p->capabilities.mp[aid]) + if (p->capabilities.mp[aid] == 2) { + printf("%s\tannounce %s enforce\n", c, aid2str(aid)); + match = 1; + } else if (p->capabilities.mp[aid]) { printf("%s\tannounce %s\n", c, aid2str(aid)); + match = 1; + } + if (!match) { + printf("%s\tannounce IPv4 none\n", c); + printf("%s\tannounce IPv6 none\n", c); + } - if (p->capabilities.refresh == 0) + if (p->capabilities.refresh == 2) + printf("%s\tannounce refresh enforce\n", c); + else if (p->capabilities.refresh == 0) printf("%s\tannounce refresh no\n", c); - if (p->capabilities.enhanced_rr == 1) + + if (p->capabilities.enhanced_rr == 2) + printf("%s\tannounce enhanced refresh enforce\n", c); + else if (p->capabilities.enhanced_rr == 1) printf("%s\tannounce enhanced refresh yes\n", c); - if (p->capabilities.grestart.restart == 0) + + if (p->capabilities.grestart.restart == 2) + printf("%s\tannounce restart enforce\n", c); + else if (p->capabilities.grestart.restart == 0) printf("%s\tannounce restart no\n", c); - if (p->capabilities.as4byte == 0) + + if (p->capabilities.as4byte == 2) + printf("%s\tannounce as4byte enforce\n", c); + else if (p->capabilities.as4byte == 0) printf("%s\tannounce as4byte no\n", c); - if (p->capabilities.add_path[0] & CAPA_AP_RECV) + + if (p->capabilities.add_path[AID_MIN] & CAPA_AP_RECV_ENFORCE) + printf("%s\tannounce add-path recv enforce\n", c); + else if (p->capabilities.add_path[AID_MIN] & CAPA_AP_RECV) printf("%s\tannounce add-path recv yes\n", c); - if (p->capabilities.add_path[0] & CAPA_AP_SEND) { + + if (p->capabilities.add_path[AID_MIN] & CAPA_AP_SEND) { printf("%s\tannounce add-path send %s", c, print_addpath_mode(p->eval.mode)); if (p->eval.extrapaths != 0) printf(" plus %d", p->eval.extrapaths); if (p->eval.maxpaths != 0) printf(" max %d", p->eval.maxpaths); + if (p->capabilities.add_path[AID_MIN] & CAPA_AP_SEND_ENFORCE) + printf(" enforce"); printf("\n"); } - if (p->capabilities.policy) { - printf("%s\tannounce policy %s\n", c, - p->capabilities.policy == 2 ? "enforce" : "yes"); - } + + if (p->capabilities.policy == 2) + printf("%s\tannounce policy enforce\n", c); + else if (p->capabilities.policy == 1) + printf("%s\tannounce policy yes\n", c); + else + printf("%s\tannounce policy no\n", c); } void diff --git a/usr.sbin/bgpd/session.c b/usr.sbin/bgpd/session.c index a5ce338080f..dd99479c272 100644 --- a/usr.sbin/bgpd/session.c +++ b/usr.sbin/bgpd/session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: session.c,v 1.467 2024/03/26 12:45:29 claudio Exp $ */ +/* $OpenBSD: session.c,v 1.468 2024/04/09 09:03:18 claudio Exp $ */ /* * Copyright (c) 2003, 2004, 2005 Henning Brauer @@ -89,7 +89,7 @@ int parse_update(struct peer *); int parse_rrefresh(struct peer *); int parse_notification(struct peer *); int parse_capabilities(struct peer *, u_char *, uint16_t, uint32_t *); -int capa_neg_calc(struct peer *, uint8_t *); +int capa_neg_calc(struct peer *); void session_dispatch_imsg(struct imsgbuf *, int, u_int *); void session_up(struct peer *); void session_down(struct peer *); @@ -1562,12 +1562,13 @@ session_open(struct peer *p) for (i = AID_MIN; i < AID_MAX; i++) { if (p->capa.ann.mp[i]) { errs += session_capa_add_afi(opb, - i, p->capa.ann.add_path[i]); + i, p->capa.ann.add_path[i] & + CAPA_AP_MASK); } } } else { /* AID_INET */ errs += session_capa_add_afi(opb, AID_INET, - p->capa.ann.add_path[AID_INET]); + p->capa.ann.add_path[AID_INET] & CAPA_AP_MASK); } } @@ -2167,7 +2168,7 @@ parse_open(struct peer *peer) uint16_t holdtime, oholdtime, myholdtime; uint32_t as, bgpid; uint16_t optparamlen, extlen, plen, op_len; - uint8_t op_type, suberr = 0; + uint8_t op_type; p = peer->rbuf->rptr; p += MSGSIZE_HEADER_MARKER; @@ -2350,8 +2351,7 @@ bad_len: return (-1); } - if (capa_neg_calc(peer, &suberr) == -1) { - session_notification(peer, ERR_OPEN, suberr, NULL); + if (capa_neg_calc(peer) == -1) { change_state(peer, STATE_IDLE, EVNT_RCVD_OPEN); return (-1); } @@ -2735,9 +2735,10 @@ parse_capabilities(struct peer *peer, u_char *d, uint16_t dlen, uint32_t *as) } int -capa_neg_calc(struct peer *p, uint8_t *suberr) +capa_neg_calc(struct peer *p) { - uint8_t i, hasmp = 0; + struct ibuf *ebuf; + uint8_t i, hasmp = 0, capa_code, capa_len, capa_aid = 0; /* a capability is accepted only if both sides announced it */ @@ -2811,6 +2812,8 @@ capa_neg_calc(struct peer *p, uint8_t *suberr) */ memset(p->capa.neg.add_path, 0, sizeof(p->capa.neg.add_path)); for (i = AID_MIN; i < AID_MAX; i++) { + if (p->capa.neg.mp[i] == 0) + continue; if ((p->capa.ann.add_path[i] & CAPA_AP_RECV) && (p->capa.peer.add_path[i] & CAPA_AP_SEND)) { p->capa.neg.add_path[i] |= CAPA_AP_RECV; @@ -2836,43 +2839,112 @@ capa_neg_calc(struct peer *p, uint8_t *suberr) switch (p->conf.role) { case ROLE_PROVIDER: if (p->remote_role != ROLE_CUSTOMER) - goto fail; + goto policyfail; break; case ROLE_RS: if (p->remote_role != ROLE_RS_CLIENT) - goto fail; + goto policyfail; break; case ROLE_RS_CLIENT: if (p->remote_role != ROLE_RS) - goto fail; + goto policyfail; break; case ROLE_CUSTOMER: if (p->remote_role != ROLE_PROVIDER) - goto fail; + goto policyfail; break; case ROLE_PEER: if (p->remote_role != ROLE_PEER) - goto fail; + goto policyfail; break; default: - fail: + policyfail: log_peer_warnx(&p->conf, "open policy role mismatch: " "our role %s, their role %s", log_policy(p->conf.role), log_policy(p->remote_role)); - *suberr = ERR_OPEN_ROLE; + session_notification(p, ERR_OPEN, ERR_OPEN_ROLE, NULL); return (-1); } p->capa.neg.policy = 1; - } else if (p->capa.ann.policy == 2 && p->conf.ebgp) { - /* enforce presence of open policy role capability */ + } + + /* enforce presence of open policy role capability */ + if (p->capa.ann.policy == 2 && p->capa.peer.policy == 0 && + p->conf.ebgp) { log_peer_warnx(&p->conf, "open policy role enforced but " "not present"); - *suberr = ERR_OPEN_ROLE; + session_notification(p, ERR_OPEN, ERR_OPEN_ROLE, NULL); return (-1); } + /* enforce presence of other capabilities */ + if (p->capa.ann.refresh == 2 && p->capa.neg.refresh == 0) { + capa_code = CAPA_REFRESH; + capa_len = 0; + goto fail; + } + if (p->capa.ann.enhanced_rr == 2 && p->capa.neg.enhanced_rr == 0) { + capa_code = CAPA_ENHANCED_RR; + capa_len = 0; + goto fail; + } + if (p->capa.ann.as4byte == 2 && p->capa.neg.as4byte == 0) { + capa_code = CAPA_AS4BYTE; + capa_len = 4; + goto fail; + } + if (p->capa.ann.grestart.restart == 2 && + p->capa.neg.grestart.restart == 0) { + capa_code = CAPA_RESTART; + capa_len = 2; + goto fail; + } + for (i = AID_MIN; i < AID_MAX; i++) { + if (p->capa.ann.mp[i] == 2 && p->capa.neg.mp[i] == 0) { + capa_code = CAPA_MP; + capa_len = 4; + capa_aid = i; + goto fail; + } + } + + for (i = AID_MIN; i < AID_MAX; i++) { + if (p->capa.neg.mp[i] == 0) + continue; + if ((p->capa.ann.add_path[i] & CAPA_AP_RECV_ENFORCE) && + (p->capa.neg.add_path[i] & CAPA_AP_RECV) == 0) { + capa_code = CAPA_ADD_PATH; + capa_len = 4; + capa_aid = i; + goto fail; + } + if ((p->capa.ann.add_path[i] & CAPA_AP_SEND_ENFORCE) && + (p->capa.neg.add_path[i] & CAPA_AP_SEND) == 0) { + capa_code = CAPA_ADD_PATH; + capa_len = 4; + capa_aid = i; + goto fail; + } + } + return (0); + + fail: + if ((ebuf = ibuf_dynamic(2, 256)) == NULL) + return (-1); + /* best effort, no problem if it fails */ + session_capa_add(ebuf, capa_code, capa_len); + if (capa_code == CAPA_MP) + session_capa_add_mp(ebuf, capa_aid); + else if (capa_code == CAPA_ADD_PATH) + session_capa_add_afi(ebuf, capa_aid, 0); + else if (capa_len > 0) + ibuf_add_zero(ebuf, capa_len); + + session_notification(p, ERR_OPEN, ERR_OPEN_CAPA, ebuf); + ibuf_free(ebuf); + return (-1); } void -- 2.20.1