diff --git a/api/core/v1alpha1/bfd_types.go b/api/core/v1alpha1/bfd_types.go new file mode 100644 index 000000000..0e3a3bccb --- /dev/null +++ b/api/core/v1alpha1/bfd_types.go @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// Package v1alpha1 contains API Schema definitions for the networking.metal.ironcore.dev v1alpha1 API group. +// +kubebuilder:validation:Required +// +kubebuilder:object:generate=true +// +groupName=networking.metal.ironcore.dev +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BFD defines the Bidirectional Forwarding Detection configuration for an interface. +type BFD struct { + // Enabled indicates whether BFD is enabled on the interface. + // +required + Enabled bool `json:"enabled"` + + // DesiredMinimumTxInterval is the minimum interval between transmission of BFD control + // packets that the operator desires. This value is advertised to the peer. + // The actual interval used is the maximum of this value and the remote + // required-minimum-receive interval value. + // +optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" + DesiredMinimumTxInterval *metav1.Duration `json:"desiredMinimumTxInterval,omitempty"` + + // RequiredMinimumReceive is the minimum interval between received BFD control packets + // that this system should support. This value is advertised to the remote peer to + // indicate the maximum frequency between BFD control packets that is acceptable + // to the local system. + // +optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" + RequiredMinimumReceive *metav1.Duration `json:"requiredMinimumReceive,omitempty"` + + // DetectionMultiplier is the number of packets that must be missed to declare + // this session as down. The detection interval for the BFD session is calculated + // by multiplying the value of the negotiated transmission interval by this value. + // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=255 + DetectionMultiplier *int32 `json:"detectionMultiplier,omitempty"` +} diff --git a/api/core/v1alpha1/bgp_peer_types.go b/api/core/v1alpha1/bgp_peer_types.go index c0c141108..a44b1712a 100644 --- a/api/core/v1alpha1/bgp_peer_types.go +++ b/api/core/v1alpha1/bgp_peer_types.go @@ -64,6 +64,11 @@ type BGPPeerSpec struct { // LocalAS configures the local AS number and how it factors into BGP announcements for this peer. // +optional LocalAS *LocalAS `json:"localAS,omitempty"` + + // BFD defines the Bidirectional Forwarding Detection configuration for the interface. + // BFD is only applicable for Layer 3 interfaces. + // +optional + BFD *BFD `json:"bfd,omitempty"` } // LocalAS defines the local AS configuration and how it factors in BGP announcements. diff --git a/api/core/v1alpha1/interface_types.go b/api/core/v1alpha1/interface_types.go index e6fca4498..70743d964 100644 --- a/api/core/v1alpha1/interface_types.go +++ b/api/core/v1alpha1/interface_types.go @@ -266,39 +266,6 @@ type InterfaceIPv4Unnumbered struct { InterfaceRef LocalObjectReference `json:"interfaceRef"` } -// BFD defines the Bidirectional Forwarding Detection configuration for an interface. -type BFD struct { - // Enabled indicates whether BFD is enabled on the interface. - // +required - Enabled bool `json:"enabled"` - - // DesiredMinimumTxInterval is the minimum interval between transmission of BFD control - // packets that the operator desires. This value is advertised to the peer. - // The actual interval used is the maximum of this value and the remote - // required-minimum-receive interval value. - // +optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" - DesiredMinimumTxInterval *metav1.Duration `json:"desiredMinimumTxInterval,omitempty"` - - // RequiredMinimumReceive is the minimum interval between received BFD control packets - // that this system should support. This value is advertised to the remote peer to - // indicate the maximum frequency between BFD control packets that is acceptable - // to the local system. - // +optional - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" - RequiredMinimumReceive *metav1.Duration `json:"requiredMinimumReceive,omitempty"` - - // DetectionMultiplier is the number of packets that must be missed to declare - // this session as down. The detection interval for the BFD session is calculated - // by multiplying the value of the negotiated transmission interval by this value. - // +optional - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:validation:Maximum=255 - DetectionMultiplier *int32 `json:"detectionMultiplier,omitempty"` -} - // Ethernet defines the ethernet-specific configuration for physical interfaces. type Ethernet struct { // FECMode specifies the Forward Error Correction mode for the interface. diff --git a/api/core/v1alpha1/zz_generated.deepcopy.go b/api/core/v1alpha1/zz_generated.deepcopy.go index 62dfc807d..0ac6c1b56 100644 --- a/api/core/v1alpha1/zz_generated.deepcopy.go +++ b/api/core/v1alpha1/zz_generated.deepcopy.go @@ -849,6 +849,11 @@ func (in *BGPPeerSpec) DeepCopyInto(out *BGPPeerSpec) { *out = new(LocalAS) (*in).DeepCopyInto(*out) } + if in.BFD != nil { + in, out := &in.BFD, &out.BFD + *out = new(BFD) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BGPPeerSpec. diff --git a/charts/network-operator/templates/crd/bgppeers.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/bgppeers.networking.metal.ironcore.dev.yaml index ff32caaf8..17fe21d93 100644 --- a/charts/network-operator/templates/crd/bgppeers.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/bgppeers.networking.metal.ironcore.dev.yaml @@ -291,6 +291,42 @@ spec: ASNumber is the autonomous system number (ASN) of the BGP peer. Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396. x-kubernetes-int-or-string: true + bfd: + description: |- + BFD defines the Bidirectional Forwarding Detection configuration for the interface. + BFD is only applicable for Layer 3 interfaces. + properties: + desiredMinimumTxInterval: + description: |- + DesiredMinimumTxInterval is the minimum interval between transmission of BFD control + packets that the operator desires. This value is advertised to the peer. + The actual interval used is the maximum of this value and the remote + required-minimum-receive interval value. + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + detectionMultiplier: + description: |- + DetectionMultiplier is the number of packets that must be missed to declare + this session as down. The detection interval for the BFD session is calculated + by multiplying the value of the negotiated transmission interval by this value. + format: int32 + maximum: 255 + minimum: 1 + type: integer + enabled: + description: Enabled indicates whether BFD is enabled on the interface. + type: boolean + requiredMinimumReceive: + description: |- + RequiredMinimumReceive is the minimum interval between received BFD control packets + that this system should support. This value is advertised to the remote peer to + indicate the maximum frequency between BFD control packets that is acceptable + to the local system. + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + required: + - enabled + type: object bgpRef: description: |- BgpRef is a reference to the BGP instance this peer belongs to. diff --git a/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml b/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml index 379cb55df..27df568bf 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml @@ -288,6 +288,42 @@ spec: ASNumber is the autonomous system number (ASN) of the BGP peer. Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396. x-kubernetes-int-or-string: true + bfd: + description: |- + BFD defines the Bidirectional Forwarding Detection configuration for the interface. + BFD is only applicable for Layer 3 interfaces. + properties: + desiredMinimumTxInterval: + description: |- + DesiredMinimumTxInterval is the minimum interval between transmission of BFD control + packets that the operator desires. This value is advertised to the peer. + The actual interval used is the maximum of this value and the remote + required-minimum-receive interval value. + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + detectionMultiplier: + description: |- + DetectionMultiplier is the number of packets that must be missed to declare + this session as down. The detection interval for the BFD session is calculated + by multiplying the value of the negotiated transmission interval by this value. + format: int32 + maximum: 255 + minimum: 1 + type: integer + enabled: + description: Enabled indicates whether BFD is enabled on the interface. + type: boolean + requiredMinimumReceive: + description: |- + RequiredMinimumReceive is the minimum interval between received BFD control packets + that this system should support. This value is advertised to the remote peer to + indicate the maximum frequency between BFD control packets that is acceptable + to the local system. + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + required: + - enabled + type: object bgpRef: description: |- BgpRef is a reference to the BGP instance this peer belongs to. diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md index 3edf197d1..ab0423764 100644 --- a/docs/api-reference/index.md +++ b/docs/api-reference/index.md @@ -11,6 +11,8 @@ Package v1alpha1 contains API Schema definitions for the networking.metal.ironcore.dev v1alpha1 API group. +Package v1alpha1 contains API Schema definitions for the networking.metal.ironcore.dev v1alpha1 API group. + SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors SPDX-License-Identifier: Apache-2.0 @@ -437,6 +439,7 @@ BFD defines the Bidirectional Forwarding Detection configuration for an interfac _Appears in:_ +- [BGPPeerSpec](#bgppeerspec) - [InterfaceSpec](#interfacespec) | Field | Description | Default | Validation | @@ -730,6 +733,7 @@ _Appears in:_ | `localAddress` _[BGPPeerLocalAddress](#bgppeerlocaladdress)_ | LocalAddress specifies the local address configuration for the BGP session with this peer.
This determines the source address/interface for BGP packets sent to this peer. | | Optional: \{\}
| | `addressFamilies` _[BGPPeerAddressFamilies](#bgppeeraddressfamilies)_ | AddressFamilies configures address family specific settings for this BGP peer.
Controls which address families are enabled and their specific configuration. | | Optional: \{\}
| | `localAS` _[LocalAS](#localas)_ | LocalAS configures the local AS number and how it factors into BGP announcements for this peer. | | Optional: \{\}
| +| `bfd` _[BFD](#bfd)_ | BFD defines the Bidirectional Forwarding Detection configuration for the interface.
BFD is only applicable for Layer 3 interfaces. | | Optional: \{\}
| #### BGPPeerStatus diff --git a/internal/provider/cisco/iosxr/bgp_peer.go b/internal/provider/cisco/iosxr/bgp_peer.go index 369c7813d..f8a025a84 100644 --- a/internal/provider/cisco/iosxr/bgp_peer.go +++ b/internal/provider/cisco/iosxr/bgp_peer.go @@ -71,6 +71,12 @@ type BGPPeer struct { Name string `json:"vrf-name"` RD RouteDistinguisher `json:"rd,omitzero"` Neighbors NeighborList `json:"neighbors,omitzero"` + BFD *BFD `json:"bfd,omitzero"` +} + +type BFD struct { + MinInterval uint32 `json:"minimum-interval,omitempty"` + Multiplier uint32 `json:"multiplier,omitempty"` } // ActivatedAddressFamilies is required for IOS XR to activate the Address-Family under the BGP process diff --git a/internal/provider/cisco/iosxr/provider.go b/internal/provider/cisco/iosxr/provider.go index c05255a09..68e79dbda 100644 --- a/internal/provider/cisco/iosxr/provider.go +++ b/internal/provider/cisco/iosxr/provider.go @@ -7,10 +7,12 @@ import ( "context" "errors" "fmt" + "math" "net" "time" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/apistatus" "github.com/ironcore-dev/network-operator/internal/deviceutil" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/transport/gnmiext" @@ -477,6 +479,31 @@ func (p *Provider) EnsureBGPPeer(ctx context.Context, req *provider.EnsureBGPPee RD: rd, } + if req.BGPPeer.Spec.BFD != nil && req.BGPPeer.Spec.BFD.Enabled { + bfd := &BFD{} + if req.BGPPeer.Spec.BFD.RequiredMinimumReceive != nil { + ms := req.BGPPeer.Spec.BFD.RequiredMinimumReceive.Duration + if ms < 0 || ms > math.MaxUint32 { + return apistatus.NewInvalidArgumentError(apistatus.FieldViolation{ + Field: "spec.bfd.requiredMinimumReceive", + Description: "BFD minimum receive interval must be between 0ms and 4294967295ms", + }) + } + bfd.MinInterval = uint32(ms) + } + if req.BGPPeer.Spec.BFD.DetectionMultiplier != nil { + multiplier := *req.BGPPeer.Spec.BFD.DetectionMultiplier + if multiplier < 0 { + return apistatus.NewInvalidArgumentError(apistatus.FieldViolation{ + Field: "spec.bfd.detectionMultiplier", + Description: "BFD detection multiplier must be non-negative", + }) + } + bfd.Multiplier = uint32(multiplier) + } + peer.BFD = bfd + } + if req.BGPPeer.Spec.AddressFamilies != nil && (req.BGPPeer.Spec.AddressFamilies.Ipv6Unicast != nil || req.BGPPeer.Spec.AddressFamilies.L2vpnEvpn != nil) { return errors.New("bgp peer: ipv6 unicast or l2vpnEvpn address family is currently not supported") } diff --git a/internal/provider/cisco/nxos/bgp.go b/internal/provider/cisco/nxos/bgp.go index 181842769..cfc032b6a 100644 --- a/internal/provider/cisco/nxos/bgp.go +++ b/internal/provider/cisco/nxos/bgp.go @@ -11,6 +11,7 @@ import ( nxv1alpha1 "github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/apistatus" "github.com/ironcore-dev/network-operator/internal/transport/gnmiext" ) @@ -187,22 +188,74 @@ func (af *BGPDomAfItem) SetMultipath(m *v1alpha1.BGPMultipath) error { } type BGPPeer struct { - VRFName string `json:"-"` - Addr string `json:"addr"` - AdminSt AdminSt `json:"adminSt"` - Asn string `json:"asn"` - AsnType PeerAsnType `json:"asnType"` - Name string `json:"name,omitempty"` - SrcIf string `json:"srcIf,omitempty"` + VRFName string `json:"-"` + Addr string `json:"addr"` + Name string `json:"name,omitempty"` + AdminSt AdminSt `json:"adminSt"` + Asn string `json:"asn"` + AsnType PeerAsnType `json:"asnType"` + SrcIf string `json:"srcIf,omitempty"` + + // BFD enablement + PeerControl string `json:"ctrl"` + // Bfd Type: none (default), single-hop, multi-hop + // Rely on default, if peer is directly connected then a single hop session is selected, + // if the peer is not connected then a multi hop session type is selected + BfdType string `json:"bfdType"` + BfdMultihop *BGPNeighborBfd `json:"mhbfdintvl-items,omitzero"` + LocalAsnItems struct { AsnPropagate AsnPropagate `json:"asnPropagate"` LocalAsn string `json:"localAsn"` } `json:"localasn-items,omitzero"` + AfItems struct { PeerAfList gnmiext.List[AddressFamily, *BGPPeerAfItem] `json:"PeerAf-list,omitzero"` } `json:"af-items,omitzero"` } +type BGPNeighborBfd struct { + DetectMult uint32 `json:"multiplier"` + MinRxIntvlMs uint32 `json:"minRxMs"` + MinTxIntvlMs uint32 `json:"minTxMs"` +} + +func NewBGPNeighborBfd(peer *v1alpha1.BGPPeerSpec) (*BGPNeighborBfd, error) { + bfd := &BGPNeighborBfd{} + if peer.BFD.DetectionMultiplier != nil { + multiplier := *peer.BFD.DetectionMultiplier + if multiplier < 0 { + return nil, apistatus.NewInvalidArgumentError(apistatus.FieldViolation{ + Field: "spec.bfd.detectionMultiplier", + Description: "BFD detection multiplier must be non-negative", + }) + } + bfd.DetectMult = uint32(multiplier) + } + if peer.BFD.RequiredMinimumReceive != nil { + ms := peer.BFD.RequiredMinimumReceive.Duration + + if ms < 250 || ms > 999 { + return nil, apistatus.NewInvalidArgumentError(apistatus.FieldViolation{ + Field: "spec.bfd.requiredMinimumReceive", + Description: "BFD minimum receive interval must be between 250ms and 999ms", + }) + } + bfd.MinRxIntvlMs = uint32(ms) + } + if peer.BFD.DesiredMinimumTxInterval != nil { + ms := peer.BFD.DesiredMinimumTxInterval.Duration + if ms < 250 || ms > 999 { + return nil, apistatus.NewInvalidArgumentError(apistatus.FieldViolation{ + Field: "spec.bfd.desiredMinimumTxInterval", + Description: "BFD desired minimum transmit interval must be between 250ms and 999ms", + }) + } + bfd.MinTxIntvlMs = uint32(ms) + } + return bfd, nil +} + type AsnPropagate string const ( @@ -362,6 +415,13 @@ const ( const RouteReflectorClient = "rr-client" +const ( + // PeerControlBFD enables BFD on BGP peer + PeerControlBFD = "bfd" + // BfdTypeNone relies on default BFD session type selection + BfdTypeNone = "none" +) + type BorderGatewayPeerType string const ( diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index 72ac8e3dd..fa865bfc6 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -539,6 +539,17 @@ func (p *Provider) EnsureBGPPeer(ctx context.Context, req *provider.EnsureBGPPee pe := new(BGPPeer) pe.VRFName = bgp.Name pe.Addr = req.BGPPeer.Spec.Address + + if req.BGPPeer.Spec.BFD != nil && req.BGPPeer.Spec.BFD.Enabled { + bfd, err := NewBGPNeighborBfd(&req.BGPPeer.Spec) + if err != nil { + return err + } + pe.PeerControl = PeerControlBFD + pe.BfdType = BfdTypeNone + pe.BfdMultihop = bfd + } + pe.AdminSt = AdminStEnabled if req.BGPPeer.Spec.AdminState == v1alpha1.AdminStateDown { pe.AdminSt = AdminStDisabled