Skip to content
Open
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
135 changes: 133 additions & 2 deletions config/modify.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"sort"
Expand Down Expand Up @@ -37,6 +38,84 @@ type OperationConfig struct {
electionID *spb.Uint128
}

// GenerateIPv4s generates a slice of IPv4 addresses based on the entry's prefix, step, count, and increment block.
// The increment-block is a 1-based index (1-4) indicating which octet to increment from left to right.
func (e *ipv4v6Entry) GenerateIPv4s() ([]string, error) {
// Separate prefix from mask if present
prefixIP, _, _ := net.ParseCIDR(e.Prefix)
if prefixIP == nil {
prefixIP = net.ParseIP(e.Prefix)
}

ip := prefixIP.To4()
if ip == nil {
return nil, fmt.Errorf("invalid IPv4 address: %s", e.Prefix)
}
// Validate that the 1-based increment-block is within the valid range (1-4).
if e.IncrementBlock < 1 || e.IncrementBlock > 4 {
return nil, fmt.Errorf("invalid increment block for IPv4: %d, must be between 1 and 4", e.IncrementBlock)
}
// Convert 1-based block to 0-based index for the byte slice.
index := e.IncrementBlock - 1

ips := make([]string, 0, e.Count)
for i := uint32(0); i < e.Count; i++ {
newIP := make(net.IP, len(ip))
copy(newIP, ip)

val := uint32(ip[index]) + (i * e.Step)
newIP[index] = byte(val)

// Re-attach mask if it existed
if strings.Contains(e.Prefix, "/") {
mask := strings.Split(e.Prefix, "/")[1]
ips = append(ips, fmt.Sprintf("%s/%s", newIP.String(), mask))
} else {
ips = append(ips, newIP.String())
}
}
return ips, nil
}

// GenerateIPv6s generates a slice of IPv6 addresses based on the entry's prefix, step, count, and increment block.
// The increment-block is a 1-based index (1-16) indicating which byte to increment from left to right.
func (e *ipv4v6Entry) GenerateIPv6s() ([]string, error) {
// Separate prefix from mask if present
prefixIP, _, _ := net.ParseCIDR(e.Prefix)
if prefixIP == nil {
prefixIP = net.ParseIP(e.Prefix)
}

ip := prefixIP.To16()
if ip == nil || ip.To4() != nil {
return nil, fmt.Errorf("invalid IPv6 address: %s", e.Prefix)
}
// Validate that the 1-based increment-block is within the valid range (1-16).
if e.IncrementBlock < 1 || e.IncrementBlock > 16 {
return nil, fmt.Errorf("invalid increment block for IPv6: %d, must be between 1 and 16", e.IncrementBlock)
}
// Convert 1-based block to 0-based index for the byte slice.
index := e.IncrementBlock - 1

ips := make([]string, 0, e.Count)
for i := uint32(0); i < e.Count; i++ {
newIP := make(net.IP, len(ip))
copy(newIP, ip)

val := uint32(ip[index]) + (i * e.Step)
newIP[index] = byte(val)

// Re-attach mask if it existed
if strings.Contains(e.Prefix, "/") {
mask := strings.Split(e.Prefix, "/")[1]
ips = append(ips, fmt.Sprintf("%s/%s", newIP.String(), mask))
} else {
ips = append(ips, newIP.String())
}
}
return ips, nil
}

