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