From 8e0a85d0835106448d3ac942f879a3b418c202b3 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Wed, 9 Sep 2026 13:04:22 +0200 Subject: [PATCH 1/2] SuppressARP per VNI This enables us to set the SuppressARP field individually per VNI. VNI.SuppressARP is of type Option[bool]. When nil (zero), the Option marshals to "DME_UNSET_PROPERTY_MARKER", which instructs the device to clear the per-VNI setting and let the global NVE.SuppressARP take precedence. Signed-off-by: Adam Trizuljak --- api/core/v1alpha1/evpninstance_types.go | 5 ++++ api/core/v1alpha1/zz_generated.deepcopy.go | 5 ++++ ...stances.networking.metal.ironcore.dev.yaml | 5 ++++ ...king.metal.ironcore.dev_evpninstances.yaml | 5 ++++ docs/api-reference/index.md | 1 + internal/provider/cisco/nxos/nve.go | 1 + internal/provider/cisco/nxos/nve_test.go | 14 +++++++++++ internal/provider/cisco/nxos/provider.go | 4 ++++ .../provider/cisco/nxos/testdata/vni.json | 3 ++- .../nxos/testdata/vni_suppress_arp_false.json | 23 +++++++++++++++++++ .../nxos/testdata/vni_suppress_arp_true.json | 23 +++++++++++++++++++ 11 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 internal/provider/cisco/nxos/testdata/vni_suppress_arp_false.json create mode 100644 internal/provider/cisco/nxos/testdata/vni_suppress_arp_true.json diff --git a/api/core/v1alpha1/evpninstance_types.go b/api/core/v1alpha1/evpninstance_types.go index 6c055c44a..50bdeec6c 100644 --- a/api/core/v1alpha1/evpninstance_types.go +++ b/api/core/v1alpha1/evpninstance_types.go @@ -85,6 +85,11 @@ type EVPNInstanceSpec struct { // +optional // +kubebuilder:validation:XValidation:rule="self.name == oldSelf.name",message="VRFRef is immutable" VRFRef *LocalObjectReference `json:"vrfRef,omitempty"` + + // SuppressARP overrides the NVE-level ARP suppression setting for this VNI. + // When unset, the NVE-level SuppressARP setting takes precedence. + // +optional + SuppressARP *bool `json:"suppressARP,omitempty"` } // EVPNInstanceType defines the type of EVPN instance. diff --git a/api/core/v1alpha1/zz_generated.deepcopy.go b/api/core/v1alpha1/zz_generated.deepcopy.go index 5e0a83e3a..999db7845 100644 --- a/api/core/v1alpha1/zz_generated.deepcopy.go +++ b/api/core/v1alpha1/zz_generated.deepcopy.go @@ -2049,6 +2049,11 @@ func (in *EVPNInstanceSpec) DeepCopyInto(out *EVPNInstanceSpec) { *out = new(LocalObjectReference) **out = **in } + if in.SuppressARP != nil { + in, out := &in.SuppressARP, &out.SuppressARP + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EVPNInstanceSpec. diff --git a/charts/network-operator/templates/crd/evpninstances.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/evpninstances.networking.metal.ironcore.dev.yaml index c9b00bfb1..f60ae8062 100644 --- a/charts/network-operator/templates/crd/evpninstances.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/evpninstances.networking.metal.ironcore.dev.yaml @@ -168,6 +168,11 @@ spec: x-kubernetes-list-map-keys: - value x-kubernetes-list-type: map + suppressARP: + description: |- + SuppressARP overrides the NVE-level ARP suppression setting for this VNI. + When unset, the NVE-level SuppressARP setting takes precedence. + type: boolean type: description: |- Type specifies the EVPN instance type. diff --git a/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml b/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml index b407a4b2f..c9ac37195 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml @@ -165,6 +165,11 @@ spec: x-kubernetes-list-map-keys: - value x-kubernetes-list-type: map + suppressARP: + description: |- + SuppressARP overrides the NVE-level ARP suppression setting for this VNI. + When unset, the NVE-level SuppressARP setting takes precedence. + type: boolean type: description: |- Type specifies the EVPN instance type. diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md index e7664c675..8d5a46bdf 100644 --- a/docs/api-reference/index.md +++ b/docs/api-reference/index.md @@ -1943,6 +1943,7 @@ _Appears in:_ | `routeTargets` _[EVPNRouteTarget](#evpnroutetarget) array_ | RouteTargets is the list of route targets for the EVI. | | MinItems: 1
Optional: \{\}
| | `vlanRef` _[LocalObjectReference](#localobjectreference)_ | VLANRef is a reference to a VLAN resource for which this EVPNInstance builds the MAC-VRF.
This field is only applicable when Type is Bridged (L2VNI).
The VLAN resource must exist in the same namespace.
Immutable. | | Optional: \{\}
| | `vrfRef` _[LocalObjectReference](#localobjectreference)_ | VRFRef is a reference to a VRF resource for which this EVPNInstance provides the L3VNI.
This field is only applicable when Type is Routed (L3VNI).
The VRF resource must exist in the same namespace.
Immutable. | | Optional: \{\}
| +| `suppressARP` _boolean_ | SuppressARP overrides the NVE-level ARP suppression setting for this VNI.
When unset, the NVE-level SuppressARP setting takes precedence. | | Optional: \{\}
| #### EVPNInstanceStatus diff --git a/internal/provider/cisco/nxos/nve.go b/internal/provider/cisco/nxos/nve.go index a1272c1ba..409c526d8 100644 --- a/internal/provider/cisco/nxos/nve.go +++ b/internal/provider/cisco/nxos/nve.go @@ -68,6 +68,7 @@ type VNI struct { AssociateVrfFlag bool `json:"associateVrfFlag"` McastGroup Option[string] `json:"mcastGroup"` Vni int32 `json:"vni"` + SuppressARP Option[bool] `json:"suppressARP"` } func (*VNI) IsListItem() {} diff --git a/internal/provider/cisco/nxos/nve_test.go b/internal/provider/cisco/nxos/nve_test.go index 30667cb71..1b95acc3f 100644 --- a/internal/provider/cisco/nxos/nve_test.go +++ b/internal/provider/cisco/nxos/nve_test.go @@ -22,6 +22,20 @@ func init() { McastGroup: NewOption("239.1.1.100"), } Register("vni", vni) + + suppressARPTrue := true + vniSuppressARPTrue := &VNI{ + Vni: 100011, + SuppressARP: Option[bool]{Value: &suppressARPTrue}, + } + Register("vni_suppress_arp_true", vniSuppressARPTrue) + + suppressARPFalse := false + vniSuppressARPFalse := &VNI{ + Vni: 100012, + SuppressARP: Option[bool]{Value: &suppressARPFalse}, + } + Register("vni_suppress_arp_false", vniSuppressARPFalse) nveInfraVLANs := &NVEInfraVLANs{ InfraVLANList: []*NVEInfraVLAN{ {ID: 4052}, diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index 697eec851..60b844384 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -1117,6 +1117,10 @@ func (p *Provider) EnsureEVPNInstance(ctx context.Context, req *provider.EVPNIns if req.EVPNInstance.Spec.MulticastGroupAddress != "" { vni.McastGroup = NewOption(req.EVPNInstance.Spec.MulticastGroupAddress) } + if req.EVPNInstance.Spec.SuppressARP != nil { + v := *req.EVPNInstance.Spec.SuppressARP + vni.SuppressARP = Option[bool]{Value: &v} + } sb.Update(vni) switch req.EVPNInstance.Spec.Type { diff --git a/internal/provider/cisco/nxos/testdata/vni.json b/internal/provider/cisco/nxos/testdata/vni.json index 8f4e8cfe0..1792184e5 100644 --- a/internal/provider/cisco/nxos/testdata/vni.json +++ b/internal/provider/cisco/nxos/testdata/vni.json @@ -10,7 +10,8 @@ { "associateVrfFlag": false, "mcastGroup": "239.1.1.100", - "vni": 100010 + "vni": 100010, + "suppressARP": "DME_UNSET_PROPERTY_MARKER" } ] } diff --git a/internal/provider/cisco/nxos/testdata/vni_suppress_arp_false.json b/internal/provider/cisco/nxos/testdata/vni_suppress_arp_false.json new file mode 100644 index 000000000..5223aff2d --- /dev/null +++ b/internal/provider/cisco/nxos/testdata/vni_suppress_arp_false.json @@ -0,0 +1,23 @@ +{ + "eps-items": { + "epId-items": { + "Ep-list": [ + { + "epId": "1", + "nws-items": { + "vni-items": { + "Nw-list": [ + { + "associateVrfFlag": false, + "mcastGroup": "DME_UNSET_PROPERTY_MARKER", + "vni": 100012, + "suppressARP": false + } + ] + } + } + } + ] + } + } +} diff --git a/internal/provider/cisco/nxos/testdata/vni_suppress_arp_true.json b/internal/provider/cisco/nxos/testdata/vni_suppress_arp_true.json new file mode 100644 index 000000000..2528b3c06 --- /dev/null +++ b/internal/provider/cisco/nxos/testdata/vni_suppress_arp_true.json @@ -0,0 +1,23 @@ +{ + "eps-items": { + "epId-items": { + "Ep-list": [ + { + "epId": "1", + "nws-items": { + "vni-items": { + "Nw-list": [ + { + "associateVrfFlag": false, + "mcastGroup": "DME_UNSET_PROPERTY_MARKER", + "vni": 100011, + "suppressARP": true + } + ] + } + } + } + ] + } + } +} From 2231f298b8abbc3ad3f5e196a906854568c3968a Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Wed, 16 Sep 2026 15:15:15 +0200 Subject: [PATCH 2/2] Set suppressARP to correct values The actual allowed values are strings `off`, `enabled`, `disabled` Value `off` means unset, no need to use the special `DME_UNSET_PROPERTY_MARKER` value See nvo_SuppressARPT definition in https://github.com/YangModels/yang/blob/main/vendor/cisco/nx/10.6-3/Cisco-NX-OS-device.yang Signed-off-by: Adam Trizuljak --- internal/provider/cisco/nxos/nve.go | 8 +++++++- internal/provider/cisco/nxos/nve_test.go | 11 +++++------ internal/provider/cisco/nxos/provider.go | 10 +++++++--- internal/provider/cisco/nxos/testdata/vni.json | 2 +- .../cisco/nxos/testdata/vni_suppress_arp_false.json | 2 +- .../cisco/nxos/testdata/vni_suppress_arp_true.json | 2 +- test/gnmi/testdata/cisco-nxos-gnmi/evpninstance.txtar | 3 ++- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/internal/provider/cisco/nxos/nve.go b/internal/provider/cisco/nxos/nve.go index 409c526d8..d920e6a15 100644 --- a/internal/provider/cisco/nxos/nve.go +++ b/internal/provider/cisco/nxos/nve.go @@ -64,11 +64,17 @@ func (n *NVE) XPath() string { return "System/eps-items/epId-items/Ep-list[epId=1]" } +const ( + suppressARPOff = "off" + suppressARPEnabled = "enabled" + suppressARPDisabled = "disabled" +) + type VNI struct { AssociateVrfFlag bool `json:"associateVrfFlag"` McastGroup Option[string] `json:"mcastGroup"` Vni int32 `json:"vni"` - SuppressARP Option[bool] `json:"suppressARP"` + SuppressARP string `json:"suppressARP"` } func (*VNI) IsListItem() {} diff --git a/internal/provider/cisco/nxos/nve_test.go b/internal/provider/cisco/nxos/nve_test.go index 1b95acc3f..b0711ab2b 100644 --- a/internal/provider/cisco/nxos/nve_test.go +++ b/internal/provider/cisco/nxos/nve_test.go @@ -18,22 +18,21 @@ func init() { Register("nve", nve) vni := &VNI{ - Vni: 100010, - McastGroup: NewOption("239.1.1.100"), + Vni: 100010, + McastGroup: NewOption("239.1.1.100"), + SuppressARP: suppressARPOff, } Register("vni", vni) - suppressARPTrue := true vniSuppressARPTrue := &VNI{ Vni: 100011, - SuppressARP: Option[bool]{Value: &suppressARPTrue}, + SuppressARP: suppressARPEnabled, } Register("vni_suppress_arp_true", vniSuppressARPTrue) - suppressARPFalse := false vniSuppressARPFalse := &VNI{ Vni: 100012, - SuppressARP: Option[bool]{Value: &suppressARPFalse}, + SuppressARP: suppressARPDisabled, } Register("vni_suppress_arp_false", vniSuppressARPFalse) nveInfraVLANs := &NVEInfraVLANs{ diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index 60b844384..501391142 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -1117,9 +1117,13 @@ func (p *Provider) EnsureEVPNInstance(ctx context.Context, req *provider.EVPNIns if req.EVPNInstance.Spec.MulticastGroupAddress != "" { vni.McastGroup = NewOption(req.EVPNInstance.Spec.MulticastGroupAddress) } - if req.EVPNInstance.Spec.SuppressARP != nil { - v := *req.EVPNInstance.Spec.SuppressARP - vni.SuppressARP = Option[bool]{Value: &v} + switch { + case req.EVPNInstance.Spec.SuppressARP == nil: + vni.SuppressARP = suppressARPOff + case *req.EVPNInstance.Spec.SuppressARP: + vni.SuppressARP = suppressARPEnabled + default: + vni.SuppressARP = suppressARPDisabled } sb.Update(vni) diff --git a/internal/provider/cisco/nxos/testdata/vni.json b/internal/provider/cisco/nxos/testdata/vni.json index 1792184e5..b8f008234 100644 --- a/internal/provider/cisco/nxos/testdata/vni.json +++ b/internal/provider/cisco/nxos/testdata/vni.json @@ -11,7 +11,7 @@ "associateVrfFlag": false, "mcastGroup": "239.1.1.100", "vni": 100010, - "suppressARP": "DME_UNSET_PROPERTY_MARKER" + "suppressARP": "off" } ] } diff --git a/internal/provider/cisco/nxos/testdata/vni_suppress_arp_false.json b/internal/provider/cisco/nxos/testdata/vni_suppress_arp_false.json index 5223aff2d..227624317 100644 --- a/internal/provider/cisco/nxos/testdata/vni_suppress_arp_false.json +++ b/internal/provider/cisco/nxos/testdata/vni_suppress_arp_false.json @@ -11,7 +11,7 @@ "associateVrfFlag": false, "mcastGroup": "DME_UNSET_PROPERTY_MARKER", "vni": 100012, - "suppressARP": false + "suppressARP": "disabled" } ] } diff --git a/internal/provider/cisco/nxos/testdata/vni_suppress_arp_true.json b/internal/provider/cisco/nxos/testdata/vni_suppress_arp_true.json index 2528b3c06..3ee15787d 100644 --- a/internal/provider/cisco/nxos/testdata/vni_suppress_arp_true.json +++ b/internal/provider/cisco/nxos/testdata/vni_suppress_arp_true.json @@ -11,7 +11,7 @@ "associateVrfFlag": false, "mcastGroup": "DME_UNSET_PROPERTY_MARKER", "vni": 100011, - "suppressARP": true + "suppressARP": "enabled" } ] } diff --git a/test/gnmi/testdata/cisco-nxos-gnmi/evpninstance.txtar b/test/gnmi/testdata/cisco-nxos-gnmi/evpninstance.txtar index fd0fae846..1dd37c90f 100644 --- a/test/gnmi/testdata/cisco-nxos-gnmi/evpninstance.txtar +++ b/test/gnmi/testdata/cisco-nxos-gnmi/evpninstance.txtar @@ -86,7 +86,8 @@ spec: { "associateVrfFlag": false, "mcastGroup": "DME_UNSET_PROPERTY_MARKER", - "vni": 10100 + "vni": 10100, + "suppressARP": "off" } ] }