Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add divide subcommand #100

Merged
merged 23 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e0d81f7
Fixed IPv6 address overflow// Added next CIDR command // Added Divide…
Phaze228 May 23, 2024
28379bd
Took out unecessary calculations in divideCmd, added -u functionality…
Phaze228 May 25, 2024
c600c32
fixed intcast in next.go; gofmt'd -s things.
Phaze228 May 27, 2024
7d286eb
Merge branch 'main' into add-next-divide
bschaatsbergen May 28, 2024
bdf4af8
fixed linter errors
Phaze228 May 29, 2024
ab73d6e
Merge branch 'add-next-divide' of github.com:Phaze228/cidr into add-n…
Phaze228 May 29, 2024
f8d4852
linter tidy
Phaze228 May 29, 2024
4e2a391
Added license....
Phaze228 May 30, 2024
e623d6c
Apply suggestions from code review
Phaze228 May 30, 2024
cc3b40d
divide: changed -u -> -H, moved some code to Core | explain: Changed …
Phaze228 Jun 1, 2024
eb2ae11
Properly committing formatter function...
Phaze228 Jun 1, 2024
08dbb6b
Merge branch 'main' into add-next-divide
bschaatsbergen Jun 5, 2024
c9959de
Merge branch 'main' into add-next-divide
mmourick Jun 5, 2024
02c2879
Update README.md
Phaze228 Jun 7, 2024
370d337
changed variable, gofmt'd for the linter
Phaze228 Jun 7, 2024
b667f47
Merge branch 'add-next-divide' of github.com:Phaze228/cidr into add-n…
Phaze228 Jun 7, 2024
a05b971
Merge branch 'main' into add-next-divide
bschaatsbergen Jun 7, 2024
e70c249
Merge branch 'main' into add-next-divide
bschaatsbergen Jun 17, 2024
6a8af6e
Merge branch 'main' into add-next-divide
bschaatsbergen Jun 18, 2024
2e1d597
feat: slim down to only feature
bschaatsbergen Jun 29, 2024
ade347f
chore: go mod tidy
bschaatsbergen Jun 29, 2024
f5e809c
docs: make consistent with other examples
bschaatsbergen Jun 29, 2024
c0390a2
chore: remove aliases
bschaatsbergen Jun 29, 2024
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
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,38 @@ $ cidr overlaps 2001:db8:1111:2222:1::/80 2001:db8:1111:2222:1:1::/96
true
```

### CIDR division

To divide a CIDR range into N distinct networks:

```
$ cidr divide 10.0.0.0/16 9
10.0.0.0/20
10.0.16.0/20
10.0.32.0/20
10.0.48.0/20
10.0.64.0/20
10.0.80.0/20
10.0.96.0/20
10.0.112.0/20
10.0.128.0/20
```

This also works with IPv6 CIDR ranges, for example:

```
$ cidr divide 2001:db8:1111:2222:1::/80 9
2001:db8:1111:2222:1::/84
2001:db8:1111:2222:1:1000::/84
2001:db8:1111:2222:1:2000::/84
2001:db8:1111:2222:1:3000::/84
2001:db8:1111:2222:1:4000::/84
2001:db8:1111:2222:1:5000::/84
2001:db8:1111:2222:1:6000::/84
2001:db8:1111:2222:1:7000::/84
2001:db8:1111:2222:1:8000::/84
```

## Contributing

Contributions are highly appreciated and always welcome.
Expand Down
3 changes: 2 additions & 1 deletion cmd/count.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cmd

import (
"fmt"
"math/big"
"net"
"os"

Expand Down Expand Up @@ -48,7 +49,7 @@ func init() {
rootCmd.AddCommand(countCmd)
}

func count(network *net.IPNet) uint64 {
func count(network *net.IPNet) *big.Int {
Phaze228 marked this conversation as resolved.
Show resolved Hide resolved
count := core.GetAddressCount(network)
return count
}
101 changes: 101 additions & 0 deletions cmd/divide.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) Bruno Schaatsbergen
// SPDX-License-Identifier: MIT

package cmd

import (
"fmt"
"net"
"strconv"

"github.com/bschaatsbergen/cidr/pkg/core"
"github.com/bschaatsbergen/cidr/pkg/helper"
"github.com/spf13/cobra"
)

const (
divideExample = "# Divides the given CIDR range into N distinct networks *Truncates output to 50\n" +
"$ cidr divide 10.0.0.0/16 9\n" +
"10.0.0.0/20\n" +
"10.0.16.0/20\n" +
"10.0.32.0/20\n" +
"10.0.48.0/20\n" +
"10.0.64.0/20\n" +
"10.0.80.0/20\n" +
"10.0.96.0/20\n" +
"10.0.112.0/20\n" +
"10.0.128.0/20\n"
)

var divideCmd = &cobra.Command{
Use: "divide",
Short: "Divides the given CIDR range into N distinct networks",
Args: cobra.MinimumNArgs(2),
Example: divideExample,
PreRunE: validateDivideArguments,
RunE: executeDivide,
}

func init() {
rootCmd.AddCommand(divideCmd)
}

func validateDivideArguments(cmd *cobra.Command, args []string) error {
// Ensure CIDR is valid
_, _, err := net.ParseCIDR(args[0])
if err != nil {
return fmt.Errorf("invalid network: %s", args[0])
}

// Ensure divisor is a valid integer
_, err = strconv.ParseInt(args[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid divisor: %s", args[1])
}

return nil
}

func executeDivide(cmd *cobra.Command, args []string) error {
network, err := core.ParseCIDR(args[0])
if err != nil {
return fmt.Errorf("invalid network: %s", args[0])
}

maskSize, _ := network.Mask.Size()
if (helper.IsIPv4Network(network) && maskSize == 32) || maskSize >= 128 {
return fmt.Errorf("invalid network mask size: %s", args[0])
}

divisor, err := strconv.ParseInt(args[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid divisor: %s", args[1])
}

networks, err := core.DivideCIDR(network, divisor)
if err != nil {
return err
}

printNetworkPartitions(networks)
return nil
}

func printNetworkPartitions(networks []net.IPNet) {
const truncateLimit = 50
networkCount := len(networks)

if networkCount <= truncateLimit {
for _, network := range networks {
fmt.Println(network.String())
}
} else {
for i := 0; i < truncateLimit/2; i++ {
fmt.Println(networks[i].String())
}
fmt.Println("......")
for i := networkCount - truncateLimit/2; i < networkCount; i++ {
fmt.Println(networks[i].String())
}
}
}
5 changes: 2 additions & 3 deletions cmd/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import (
"github.com/bschaatsbergen/cidr/pkg/helper"
"github.com/fatih/color"
"github.com/spf13/cobra"
"golang.org/x/text/language"
"golang.org/x/text/message"
)

const (
Expand All @@ -29,6 +27,7 @@ var (
Use: "explain",
Short: "Provides information about a CIDR range",
Example: explainExample,
Aliases: []string{"e", "exp", "expl"},
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
fmt.Println("error: provide a CIDR range")
Expand Down Expand Up @@ -97,7 +96,7 @@ func getNetworkDetails(network *net.IPNet) *networkDetailsToDisplay {
// Obtain the total count of addresses in the network.
count := core.GetAddressCount(network)
// Format the count as a human-readable string and store it in the details struct.
details.Count = message.NewPrinter(language.English).Sprintf("%d", count)
details.Count = helper.FormatNumber(count.String())
Phaze228 marked this conversation as resolved.
Show resolved Hide resolved

// Obtain the first and last usable IP addresses, handling errors if they occur.
firstUsableIP, err := core.GetFirstUsableIPAddress(network)
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ go 1.21.4

require (
github.com/fatih/color v1.17.0
github.com/spf13/cobra v1.8.1
golang.org/x/text v0.16.0
github.com/spf13/cobra v1.8.0
)

require (
Expand Down
8 changes: 3 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
Expand All @@ -13,8 +13,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
Expand All @@ -23,8 +23,6 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
63 changes: 58 additions & 5 deletions pkg/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package core

import (
"errors"
"fmt"
"math/big"
"net"

"github.com/bschaatsbergen/cidr/pkg/helper"
Expand All @@ -19,23 +21,23 @@ func ParseCIDR(network string) (*net.IPNet, error) {
return ip, err
}

// GetAddressCount returns the number of usable addresses in the given IP network.
// 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.
func GetAddressCount(network *net.IPNet) uint64 {
func GetAddressCount(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 {
switch prefixLen {
case 32:
return 1
return big.NewInt(1)
case 31:
return 2
return big.NewInt(2)
}
}

return 1 << (uint64(bits) - uint64(prefixLen))
return big.NewInt(0).Lsh(big.NewInt(1), uint(bits-prefixLen))
}

// ContainsAddress checks if the given IP network contains the specified IP address.
Expand Down Expand Up @@ -167,3 +169,54 @@ func GetBroadcastAddress(network *net.IPNet) (net.IP, error) {

return ip, nil
}

// GetMaskWithDivisor calculates the subnet mask for the given divisor and address count.
func GetMaskWithDivisor(divisor int64, addressCount *big.Int, IPv4 bool) (net.IPMask, error) {
div := big.NewInt(divisor)
if addressCount.Cmp(div) == -1 || div.Cmp(big.NewInt(0)) == 0 {
return nil, fmt.Errorf("cannot divide %d addresses into %d divisions", addressCount, div)
}

addressPartition := new(big.Int).Div(addressCount, div)
two := big.NewInt(2)
exponent := big.NewInt(0)
for two.Cmp(addressPartition) <= 0 {
two.Lsh(two, 1)
exponent.Add(exponent, big.NewInt(1))
}
subnetPrefix := int(exponent.Int64())
bits := net.IPv6len * 8
if IPv4 {
bits = net.IPv4len * 8
if subnetPrefix > 30 {
return nil, fmt.Errorf("address Space is insufficient for %d subnets", div)
}
}
if subnetPrefix > 126 {
return nil, fmt.Errorf("address Space is insufficient for %d subnets", div)
}
return net.CIDRMask(bits-subnetPrefix, bits), nil
}

// DivideCIDR divides the given IP network into the specified number of subnets.
func DivideCIDR(network *net.IPNet, divisor int64) ([]net.IPNet, error) {
isIPv4 := helper.IsIPv4Network(network)

addressCount := GetAddressCount(network)
newSubnetMask, err := GetMaskWithDivisor(divisor, addressCount, isIPv4)
if err != nil {
return nil, fmt.Errorf("%s", err)
}

networks := make([]net.IPNet, divisor)
nextAddress := new(net.IPNet)
nextAddress.IP = network.IP
nextAddress.Mask = newSubnetMask
subnetSize := GetAddressCount(nextAddress)
for i := int64(0); i < divisor; i++ {
networks[i] = *nextAddress
ipAsInt := new(big.Int).SetBytes(nextAddress.IP)
nextAddress.IP = new(big.Int).Add(ipAsInt, subnetSize).Bytes()
}
return networks, nil
}
Loading
Loading