Implement RFC 7313 enhanced route refresh. It is off by default and
authorclaudio <claudio@openbsd.org>
Thu, 17 Jun 2021 16:05:25 +0000 (16:05 +0000)
committerclaudio <claudio@openbsd.org>
Thu, 17 Jun 2021 16:05:25 +0000 (16:05 +0000)
can be enabled with 'announce enhanced refresh yes'
Similar to graceful restart this allows to mark routes as stale, refresh
them and the flush out routes that are still stale. Enhanced route refresh
uses a begin of rr and a end of rr message to signal the various stages.
A future enhancement would be the addition of a timeout in case the EoRR
message is not sent in reasonable time.
OK denis@ job@

usr.sbin/bgpd/bgpd.8
usr.sbin/bgpd/bgpd.conf.5
usr.sbin/bgpd/bgpd.h
usr.sbin/bgpd/parse.y
usr.sbin/bgpd/rde.c
usr.sbin/bgpd/rde.h
usr.sbin/bgpd/rde_peer.c
usr.sbin/bgpd/session.c

index 58ce1cf..345e0db 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: bgpd.8,v 1.68 2021/06/16 16:24:12 job Exp $
+.\" $OpenBSD: bgpd.8,v 1.69 2021/06/17 16:05:25 claudio Exp $
 .\"
 .\" Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
 .\"
@@ -14,7 +14,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: June 16 2021 $
+.Dd $Mdocdate: June 17 2021 $
 .Dt BGPD 8
 .Os
 .Sh NAME
@@ -382,6 +382,15 @@ has been started.
 .Re
 .Pp
 .Rs
+.%A K. Patel
+.%A E. Chen
+.%A B. Venkatachalapathy
+.%D July 2014
+.%R RFC 7313
+.%T Enhanced Route Refresh Capability for BGP-4
+.Re
+.Pp
+.Rs
 .%A W. Kumari
 .%A R. Bush
 .%A H. Schiller
index dbb8f95..91dc05e 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: bgpd.conf.5,v 1.210 2021/05/06 09:21:35 claudio Exp $
+.\" $OpenBSD: bgpd.conf.5,v 1.211 2021/06/17 16:05:25 claudio Exp $
 .\"
 .\" Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org>
 .\" Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -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: May 6 2021 $
+.Dd $Mdocdate: June 17 2021 $
 .Dt BGPD.CONF 5
 .Os
 .Sh NAME
@@ -831,6 +831,16 @@ The default is
 .Ic yes .
 .Pp
 .It Xo
+.Ic announce enhanced refresh
+.Pq Ic yes Ns | Ns Ic no
+.Xc
+If set to
+.Ic yes ,
+the enhanced route refresh capability is announced.
+The default is
+.Ic no .
+.Pp
+.It Xo
 .Ic announce refresh
 .Pq Ic yes Ns | Ns Ic no
 .Xc
index f17010f..ffd27b1 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: bgpd.h,v 1.414 2021/05/27 08:27:48 claudio Exp $ */
+/*     $OpenBSD: bgpd.h,v 1.415 2021/06/17 16:05:26 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -96,6 +96,9 @@
 #define        F_CTL_OVS_NOTFOUND      0x200000
 #define        F_CTL_NEIGHBORS         0x400000 /* only used by bgpctl */
 
