diff --git a/Makefile b/Makefile index 9702c22..a974825 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -default: fmt lint build install +default: fmt lint build install test build: go build -v ./... diff --git a/README.md b/README.md index fe525eb..90da3c5 100644 --- a/README.md +++ b/README.md @@ -25,20 +25,20 @@ To get more information on a CIDR range: ``` $ cidr explain 10.0.0.0/16 Base Address: 10.0.0.0 -Usable Address Range: 10.0.0.1 to 10.0.255.254 +Usable Address Range: 10.0.0.1 to 10.0.255.254 (65,534) Broadcast Address: 10.0.255.255 -Address Count: 65,534 +Addresses: 65,536 Netmask: 255.255.0.0 (/16 bits) ``` This also works with IPv6 CIDR ranges, for example: ``` -$ cidr explain 2001:db8:1234:1a00::/64 +$ cidr explain 2001:db8:1234:1a00::/110 Base Address: 2001:db8:1234:1a00:: -Usable Address Range: 2001:db8:1234:1a00:: to 2001:db8:1234:1a00:ffff:ffff:ffff:ffff -Address Count: 18,446,744,073,709,551,614 -Netmask: ffff:ffff:ffff:ffff:: (/64 bits) +Usable Address Range: 2001:db8:1234:1a00:: to 2001:db8:1234:1a00::3:ffff (262,142) +Addresses: 262,144 +Netmask: ffff:ffff:ffff:ffff:ffff:ffff:fffc:0 (/110 bits) ``` ### Check whether an address belongs to a CIDR range @@ -57,20 +57,20 @@ $ cidr contains 2001:db8:1234:1a00::/106 2001:db8:1234:1a00:: true ``` -### Count distinct host addresses +### Count -To get all distinct host addresses part of a given CIDR range: +To get a count of all addresses in a CIDR range: ``` $ cidr count 10.0.0.0/16 -65534 +65536 ``` This also works with a IPv6 CIDR range, for example: ``` $ cidr count 2001:db8:1234:1a00::/106 -4194302 +4194304 ``` Or with a large prefix like a point-to-point link CIDR range: diff --git a/cmd/count.go b/cmd/count.go index 2840029..24aaebf 100644 --- a/cmd/count.go +++ b/cmd/count.go @@ -11,17 +11,17 @@ import ( ) const ( - countExample = "# Return the count of all distinct host addresses within a given IPv4 CIDR range\n" + + countExample = "# Return the count of all addresses within a given IPv4 CIDR range\n" + "cidr count 10.0.0.0/16\n" + "\n" + - "# Return the count of all distinct host addresses within a given IPv6 CIDR range\n" + + "# Return the count of all addresses within a given IPv6 CIDR range\n" + "cidr count 2001:db8:1234:1a00::/106" ) var ( countCmd = &cobra.Command{ Use: "count", - Short: "Return the count of all distinct host addresses in a given CIDR range", + Short: "Return the count of all addresses in a given CIDR range", Example: countExample, Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { diff --git a/cmd/explain.go b/cmd/explain.go index 9cf7817..000c3b7 100644 --- a/cmd/explain.go +++ b/cmd/explain.go @@ -55,6 +55,7 @@ type networkDetailsToDisplay struct { PrefixLength int BaseAddress net.IP Count string + HostCount string UsableAddressRangeHasError bool FirstUsableIPAddress string LastUsableIPAddress string @@ -94,6 +95,11 @@ func getNetworkDetails(network *net.IPNet) *networkDetailsToDisplay { // Format the count as a human-readable string and store it in the details struct. details.Count = helper.FormatNumber(count.String()) + // Obtain the total count of distinct host addresses in the network. + hostCount := core.GetHostAddressCount(network) + // Format the count as a human-readable string and store it in the details struct. + details.HostCount = helper.FormatNumber(hostCount.String()) + // Obtain the first and last usable IP addresses, handling errors if they occur. firstUsableIP, err := core.GetFirstUsableIPAddress(network) if err != nil { @@ -120,17 +126,20 @@ func explain(details *networkDetailsToDisplay) { var lengthIndicator string fmt.Printf(color.BlueString("Base Address:\t\t ")+"%s\n", details.BaseAddress) + if !details.UsableAddressRangeHasError { - fmt.Printf(color.BlueString("Usable Address Range:\t ")+"%s to %s\n", details.FirstUsableIPAddress, details.LastUsableIPAddress) + fmt.Printf(color.BlueString("Usable Address Range:\t ")+"%s to %s (%s)\n", details.FirstUsableIPAddress, details.LastUsableIPAddress, details.HostCount) } else { fmt.Printf(color.RedString("Usable Address Range:\t ")+"%s\n", "unable to calculate usable address range") } + if !details.BroadcastAddressHasError && details.IsIPV4Network { fmt.Printf(color.BlueString("Broadcast Address:\t ")+"%s\n", details.BroadcastAddress) } else if details.BroadcastAddressHasError && details.IsIPV4Network { fmt.Printf(color.RedString("Broadcast Address:\t ")+"%s\n", details.BroadcastAddress) } - fmt.Printf(color.BlueString("Host Addresses:\t\t ")+"%s\n", details.Count) + + fmt.Printf(color.BlueString("Addresses:\t\t ")+"%s\n", details.Count) if details.PrefixLength > 1 { lengthIndicator = "bits" diff --git a/pkg/core/core.go b/pkg/core/core.go index e74bb29..d7bc683 100644 --- a/pkg/core/core.go +++ b/pkg/core/core.go @@ -18,23 +18,48 @@ func ParseCIDR(network string) (*net.IPNet, error) { return ip, err } -// GetAddressCount returns the number of addresses in the given IP network. -// It considers the network type (IPv4 or IPv6) and handles edge cases for specific prefix lengths. -// The result excludes the network address and broadcast address. +// GetAddressCount returns the total number of addresses in the given IP network. +// It accounts for both IPv4 and IPv6 networks, and handles specific cases for certain prefix lengths. func GetAddressCount(network *net.IPNet) *big.Int { prefixLen, bits := network.Mask.Size() + // Handle specific cases for IPv4 prefix lengths. + if network.IP.To4() != nil { + switch prefixLen { + case 32: + // A /32 prefix contains a single address. + return big.NewInt(1) + case 31: + // A /31 prefix is used for point-to-point links and contains two addresses. + return big.NewInt(2) + } + } + + // Calculate the total number of addresses based on the prefix length. + return new(big.Int).Lsh(big.NewInt(1), uint(bits-prefixLen)) +} + +// GetHostAddressCount returns the number of distinct host addresses in the given IP network. +// It considers the network type (IPv4 or IPv6) and handles edge cases for specific prefix lengths. +// The result excludes the network address and the broadcast address, if applicable. +func GetHostAddressCount(network *net.IPNet) *big.Int { + prefixLen, bits := network.Mask.Size() + // Handle edge cases for specific IPv4 prefix lengths. - if network.Mask != nil && network.IP.To4() != nil { + if network.IP.To4() != nil { switch prefixLen { case 32: + // Single IP address for /32 (e.g., point-to-point link). return big.NewInt(1) case 31: + // Two IP addresses for /31 (point-to-point link). return big.NewInt(2) } } - return big.NewInt(0).Lsh(big.NewInt(1), uint(bits-prefixLen)) + // Calculate the total number of addresses and subtract 2 (network and broadcast addresses). + totalAddresses := new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(bits-prefixLen)), nil) + return totalAddresses.Sub(totalAddresses, big.NewInt(2)) } // ContainsAddress checks if the given IP network contains the specified IP address. diff --git a/pkg/core/core_test.go b/pkg/core/core_test.go index 68e8745..4638a1a 100644 --- a/pkg/core/core_test.go +++ b/pkg/core/core_test.go @@ -40,22 +40,22 @@ func TestGetAddressCount(t *testing.T) { expectedCount *big.Int }{ { - name: "Return the count of all distinct host addresses in a common IPv4 CIDR", + name: "Return the count of all addresses in a common IPv4 CIDR", cidr: IPv4CIDR, expectedCount: big.NewInt(65536), }, { - name: "Return the count of all distinct host addresses in a common IPv6 CIDR", + name: "Return the count of all addresses in a common IPv6 CIDR", cidr: IPv6CIDR, expectedCount: big.NewInt(4194304), }, { - name: "Return the count of all distinct host addresses in an uncommon (large prefix) IPv4 CIDR", + name: "Return the count of all addresses in an uncommon (large prefix) IPv4 CIDR", cidr: largeIPv4PrefixCIDR, expectedCount: big.NewInt(2), }, { - name: "Return the count of all distinct host addresses in an uncommon (largest prefix) IPv4 CIDR", + name: "Return the count of all addresses in an uncommon (largest prefix) IPv4 CIDR", cidr: largestIPv4PrefixCIDR, expectedCount: big.NewInt(1), },