-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implemented CIDRContains() function.
- Loading branch information
Showing
7 changed files
with
298 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright 2023-2024, Northwood Labs | ||
// Copyright 2023-2024, Ryan Parman <[email protected]> | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package corefunc | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
|
||
"github.com/apparentlymart/go-cidr/cidr" | ||
) | ||
|
||
/* | ||
CIDRContains checks to see if an IP address or CIDR block is contained within | ||
another CIDR block. | ||
Ported from OpenTofu. This function licensed as MPL-2.0. | ||
---- | ||
- containingCidr (string): A CIDR range to check as a containing range. | ||
- containedIpOrCidr (string): An IP address or CIDR range to check as a | ||
contained range. | ||
*/ | ||
func CIDRContains(containingCidr, containedIPOrCidr string) (bool, error) { | ||
// The first argument must be a CIDR prefix. | ||
_, containing, err := net.ParseCIDR(containingCidr) | ||
if err != nil { | ||
return false, fmt.Errorf( | ||
"invalid containing CIDR `%s`: %w", | ||
containingCidr, | ||
err, | ||
) | ||
} | ||
|
||
// The second argument can be either an IP address or a CIDR prefix. We will | ||
// try parsing it as an IP address first. | ||
startIP := net.ParseIP(containedIPOrCidr) | ||
|
||
var endIP net.IP | ||
|
||
// If the second argument did not parse as an IP, we will try parsing it as | ||
// a CIDR prefix. | ||
if startIP == nil { | ||
_, contained, err := net.ParseCIDR(containedIPOrCidr) | ||
// If that also fails, we'll return an error. | ||
if err != nil { | ||
return false, fmt.Errorf( | ||
"invalid IP address or containing CIDR: %s", | ||
containedIPOrCidr, | ||
) | ||
} | ||
|
||
// Otherwise, we will want to know the start and the end IP of the | ||
// prefix, so that we can check whether both are contained in the | ||
// containing prefix. | ||
startIP, endIP = cidr.AddressRange(contained) | ||
} | ||
|
||
// We require that both addresses are of the same type, so that we can't | ||
// accidentally compare an IPv4 address to an IPv6 prefix. The underlying Go | ||
// function will always return false if this happens, but we want to return | ||
// an error instead so that the caller can distinguish between a | ||
// "legitimate" false result and an erroneous check. | ||
if (startIP.To4() == nil) != (containing.IP.To4() == nil) { | ||
return false, fmt.Errorf( | ||
"address family mismatch: %s vs. %s", | ||
containingCidr, | ||
containedIPOrCidr, | ||
) | ||
} | ||
|
||
// If the second argument was an IP address, we will check whether it is | ||
// contained in the containing prefix, and that's our result. | ||
result := containing.Contains(startIP) | ||
|
||
// If the second argument was a CIDR prefix, we will also check whether the | ||
// end IP of the prefix is contained in the containing prefix. Once CIDR is | ||
// contained in another CIDR if both the start and the end IP of the | ||
// contained CIDR are contained in the containing CIDR. | ||
if endIP != nil { | ||
result = result && containing.Contains(endIP) | ||
} | ||
|
||
return result, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright 2023-2024, Northwood Labs | ||
// Copyright 2023-2024, Ryan Parman <[email protected]> | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package corefunc | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/northwood-labs/terraform-provider-corefunc/testfixtures" | ||
) | ||
|
||
func ExampleCIDRContains() { | ||
output, err := CIDRContains("192.168.2.0/20", "192.168.2.1") | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
fmt.Println(output) | ||
|
||
// Output: | ||
// true | ||
} | ||
|
||
func TestCIDRContains(t *testing.T) { // lint:allow_complexity | ||
for name, tc := range testfixtures.CIDRContainsTestTable { | ||
t.Run(name, func(t *testing.T) { | ||
output, err := CIDRContains(tc.ContainerCidr, tc.ContainedIPOrCidr) | ||
|
||
// We expect an error. | ||
if err != nil && !tc.ExpectedErr { | ||
t.Errorf("Unexpected error: %v", err) | ||
} | ||
|
||
if output != tc.Expected { | ||
t.Errorf("Expected %v, got %v", tc.Expected, output) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright 2023-2024, Northwood Labs | ||
// Copyright 2023-2024, Ryan Parman <[email protected]> | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"; | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package testfixtures // lint:no_dupe | ||
|
||
// CIDRContainsTestTable is used by both the standard Go tests and also the | ||
// Terraform acceptance tests. | ||
// <https://github.com/golang/go/wiki/TableDrivenTests> | ||
var CIDRContainsTestTable = map[string]struct { // lint:no_dupe | ||
ContainerCidr string | ||
ContainedIPOrCidr string | ||
Expected bool | ||
ExpectedErr bool | ||
}{ | ||
"192.168.2.0/20 contains 192.168.2.1": { | ||
// IPv4, contained (IP). | ||
ContainerCidr: "192.168.2.0/20", | ||
ContainedIPOrCidr: "192.168.2.1", | ||
Expected: true, | ||
ExpectedErr: false, | ||
}, | ||
"192.168.2.0/20 contains 192.168.2.0/22": { | ||
// IPv4, contained (CIDR). | ||
ContainerCidr: "192.168.2.0/20", | ||
ContainedIPOrCidr: "192.168.2.0/22", | ||
Expected: true, | ||
ExpectedErr: false, | ||
}, | ||
"192.168.2.0/20 contains 192.126.2.1": { | ||
// IPv4, not contained. | ||
ContainerCidr: "192.168.2.0/20", | ||
ContainedIPOrCidr: "192.126.2.1", | ||
Expected: false, | ||
ExpectedErr: false, | ||
}, | ||
"192.168.2.0/20 contains 192.126.2.0/18": { | ||
// IPv4, not contained (CIDR). | ||
ContainerCidr: "192.168.2.0/20", | ||
ContainedIPOrCidr: "192.126.2.0/18", | ||
Expected: false, | ||
ExpectedErr: false, | ||
}, | ||
"fe80::/48 contains fe80::1": { | ||
// IPv6, contained. | ||
ContainerCidr: "fe80::/48", | ||
ContainedIPOrCidr: "fe80::1", | ||
Expected: true, | ||
ExpectedErr: false, | ||
}, | ||
"fe80::/48 contains fe81::1": { | ||
// IPv6, not contained. | ||
ContainerCidr: "fe80::/48", | ||
ContainedIPOrCidr: "fe81::1", | ||
Expected: false, | ||
ExpectedErr: false, | ||
}, | ||
"192.168.2.0/20 contains fe80::1": { | ||
// Address family mismatch: IPv4 containing_prefix, IPv6 | ||
// contained_ip_or_prefix (IP). | ||
ContainerCidr: "192.168.2.0/20", | ||
ContainedIPOrCidr: "fe80::1", | ||
Expected: false, | ||
ExpectedErr: true, | ||
}, | ||
"192.168.2.0/20 contains fe80::/24": { | ||
// Address family mismatch: IPv4 containing_prefix, IPv6 | ||
// contained_ip_or_prefix (prefix). | ||
ContainerCidr: "192.168.2.0/20", | ||
ContainedIPOrCidr: "fe80::/24", | ||
Expected: false, | ||
ExpectedErr: true, | ||
}, | ||
"fe80::/48 contains 192.168.2.1": { | ||
// Address family mismatch: IPv6 containing_prefix, IPv4 | ||
// contained_ip_or_prefix (IP). | ||
ContainerCidr: "fe80::/48", | ||
ContainedIPOrCidr: "192.168.2.1", | ||
Expected: false, | ||
ExpectedErr: true, | ||
}, | ||
"fe80::/48 contains 192.168.2.0/20": { | ||
// Address family mismatch: IPv6 containing_prefix, IPv4 | ||
// contained_ip_or_prefix (prefix). | ||
ContainerCidr: "fe80::/48", | ||
ContainedIPOrCidr: "192.168.2.0/20", | ||
Expected: false, | ||
ExpectedErr: true, | ||
}, | ||
"not-a-cidr contains 192.168.2.1": { | ||
// Input error: invalid CIDR address. | ||
ContainerCidr: "not-a-cidr", | ||
ContainedIPOrCidr: "192.168.2.1", | ||
Expected: false, | ||
ExpectedErr: true, | ||
}, | ||
"192.168.2.0/20 contains not-an-address": { | ||
// Input error: invalid IP address. | ||
ContainerCidr: "192.168.2.0/20", | ||
ContainedIPOrCidr: "not-an-address", | ||
Expected: false, | ||
ExpectedErr: true, | ||
}, | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.