+#define CTASSERT(x)    extern char  _ctassert[(x) ? 1 : -1 ] \
+                           __attribute__((__unused__))
+
 /*
  * Note that these numeric assignments differ from the numbers commonly
  * used in route origin validation context.
index df91c68..82ea228 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.416 2021/05/20 10:06:20 claudio Exp $ */
+/*     $OpenBSD: parse.y,v 1.417 2021/06/17 16:05:26 claudio Exp $ */
 
 /*
  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -204,7 +204,7 @@ typedef struct {
 %token GROUP NEIGHBOR NETWORK
 %token EBGP IBGP
 %token LOCALAS REMOTEAS DESCR LOCALADDR MULTIHOP PASSIVE MAXPREFIX RESTART
-%token ANNOUNCE CAPABILITIES REFRESH AS4BYTE CONNECTRETRY
+%token ANNOUNCE CAPABILITIES REFRESH AS4BYTE CONNECTRETRY ENHANCED
 %token DEMOTE ENFORCE NEIGHBORAS ASOVERRIDE REFLECTOR DEPEND DOWN
 %token DUMP IN OUT SOCKET RESTRICTED
 %token LOG TRANSPARENT
@@ -1446,6 +1446,9 @@ peeropts  : REMOTEAS as4number    {
                | ANNOUNCE REFRESH yesno {
                        curpeer->conf.capabilities.refresh = $3;
                }
+               | ANNOUNCE ENHANCED REFRESH yesno {
+                       curpeer->conf.capabilities.enhanced_rr = $4;
+               }
                | ANNOUNCE RESTART yesno {
                        curpeer->conf.capabilities.grestart.restart = $3;
                }
@@ -2898,6 +2901,7 @@ lookup(char *s)
                { "dump",               DUMP},
                { "ebgp",               EBGP},
                { "enforce",            ENFORCE},
+               { "enhanced",           ENHANCED },
                { "esp",                ESP},
                { "evaluate",           EVALUATE},
                { "export",             EXPORT},
index 85d8172..89c0017 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: rde.c,v 1.526 2021/06/17 10:28:36 claudio Exp $ */
+/*     $OpenBSD: rde.c,v 1.527 2021/06/17 16:05:26 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -1068,6 +1068,7 @@ rde_dispatch_imsg_rtr(struct imsgbuf *ibuf)
 void
 rde_dispatch_imsg_peer(struct rde_peer *peer, void *bula)
 {
+       struct route_refresh rr;
        struct session_up sup;
        struct imsg imsg;
        u_int8_t aid;
@@ -1097,7 +1098,6 @@ rde_dispatch_imsg_peer(struct rde_peer *peer, void *bula)
        case IMSG_SESSION_STALE:
        case IMSG_SESSION_FLUSH:
        case IMSG_SESSION_RESTARTED:
-       case IMSG_REFRESH:
                if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(aid)) {
                        log_warnx("%s: wrong imsg len", __func__);
                        break;
@@ -1119,8 +1119,44 @@ rde_dispatch_imsg_peer(struct rde_peer *peer, void *bula)
                        if (peer->staletime[aid])
                                peer_flush(peer, aid, peer->staletime[aid]);
                        break;
-               case IMSG_REFRESH:
-                       peer_dump(peer, aid);
+               }
+               break;
+       case IMSG_REFRESH:
+               if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(rr)) {
+                       log_warnx("%s: wrong imsg len", __func__);
+                       break;
+               }
+               memcpy(&rr, imsg.data, sizeof(rr));
+               if (rr.aid >= AID_MAX) {
+                       log_warnx("%s: bad AID", __func__);
+                       break;
+               }
+               switch (rr.subtype) {
+               case ROUTE_REFRESH_REQUEST:
+                       peer_dump(peer, rr.aid);
+                       break;
+               case ROUTE_REFRESH_BEGIN_RR:
+                       /* check if graceful restart EOR was received */
+                       if ((peer->recv_eor & (1 << rr.aid)) == 0) {
+                               log_peer_warnx(&peer->conf,
+                                   "received %s BoRR before EoR",
+                                   aid2str(rr.aid));
+                               break;
+                       }
+                       peer_begin_rrefresh(peer, rr.aid);
+                       break;
+               case ROUTE_REFRESH_END_RR:
+                       if ((peer->recv_eor & (1 << rr.aid)) != 0 &&
+                           peer->staletime[rr.aid])
+                               peer_flush(peer, rr.aid,
+                                   peer->staletime[rr.aid]);
+                       else
+                               log_peer_warnx(&peer->conf,
+                                   "received unexpected %s EoRR",
+                                   aid2str(rr.aid));
+                       break;
+               default:
+                       log_warnx("%s: bad subtype %d", __func__, rr.subtype);
                        break;
                }
                break;
@@ -3016,8 +3052,14 @@ rde_update_queue_runner(void)
                                            __func__, __LINE__);
                                sent++;
                        }
-                       if (eor)
-                               rde_peer_send_eor(peer, AID_INET);
+                       if (eor) {
+                               int sent_eor = peer->sent_eor & (1 << AID_INET);
+                               if (peer->capa.grestart.restart && !sent_eor)
+                                       rde_peer_send_eor(peer, AID_INET);
+                               if (peer->capa.enhanced_rr && sent_eor)
+                                       rde_peer_send_rrefresh(peer, AID_INET,
+                                           ROUTE_REFRESH_END_RR);
+                       }
                }
                max -= sent;
        } while (sent != 0 && max > 0);
@@ -3067,7 +3109,12 @@ rde_update6_queue_runner(u_int8_t aid)
                                continue;
                        len = sizeof(queue_buf) - MSGSIZE_HEADER;
                        if (up_is_eor(peer, aid)) {
-                               rde_peer_send_eor(peer, aid);
+                               int sent_eor = peer->sent_eor & (1 << aid);
+                               if (peer->capa.grestart.restart && !sent_eor)
+                                       rde_peer_send_eor(peer, aid);
+                               if (peer->capa.enhanced_rr && sent_eor)
+                                       rde_peer_send_rrefresh(peer, aid,
+                                           ROUTE_REFRESH_END_RR);
                                continue;
                        }
                        r = up_dump_mp_reach(queue_buf, len, peer, aid);
