From 970ee2eaa7437bfc2802c78b710ae5c809b6cb82 Mon Sep 17 00:00:00 2001 From: Sven Rosenzweig Date: Mon, 20 Jul 2026 17:23:53 +0200 Subject: [PATCH] feat(iosxr): Implement PrefixSets Signed-off-by: Sven Rosenzweig --- internal/provider/cisco/iosxr/prefix.go | 61 +++++++++++++++++++ internal/provider/cisco/iosxr/prefix_test.go | 52 ++++++++++++++++ internal/provider/cisco/iosxr/provider.go | 37 +++++++++++ .../provider/cisco/iosxr/testdata/prefix.json | 41 +++++++++++++ .../cisco/iosxr/testdata/prefix.json.txt | 4 ++ 5 files changed, 195 insertions(+) create mode 100644 internal/provider/cisco/iosxr/prefix.go create mode 100644 internal/provider/cisco/iosxr/prefix_test.go create mode 100644 internal/provider/cisco/iosxr/testdata/prefix.json create mode 100644 internal/provider/cisco/iosxr/testdata/prefix.json.txt diff --git a/internal/provider/cisco/iosxr/prefix.go b/internal/provider/cisco/iosxr/prefix.go new file mode 100644 index 000000000..e2012be9e --- /dev/null +++ b/internal/provider/cisco/iosxr/prefix.go @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package iosxr + +import ( + "fmt" + + "github.com/ironcore-dev/network-operator/internal/transport/gnmiext" +) + +var _ gnmiext.DataElement = (*PrefixList)(nil) + +type PrefixList struct { + Name string `json:"prefix-list-name"` + Sequences struct { + Sequence gnmiext.List[int32, *PrefixEntry] `json:"sequence"` + } `json:"sequences"` + // Is6 indicates whether this is an IPv6 prefix list. This field is not serialized to JSON + // and is only used internally to determine the correct XPath for the prefix list. + Is6 bool `json:"-"` +} + +func (*PrefixList) IsListItem() {} + +func (p *PrefixList) XPath() string { + if p.Is6 { + return fmt.Sprintf("Cisco-IOS-XR-um-ipv6-prefix-list-cfg:ipv6/prefix-lists/prefix-list[prefix-list-name=%s]", p.Name) + } + return fmt.Sprintf("Cisco-IOS-XR-um-ipv4-prefix-list-cfg:ipv4/prefix-lists/prefix-list[prefix-list-name=%s]", p.Name) +} + +type PrefixEntry struct { + SequenceNumber int32 `json:"sequence-number"` + Permission Permission `json:"permission"` + Prefix string `json:"prefix"` + Mask string `json:"mask"` + MatchPrefixLength *MatchPrefixLength `json:"match-prefix-length,omitempty"` +} + +func (e *PrefixEntry) Key() int32 { return e.SequenceNumber } + +type Permission string + +const ( + PermissionPermit Permission = "permit" + PermissionDeny Permission = "deny" +) + +type PrefixType string + +const ( + PrefixTypeIPv4 PrefixType = "ipv4" + PrefixTypeIPv6 PrefixType = "ipv6" +) + +type MatchPrefixLength struct { + EQ int8 `json:"eq,omitempty"` + GE int8 `json:"ge,omitempty"` + LE int8 `json:"le,omitempty"` +} diff --git a/internal/provider/cisco/iosxr/prefix_test.go b/internal/provider/cisco/iosxr/prefix_test.go new file mode 100644 index 000000000..1dd11e66b --- /dev/null +++ b/internal/provider/cisco/iosxr/prefix_test.go @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package iosxr + +import ( + "github.com/ironcore-dev/network-operator/internal/transport/gnmiext" +) + +func init() { + prefixList := new(PrefixList) + prefixList.Name = "PL-IPv4" + prefixList.Is6 = false + prefixList.Sequences.Sequence = gnmiext.List[int32, *PrefixEntry]{} + + p1 := &PrefixEntry{ + SequenceNumber: 10, + Permission: PermissionPermit, + Prefix: "10.10.0.0", + Mask: "255.255.0.0", + MatchPrefixLength: &MatchPrefixLength{ + EQ: 16, + }, + } + + p2 := &PrefixEntry{ + SequenceNumber: 20, + Permission: PermissionPermit, + Prefix: "192.168.1.0", + Mask: "255.255.255.0", + MatchPrefixLength: &MatchPrefixLength{ + LE: 30, + }, + } + + p3 := &PrefixEntry{ + SequenceNumber: 30, + Permission: PermissionPermit, + Prefix: "172.16.0.0", + Mask: "255.240.0.0", + MatchPrefixLength: &MatchPrefixLength{ + GE: 12, + LE: 24, + }, + } + + prefixList.Sequences.Sequence.Set(p1) + prefixList.Sequences.Sequence.Set(p2) + prefixList.Sequences.Sequence.Set(p3) + + Register("prefix", prefixList) +} diff --git a/internal/provider/cisco/iosxr/provider.go b/internal/provider/cisco/iosxr/provider.go index c05255a09..39d9aa68a 100644 --- a/internal/provider/cisco/iosxr/provider.go +++ b/internal/provider/cisco/iosxr/provider.go @@ -26,6 +26,7 @@ var ( _ provider.VRFProvider = &Provider{} _ provider.BGPProvider = &Provider{} _ provider.BGPPeerProvider = &Provider{} + _ provider.PrefixSetProvider = &Provider{} ) type Provider struct { @@ -574,6 +575,42 @@ func (p *Provider) GetPeerStatus(ctx context.Context, req *provider.BGPPeerStatu return state, nil } +func (p *Provider) EnsurePrefixSet(ctx context.Context, req *provider.PrefixSetRequest) error { + s := new(PrefixList) + s.Name = req.PrefixSet.Spec.Name + s.Is6 = req.PrefixSet.Is6() + for _, entry := range req.PrefixSet.Spec.Entries { + bits := int8(entry.Prefix.Bits()) // #nosec G115 -- prefix length is max 32 (IPv4) or 128 (IPv6) + pfxEntry := &PrefixEntry{ + SequenceNumber: entry.Sequence, + Permission: PermissionPermit, + Prefix: entry.Prefix.Addr().String(), + Mask: net.IP(net.CIDRMask(entry.Prefix.Bits(), entry.Prefix.Addr().BitLen())).String(), + MatchPrefixLength: &MatchPrefixLength{ + EQ: bits, + }, + } + + if entry.MaskLengthRange != nil && (entry.MaskLengthRange.Min != bits || entry.MaskLengthRange.Max != bits) { + pfxEntry.MatchPrefixLength.GE = entry.MaskLengthRange.Min + pfxEntry.MatchPrefixLength.LE = entry.MaskLengthRange.Max + pfxEntry.MatchPrefixLength.EQ = 0 + } + + s.Sequences.Sequence.Set(pfxEntry) + } + + return p.client.Update(ctx, s) +} + +func (p *Provider) DeletePrefixSet(ctx context.Context, req *provider.PrefixSetRequest) error { + s := new(PrefixList) + s.Name = req.PrefixSet.Spec.Name + s.Is6 = req.PrefixSet.Is6() + + return p.client.Delete(ctx, s) +} + func init() { provider.Register("cisco-iosxr-gnmi", NewProvider) } diff --git a/internal/provider/cisco/iosxr/testdata/prefix.json b/internal/provider/cisco/iosxr/testdata/prefix.json new file mode 100644 index 000000000..dd3d28983 --- /dev/null +++ b/internal/provider/cisco/iosxr/testdata/prefix.json @@ -0,0 +1,41 @@ +{ + "ipv4": { + "prefix-lists": { + "prefix-list": { + "prefix-list-name": "PL-IPv4", + "sequences": { + "sequence": [ + { + "sequence-number": 10, + "permission": "permit", + "prefix": "10.10.0.0", + "mask": "255.255.0.0", + "match-prefix-length": { + "eq": 16 + } + }, + { + "sequence-number": 20, + "permission": "permit", + "prefix": "192.168.1.0", + "mask": "255.255.255.0", + "match-prefix-length": { + "le": 30 + } + }, + { + "sequence-number": 30, + "permission": "permit", + "prefix": "172.16.0.0", + "mask": "255.240.0.0", + "match-prefix-length": { + "ge": 12, + "le": 24 + } + } + ] + } + } + } + } +} diff --git a/internal/provider/cisco/iosxr/testdata/prefix.json.txt b/internal/provider/cisco/iosxr/testdata/prefix.json.txt new file mode 100644 index 000000000..4e22f93ea --- /dev/null +++ b/internal/provider/cisco/iosxr/testdata/prefix.json.txt @@ -0,0 +1,4 @@ +ipv4 prefix-list PL-IPv4 + 10 permit 10.10.0.0/16 ge 16 + 20 permit 192.168.1.0/24 le 30 + 30 permit 172.16.0.0/12 ge 12 le 24