From b78f6b9fa200841e78b4e802176f089deacf9597 Mon Sep 17 00:00:00 2001 From: Yunkon Kim Date: Thu, 11 Jan 2024 17:26:34 +0900 Subject: [PATCH] Add a function for subnetting by requested rules * Add models for subnetting request including CIDR block and rules * Add SubnettingBy() and related functions * Add GetName() which is missing member method --- src/core/common/netutil/netutil.go | 266 ++++++++++++++++++----------- src/examples/netutil/netutil.go | 31 +++- 2 files changed, 198 insertions(+), 99 deletions(-) diff --git a/src/core/common/netutil/netutil.go b/src/core/common/netutil/netutil.go index 6b321574a..52d36266a 100644 --- a/src/core/common/netutil/netutil.go +++ b/src/core/common/netutil/netutil.go @@ -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 } @@ -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. @@ -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. @@ -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. @@ -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) @@ -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) @@ -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 } // /////////////////////////////////////////////////////////////////// diff --git a/src/examples/netutil/netutil.go b/src/examples/netutil/netutil.go index 18f475e73..5ad5d1ed6 100644 --- a/src/examples/netutil/netutil.go +++ b/src/examples/netutil/netutil.go @@ -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) } @@ -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) // } @@ -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)) + }