Skip to content

Commit

Permalink
Add a function for subnetting by requested rules
Browse files Browse the repository at this point in the history
* Add models for subnetting request including CIDR block and rules
* Add SubnettingBy() and related functions
* Add GetName() which is missing member method
  • Loading branch information
yunkon-kim committed Jan 11, 2024
1 parent fe9501d commit b78f6b9
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 99 deletions.
266 changes: 171 additions & 95 deletions src/core/common/netutil/netutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type NetworkConfig struct {
// NetworkInterface defines the methods that both Network and NetworkDetails should implement.
type NetworkInterface interface {
GetCIDRBlock() string
GetName() string
GetSubnets() []Network
}

Expand All @@ -55,8 +56,8 @@ type Network struct {
Subnets []Network `json:"subnets,omitempty"`
}

func (n *Network) GetName() string { return n.Name }
func (n *Network) GetCIDRBlock() string { return n.CIDRBlock }
func (n *Network) GetName() string { return n.Name }
func (n *Network) GetSubnets() []Network { return n.Subnets }

// New creates a new NetworkDetails object.
Expand Down Expand Up @@ -134,84 +135,13 @@ func NewNetworkDetails(cidrBlock string) (*NetworkDetails, error) {
return network, nil
}

// SubnettingByMininumSubnetCount divides the CIDR block into subnets to accommodate the minimum number of subnets entered.
func SubnettingByMininumSubnetCount(cidrBlock string, minSubnets int) ([]string, error) {
_, network, err := net.ParseCIDR(cidrBlock)
if err != nil {
return nil, err
}

// Calculate the new subnet mask size
maskSize, _ := network.Mask.Size()
subnetBits := int(math.Ceil(math.Log2(float64(minSubnets))))
newMaskSize := maskSize + subnetBits

if newMaskSize > 32 {
return nil, fmt.Errorf("cannot split %s to accommodate at least %d subnets", cidrBlock, minSubnets)
}

// Calculate the actual number of subnets that can be created with the new mask size
numSubnets := int(math.Pow(2, float64(subnetBits)))

var subnets []string
for i := 0; i < numSubnets; i++ {
ip := make(net.IP, len(network.IP))
copy(ip, network.IP)

// Calculate the offset to apply to the base IP address
offset := int64(i) << (32 - newMaskSize)
for j := 3; j >= 0; j-- {
shift := uint((3 - j) * 8)
ip[j] += byte((offset >> shift) & 0xff)
}

subnets = append(subnets, fmt.Sprintf("%s/%d", ip.String(), newMaskSize))
}

return subnets, nil
}

// IpToUint32 converts an IP address to a uint32.
func IpToUint32(ip net.IP) uint32 {
ip = ip.To4()
return uint32(ip[0])<<24 + uint32(ip[1])<<16 + uint32(ip[2])<<8 + uint32(ip[3])
}

// Uint32ToIP converts a uint32 to an IP address.
func Uint32ToIP(n uint32) net.IP {
return net.IPv4(byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
}

// SubnettingByHosts divides a CIDR block into subnets based on the number of hosts required for one subnet.
func SubnettingByHosts(cidrBlock string, hostsPerSubnet int) ([]string, error) {
if hostsPerSubnet < 2 {
return nil, fmt.Errorf("number of hosts per subnet should be at least 2")
}

_, network, err := net.ParseCIDR(cidrBlock)
// GetNetworkAddr calculates the network address for a given CIDR block.
func GetNetworkAddr(cidrBlock string) (string, error) {
_, ipNet, err := net.ParseCIDR(cidrBlock)
if err != nil {
return nil, err
}

maskSize, bits := network.Mask.Size()
// Adjusting for network and broadcast addresses
hostBits := int(math.Ceil(math.Log2(float64(hostsPerSubnet + 2))))
newMaskSize := bits - hostBits

if newMaskSize <= maskSize {
return nil, fmt.Errorf("not enough room to create subnets for %d hosts in %s", hostsPerSubnet, cidrBlock)
}

baseIP := IpToUint32(network.IP)
subnetMask := uint32(math.Pow(2, float64(hostBits)) - 1)
var subnets []string

for currentIP := baseIP; currentIP < baseIP+uint32(math.Pow(2, float64(bits-maskSize))); currentIP += subnetMask + 1 {
subnetIP := Uint32ToIP(currentIP)
subnets = append(subnets, fmt.Sprintf("%s/%d", subnetIP.String(), newMaskSize))
return "", err
}

return subnets, nil
return CalculateNetworkAddr(ipNet)
}

// CalculateNetworkAddr calculates the network address for a given IPNet.
Expand All @@ -221,13 +151,14 @@ func CalculateNetworkAddr(ipNet *net.IPNet) (string, error) {
return networkIP.String(), nil
}

// GetNetworkAddr calculates the network address for a given CIDR block.
func GetNetworkAddr(cidrBlock string) (string, error) {
// GetBroadcastAddr calculates the broadcast address for a given CIDR block.
func GetBroadcastAddr(cidrBlock string) (string, error) {
_, ipNet, err := net.ParseCIDR(cidrBlock)
if err != nil {
return "", err
}
return CalculateNetworkAddr(ipNet)

return CalculateBroadcastAddr(ipNet)
}

// CalculateBroadcastAddr calculates the broadcast address for a given IPNet.
Expand All @@ -244,16 +175,6 @@ func CalculateBroadcastAddr(ipNet *net.IPNet) (string, error) {
return broadcastAddress, nil
}

// GetBroadcastAddr calculates the broadcast address for a given CIDR block.
func GetBroadcastAddr(cidrBlock string) (string, error) {
_, ipNet, err := net.ParseCIDR(cidrBlock)
if err != nil {
return "", err
}

return CalculateBroadcastAddr(ipNet)
}

// GetPrefix calculates the prefix for a given CIDR block.
func GetPrefix(cidrBlock string) (int, error) {
_, ipNet, err := net.ParseCIDR(cidrBlock)
Expand All @@ -275,10 +196,24 @@ func GetNetmask(cidrBlock string) (string, error) {
return net.IP(mask).String(), nil
}

// GetSizeOfHosts calculates the number of hosts that can be accommodated in a given CIDR block.
func GetSizeOfHosts(cidrBlock string) (int, error) {
_, ipNet, err := net.ParseCIDR(cidrBlock)
if err != nil {
return -1, err
}

return CalculateHostCapacity(ipNet)
}

// CalculateHostCapacity calculates the number of hosts that can be accommodated in a given IPNet.
func CalculateHostCapacity(ipNet *net.IPNet) (int, error) {

maskSize, bits := ipNet.Mask.Size()
return calculateHostCapacity(maskSize, bits)
}

func calculateHostCapacity(maskSize, bits int) (int, error) {
switch maskSize {
case 31:
// Special case for /31 subnets, typically used in point-to-point links (RFC 3021)
Expand All @@ -293,14 +228,155 @@ func CalculateHostCapacity(ipNet *net.IPNet) (int, error) {
}
}

// GetSizeOfHosts calculates the number of hosts that can be accommodated in a given CIDR block.
func GetSizeOfHosts(cidrBlock string) (int, error) {
_, ipNet, err := net.ParseCIDR(cidrBlock)
// ///////////////////////////////////////////////////////////////////
// Models for subnetting
type SubnettingRequest struct {
CIDRBlock string `json:"cidrBlock"`
SubnettingRules []SubnettingRule `json:"subnettingRules"`
}

type SubnettingRule struct {
Type string `json:"type"`
Value int `json:"value"`
}

// Functions for subnetting
// SubnettingBy divides a CIDR block into subnets based on the given rules.
func SubnettingBy(request SubnettingRequest) (Network, error) {
network, err := NewNetwork(request.CIDRBlock)
if err != nil {
return -1, err
return Network{}, fmt.Errorf("error creating base network: %w", err)
}

return CalculateHostCapacity(ipNet)
return subnetting(*network, request.SubnettingRules)
}

// subnetting recursivly divides a CIDR block into subnets according to the subnetting rules.
func subnetting(network Network, rules []SubnettingRule) (Network, error) {
// return the network if there are no more rule
if len(rules) == 0 {
return network, nil
}

rule := rules[0]
remainingRules := rules[1:]

var subnetsStr []string
var err error
var subnets []Network

// Subnetting by the given rule
switch rule.Type {
case "minSubnets":
subnetsStr, err = SubnettingByMinimumSubnetCount(network.CIDRBlock, rule.Value)
case "minHosts":
subnetsStr, err = SubnettingByMinimumHosts(network.CIDRBlock, rule.Value)
default:
return network, fmt.Errorf("unknown rule type: %s", rule.Type)
}

if err != nil {
return network, err
}

// Recursively subnetting again for each subnet
for _, cidr := range subnetsStr {
// a subnet without subnets
subnetWithoutSubnets, err := NewNetwork(cidr)
if err != nil {
return network, err
}
// subnetting this subnet recursively
subnetWithSubnets, err := subnetting(*subnetWithoutSubnets, remainingRules)
if err != nil {
return network, err
}
subnets = append(subnets, subnetWithSubnets)
}

network.Subnets = subnets
return network, nil
}

// SubnettingByMinimumSubnetCount divides the CIDR block into subnets to accommodate the minimum number of subnets entered.
func SubnettingByMinimumSubnetCount(cidrBlock string, minSubnets int) ([]string, error) {
_, network, err := net.ParseCIDR(cidrBlock)
if err != nil {
return nil, err
}

// Calculate the new subnet mask size
maskSize, _ := network.Mask.Size()
subnetBits := int(math.Ceil(math.Log2(float64(minSubnets))))
newMaskSize := maskSize + subnetBits

if newMaskSize > 32 {
return nil, fmt.Errorf("cannot split '%s' to accommodate at least %d subnets", cidrBlock, minSubnets)
}

// Calculate the actual number of subnets that can be created with the new mask size
numSubnets := int(math.Pow(2, float64(subnetBits)))

var subnets []string
for i := 0; i < numSubnets; i++ {
ip := make(net.IP, len(network.IP))
copy(ip, network.IP)

// Calculate the offset to apply to the base IP address
offset := int64(i) << (32 - newMaskSize)
for j := 3; j >= 0; j-- {
shift := uint((3 - j) * 8)
ip[j] += byte((offset >> shift) & 0xff)
}

subnets = append(subnets, fmt.Sprintf("%s/%d", ip.String(), newMaskSize))
}

return subnets, nil
}

// IpToUint32 converts an IP address to a uint32.
func IpToUint32(ip net.IP) uint32 {
ip = ip.To4()
return uint32(ip[0])<<24 + uint32(ip[1])<<16 + uint32(ip[2])<<8 + uint32(ip[3])
}

// Uint32ToIP converts a uint32 to an IP address.
func Uint32ToIP(n uint32) net.IP {
return net.IPv4(byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
}

// SubnettingByMinimumHosts divides a CIDR block into subnets based on the number of hosts required for one subnet.
func SubnettingByMinimumHosts(cidrBlock string, hostsPerSubnet int) ([]string, error) {
if hostsPerSubnet < 2 {
return nil, fmt.Errorf("number of hosts per subnet should be at least 2")
}

_, network, err := net.ParseCIDR(cidrBlock)
if err != nil {
return nil, err
}

maskSize, bits := network.Mask.Size()
// Adjusting for network and broadcast addresses
hostBits := int(math.Ceil(math.Log2(float64(hostsPerSubnet + 2))))
newMaskSize := bits - hostBits

if newMaskSize <= maskSize {
capa, _ := calculateHostCapacity(newMaskSize, bits)
return nil, fmt.Errorf("cannot split '%s' (host capacity: %d) into multiple subnets, each containing at least %d hosts", cidrBlock, capa, hostsPerSubnet)
}

baseIP := IpToUint32(network.IP)
subnetMask := uint32(math.Pow(2, float64(hostBits)) - 1)
var subnets []string

for currentIP := baseIP; currentIP < baseIP+uint32(math.Pow(2, float64(bits-maskSize))); currentIP += subnetMask + 1 {
subnetIP := Uint32ToIP(currentIP)
subnets = append(subnets, fmt.Sprintf("%s/%d", subnetIP.String(), newMaskSize))
}

return subnets, nil
}

// ///////////////////////////////////////////////////////////////////
Expand Down
31 changes: 27 additions & 4 deletions src/examples/netutil/netutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,21 @@ func runExample(cmd *cobra.Command, args []string) {

///////////////////////////////////////////////////////////////////////////////////////////////////
fmt.Println("\nDivide CIDR block into subnets to accommodate at least minimum number of subnets")
fmt.Println("Divide CIDR block by a specified number of hosts\n")
fmt.Printf("Divide CIDR block by a specified number of hosts\n")

fmt.Println("[Usecase] Get superneted VPCs and its subnets inside")
fmt.Printf("- Base network: %v\n", cidrBlock)
fmt.Printf("- Minimum number of VPCs (subnets of the base network): %d\n", minSubnets)
fmt.Printf("- Subnets in a VPC base on the number of hosts per subnet: %d\n", hostsPerSubnet)

subnets, err := netutil.SubnettingByMininumSubnetCount(cidrBlock, minSubnets)
subnets, err := netutil.SubnettingByMinimumSubnetCount(cidrBlock, minSubnets)
if err != nil {
fmt.Println(err)
}

for i, vpc := range subnets {
fmt.Printf("\nVPC[%03d]:\t%v\nSubnets:\t", i+1, vpc)
vpcsubnets, err := netutil.SubnettingByHosts(vpc, hostsPerSubnet)
vpcsubnets, err := netutil.SubnettingByMinimumHosts(vpc, hostsPerSubnet)
if err != nil {
fmt.Println(err)
}
Expand All @@ -73,7 +73,7 @@ func runExample(cmd *cobra.Command, args []string) {
// fmt.Println("\nDivide CIDR block by a specified number of hosts")
// fmt.Printf("Number of hosts per subnet: %d\n", hostsPerSubnet)

// subnets, err = netutil.SubnettingByHosts(cidrBlock, hostsPerSubnet)
// subnets, err = netutil.SubnettingByMinimumHosts(cidrBlock, hostsPerSubnet)
// if err != nil {
// fmt.Println(err)
// }
Expand Down Expand Up @@ -194,4 +194,27 @@ func runExample(cmd *cobra.Command, args []string) {
fmt.Println("Network configuration is invalid.")
}

///////////////////////////////////////////////////////////////////////////////////////////////////
fmt.Println("\nSubnetting a CIDR block by requests")
request := netutil.SubnettingRequest{
CIDRBlock: cidrBlock,
SubnettingRules: []netutil.SubnettingRule{
{Type: "minSubnets", Value: minSubnets},
{Type: "minHosts", Value: hostsPerSubnet},
},
}

// Subnetting by requests
networkConfig, err := netutil.SubnettingBy(request)
if err != nil {
fmt.Println("Error subnetting network:", err)
return
}

pretty, err = json.MarshalIndent(networkConfig, "", " ")
if err != nil {
fmt.Printf("marshaling error: %s\n", err)
}
fmt.Printf("[Subnetting result]\n%s\n", string(pretty))

}

0 comments on commit b78f6b9

Please sign in to comment.