Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions api/core/v1alpha1/nve_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,11 @@ const (
type MulticastGroups struct {
// L2 is the multicast group for Layer 2 VNIs (BUM traffic in bridged VLANs).
// +optional
// +kubebuilder:validation:Format=ipv4
L2 string `json:"l2,omitempty"`
L2 *IPPrefix `json:"l2,omitempty"`

// L3 is the multicast group for Layer 3 VNIs (BUM traffic in routed VRFs).
// +optional
// +kubebuilder:validation:Format=ipv4
L3 string `json:"l3,omitempty"`
L3 *IPPrefix `json:"l3,omitempty"`
}

// AnycastGateway defines distributed anycast gateway configuration.
Expand Down
10 changes: 9 additions & 1 deletion api/core/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,12 @@ spec:
l2:
description: L2 is the multicast group for Layer 2 VNIs (BUM traffic
in bridged VLANs).
format: ipv4
format: cidr
type: string
l3:
description: L3 is the multicast group for Layer 3 VNIs (BUM traffic
in routed VRFs).
format: ipv4
format: cidr
type: string
type: object
providerConfigRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ spec:
l2:
description: L2 is the multicast group for Layer 2 VNIs (BUM traffic
in bridged VLANs).
format: ipv4
format: cidr
type: string
l3:
description: L3 is the multicast group for Layer 3 VNIs (BUM traffic
in routed VRFs).
format: ipv4
format: cidr
type: string
type: object
providerConfigRef:
Expand Down
2 changes: 1 addition & 1 deletion config/samples/v1alpha1_nve.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ spec:
anycastSourceInterfaceRef:
name: lo1
multicastGroups:
l2: 224.0.0.2
l2: 239.1.1.0/24
anycastGateway:
virtualMAC: 00:00:11:11:22:22
8 changes: 5 additions & 3 deletions docs/api-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,7 @@ _Validation:_
_Appears in:_
- [ACLEntry](#aclentry)
- [InterfaceIPv4](#interfaceipv4)
- [MulticastGroups](#multicastgroups)
- [PrefixEntry](#prefixentry)
- [RendezvousPoint](#rendezvouspoint)

Expand Down Expand Up @@ -1559,8 +1560,8 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `l2` _string_ | L2 is the multicast group for Layer 2 VNIs (BUM traffic in bridged VLANs). | | Format: ipv4 <br />Optional: \{\} <br /> |
| `l3` _string_ | L3 is the multicast group for Layer 3 VNIs (BUM traffic in routed VRFs). | | Format: ipv4 <br />Optional: \{\} <br /> |
| `l2` _[IPPrefix](#ipprefix)_ | L2 is the multicast group for Layer 2 VNIs (BUM traffic in bridged VLANs). | | Format: cidr <br />Type: string <br />Optional: \{\} <br /> |
| `l3` _[IPPrefix](#ipprefix)_ | L3 is the multicast group for Layer 3 VNIs (BUM traffic in routed VRFs). | | Format: cidr <br />Type: string <br />Optional: \{\} <br /> |


#### NTP
Expand Down Expand Up @@ -3186,7 +3187,8 @@ _Appears in:_
| --- | --- | --- | --- |
| `destination` _string_ | Destination is the destination IP address of the vPC's domain peer keepalive interface.<br />This is the IP address the local switch will send keepalive messages to. | | Format: ipv4 <br />Required: \{\} <br /> |
| `source` _string_ | Source is the source IP address for keepalive messages.<br />This is the local IP address used to send keepalive packets to the peer. | | Format: ipv4 <br />Required: \{\} <br /> |
| `vrfRef` _[LocalObjectReference](#localobjectreference)_ | VRFRef is an optional reference to a VRF resource, e.g., the management VRF.<br />If specified, the switch sends keepalive packets throughout this VRF.<br />If omitted, the management VRF is used. | | Optional: \{\} <br /> |
| `vrfName` _string_ | The name of the vrf used to send keepalive packets to the peer.<br />Mutually exclusive with VrfRef. | | MaxLength: 63 <br />MinLength: 1 <br />Optional: \{\} <br /> |
| `vrfRef` _[LocalObjectReference](#localobjectreference)_ | The reference to a VRF resource used to send keepalive packets to the peer.<br />Mutually exclusive with VrfName. | | Optional: \{\} <br /> |


#### ManagementAccessConfig
Expand Down
14 changes: 9 additions & 5 deletions internal/controller/core/nve_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,14 @@ var _ = Describe("NVE Controller", func() {
ensureInterfaces(deviceName, interfaceNames, v1alpha1.InterfaceTypeLoopback)

By("Creating the custom resource for the Kind NVE")
l2Prefix := v1alpha1.MustParsePrefix("234.0.0.0/8")
nve = ensureNVE(nveKey, v1alpha1.NetworkVirtualizationEdgeSpec{
DeviceRef: v1alpha1.LocalObjectReference{Name: deviceName},
SuppressARP: true,
HostReachability: "BGP",
SourceInterfaceRef: v1alpha1.LocalObjectReference{Name: interfaceNames[0]},
AnycastSourceInterfaceRef: &v1alpha1.LocalObjectReference{Name: interfaceNames[1]},
MulticastGroups: &v1alpha1.MulticastGroups{L2: "234.0.0.1"},
MulticastGroups: &v1alpha1.MulticastGroups{L2: &l2Prefix},
AdminState: v1alpha1.AdminStateUp,
})
})
Expand Down Expand Up @@ -190,7 +191,7 @@ var _ = Describe("NVE Controller", func() {
g.Expect(testProvider.NVE.Spec.HostReachability).To(BeEquivalentTo("BGP"), "Provider NVE hostreachability should be BGP")
g.Expect(testProvider.NVE.Spec.SourceInterfaceRef.Name).To(Equal("lo0"), "Provider NVE primary interface should be lo0")
g.Expect(testProvider.NVE.Spec.MulticastGroups).ToNot(BeNil(), "Provider NVE multicast group should not be nil")
g.Expect(testProvider.NVE.Spec.MulticastGroups.L2).To(Equal("234.0.0.1"), "Provider NVE multicast group prefix should be seet")
g.Expect(testProvider.NVE.Spec.MulticastGroups.L2).To(HaveValue(Equal(v1alpha1.MustParsePrefix("234.0.0.0/8"))), "Provider NVE multicast group prefix should be set")
}).Should(Succeed())

By("Verifying referenced interfaces exist and are loopbacks")
Expand Down Expand Up @@ -250,6 +251,7 @@ var _ = Describe("NVE Controller", func() {
By("Creating the custom resource for the Kind NVE")
nve = &v1alpha1.NetworkVirtualizationEdge{}
if err := k8sClient.Get(ctx, nveKey, nve); errors.IsNotFound(err) {
l2Prefix := v1alpha1.MustParsePrefix("234.0.0.0/8")
nve = &v1alpha1.NetworkVirtualizationEdge{
ObjectMeta: metav1.ObjectMeta{
Name: nveName,
Expand All @@ -261,7 +263,7 @@ var _ = Describe("NVE Controller", func() {
HostReachability: "BGP",
SourceInterfaceRef: v1alpha1.LocalObjectReference{Name: interfaceNames[0]},
MulticastGroups: &v1alpha1.MulticastGroups{
L2: "234.0.0.1",
L2: &l2Prefix,
},
AdminState: v1alpha1.AdminStateUp,
},
Expand Down Expand Up @@ -477,6 +479,7 @@ var _ = Describe("NVE Controller", func() {
By("Creating the custom resource for the Kind NetworkVirtualizationEdge")
nve = &v1alpha1.NetworkVirtualizationEdge{}
if err := k8sClient.Get(ctx, nveKey, nve); errors.IsNotFound(err) {
l2Prefix := v1alpha1.MustParsePrefix("234.0.0.0/8")
nve = &v1alpha1.NetworkVirtualizationEdge{
ObjectMeta: metav1.ObjectMeta{
Name: nveName,
Expand All @@ -489,7 +492,7 @@ var _ = Describe("NVE Controller", func() {
SourceInterfaceRef: v1alpha1.LocalObjectReference{Name: interfaceNames[0]},
AnycastSourceInterfaceRef: &v1alpha1.LocalObjectReference{Name: interfaceNames[1]},
MulticastGroups: &v1alpha1.MulticastGroups{
L2: "234.0.0.1",
L2: &l2Prefix,
},
AdminState: v1alpha1.AdminStateUp,
},
Expand Down Expand Up @@ -549,6 +552,7 @@ var _ = Describe("NVE Controller", func() {
By("Creating the custom resource for the Kind NetworkVirtualizationEdge")
nve = &v1alpha1.NetworkVirtualizationEdge{}
if err := k8sClient.Get(ctx, nveKey, nve); errors.IsNotFound(err) {
l2Prefix := v1alpha1.MustParsePrefix("234.0.0.0/8")
nve = &v1alpha1.NetworkVirtualizationEdge{
ObjectMeta: metav1.ObjectMeta{
Name: nveName,
Expand All @@ -561,7 +565,7 @@ var _ = Describe("NVE Controller", func() {
SourceInterfaceRef: v1alpha1.LocalObjectReference{Name: interfaceNames[0]},
AnycastSourceInterfaceRef: &v1alpha1.LocalObjectReference{Name: interfaceNames[1]},
MulticastGroups: &v1alpha1.MulticastGroups{
L2: "234.0.0.1",
L2: &l2Prefix,
},
AdminState: v1alpha1.AdminStateUp,
},
Expand Down
8 changes: 4 additions & 4 deletions internal/provider/cisco/nxos/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -2532,11 +2532,11 @@ func (p *Provider) EnsureNVE(ctx context.Context, req *provider.NVERequest) erro
if req.AnycastSourceInterface != nil {
n.AnycastInterface = NewOption(req.AnycastSourceInterface.Spec.Name)
}
if req.NVE.Spec.MulticastGroups != nil && req.NVE.Spec.MulticastGroups.L2 != "" {
n.McastGroupL2 = NewOption(req.NVE.Spec.MulticastGroups.L2)
if req.NVE.Spec.MulticastGroups != nil && req.NVE.Spec.MulticastGroups.L2 != nil {
n.McastGroupL2 = NewOption(req.NVE.Spec.MulticastGroups.L2.Addr().String())
}
if req.NVE.Spec.MulticastGroups != nil && req.NVE.Spec.MulticastGroups.L3 != "" {
n.McastGroupL3 = NewOption(req.NVE.Spec.MulticastGroups.L3)
if req.NVE.Spec.MulticastGroups != nil && req.NVE.Spec.MulticastGroups.L3 != nil {
n.McastGroupL3 = NewOption(req.NVE.Spec.MulticastGroups.L3.Addr().String())
}

n.SuppressARP = req.NVE.Spec.SuppressARP
Expand Down
30 changes: 19 additions & 11 deletions internal/webhook/core/v1alpha1/nve_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package v1alpha1

import (
"context"
"errors"
"fmt"
"net/netip"

Expand Down Expand Up @@ -75,23 +76,30 @@ func (v *NetworkVirtualizationEdgeCustomValidator) validateNetworkVirtualization
if nve.Spec.MulticastGroups == nil {
return nil
}
if nve.Spec.MulticastGroups.L2 != "" {
if ok, err := v.isMulticast(nve.Spec.MulticastGroups.L2); err != nil || !ok {
return fmt.Errorf("%q is not a multicast address", nve.Spec.MulticastGroups.L2)
if nve.Spec.MulticastGroups.L2 != nil {
if !v.validateMulticastAddr(nve.Spec.MulticastGroups.L2.Prefix) {
return errors.New("invalid L2 multicast group: must be a valid IPv4 multicast CIDR with no host bits set")
}
}
if nve.Spec.MulticastGroups.L3 != "" {
if ok, err := v.isMulticast(nve.Spec.MulticastGroups.L3); err != nil || !ok {
return fmt.Errorf("%q is not a multicast address", nve.Spec.MulticastGroups.L3)
if nve.Spec.MulticastGroups.L3 != nil {
if !v.validateMulticastAddr(nve.Spec.MulticastGroups.L3.Prefix) {
return errors.New("invalid L3 multicast group: must be a valid IPv4 multicast CIDR with no host bits set")
}
}
return nil
}

func (*NetworkVirtualizationEdgeCustomValidator) isMulticast(s string) (bool, error) {
addr, err := netip.ParseAddr(s)
if err != nil || !addr.IsValid() {
return false, fmt.Errorf("%q is not a valid IP addr: %w", s, err)
// validateMulticastAddr checks if the provided prefix is a valid multicast address with no host bits set.
func (*NetworkVirtualizationEdgeCustomValidator) validateMulticastAddr(pfx netip.Prefix) bool {
// Check it's a multicast address
if !pfx.Addr().Is4() || !pfx.Addr().IsMulticast() {
return false
}
return addr.IsMulticast(), nil

// Check no host bits are set (canonical form)
if pfx.Masked().Addr() != pfx.Addr() {
return false
}

return true
}
40 changes: 35 additions & 5 deletions internal/webhook/core/v1alpha1/nve_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,46 @@ var _ = Describe("NetworkVirtualizationEdge Webhook", func() {
Expect(err).ToNot(HaveOccurred())
})

It("accepts valid IPv4 multicast address", func() {
It("accepts valid IPv4 multicast CIDR", func() {
l2Prefix := corev1alpha1.MustParsePrefix("239.1.0.0/16")
obj.Spec.MulticastGroups = &corev1alpha1.MulticastGroups{
L2: "239.1.1.1",
L2: &l2Prefix,
}
_, err := validator.ValidateCreate(ctx, obj)
Expect(err).ToNot(HaveOccurred())
})

It("rejects non-multicast IPv4 address", func() {
It("accepts valid IPv4 multicast CIDR for L3", func() {
l3Prefix := corev1alpha1.MustParsePrefix("239.2.0.0/16")
obj.Spec.MulticastGroups = &corev1alpha1.MulticastGroups{
L3: "10.0.0.1",
L3: &l3Prefix,
}
_, err := validator.ValidateCreate(ctx, obj)
Expect(err).ToNot(HaveOccurred())
})

It("accepts /32 CIDR for single multicast IP", func() {
l2Prefix := corev1alpha1.MustParsePrefix("239.1.1.1/32")
obj.Spec.MulticastGroups = &corev1alpha1.MulticastGroups{
L2: &l2Prefix,
}
_, err := validator.ValidateCreate(ctx, obj)
Expect(err).ToNot(HaveOccurred())
})

It("rejects non-multicast IPv4 CIDR", func() {
l3Prefix := corev1alpha1.MustParsePrefix("10.0.0.0/8")
obj.Spec.MulticastGroups = &corev1alpha1.MulticastGroups{
L3: &l3Prefix,
}
_, err := validator.ValidateCreate(ctx, obj)
Expect(err).To(HaveOccurred())
})

It("rejects multicast CIDR with host bits set", func() {
l2Prefix := corev1alpha1.MustParsePrefix("239.1.1.1/16")
obj.Spec.MulticastGroups = &corev1alpha1.MulticastGroups{
L2: &l2Prefix,
}
_, err := validator.ValidateCreate(ctx, obj)
Expect(err).To(HaveOccurred())
Expand All @@ -62,8 +91,9 @@ var _ = Describe("NetworkVirtualizationEdge Webhook", func() {
Context("Validate Update MulticastGroup IPv4 prefix", func() {
It("allows unchanged valid multicastGroup", func() {
oldObj := obj.DeepCopy()
l2Prefix := corev1alpha1.MustParsePrefix("239.10.0.0/16")
oldObj.Spec.MulticastGroups = &corev1alpha1.MulticastGroups{
L2: "239.10.10.1",
L2: &l2Prefix,
}
newObj := oldObj.DeepCopy()
_, err := validator.ValidateUpdate(ctx, oldObj, newObj)
Expand Down