diff --git a/api/core/v1alpha1/bfd_types.go b/api/core/v1alpha1/bfd_types.go
new file mode 100644
index 000000000..21d762f52
--- /dev/null
+++ b/api/core/v1alpha1/bfd_types.go
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors
+// SPDX-License-Identifier: Apache-2.0
+
+package v1alpha1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// BFD defines the Bidirectional Forwarding Detection configuration for interfaces, bgp peerings and static routes.
+type BFD struct {
+ // Enabled indicates whether BFD is enabled on the network object.
+ // +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 a95dbc837..96da89dff 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..926782495 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,43 @@ 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 network
+ object.
+ 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/charts/network-operator/templates/crd/interfaces.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/interfaces.networking.metal.ironcore.dev.yaml
index 396ebd76f..f22c86cdb 100644
--- a/charts/network-operator/templates/crd/interfaces.networking.metal.ironcore.dev.yaml
+++ b/charts/network-operator/templates/crd/interfaces.networking.metal.ironcore.dev.yaml
@@ -180,7 +180,8 @@ spec:
minimum: 1
type: integer
enabled:
- description: Enabled indicates whether BFD is enabled on the interface.
+ description: Enabled indicates whether BFD is enabled on the network
+ object.
type: boolean
requiredMinimumReceive:
description: |-
diff --git a/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml b/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml
index 379cb55df..efa2452fe 100644
--- a/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml
+++ b/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml
@@ -288,6 +288,43 @@ 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 network
+ object.
+ 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_interfaces.yaml b/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml
index ea0449394..94f870941 100644
--- a/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml
+++ b/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml
@@ -177,7 +177,8 @@ spec:
minimum: 1
type: integer
enabled:
- description: Enabled indicates whether BFD is enabled on the interface.
+ description: Enabled indicates whether BFD is enabled on the network
+ object.
type: boolean
requiredMinimumReceive:
description: |-
diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md
index 53c923bea..a2482d09a 100644
--- a/docs/api-reference/index.md
+++ b/docs/api-reference/index.md
@@ -744,16 +744,17 @@ _Appears in:_
-BFD defines the Bidirectional Forwarding Detection configuration for an interface.
+BFD defines the Bidirectional Forwarding Detection configuration for interfaces, bgp peerings and static routes.
_Appears in:_
+- [BGPPeerSpec](#bgppeerspec)
- [InterfaceSpec](#interfacespec)
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
-| `enabled` _boolean_ | Enabled indicates whether BFD is enabled on the interface. | | Required: \{\}
|
+| `enabled` _boolean_ | Enabled indicates whether BFD is enabled on the network object. | | Required: \{\}
|
| `desiredMinimumTxInterval` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#duration-v1-meta)_ | 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
Optional: \{\}
|
| `requiredMinimumReceive` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#duration-v1-meta)_ | 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
Optional: \{\}
|
| `detectionMultiplier` _integer_ | 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. | | Maximum: 255
Minimum: 1
Optional: \{\}
|
@@ -1042,6 +1043,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 5184f2b4f..158f85f6f 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"
@@ -478,6 +480,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 54a9a5a19..08a27a512 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"
)
@@ -189,22 +190,73 @@ 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 Option[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 BfdType `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.BFD) (*BGPNeighborBfd, error) {
+ bfd := &BGPNeighborBfd{}
+ if peer.DetectionMultiplier != nil {
+ multiplier := *peer.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.RequiredMinimumReceive != nil {
+ ms := peer.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.DesiredMinimumTxInterval != nil {
+ ms := peer.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 (
@@ -364,6 +416,17 @@ const (
const RouteReflectorClient = "rr-client"
+const PeerControlType = "bfd"
+
+type BfdType string
+
+const (
+ // BfdTypeNone relies on default BFD session type selection
+ BfdTypeNone BfdType = "none"
+ BfdTypeSingleHop BfdType = "single-hop"
+ BfdTypeMultiHop BfdType = "multi-hop"
+)
+
type BorderGatewayPeerType string
const (
diff --git a/internal/provider/cisco/nxos/bgp_test.go b/internal/provider/cisco/nxos/bgp_test.go
index cb983faf2..5be8893df 100644
--- a/internal/provider/cisco/nxos/bgp_test.go
+++ b/internal/provider/cisco/nxos/bgp_test.go
@@ -33,6 +33,7 @@ func init() {
AsnType: PeerAsnTypeNone,
Name: "EVPN peering with spine",
SrcIf: "lo0",
+ BfdType: BfdTypeNone,
}
bgpPeer.AfItems.PeerAfList.Set(&BGPPeerAfItem{
Ctrl: Option[string]{Value: new(RouteReflectorClient)},
@@ -51,6 +52,7 @@ func init() {
AdminSt: AdminStEnabled,
Asn: "65000",
AsnType: PeerAsnTypeNone,
+ BfdType: BfdTypeNone,
}
bgpPeerRpAf := &BGPPeerAfItem{
SendComExt: AdminStDisabled,
@@ -81,6 +83,7 @@ func init() {
AdminSt: AdminStEnabled,
Asn: "65001",
AsnType: PeerAsnTypeNone,
+ BfdType: BfdTypeNone,
}
bgpPeerLocalAs.LocalAsnItems.AsnPropagate = AsnPropagateNone
bgpPeerLocalAs.LocalAsnItems.LocalAsn = "65002"
diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go
index d8751058c..5304a01bc 100644
--- a/internal/provider/cisco/nxos/provider.go
+++ b/internal/provider/cisco/nxos/provider.go
@@ -84,6 +84,8 @@ type Provider struct {
// timeout is the default timeout for all HTTP/gRPC requests made by the provider.
const timeout = 30 * time.Second
+const featureBFD = "bfd"
+
func NewProvider() provider.Provider {
return &Provider{}
}
@@ -750,6 +752,28 @@ func (p *Provider) deleteBGP(ctx context.Context, vrfName string) error {
return p.client.Delete(ctx, new(BGP))
}
+// applyBGPPeerBFD configures BFD on pe and returns the Feature to enable if BFD is requested.
+// As PeerControl and BfdMultihop are only set when BFD is configured in the spec, this will not
+// work with a gnmiext.Client.Patch call if somebody outside of the network operator configures BFD.
+// "mhbfdintvl-items" would appear in the unmarshaled response and thus we would detect a config
+// drift and reconcile over and over again.
+func applyBGPPeerBFD(pe *BGPPeer, spec *v1alpha1.BFD) (*Feature, error) {
+ f := new(Feature)
+ pe.BfdType = BfdTypeNone
+ if spec == nil || !spec.Enabled {
+ return f, nil
+ }
+ f.Name = featureBFD
+ f.AdminSt = AdminStEnabled
+ bfd, err := NewBGPNeighborBfd(spec)
+ if err != nil {
+ return nil, err
+ }
+ pe.PeerControl = NewOption(PeerControlType)
+ pe.BfdMultihop = bfd
+ return f, nil
+}
+
func (p *Provider) EnsureBGPPeer(ctx context.Context, req *provider.EnsureBGPPeerRequest) error {
// Ensure that the BGP domain exists before configuring a peer under it.
bgp := new(BGPDom)
@@ -764,6 +788,12 @@ func (p *Provider) EnsureBGPPeer(ctx context.Context, req *provider.EnsureBGPPee
pe := new(BGPPeer)
pe.VRFName = bgp.Name
pe.Addr = req.BGPPeer.Spec.Address
+
+ f, err := applyBGPPeerBFD(pe, req.BGPPeer.Spec.BFD)
+ if err != nil {
+ return err
+ }
+
pe.AdminSt = AdminStEnabled
if req.BGPPeer.Spec.AdminState == v1alpha1.AdminStateDown {
pe.AdminSt = AdminStDisabled
@@ -841,6 +871,9 @@ func (p *Provider) EnsureBGPPeer(ctx context.Context, req *provider.EnsureBGPPee
}
}
+ if f.AdminSt == AdminStEnabled {
+ return p.client.Update(ctx, f, pe)
+ }
return p.client.Update(ctx, pe)
}
@@ -1598,7 +1631,7 @@ func (p *Provider) EnsureInterface(ctx context.Context, req *provider.EnsureInte
switch {
case req.Interface.Spec.BFD != nil && req.Interface.Spec.BFD.Enabled:
f := new(Feature)
- f.Name = "bfd"
+ f.Name = featureBFD
f.AdminSt = AdminStEnabled
sb.Update(f)
@@ -1631,7 +1664,7 @@ func (p *Provider) EnsureInterface(ctx context.Context, req *provider.EnsureInte
case req.Interface.Spec.BFD != nil && !req.Interface.Spec.BFD.Enabled:
f := new(Feature)
- f.Name = "bfd"
+ f.Name = featureBFD
f.AdminSt = AdminStEnabled
sb.Update(f)
@@ -1888,7 +1921,7 @@ func (p *Provider) EnsureISIS(ctx context.Context, req *provider.EnsureISISReque
return intf.Spec.BFD != nil && intf.Spec.BFD.Enabled
}) {
f := new(Feature)
- f.Name = "bfd"
+ f.Name = featureBFD
f.AdminSt = AdminStEnabled
sb.Update(f)
}
@@ -2233,7 +2266,7 @@ func (p *Provider) EnsureOSPF(ctx context.Context, req *provider.EnsureOSPFReque
// BFD-specific fields. Activate it via a separate Set RPC to ensure
// the feature is present before the OSPF interface is configured.
fb := new(Feature)
- fb.Name = "bfd"
+ fb.Name = featureBFD
fb.AdminSt = AdminStEnabled
if err := p.client.Update(ctx, fb); err != nil {
return err
diff --git a/internal/provider/cisco/nxos/testdata/bgp_dom_rp.json b/internal/provider/cisco/nxos/testdata/bgp_dom_rp.json
index 1c09a4564..6b28ad744 100644
--- a/internal/provider/cisco/nxos/testdata/bgp_dom_rp.json
+++ b/internal/provider/cisco/nxos/testdata/bgp_dom_rp.json
@@ -13,6 +13,8 @@
"adminSt": "enabled",
"asn": "65000",
"asnType": "none",
+ "bfdType": "none",
+ "ctrl": "DME_UNSET_PROPERTY_MARKER",
"af-items": {
"PeerAf-list": [
{
diff --git a/internal/provider/cisco/nxos/testdata/bgp_peer.json b/internal/provider/cisco/nxos/testdata/bgp_peer.json
index b8e6ee166..28920515f 100644
--- a/internal/provider/cisco/nxos/testdata/bgp_peer.json
+++ b/internal/provider/cisco/nxos/testdata/bgp_peer.json
@@ -12,6 +12,8 @@
"adminSt": "enabled",
"asn": "65000",
"asnType": "none",
+ "ctrl": "DME_UNSET_PROPERTY_MARKER",
+ "bfdType": "none",
"name": "EVPN peering with spine",
"srcIf": "lo0",
"af-items": {
diff --git a/internal/provider/cisco/nxos/testdata/bgp_peer_local_as.json b/internal/provider/cisco/nxos/testdata/bgp_peer_local_as.json
index 89b6a813f..58de29ae3 100644
--- a/internal/provider/cisco/nxos/testdata/bgp_peer_local_as.json
+++ b/internal/provider/cisco/nxos/testdata/bgp_peer_local_as.json
@@ -13,6 +13,8 @@
"adminSt": "enabled",
"asn": "65001",
"asnType": "none",
+ "ctrl": "DME_UNSET_PROPERTY_MARKER",
+ "bfdType": "none",
"localasn-items": {
"asnPropagate": "none",
"localAsn": "65002"