DEV Community

Rez Moss
Rez Moss

Posted on

1

Mastering Prefix (CIDR) Operations in net/netip 3/7

Hey there! In our previous articles, we covered Addr and AddrPort types. Today, we're diving into the Prefix type, which represents an IP network in CIDR notation. If you've ever worked with subnetting or network configuration, you know how important this is. Let's make it less intimidating and more practical.

Understanding Prefix

A Prefix represents an IP address range using CIDR (Classless Inter-Domain Routing) notation. For example:

  • 192.168.1.0/24 represents 256 IPv4 addresses (192.168.1.0 to 192.168.1.255)
  • 2001:db8::/32 represents a large IPv6 network

Creating and Parsing Prefixes

Let's start with the basics:

package main

import (
    "fmt"
    "net/netip"
)

func main() {
    // Parse from CIDR string
    prefix, err := netip.ParsePrefix("192.168.1.0/24")
    if err != nil {
        panic(err)
    }

    // Create from Addr and bits
    addr := netip.MustParseAddr("192.168.1.0")
    prefix2 := netip.PrefixFrom(addr, 24)

    fmt.Printf("From string: %v\nFrom components: %v\n", prefix, prefix2)
}
Enter fullscreen mode Exit fullscreen mode

Some important validation rules:

  • Bits must be valid (0-32 for IPv4, 0-128 for IPv6)
  • The host portion of the address should be zero
  • The address must be valid

Deep Dive into Prefix Methods

Let's explore all the key methods you'll need when working with Prefix.

Basic Properties

func explorePrefix(p netip.Prefix) {
    // Get the network address
    addr := p.Addr()
    fmt.Printf("Network address: %v\n", addr)

    // Get the prefix length (bits)
    bits := p.Bits()
    fmt.Printf("Prefix length: %d\n", bits)

    // Check if it's IPv4 or IPv6
    fmt.Printf("Is IPv4: %v\n", p.Addr().Is4())
    fmt.Printf("Is IPv6: %v\n", p.Addr().Is6())

    // Check if it represents a single IP
    fmt.Printf("Is single IP: %v\n", p.IsSingleIP())
}
Enter fullscreen mode Exit fullscreen mode

Network Operations

Here's where Prefix really shines - lets look at containment and overlap checks:

func networkOperations() {
    network := netip.MustParsePrefix("192.168.1.0/24")

    // Check if an IP is in the network
    ip := netip.MustParseAddr("192.168.1.100")
    fmt.Printf("Contains IP? %v\n", network.Contains(ip))

    // Check if a smaller network is contained
    subnet := netip.MustParsePrefix("192.168.1.0/25")
    fmt.Printf("Contains subnet? %v\n", network.Contains(subnet.Addr()))

    // Check if networks overlap
    other := netip.MustParsePrefix("192.168.1.128/25")
    fmt.Printf("Overlaps? %v\n", network.Overlaps(other))
}
Enter fullscreen mode Exit fullscreen mode

Real-World Applications

Let's look at some practical applications of Prefix.

1. IPAM (IP Address Management) System

Here's a simple IPAM system that manages IP allocation within a network:

type IPAM struct {
    network netip.Prefix
    used    map[netip.Addr]bool
    mu      sync.Mutex
}

func NewIPAM(network string) (*IPAM, error) {
    prefix, err := netip.ParsePrefix(network)
    if err != nil {
        return nil, fmt.Errorf("invalid network: %w", err)
    }

    return &IPAM{
        network: prefix,
        used:    make(map[netip.Addr]bool),
    }, nil
}

func (ip *IPAM) Allocate() (netip.Addr, error) {
    ip.mu.Lock()
    defer ip.mu.Unlock()

    // Start from network address + 1
    addr := ip.network.Addr()
    for {
        addr = addr.Next()

        // Check if we're still in network
        if !ip.network.Contains(addr) {
            return netip.Addr{}, fmt.Errorf("no addresses available")
        }

        // Skip if already used
        if ip.used[addr] {
            continue
        }

        // Found an available address
        ip.used[addr] = true
        return addr, nil
    }
}

func (ip *IPAM) Release(addr netip.Addr) error {
    ip.mu.Lock()
    defer ip.mu.Unlock()

    if !ip.network.Contains(addr) {
        return fmt.Errorf("address %v not in network %v", addr, ip.network)
    }

    delete(ip.used, addr)
    return nil
}
Enter fullscreen mode Exit fullscreen mode

2. Subnet Calculator

Here's a useful tool for network planning:

type SubnetInfo struct {
    Network     netip.Prefix
    NumIPs      int
    FirstUsable netip.Addr
    LastUsable  netip.Addr
    Broadcast   netip.Addr // IPv4 only
}