func (oc *OperationConfig) String() string {
b, _ := json.MarshalIndent(oc, "", " ")
return string(b)
Expand Down Expand Up @@ -189,8 +268,12 @@ type sessionParams struct {
type ipv4v6Entry struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
// ipv4v6
Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"`
NHG uint64 `yaml:"nhg,omitempty" json:"nhg,omitempty"`
Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"`
NHG uint64 `yaml:"nhg,omitempty" json:"nhg,omitempty"`
// bulk prefix creation
Step uint32 `yaml:"step,omitempty" json:"step,omitempty"`
Count uint32 `yaml:"count,omitempty" json:"count,omitempty"`
IncrementBlock uint32 `yaml:"increment-block,omitempty" json:"increment-block,omitempty"` // 1-based index
NHGNetworkInstance string `yaml:"nhg-network-instance,omitempty" json:"nhg-network-instance,omitempty"`
DecapsulateHeader string `yaml:"decapsulate-header,omitempty" json:"decapsulate-header,omitempty"`
EntryMetadata string `yaml:"entry-metadata,omitempty" json:"entry-metadata,omitempty"`
Expand Down Expand Up @@ -251,6 +334,54 @@ func (c *Config) GenerateModifyInputs(targetName string) (*ModifyInput, error) {
if err != nil {
return nil, err
}

// This new section expands operations that use IP generation.
expandedOps := make([]*OperationConfig, 0, len(result.Operations))
for _, op := range result.Operations {
// Check for IPv4 generation request
if op.IPv4 != nil && op.IPv4.Count > 1 {
ips, err := op.IPv4.GenerateIPv4s()
if err != nil {
return nil, fmt.Errorf("failed to generate IPv4 addresses for operation: %w", err)
}
for _, ip := range ips {
// Create a deep copy of the operation for each generated IP
newOp := *op
newIPv4Entry := *op.IPv4
newOp.IPv4 = &newIPv4Entry

// Update the prefix to the newly generated IP
newOp.IPv4.Prefix = ip
expandedOps = append(expandedOps, &newOp)
}
continue // Move to the next operation in the original list
}

// Check for IPv6 generation request
if op.IPv6 != nil && op.IPv6.Count > 1 {
ips, err := op.IPv6.GenerateIPv6s()
if err != nil {
return nil, fmt.Errorf("failed to generate IPv6 addresses for operation: %w", err)
}
for _, ip := range ips {
// Create a deep copy of the operation for each generated IP
newOp := *op
newIPv6Entry := *op.IPv6
newOp.IPv6 = &newIPv6Entry

// Update the prefix to the newly generated IP
newOp.IPv6.Prefix = ip
expandedOps = append(expandedOps, &newOp)
}
continue // Move to the next operation in the original list
}

// If no generation is needed, add the original operation to the list
expandedOps = append(expandedOps, op)
}
// Replace the original operations list with the new, expanded list
result.Operations = expandedOps

sortOperations(result.Operations, "DRA")
for i, op := range result.Operations {
if op.NetworkInstance == "" {
Expand Down
122 changes: 122 additions & 0 deletions examples/operations/add_bulk_oper_single_primary.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# the network instance name to be used if none is
# set under an operation configuration.
default-network-instance: default

params:
redundancy: single-primary
persistence: preserve
ack-type: fib

# list of operations to send towards targets,
# only NH, NHG and IPv4 are supported.
operations:
- op: add
election-id: 110:110
nhg:
id: 1
# backup-nhg: # uint
# color: # uint
next-hop:
- index: 1
weight: 1 # uint
- index: 2
weight: 1 # uint
# programmed-id: # uint

- op: add
election-id: 110:110
# network-instance: #
# election-id: #
nh:
index: 1
ip-address: 192.168.1.2
# interface-reference:
# interface:
# subinterface:
# ip-in-ip:
# dst-ip:
# src-ip:
# mac:
# network-instance:
# programmed-index:
# pushed-mpls-label-stack:
# - type: # ipv4-explicit, router-alert, ipv6-explicit, implicit, entropy-label-indicator, no-label
# label: # uint

- op: add
election-id: 110:110
# network-instance: #
# election-id: #
nh:
index: 2
ip-address: 192.168.2.2
# interface-reference:
# interface:
# subinterface:
# ip-in-ip:
# dst-ip:
# src-ip:
# mac:
# network-instance:
# programmed-index:
# pushed-mpls-label-stack:
# - type: # ipv4-explicit, router-alert, ipv6-explicit, implicit, entropy-label-indicator, no-label
# label: # uint

- op: add
election-id: 110:110
# network-instance: not_default
ipv4:
prefix: 1.1.1.0/24
step: 1
count: 100
increment-block: 3
nhg: 1
nhg-network-instance: default
# decapsulate-header: # enum: gre, ipv4, ipv6, mpls
# entry-metadata: # string

- op: add
election-id: 110:110
network-instance: default
# election-id: #
nh:
index: 1
ip-address: 192.168.1.2
# interface-reference:
# interface:
# subinterface:
# ip-in-ip:
# dst-ip:
# src-ip:
# mac:
# network-instance:
# programmed-index:
# pushed-mpls-label-stack:
# - type: # ipv4-explicit, router-alert, ipv6-explicit, implicit, entropy-label-indicator, no-label
# label: # uint

- op: add
election-id: 110:110
network-instance: default
nhg:
id: 1
# backup-nhg: # uint
# color: # uint
next-hop:
- index: 1
weight: 1 # uint
# programmed-id: # uint

- op: add
election-id: 110:110
network-instance: default
ipv4:
prefix: 1.2.1.0/24
nhg: 1
step: 2
count: 100
increment-block: 3
nhg-network-instance: default
# decapsulate-header: # enum: gre, ipv4, ipv6, mpls
# entry-metadata: # string