diff --git a/docs/sources/graceful-restart.md b/docs/sources/graceful-restart.md index 914d23050..76d1e6881 100644 --- a/docs/sources/graceful-restart.md +++ b/docs/sources/graceful-restart.md @@ -1,7 +1,7 @@ # Graceful Restart This page explains how to configure [Graceful Restart](https://tools.ietf.org/html/rfc4724), -[Graceful Restart Notification Support](https://tools.ietf.org/html/draft-ietf-idr-bgp-gr-notification-07) and +[Graceful Restart Notification Support](https://tools.ietf.org/html/rfc8538) and [Long Lived Graceful Restart](https://tools.ietf.org/html/draft-uttaro-idr-bgp-persistence-02). Graceful Restart has two sides. One is restarting speaker which does restart, the other is receiving speaker (helper speaker) which helps a restarting speaker @@ -153,7 +153,7 @@ Default value of `restart-time` is equal to `hold-time`. [RFC4724](https://tools.ietf.org/html/rfc4724) specifies gracful restart procedures are triggered only when the BGP session between graceful restart capable peers turns down without a notification message for backward compatibility. -[Graceful Restart Notification Support](https://tools.ietf.org/html/draft-ietf-idr-bgp-gr-notification-07) +[Graceful Restart Notification Support](https://tools.ietf.org/html/rfc8538) expands this to trigger graceful restart procedures also with a notification message. To turn on this feature, add `notification-enabled = true` to configuration like below. diff --git a/pkg/packet/bgp/bgp.go b/pkg/packet/bgp/bgp.go index fad11c1f4..49cdc73aa 100644 --- a/pkg/packet/bgp/bgp.go +++ b/pkg/packet/bgp/bgp.go @@ -9877,7 +9877,7 @@ const ( BGP_ERROR_SUB_OTHER_CONFIGURATION_CHANGE BGP_ERROR_SUB_CONNECTION_COLLISION_RESOLUTION BGP_ERROR_SUB_OUT_OF_RESOURCES - BGP_ERROR_SUB_HARD_RESET // draft-ietf-idr-bgp-gr-notification-07 + BGP_ERROR_SUB_HARD_RESET // RFC8538 ) // Constants for BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN and BGP_ERROR_SUB_ADMINISTRATIVE_RESET @@ -14647,6 +14647,34 @@ func NewBGPNotificationMessage(errcode uint8, errsubcode uint8, data []byte) *BG } } +// RFC8538 makes a suggestion that which Cease notification subcodes should be +// mapped to the Hard Reset. This function takes a subcode and returns true if +// the subcode should be treated as a Hard Reset. Otherwise, it returns false. +// +// The second argument is a boolean value that indicates whether the Hard Reset +// should be performed on the Admin Reset. This reflects the RFC8538's +// suggestion that the implementation should provide a control to treat the +// Admin Reset as a Hard Reset. When the second argument is true, the function +// returns true if the subcode is BGP_ERROR_SUB_ADMINISTRATIVE_RESET. +// Otherwise, it returns false. +// +// As RFC8538 states, it is not mandatory to follow this suggestion. You can +// use this function when you want to follow the suggestion. +func ShouldHardReset(subcode uint8, hardResetOnAdminReset bool) bool { + switch subcode { + case BGP_ERROR_SUB_MAXIMUM_NUMBER_OF_PREFIXES_REACHED, + BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN, + BGP_ERROR_SUB_PEER_DECONFIGURED, + BGP_ERROR_SUB_HARD_RESET: + return true + default: + if hardResetOnAdminReset && subcode == BGP_ERROR_SUB_ADMINISTRATIVE_RESET { + return true + } + return false + } +} + type BGPKeepAlive struct { } diff --git a/pkg/server/fsm.go b/pkg/server/fsm.go index 3aee70b28..9b4b75f51 100644 --- a/pkg/server/fsm.go +++ b/pkg/server/fsm.go @@ -1659,6 +1659,22 @@ func (h *fsmHandler) sendMessageloop(ctx context.Context, wg *sync.WaitGroup) er table.UpdatePathAttrs2ByteAs(m.Body.(*bgp.BGPUpdate)) table.UpdatePathAggregator2ByteAs(m.Body.(*bgp.BGPUpdate)) } + + // RFC8538 defines a Hard Reset notification subcode which + // indicates that the BGP speaker wants to reset the session + // without triggering graceful restart procedures. Here we map + // notification subcodes to the Hard Reset subcode following + // the RFC8538 suggestion. + // + // We check Status instead of Config because RFC8538 states + // that A BGP speaker SHOULD NOT send a Hard Reset to a peer + // from which it has not received the "N" bit. + if fsm.pConf.GracefulRestart.State.NotificationEnabled && m.Header.Type == bgp.BGP_MSG_NOTIFICATION { + if body := m.Body.(*bgp.BGPNotification); body.ErrorCode == bgp.BGP_ERROR_CEASE && bgp.ShouldHardReset(body.ErrorSubcode, false) { + body.ErrorSubcode = bgp.BGP_ERROR_SUB_HARD_RESET + } + } + b, err := m.Serialize(h.fsm.marshallingOptions) fsm.lock.RUnlock() if err != nil { @@ -1834,6 +1850,20 @@ func (h *fsmHandler) established(ctx context.Context) (bgp.FSMState, *fsmStateRe case <-ctx.Done(): select { case m := <-fsm.notification: + // RFC8538 defines a Hard Reset notification subcode which + // indicates that the BGP speaker wants to reset the session + // without triggering graceful restart procedures. Here we map + // notification subcodes to the Hard Reset subcode following + // the RFC8538 suggestion. + // + // We check Status instead of Config because RFC8538 states + // that A BGP speaker SHOULD NOT send a Hard Reset to a peer + // from which it has not received the "N" bit. + if fsm.pConf.GracefulRestart.State.NotificationEnabled { + if body := m.Body.(*bgp.BGPNotification); body.ErrorCode == bgp.BGP_ERROR_CEASE && bgp.ShouldHardReset(body.ErrorSubcode, false) { + body.ErrorSubcode = bgp.BGP_ERROR_SUB_HARD_RESET + } + } b, _ := m.Serialize(h.fsm.marshallingOptions) h.conn.Write(b) default: