Skip to content

Commit

Permalink
refactor: updates func IsIPInRange to handle IP range, CIDR, single IP
Browse files Browse the repository at this point in the history
  • Loading branch information
manoj-nutanix committed Jan 7, 2025
1 parent d5d6cfd commit 2629ac4
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 42 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/samber/lo v1.47.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.10.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.30.7
k8s.io/apiextensions-apiserver v0.30.7
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand Down
43 changes: 30 additions & 13 deletions pkg/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,39 @@ package helpers
import (
"fmt"
"net/netip"
"strings"

"go4.org/netipx"
)

// IsIPInRange checks if the target IP falls within the start and end IP range (inclusive).
func IsIPInRange(startIP, endIP, targetIP string) (bool, error) {
start, err := netip.ParseAddr(startIP)
if err != nil {
return false, fmt.Errorf("invalid start IP: %w", err)
}
end, err := netip.ParseAddr(endIP)
if err != nil {
return false, fmt.Errorf("invalid end IP: %w", err)
}
target, err := netip.ParseAddr(targetIP)
// IsIPInRange returns true if target IP falls within the IP range(inclusive of start and end IP),
// CIDR(inclusive of start and end IP), single IP.
func IsIPInRange(ipAddr, targetIP string) (bool, error) {
parsedTargetIP, err := netip.ParseAddr(targetIP)
if err != nil {
return false, fmt.Errorf("invalid target IP: %w", err)
return false, fmt.Errorf("failed to parse target IP %q: %v", targetIP, err)
}

return start.Compare(target) <= 0 && end.Compare(target) >= 0, nil
switch {
case strings.Contains(ipAddr, "-"):
ipRange, err := netipx.ParseIPRange(ipAddr)
if err != nil {
return false, fmt.Errorf("failed to parse IP range %q: %v", ipAddr, err)
}
return ipRange.Contains(parsedTargetIP), nil

case strings.Contains(ipAddr, "/"):
prefix, err := netip.ParsePrefix(ipAddr)
if err != nil {
return false, fmt.Errorf("failed to parse IP prefix %q: %v", ipAddr, err)
}
return prefix.Contains(parsedTargetIP), nil

default:
parsedIP, err := netip.ParseAddr(ipAddr)
if err != nil {
return false, fmt.Errorf("failed to parse IP address %q: %v", ipAddr, err)
}
return parsedIP.Compare(parsedTargetIP) == 0, nil
}
}
102 changes: 74 additions & 28 deletions pkg/helpers/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,98 +13,144 @@ import (
func TestIsIPInRange(t *testing.T) {
tests := []struct {
name string
startIP string
endIP string
ipRange string
targetIP string
expectedInRange bool
expectedErr error
}{
{
name: "Valid range - target within range",
startIP: "192.168.1.1",
endIP: "192.168.1.10",
ipRange: "192.168.1.1-192.168.1.10",
targetIP: "192.168.1.5",
expectedInRange: true,
expectedErr: nil,
},
{
name: "Valid range - target same as start IP",
startIP: "192.168.1.1",
endIP: "192.168.1.10",
ipRange: "192.168.1.1-192.168.1.10",
targetIP: "192.168.1.1",
expectedInRange: true,
expectedErr: nil,
},
{
name: "Valid range - target same as end IP",
startIP: "192.168.1.1",
endIP: "192.168.1.10",
ipRange: "192.168.1.1-192.168.1.10",
targetIP: "192.168.1.10",
expectedInRange: true,
expectedErr: nil,
},
{
name: "Valid range - target outside range",
startIP: "192.168.1.1",
endIP: "192.168.1.10",
ipRange: "192.168.1.1-192.168.1.10",
targetIP: "192.168.1.15",
expectedInRange: false,
expectedErr: nil,
},
{
name: "Invalid start IP",
startIP: "invalid-ip",
endIP: "192.168.1.10",
ipRange: "invalidIP-192.168.1.10",
targetIP: "192.168.1.5",
expectedInRange: false,
expectedErr: fmt.Errorf(
"invalid start IP: ParseAddr(%q): unable to parse IP",
"invalid-ip",
"failed to parse IP range %q: invalid From IP %q in range %q",
"invalidIP-192.168.1.10",
"invalidIP",
"invalidIP-192.168.1.10",
),
},
{
name: "Invalid end IP",
startIP: "192.168.1.1",
endIP: "invalid-ip",
ipRange: "192.168.1.1-invalidIP",
targetIP: "192.168.1.5",
expectedInRange: false,
expectedErr: fmt.Errorf(
"invalid end IP: ParseAddr(%q): unable to parse IP",
"invalid-ip",
"failed to parse IP range %q: invalid To IP %q in range %q",
"192.168.1.1-invalidIP",
"invalidIP",
"192.168.1.1-invalidIP",
),
},
{
name: "Invalid target IP",
startIP: "192.168.1.1",
endIP: "192.168.1.10",
targetIP: "invalid-ip",
ipRange: "192.168.1.1-192.168.1.10",
targetIP: "invalidIP",
expectedInRange: false,
expectedErr: fmt.Errorf(
"invalid target IP: ParseAddr(%q): unable to parse IP",
"invalid-ip",
"failed to parse target IP %q: ParseAddr(%q): unable to parse IP",
"invalidIP",
"invalidIP",
),
},
{
name: "IPv6 range - target within range",
startIP: "2001:db8::1",
endIP: "2001:db8::10",
ipRange: "2001:db8::1-2001:db8::10",
targetIP: "2001:db8::5",
expectedInRange: true,
expectedErr: nil,
},
{
name: "IPv6 range - target outside range",
startIP: "2001:db8::1",
endIP: "2001:db8::10",
ipRange: "2001:db8::1-2001:db8::10",
targetIP: "2001:db8::11",
expectedInRange: false,
expectedErr: nil,
},
{
name: "IP prefix - target IP inside range",
ipRange: "192.168.1.1/25",
targetIP: "192.168.1.1",
expectedInRange: true,
expectedErr: nil,
},
{
name: "IP prefix - target IP outside range",
ipRange: "192.168.1.1/25",
targetIP: "192.168.1.251",
expectedInRange: false,
expectedErr: nil,
},
{
name: "Invalid IP prefix",
ipRange: "192.168.1/25",
targetIP: "192.168.1.251",
expectedInRange: false,
expectedErr: fmt.Errorf(
"failed to parse IP prefix %q: netip.ParsePrefix(%q): ParseAddr(%q): IPv4 address too short",
"192.168.1/25",
"192.168.1/25",
"192.168.1",
),
},
{
name: "Single IP - same as target IP",
ipRange: "192.168.1.21",
targetIP: "192.168.1.21",
expectedInRange: true,
expectedErr: nil,
},
{
name: "Single IP - different from target IP",
ipRange: "192.168.1.21",
targetIP: "192.168.1.211",
expectedInRange: false,
expectedErr: nil,
},
{
name: "Invalid single IP",
ipRange: "192.168.1",
targetIP: "192.168.1.211",
expectedInRange: false,
expectedErr: fmt.Errorf(
"failed to parse IP address %q: ParseAddr(%q): IPv4 address too short",
"192.168.1",
"192.168.1",
),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := IsIPInRange(tt.startIP, tt.endIP, tt.targetIP)
got, err := IsIPInRange(tt.ipRange, tt.targetIP)
assert.Equal(t, tt.expectedInRange, got)
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
Expand Down
2 changes: 1 addition & 1 deletion pkg/webhook/cluster/nutanix_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func validatePrismCentralIPNotInLoadBalancerIPRange(
}

for _, pool := range serviceLoadBalancerConfiguration.Configuration.AddressRanges {
isIPInRange, err := helpers.IsIPInRange(pool.Start, pool.End, pcIP.String())
isIPInRange, err := helpers.IsIPInRange(pool.Start+"-"+pool.End, pcIP.String())
if err != nil {
return fmt.Errorf(
"failed to check if Prism Central IP %q is part of MetalLB address range %q-%q: %w",
Expand Down

0 comments on commit 2629ac4

Please sign in to comment.