@@ -3765,6 +3812,7 @@ static void
 rde_peer_recv_eor(struct rde_peer *peer, u_int8_t aid)
 {
        peer->prefix_rcvd_eor++;
+       peer->recv_eor |= 1 << aid;
 
        /*
         * First notify SE to avert a possible race with the restart timeout.
@@ -3789,6 +3837,7 @@ rde_peer_send_eor(struct rde_peer *peer, u_int8_t aid)
        u_int8_t        safi;
 
        peer->prefix_sent_eor++;
+       peer->sent_eor |= 1 << aid;
 
        if (aid == AID_INET) {
                u_char null[4];
@@ -3825,6 +3874,33 @@ rde_peer_send_eor(struct rde_peer *peer, u_int8_t aid)
            aid2str(aid));
 }
 
+void
+rde_peer_send_rrefresh(struct rde_peer *peer, u_int8_t aid, u_int8_t subtype)
+{
+       struct route_refresh rr;
+
+       /* not strickly needed, the SE checks as well */
+        if (peer->capa.enhanced_rr == 0)
+               return;
+
+       switch (subtype) {
+       case ROUTE_REFRESH_END_RR:
+       case ROUTE_REFRESH_BEGIN_RR:
+               break;
+       default:
+               fatalx("%s unexpected subtype %d", __func__, subtype);
+       }
+
+       rr.aid = aid;
+       rr.subtype = subtype;
+
+       if (imsg_compose(ibuf_se, IMSG_REFRESH, peer->conf.id, 0, -1,
+           &rr, sizeof(rr)) == -1)
+
+       log_peer_info(&peer->conf, "sending %s %s marker",
+           aid2str(aid), subtype == ROUTE_REFRESH_END_RR ? "EoRR" : "BoRR");
+}
+
 /*
  * network announcement stuff
  */
index 3cbfec7..52bee3a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: rde.h,v 1.239 2021/05/27 14:32:08 claudio Exp $ */
+/*     $OpenBSD: rde.h,v 1.240 2021/06/17 16:05:26 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Claudio Jeker <claudio@openbsd.org> and
@@ -107,6 +107,8 @@ struct rde_peer {
        u_int16_t                        loc_rib_id;
        u_int16_t                        short_as;
        u_int16_t                        mrt_idx;
+       u_int8_t                         recv_eor;      /* bitfield per AID */
+       u_int8_t                         sent_eor;      /* bitfield per AID */
        u_int8_t                         reconf_out;    /* out filter changed */
        u_int8_t                         reconf_rib;    /* rib changed */
        u_int8_t                         throttled;
@@ -378,6 +380,7 @@ void                rde_generate_updates(struct rib *, struct prefix *,
                    struct prefix *, int);
 u_int32_t      rde_local_as(void);
 int            rde_decisionflags(void);
+void           rde_peer_send_rrefresh(struct rde_peer *, u_int8_t, u_int8_t);
 int            rde_match_peer(struct rde_peer *, struct ctl_neighbor *);
 
 /* rde_peer.c */
@@ -395,6 +398,7 @@ void                 peer_down(struct rde_peer *, void *);
 void            peer_flush(struct rde_peer *, u_int8_t, time_t);
 void            peer_stale(struct rde_peer *, u_int8_t);
 void            peer_dump(struct rde_peer *, u_int8_t);
+void            peer_begin_rrefresh(struct rde_peer *, u_int8_t);
 
 void            peer_imsg_push(struct rde_peer *, struct imsg *);
 int             peer_imsg_pop(struct rde_peer *, struct imsg *);
