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

Add a function for subnetting by requested rules #1419

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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))

}