Skip to content

Commit 68b99b4

Browse files
[NX-OS] Implement support for RoutingPolicy on Cisco NX-OS
1 parent 58bc153 commit 68b99b4

File tree

6 files changed

+274
-7
lines changed

6 files changed

+274
-7
lines changed

internal/provider/cisco/nxos/evi.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ func (b *BDEVI) XPath() string {
3131
return "System/evpn-items/bdevi-items/BDEvi-list[encap=" + b.Encap + "]"
3232
}
3333

34+
func Community(c string) (string, error) {
35+
s, err := stdcommunity(c)
36+
if err != nil {
37+
return "", err
38+
}
39+
return "regular:" + s, nil
40+
}
41+
3442
func RouteDistinguisher(rd string) (string, error) {
3543
s, err := extcommunity(rd)
3644
if err != nil {
@@ -47,27 +55,61 @@ func RouteTarget(rt string) (string, error) {
4755
return "route-target:" + s, nil
4856
}
4957

58+
// stdcommunity converts a value to a standard community string.
59+
func stdcommunity(s string) (string, error) {
60+
parts := strings.SplitN(s, ":", 2)
61+
if len(parts) != 2 {
62+
return "", errors.New("invalid bgp community format")
63+
}
64+
admin, err := strconv.ParseUint(parts[0], 10, 32)
65+
if err != nil {
66+
return "", fmt.Errorf("invalid bgp community format: %w", err)
67+
}
68+
if admin > math.MaxUint16 {
69+
return "", fmt.Errorf("standard community 'Administrator' must be in range 0–65535, got %d", admin)
70+
}
71+
assigned, err := strconv.ParseUint(parts[1], 10, 32)
72+
if err != nil {
73+
return "", fmt.Errorf("invalid bgp community format: %w", err)
74+
}
75+
if assigned > math.MaxUint16 {
76+
return "", fmt.Errorf("standard community 'Assigned Number' must be in range 0–65535, got %d", assigned)
77+
}
78+
return "as2-nn2:" + s, nil
79+
}
80+
5081
// extcommunity converts a value to an extended community string.
5182
func extcommunity(s string) (string, error) {
5283
if s == "" {
5384
return "unknown:0:0", nil
5485
}
5586
parts := strings.SplitN(s, ":", 2)
5687
if len(parts) != 2 {
57-
return "", errors.New("invalid route distinguisher format")
88+
return "", errors.New("invalid extended community format")
5889
}
59-
asn, err := strconv.ParseUint(parts[1], 10, 32)
90+
assigned, err := strconv.ParseUint(parts[1], 10, 32)
6091
if err != nil {
61-
return "", fmt.Errorf("invalid route distinguisher format: %w", err)
62-
}
63-
// Type-0
64-
if asn > math.MaxUint16 {
65-
return "as2-nn4:" + s, nil
92+
return "", fmt.Errorf("invalid extended community format: %w", err)
6693
}
6794
// Type-1
6895
if _, err := netip.ParseAddr(parts[0]); err == nil {
96+
if assigned > math.MaxUint16 {
97+
return "", fmt.Errorf("extended community 'Assigned Number' must be in range 0–65535, got %d", assigned)
98+
}
6999
return "ipv4-nn2:" + s, nil
70100
}
101+
asn, err := strconv.ParseUint(parts[0], 10, 32)
102+
if err != nil {
103+
return "", fmt.Errorf("invalid bgp extended community format: %w", err)
104+
}
105+
// Type-0
106+
if asn <= math.MaxUint16 {
107+
// standard 2-byte ASN
108+
if assigned <= math.MaxUint16 {
109+
return "as2-nn2:" + s, nil
110+
}
111+
return "as2-nn4:" + s, nil
112+
}
71113
// Type-2
72114
return "as4-nn2:" + s, nil
73115
}

internal/provider/cisco/nxos/provider.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ var (
5252
_ provider.PIMProvider = (*Provider)(nil)
5353
_ provider.SNMPProvider = (*Provider)(nil)
5454
_ provider.PrefixSetProvider = (*Provider)(nil)
55+
_ provider.RoutingPolicyProvider = (*Provider)(nil)
5556
_ provider.SyslogProvider = (*Provider)(nil)
5657
_ provider.UserProvider = (*Provider)(nil)
5758
_ provider.VLANProvider = (*Provider)(nil)
@@ -1621,6 +1622,55 @@ func (p *Provider) DeletePrefixSet(ctx context.Context, req *provider.PrefixSetR
16211622
return p.client.Delete(ctx, s)
16221623
}
16231624

1625+
func (p *Provider) EnsureRoutingPolicy(ctx context.Context, req *provider.EnsureRoutingPolicyRequest) error {
1626+
rm := new(RouteMap)
1627+
rm.Name = req.Name
1628+
for _, stmt := range req.Statements {
1629+
e := new(RouteMapEntry)
1630+
e.Order = stmt.Sequence
1631+
1632+
for _, cond := range stmt.Conditions {
1633+
switch v := cond.(type) {
1634+
case provider.MatchPrefixSetCondition:
1635+
e.SetPrefixSet(v.PrefixSet)
1636+
default:
1637+
return fmt.Errorf("routing policy: unsupported condition type %T", cond)
1638+
}
1639+
}
1640+
1641+
switch stmt.Actions.RouteDisposition {
1642+
case v1alpha1.AcceptRoute:
1643+
e.Action = ActionPermit
1644+
case v1alpha1.RejectRoute:
1645+
e.Action = ActionDeny
1646+
default:
1647+
return fmt.Errorf("routing policy: unsupported action %q", stmt.Actions.RouteDisposition)
1648+
}
1649+
1650+
if stmt.Actions.BgpActions != nil {
1651+
if stmt.Actions.BgpActions.SetCommunity != nil {
1652+
if err := e.SetCommunities(stmt.Actions.BgpActions.SetCommunity.Communities); err != nil {
1653+
return err
1654+
}
1655+
}
1656+
if stmt.Actions.BgpActions.SetExtCommunity != nil {
1657+
if err := e.SetExtCommunities(stmt.Actions.BgpActions.SetExtCommunity.Communities); err != nil {
1658+
return err
1659+
}
1660+
}
1661+
}
1662+
1663+
rm.EntItems.EntryList.Set(e)
1664+
}
1665+
return p.client.Update(ctx, rm)
1666+
}
1667+
1668+
func (p *Provider) DeleteRoutingPolicy(ctx context.Context, req *provider.DeleteRoutingPolicyRequest) error {
1669+
rm := new(RouteMap)
1670+
rm.Name = req.Name
1671+
return p.client.Delete(ctx, rm)
1672+
}
1673+
16241674
func (p *Provider) EnsureUser(ctx context.Context, req *provider.EnsureUserRequest) error {
16251675
u := new(User)
16261676
u.AllowExpired = "no"
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package nxos
5+
6+
import (
7+
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
8+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2"
9+
)
10+
11+
var _ gnmiext.Configurable = (*RouteMap)(nil)
12+
13+
type RouteMap struct {
14+
Name string `json:"name"`
15+
EntItems struct {
16+
EntryList gnmiext.List[int32, *RouteMapEntry] `json:"Entry-list,omitzero"`
17+
} `json:"ent-items,omitzero"`
18+
}
19+
20+
func (*RouteMap) IsListItem() {}
21+
22+
func (rm *RouteMap) XPath() string {
23+
return "System/rpm-items/rtmap-items/Rule-list[name=" + rm.Name + "]"
24+
}
25+
26+
type RouteMapEntry struct {
27+
Action Action `json:"action"`
28+
Order int32 `json:"order"`
29+
SrttItems struct {
30+
ItemItems struct {
31+
ItemList gnmiext.List[ExtCommItem, *ExtCommItem] `json:"Item-list,omitzero"`
32+
} `json:"item-items,omitzero"`
33+
} `json:"srtt-items,omitzero"`
34+
SregcommItems struct {
35+
NoCommAttr AdminSt `json:"noCommAttr"`
36+
ItemItems struct {
37+
ItemList gnmiext.List[string, *CommItem] `json:"Item-list,omitzero"`
38+
} `json:"item-items,omitzero"`
39+
} `json:"sregcomm-items,omitzero"`
40+
MrtdstItems struct {
41+
RsrtDstAttItems struct {
42+
RsRtDstAttList gnmiext.List[string, *RsRtDstAtt] `json:"RsRtDstAtt-list,omitzero"`
43+
} `json:"rsrtDstAtt-items,omitzero"`
44+
} `json:"mrtdst-items,omitzero"`
45+
}
46+
47+
func (e *RouteMapEntry) Key() int32 { return e.Order }
48+
49+
func (e *RouteMapEntry) SetCommunities(communities []string) error {
50+
for _, comm := range communities {
51+
c, err := Community(comm)
52+
if err != nil {
53+
return err
54+
}
55+
e.SregcommItems.NoCommAttr = AdminStDisabled
56+
e.SregcommItems.ItemItems.ItemList.Set(&CommItem{Community: c})
57+
}
58+
return nil
59+
}
60+
61+
func (e *RouteMapEntry) SetExtCommunities(communities []string) error {
62+
for _, comm := range communities {
63+
c, err := RouteTarget(comm)
64+
if err != nil {
65+
return err
66+
}
67+
e.SrttItems.ItemItems.ItemList.Set(&ExtCommItem{Community: c, Scope: RtExtComScopeTransitive})
68+
}
69+
return nil
70+
}
71+
72+
func (e *RouteMapEntry) SetPrefixSet(ps *v1alpha1.PrefixSet) {
73+
tdn := "/System/rpm-items/pfxlistv4-items/RuleV4-list[name='" + ps.Name + "']"
74+
if ps.Is6() {
75+
tdn = "/System/rpm-items/pfxlistv6-items/RuleV6-list[name='" + ps.Name + "']"
76+
}
77+
e.MrtdstItems.RsrtDstAttItems.RsRtDstAttList.Set(&RsRtDstAtt{TDn: tdn})
78+
}
79+
80+
type RsRtDstAtt struct {
81+
TDn string `json:"tDn"`
82+
}
83+
84+
func (r *RsRtDstAtt) Key() string { return r.TDn }
85+
86+
type CommItem struct {
87+
Community string `json:"community"`
88+
}
89+
90+
func (c *CommItem) Key() string { return c.Community }
91+
92+
type ExtCommItem struct {
93+
Community string `json:"community"`
94+
Scope RtExtComScope `json:"scope"`
95+
}
96+
97+
func (c *ExtCommItem) Key() ExtCommItem { return *c }
98+
99+
type RtExtComScope string
100+
101+
const (
102+
RtExtComScopeTransitive RtExtComScope = "transitive"
103+
RtExtComScopeNonTransitive RtExtComScope = "non-transitive"
104+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package nxos
5+
6+
func init() {
7+
e := &RouteMapEntry{}
8+
e.Order = 10
9+
e.Action = ActionPermit
10+
e.SrttItems.ItemItems.ItemList.Set(&ExtCommItem{Community: "route-target:as2-nn2:65137:107", Scope: RtExtComScopeTransitive})
11+
e.SregcommItems.NoCommAttr = AdminStDisabled
12+
e.SregcommItems.ItemItems.ItemList.Set(&CommItem{Community: "regular:as2-nn2:65137:107"})
13+
e.MrtdstItems.RsrtDstAttItems.RsRtDstAttList.Set(&RsRtDstAtt{TDn: "/System/rpm-items/pfxlistv4-items/RuleV4-list[name='PL-CLOUD07']"})
14+
15+
rm := &RouteMap{}
16+
rm.Name = "RM-REDIST"
17+
rm.EntItems.EntryList.Set(e)
18+
Register("route_map", rm)
19+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"rpm-items": {
3+
"rtmap-items": {
4+
"Rule-list": [
5+
{
6+
"name": "RM-REDIST",
7+
"ent-items": {
8+
"Entry-list": [
9+
{
10+
"action": "permit",
11+
"order": 10,
12+
"srtt-items": {
13+
"item-items": {
14+
"Item-list": [
15+
{
16+
"community": "route-target:as2-nn2:65137:107",
17+
"scope": "transitive"
18+
}
19+
]
20+
}
21+
},
22+
"sregcomm-items": {
23+
"noCommAttr": "disabled",
24+
"item-items": {
25+
"Item-list": [
26+
{
27+
"community": "regular:as2-nn2:65137:107"
28+
}
29+
]
30+
}
31+
},
32+
"mrtdst-items": {
33+
"rsrtDstAtt-items": {
34+
"RsRtDstAtt-list": [
35+
{
36+
"tDn": "/System/rpm-items/pfxlistv4-items/RuleV4-list[name='PL-CLOUD07']"
37+
}
38+
]
39+
}
40+
}
41+
}
42+
]
43+
}
44+
}
45+
]
46+
}
47+
}
48+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
route-map RM-REDIST permit 10
2+
match ip address prefix-list PL-CLOUD07
3+
set community 65137:107
4+
set extcommunity rt 65137:107

0 commit comments

Comments
 (0)