index 23ec2f7..502826d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: rde_peer.c,v 1.10 2021/06/17 08:45:37 claudio Exp $ */
+/*     $OpenBSD: rde_peer.c,v 1.11 2021/06/17 16:05:26 claudio Exp $ */
 
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
@@ -37,6 +37,9 @@ struct peer_table {
 struct rde_peer_head    peerlist;
 struct rde_peer                *peerself;
 
+CTASSERT(sizeof(peerself->recv_eor) * 8 > AID_MAX);
+CTASSERT(sizeof(peerself->sent_eor) * 8 > AID_MAX);
+
 struct iq {
        SIMPLEQ_ENTRY(iq)       entry;
        struct imsg             imsg;
@@ -353,6 +356,15 @@ peer_up(struct rde_peer *peer, struct session_up *sup)
        peer->local_v6_addr = sup->local_v6_addr;
        memcpy(&peer->capa, &sup->capa, sizeof(peer->capa));
 
+       /* clear eor markers depending on GR flags */
+       if (peer->capa.grestart.restart) {
+               peer->sent_eor = 0;
+               peer->recv_eor = 0;
+       } else {
+               /* no EOR expected */
+               peer->sent_eor = ~0;
+               peer->recv_eor = ~0;
+       }
        peer->state = PEER_UP;
 
        for (i = 0; i < AID_MAX; i++) {
@@ -451,6 +463,9 @@ peer_stale(struct rde_peer *peer, u_int8_t aid)
 void
 peer_dump(struct rde_peer *peer, u_int8_t aid)
 {
+       if (peer->capa.enhanced_rr && (peer->sent_eor & (1 << aid)))
+               rde_peer_send_rrefresh(peer, aid, ROUTE_REFRESH_BEGIN_RR);
+
        if (peer->export_type == EXPORT_NONE) {
                /* nothing to send apart from the marker */
                if (peer->capa.grestart.restart)
@@ -467,6 +482,27 @@ peer_dump(struct rde_peer *peer, u_int8_t aid)
        }
 }
 
+/*
+ * Start of an enhanced route refresh. Mark all routes as stale.
+ * Once the route refresh ends a End of Route Refresh message is sent
+ * which calls peer_flush() to remove all stale routes.
+ */
+void
+peer_begin_rrefresh(struct rde_peer *peer, u_int8_t aid)
+{
+       time_t now;
+
+       /* flush the now even staler routes out */
+       if (peer->staletime[aid])
+               peer_flush(peer, aid, peer->staletime[aid]);
+
+       peer->staletime[aid] = now = getmonotime();
+
+       /* make sure new prefixes start on a higher timestamp */
+       while (now >= getmonotime())
+               sleep(1);
+}
+
 /*
  * move an imsg from src to dst, disconnecting any dynamic memory from src.
  */
index 20a8bf4..93debc1 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: session.c,v 1.420 2021/05/27 09:15:51 claudio Exp $ */
+/*     $OpenBSD: session.c,v 1.421 2021/06/17 16:05:26 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004, 2005 Henning Brauer <henning@openbsd.org>
@@ -2256,6 +2256,7 @@ parse_update(struct peer *peer)
 int
 parse_rrefresh(struct peer *peer)
 {
+       struct route_refresh rr;
        u_int16_t afi, datalen;
        u_int8_t aid, safi, subtype;
        u_char *p;
@@ -2343,7 +2344,10 @@ parse_rrefresh(struct peer *peer)
                return (0);
        }
 
-       if (imsg_rde(IMSG_REFRESH, peer->conf.id, &aid, sizeof(aid)) == -1)
+       rr.aid = aid;
+       rr.subtype = subtype;
+
+       if (imsg_rde(IMSG_REFRESH, peer->conf.id, &rr, sizeof(rr)) == -1)
                return (-1);
 
        return (0);
@@ -2768,6 +2772,7 @@ session_dispatch_imsg(struct imsgbuf *ibuf, int idx, u_int *listener_cnt)
 {
        struct imsg              imsg;
        struct mrt               xmrt;
+       struct route_refresh     rr;
        struct mrt              *mrt;
        struct imsgbuf          *i;
        struct peer             *p;
@@ -3095,6 +3100,23 @@ session_dispatch_imsg(struct imsgbuf *ibuf, int idx, u_int *listener_cnt)
                                break;
                        }
                        break;
+               case IMSG_REFRESH:
+                       if (idx != PFD_PIPE_ROUTE)
+                               fatalx("route refresh request not from RDE");
+                       if (imsg.hdr.len < IMSG_HEADER_SIZE + sizeof(rr)) {
+                               log_warnx("RDE sent invalid refresh msg");
+                               break;
+                       }
+                       if ((p = getpeerbyid(conf, imsg.hdr.peerid)) == NULL) {
+                               log_warnx("no such peer: id=%u",
+                                   imsg.hdr.peerid);
+                               break;
+                       }
+                       memcpy(&rr, imsg.data, sizeof(rr));
+                       if (rr.aid >= AID_MAX)
+                               fatalx("IMSG_REFRESH: bad AID");
+                       session_rrefresh(p, rr.aid, rr.subtype);
+                       break;
                case IMSG_SESSION_RESTARTED:
                        if (idx != PFD_PIPE_ROUTE)
                                fatalx("update request not from RDE");