Skip to content

Commit 61df5f2

Browse files
committed
cisco-nxos-provider: enable NVE
This commit enables configuring the Network Virtualization Edge (NVE) interface on cisco NXOS. It supports the following configuration: ``` interface nve1 no shutdown host-reachability protocol bgp advertise virtual-rmac source-interface loopback1 anycast loopback2 global suppress-arp global mcast-group 237.0.0.1 L2 source-interface hold-down-time 300 ``` which can be achieved with: ``` nveCfg, err := nve.NewNVE( nve.WithHostReachabilityProtocol(nve.HostReachBGP), nve.WithAdvertiseVirtualRmac(true), nve.WithSourceInterface("Loopback1"), nve.WithAnycastInterface("Loopback2"), nve.WithSuppressARP(true), nve.WithMulticastGroupL2("237.0.0.1"), nve.WithHoldDownTime(300), ) ```
1 parent bf66eb3 commit 61df5f2

File tree

2 files changed

+721
-0
lines changed

2 files changed

+721
-0
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
package nve
4+
5+
import (
6+
"context"
7+
"errors"
8+
"fmt"
9+
"net/netip"
10+
"strconv"
11+
12+
"github.com/openconfig/ygot/ygot"
13+
14+
nxos "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/genyang"
15+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/gnmiext"
16+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/iface"
17+
)
18+
19+
var _ gnmiext.DeviceConf = (*NVE)(nil)
20+
21+
type HostReachType uint
22+
23+
const (
24+
HostReachFloodAndLearn HostReachType = iota + 1
25+
HostReachBGP
26+
)
27+
28+
var (
29+
ErrInvalidInterfaceName = errors.New("nve: invalid interface name")
30+
ErrInvalidInterfaceNumber = errors.New("nve: interface number must be between 0 and 1023")
31+
ErrInvalidHoldDownTime = errors.New("nve: hold down time must be between 1 and 1500 seconds")
32+
ErrInvalidHostReachProto = errors.New("nve: invalid host reachability protocol")
33+
ErrMissingSourceInterface = errors.New("nve: source interface must be set before setting anycast interface")
34+
ErrIdenticalInterfaceNames = errors.New("nve: source and anycast interface names must be different")
35+
ErrInvalidFormatIPAddress = errors.New("nve: failed to parse IP address")
36+
)
37+
38+
// NVE represents the Network Virtualization Edge interface (nve1). This object must be
39+
// initialized with the NewNVE function.
40+
type NVE struct {
41+
adminSt bool
42+
hostReach HostReachType
43+
advertiseVirtualRmac *bool
44+
// the name of the loopback to use as source
45+
sourceInterface string
46+
// the name of the loopback to use for anycast
47+
anycastInterface string
48+
suppressARP *bool
49+
// multicast group for L2 VTEP discovery
50+
mcastL2 *netip.Addr
51+
// multicast group for L3 VTEP discovery
52+
mcastL3 *netip.Addr
53+
holdDownTime uint16 // in seconds
54+
}
55+
56+
type NVEOption func(*NVE) error
57+
58+
func NewNVE(opts ...NVEOption) (*NVE, error) {
59+
n := &NVE{
60+
adminSt: true,
61+
}
62+
for _, opt := range opts {
63+
if err := opt(n); err != nil {
64+
return nil, err
65+
}
66+
}
67+
return n, nil
68+
}
69+
70+
// WithAdminState sets the administrative state of the NVE interface. If not set, the default is `up`.
71+
func WithAdminState(adminSt bool) NVEOption {
72+
return func(n *NVE) error {
73+
n.adminSt = adminSt
74+
return nil
75+
}
76+
}
77+
78+
// WithHostReachabilityProtocol sets the host reachability protocol for the NVE.
79+
func WithHostReachabilityProtocol(proto HostReachType) NVEOption {
80+
return func(n *NVE) error {
81+
switch proto {
82+
case HostReachBGP, HostReachFloodAndLearn:
83+
n.hostReach = proto
84+
default:
85+
return ErrInvalidHostReachProto
86+
}
87+
return nil
88+
}
89+
}
90+
91+
// WithAdvertiseVirtualRmac enables or disables the advertisement of the virtual RMAC address for the NVE interface.
92+
func WithAdvertiseVirtualRmac(enable bool) NVEOption {
93+
return func(n *NVE) error {
94+
n.advertiseVirtualRmac = ygot.Bool(enable)
95+
return nil
96+
}
97+
}
98+
99+
// WithSourceInterface sets the source interface for the NVE. It must be a loopback interface as per the naming convention
100+
// defined in the `iface` package.
101+
func WithSourceInterface(loopback string) NVEOption {
102+
return func(n *NVE) error {
103+
loName, err := iface.ShortNameLoopback(loopback)
104+
if err != nil {
105+
return ErrInvalidInterfaceName
106+
}
107+
loNr, err := strconv.Atoi(loName[2:])
108+
if err != nil || loNr > 1023 {
109+
return ErrInvalidInterfaceNumber
110+
}
111+
n.sourceInterface = loName
112+
return nil
113+
}
114+
}
115+
116+
// WithAnycastInterface sets the anycast interface for the NVE. It must be a loopback interface as per the naming convention
117+
// defined in the `iface` package. The anycast interface must be different from the source interface. The source interface
118+
// must be set before setting the anycast interface.
119+
func WithAnycastInterface(loopback string) NVEOption {
120+
return func(n *NVE) error {
121+
if n.sourceInterface == "" {
122+
return ErrMissingSourceInterface
123+
}
124+
loName, err := iface.ShortNameLoopback(loopback)
125+
if err != nil {
126+
return ErrInvalidInterfaceName
127+
}
128+
loNr, err := strconv.Atoi(loName[2:])
129+
if err != nil || loNr > 1023 {
130+
return ErrInvalidInterfaceNumber
131+
}
132+
if loName == n.sourceInterface {
133+
return ErrIdenticalInterfaceNames
134+
}
135+
n.anycastInterface = loName
136+
return nil
137+
}
138+
}
139+
140+
// WithSuppressARP sets the NVE to suppress ARP requests for VTEP IP addresses. If not set, the NVE will
141+
// use the default behavior. When set, this is the equivalent to the configuration statement `global suppress-arp`.
142+
// This config will not be shown with `show running-config interface nve1` but only over gNMI.
143+
func WithSuppressARP(enable bool) NVEOption {
144+
return func(n *NVE) error {
145+
n.suppressARP = ygot.Bool(enable)
146+
return nil
147+
}
148+
}
149+
150+
func validateMulticastAddress(addr string) (*netip.Addr, error) {
151+
ip, err := netip.ParseAddr(addr)
152+
if err != nil {
153+
return nil, ErrInvalidFormatIPAddress
154+
}
155+
if !ip.Is4() || !ip.IsMulticast() {
156+
return nil, fmt.Errorf("nve: invalid multicast IPv4 address: %s", addr)
157+
}
158+
return &ip, nil
159+
}
160+
161+
// WithMulticastGroupL2 configures the global multicast group for the Layer 2 VNI.
162+
// Addr must be a valid IPv4 multicast address.
163+
func WithMulticastGroupL2(addr string) NVEOption {
164+
return func(n *NVE) error {
165+
ip, err := validateMulticastAddress(addr)
166+
if err != nil {
167+
return err
168+
}
169+
n.mcastL2 = ip
170+
return nil
171+
}
172+
}
173+
174+
// WithMulticastGroupL3 configures the global multicast group for the Layer 3 VNI.
175+
// Addr must be a valid IPv4 multicast address.
176+
func WithMulticastGroupL3(addr string) NVEOption {
177+
return func(n *NVE) error {
178+
ip, err := validateMulticastAddress(addr)
179+
if err != nil {
180+
return err
181+
}
182+
n.mcastL3 = ip
183+
return nil
184+
}
185+
}
186+
187+
// WithHoldDownTime sets the hold down time for the NVE interface in seconds (1-1500).
188+
func WithHoldDownTime(seconds uint16) NVEOption {
189+
return func(n *NVE) error {
190+
if seconds < 1 || seconds > 1500 {
191+
return ErrInvalidHoldDownTime
192+
}
193+
n.holdDownTime = seconds
194+
return nil
195+
}
196+
}
197+
198+
// ToYGOT converts the NVE configuration to these gNMI updates:
199+
// - enable the NV feature on the device
200+
// - configure the NVE interface with the provided settings
201+
// - enable the NG-MVPN feature (only if a multicast group for L3 is set)
202+
func (n *NVE) ToYGOT(_ context.Context, _ gnmiext.Client) ([]gnmiext.Update, error) {
203+
updates := []gnmiext.Update{}
204+
val := nxos.Cisco_NX_OSDevice_System_EpsItems_EpIdItems_EpList{}
205+
206+
val.AdminSt = nxos.Cisco_NX_OSDevice_Nw_AdminSt_enabled
207+
if !n.adminSt {
208+
val.AdminSt = nxos.Cisco_NX_OSDevice_Nw_AdminSt_disabled
209+
}
210+
211+
switch n.hostReach {
212+
case HostReachBGP:
213+
val.HostReach = nxos.Cisco_NX_OSDevice_Nvo_HostReachT_bgp
214+
case HostReachFloodAndLearn:
215+
val.HostReach = nxos.Cisco_NX_OSDevice_Nvo_HostReachT_Flood_and_learn
216+
default:
217+
// No-op
218+
}
219+
220+
val.AdvertiseVmac = n.advertiseVirtualRmac
221+
222+
if n.sourceInterface != "" {
223+
val.SourceInterface = ygot.String(n.sourceInterface)
224+
if n.anycastInterface != "" {
225+
val.AnycastIntf = ygot.String(n.anycastInterface)
226+
}
227+
}
228+
229+
val.SuppressARP = n.suppressARP
230+
231+
if n.mcastL2 != nil {
232+
val.McastGroupL2 = ygot.String(n.mcastL2.String())
233+
}
234+
if n.mcastL3 != nil {
235+
updates = append(updates, gnmiext.EditingUpdate{
236+
XPath: "/System/fm-items/ngmvpn-items",
237+
Value: &nxos.Cisco_NX_OSDevice_System_FmItems_NgmvpnItems{
238+
AdminSt: nxos.Cisco_NX_OSDevice_Fm_AdminState_enabled,
239+
},
240+
})
241+
val.McastGroupL3 = ygot.String(n.mcastL3.String())
242+
}
243+
244+
if n.holdDownTime != 0 {
245+
val.HoldDownTime = ygot.Uint16(n.holdDownTime)
246+
}
247+
248+
return append(updates, []gnmiext.Update{
249+
gnmiext.EditingUpdate{
250+
XPath: "/System/fm-items/nvo-items",
251+
Value: &nxos.Cisco_NX_OSDevice_System_FmItems_NvoItems{
252+
AdminSt: nxos.Cisco_NX_OSDevice_Fm_AdminState_enabled,
253+
},
254+
},
255+
gnmiext.ReplacingUpdate{
256+
XPath: "/System/eps-items/epId-items/Ep-list[epId=1]",
257+
Value: &val,
258+
},
259+
}...), nil
260+
}
261+
262+
// Reset removes the nve1 interface and disables the NV and NGMVPN feature on the device.
263+
func (n *NVE) Reset(_ context.Context, _ gnmiext.Client) ([]gnmiext.Update, error) {
264+
return []gnmiext.Update{
265+
gnmiext.DeletingUpdate{
266+
XPath: "/System/eps-items/epId-items/Ep-list[epId=1]",
267+
},
268+
gnmiext.EditingUpdate{
269+
XPath: "/System/fm-items/ngmvpn-items",
270+
Value: &nxos.Cisco_NX_OSDevice_System_FmItems_NgmvpnItems{
271+
AdminSt: nxos.Cisco_NX_OSDevice_Fm_AdminState_disabled,
272+
},
273+
},
274+
gnmiext.EditingUpdate{
275+
XPath: "/System/fm-items/nvo-items",
276+
Value: &nxos.Cisco_NX_OSDevice_System_FmItems_NvoItems{
277+
AdminSt: nxos.Cisco_NX_OSDevice_Fm_AdminState_disabled,
278+
},
279+
},
280+
}, nil
281+
}

0 commit comments

Comments
 (0)