func AnalyzeSubnet(prefix netip.Prefix) SubnetInfo {
    info := SubnetInfo{
        Network: prefix,
    }

    if prefix.Addr().Is4() {
        // IPv4 calculations
        bits := 32 - prefix.Bits()
        info.NumIPs = 1 << bits

        first := prefix.Addr()
        last := first
        for i := 0; i < info.NumIPs-1; i++ {
            last = last.Next()
        }

        // For IPv4, first and last addresses are network and broadcast
        if info.NumIPs > 2 {
            info.FirstUsable = first.Next()
            info.LastUsable = last.Prev()
            info.Broadcast = last
        } else {
            info.FirstUsable = first
            info.LastUsable = last
        }
    } else {
        // IPv6 calculations
        bits := 128 - prefix.Bits()
        if bits < 64 {
            info.NumIPs = 1 << bits
        } else {
            info.NumIPs = -1 // Too large to represent
        }

        info.FirstUsable = prefix.Addr()
        // Last address calculation omitted for brevity
        // (would need big.Int for proper calculation)
    }

    return info
}
Enter fullscreen mode Exit fullscreen mode

3. Firewall Rule Manager

Here's how you might use Prefix in a simple firewall rule system:

type Action string

const (
    Allow Action = "allow"
    Deny  Action = "deny"
)

type Rule struct {
    Network netip.Prefix
    Action  Action
}

type Firewall struct {
    rules []Rule
}

func NewFirewall() *Firewall {
    return &Firewall{}
}

func (fw *Firewall) AddRule(network string, action Action) error {
    prefix, err := netip.ParsePrefix(network)
    if err != nil {
        return fmt.Errorf("invalid network: %w", err)
    }

    fw.rules = append(fw.rules, Rule{
        Network: prefix,
        Action:  action,
    })

    return nil
}

func (fw *Firewall) CheckAccess(addr netip.Addr) Action {
    // Check rules in order (last matching rule wins)
    result := Allow // default

    for _, rule := range fw.rules {
        if rule.Network.Contains(addr) {
            result = rule.Action
        }
    }

    return result
}
Enter fullscreen mode Exit fullscreen mode

Advanced Operations

1. Subnet Division

Here's how to divide a network into smaller subnets:

func divideNetwork(network netip.Prefix, newBits int) ([]netip.Prefix, error) {
    if newBits <= network.Bits() {
        return nil, fmt.Errorf("new prefix length must be larger than current")
    }

    var subnets []netip.Prefix
    current := network.Addr()

    // Calculate number of subnets
    numSubnets := 1 << (newBits - network.Bits())

    for i := 0; i < numSubnets; i++ {
        subnet := netip.PrefixFrom(current, newBits)
        subnets = append(subnets, subnet)

        // Skip to next subnet
        for j := 0; j < 1<<(32-newBits); j++ {
            current = current.Next()
        }
    }

    return subnets, nil
}
Enter fullscreen mode Exit fullscreen mode

2. Network Aggregation

This function tries to combine adjacent networks when possible:

func aggregateNetworks(networks []netip.Prefix) []netip.Prefix {
    if len(networks) < 2 {
        return networks
    }

    // Sort networks first
    sort.Slice(networks, func(i, j int) bool {
        return networks[i].Addr().Less(networks[j].Addr())
    })

    var result []netip.Prefix
    current := networks[0]

    for i := 1; i < len(networks); i++ {
        // Check if networks can be combined
        if canCombine(current, networks[i]) {
            // Create a new network with one less bit
            current = netip.PrefixFrom(current.Addr(), current.Bits()-1)
        } else {
            result = append(result, current)
            current = networks[i]
        }
    }

    result = append(result, current)
    return result
}

func canCombine(a, b netip.Prefix) bool {
    // Networks must have same prefix length
    if a.Bits() != b.Bits() {
        return false
    }

    // Networks must be adjacent
    bits := a.Bits() - 1
    parentPrefix := netip.PrefixFrom(a.Addr(), bits)
    return parentPrefix.Contains(a.Addr()) && parentPrefix.Contains(b.Addr())
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Always Validate Input
   func validateNetwork(input string) error {
       prefix, err := netip.ParsePrefix(input)
       if err != nil {
           return fmt.Errorf("invalid network %q: %w", input, err)
       }

       // Ensure host bits are zero
       if prefix.Addr().String() != prefix.Addr().Masking(prefix.Bits()).String() {
           return fmt.Errorf("network address has non-zero host bits")
       }

       return nil
   }
Enter fullscreen mode Exit fullscreen mode
  1. Handle IPv4 and IPv6 Appropriately
   func getNetworkType(prefix netip.Prefix) string {
       addr := prefix.Addr()
       if addr.Is4() {
           return fmt.Sprintf("IPv4 /%d", prefix.Bits())
       }
       return fmt.Sprintf("IPv6 /%d", prefix.Bits())
   }
Enter fullscreen mode Exit fullscreen mode
  1. Use Contains() for Network Membership
   func isInNetwork(network netip.Prefix, addrs ...netip.Addr) bool {
       for _, addr := range addrs {
           if !network.Contains(addr) {
               return false
           }
       }
       return true
   }
Enter fullscreen mode Exit fullscreen mode

What's Next?

In our next article, we'll explore IP address set operations, which build upon everything we've learned about Addr and Prefix to handle complex network range manipulations.

Until then, happy networking! Remember, CIDR might seem complex at first, but with net/netip's Prefix type, it becomes much more manageable. Take your time experimenting with these examples, and don't hesitate to modify them for your specific needs.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

Heroku

This site is powered by Heroku

Heroku was created by developers, for developers. Get started today and find out why Heroku has been the platform of choice for brands like DEV for over a decade.

Sign Up

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay