DEV Community

Rez Moss
Rez Moss

Posted on

In-depth Guide to net/netip Prefix Methods 7/7

Hey there! We've made it to our final deep dive into net/netip's core types. Today we're focusing on the Prefix type and its methods. If you've worked with networks, you're familiar with CIDR notation (like 192.168.1.0/24). That's exactly what Prefix handles, and we're going to explore every method you can use with it.

Core Method Exploration

Let's start by looking at all the ways to create and work with Prefix.

Creation and Parsing

package main import ( "fmt" "net/netip" ) func demoPrefixCreation() { // From CIDR string prefix1, _ := netip.ParsePrefix("192.168.1.0/24") // From Addr and bits addr := netip.MustParseAddr("192.168.1.0") prefix2 := netip.PrefixFrom(addr, 24) fmt.Printf("From string: %v\n", prefix1) fmt.Printf("From components: %v\n", prefix2) // Parsing with validation prefixes := []string{ "192.168.1.1/24", // Host bits set "192.168.1.0/24", // Correct form "2001:db8::/32", // IPv6 "invalid/24", // Invalid "192.168.1.0/33", // Invalid mask } for _, p := range prefixes { if prefix, err := netip.ParsePrefix(p); err != nil { fmt.Printf("%s - Error: %v\n", p, err) } else { fmt.Printf("%s - Valid: %v\n", p, prefix) } } } 
Enter fullscreen mode Exit fullscreen mode

Core Methods

Let's explore the essential methods every Prefix provides:

func explorePrefixMethods(p netip.Prefix) { // Basic properties fmt.Printf("Prefix: %v\n", p) fmt.Printf("Address: %v\n", p.Addr()) fmt.Printf("Bits: %d\n", p.Bits()) // Network properties fmt.Printf("Contains own address? %v\n", p.Contains(p.Addr())) fmt.Printf("Is single IP? %v\n", p.IsSingleIP()) // String representation fmt.Printf("String form: %s\n", p.String()) // Masking fmt.Printf("Masked address: %v\n", p.Addr().Masking(p.Bits())) } 
Enter fullscreen mode Exit fullscreen mode

Real-World Applications

1. IPAM (IP Address Management) System

A comprehensive IPAM system using Prefix:

type IPRange struct { Network netip.Prefix Used map[netip.Addr]bool mu sync.RWMutex } type IPAM struct { ranges map[string]*IPRange // Key is range name mu sync.RWMutex } func NewIPAM() *IPAM { return &IPAM{ ranges: make(map[string]*IPRange), } } func (ipam *IPAM) AddRange(name string, cidr string) error { prefix, err := netip.ParsePrefix(cidr) if err != nil { return fmt.Errorf("invalid CIDR: %w", err) } ipam.mu.Lock() defer ipam.mu.Unlock() if _, exists := ipam.ranges[name]; exists { return fmt.Errorf("range %q already exists", name) } ipam.ranges[name] = &IPRange{ Network: prefix, Used: make(map[netip.Addr]bool), } return nil } func (ipam *IPAM) AllocateIP(rangeName string) (netip.Addr, error) { ipam.mu.RLock() r, exists := ipam.ranges[rangeName] ipam.mu.RUnlock() if !exists { return netip.Addr{}, fmt.Errorf("range %q not found", rangeName) } r.mu.Lock() defer r.mu.Unlock() addr := r.Network.Addr() maxHosts := uint64(1) << (32 - r.Network.Bits()) // For IPv4 for i := uint64(1); i < maxHosts-1; i++ { // Skip network and broadcast addr = addr.Next() if !r.Used[addr] { r.Used[addr] = true return addr, nil } } return netip.Addr{}, fmt.Errorf("no available addresses in range %q", rangeName) } func (ipam *IPAM) ReleaseIP(rangeName string, addr netip.Addr) error { ipam.mu.RLock() r, exists := ipam.ranges[rangeName] ipam.mu.RUnlock() if !exists { return fmt.Errorf("range %q not found", rangeName) } r.mu.Lock() defer r.mu.Unlock() if !r.Network.Contains(addr) { return fmt.Errorf("address %v is not in range %q", addr, rangeName) } delete(r.Used, addr) return nil } 
Enter fullscreen mode Exit fullscreen mode

2. Subnet Calculator

A tool for network planning and analysis:

type SubnetInfo struct { Network netip.Prefix FirstUsable netip.Addr LastUsable netip.Addr NumHosts uint64 BroadcastAddr netip.Addr // IPv4 only } func AnalyzeSubnet(prefix netip.Prefix) (SubnetInfo, error) { info := SubnetInfo{Network: prefix} if !prefix.IsValid() { return info, fmt.Errorf("invalid prefix") } if prefix.Addr().Is4() { // IPv4 calculations bits := 32 - prefix.Bits() info.NumHosts = (1 << bits) - 2 // Subtract network and broadcast network := prefix.Addr() info.FirstUsable = network.Next() // Calculate broadcast address broadcast := network for i := 0; i < 1<<bits-1; i++ { broadcast = broadcast.Next() } info.BroadcastAddr = broadcast info.LastUsable = broadcast.Prev() } else { // IPv6 calculations bits := 128 - prefix.Bits() if bits > 64 { info.NumHosts = 0 // Too large to represent } else { info.NumHosts = 1 << bits } info.FirstUsable = prefix.Addr() // IPv6 doesn't use broadcast addresses info.LastUsable = info.FirstUsable for i := 0; i < 1<<bits-1; i++ { info.LastUsable = info.LastUsable.Next() } } return info, nil } func SubnetNetwork(prefix netip.Prefix, newBits int) ([]netip.Prefix, error) { if newBits <= prefix.Bits() { return nil, fmt.Errorf("new prefix must be larger than current") } if prefix.Addr().Is4() && newBits > 32 { return nil, fmt.Errorf("invalid IPv4 prefix length") } if prefix.Addr().Is6() && newBits > 128 { return nil, fmt.Errorf("invalid IPv6 prefix length") } numSubnets := 1 << (newBits - prefix.Bits()) subnets := make([]netip.Prefix, 0, numSubnets) current := prefix.Addr() 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

3. Network ACL Manager

A system for managing network access control lists:

type Action string const ( Allow Action = "allow" Deny Action = "deny" ) type ACLRule struct { Network netip.Prefix Action Action Order int } type NetworkACL struct { rules []ACLRule mu sync.RWMutex } func NewNetworkACL() *NetworkACL { return &NetworkACL{} } func (acl *NetworkACL) AddRule(cidr string, action Action, order int) error { prefix, err := netip.ParsePrefix(cidr) if err != nil { return fmt.Errorf("invalid CIDR: %w", err) } acl.mu.Lock() defer acl.mu.Unlock() // Check for duplicate rules for _, rule := range acl.rules { if rule.Network == prefix && rule.Action == action { return fmt.Errorf("duplicate rule") } } acl.rules = append(acl.rules, ACLRule{ Network: prefix, Action: action, Order: order, }) // Sort rules by order sort.Slice(acl.rules, func(i, j int) bool { return acl.rules[i].Order < acl.rules[j].Order }) return nil } func (acl *NetworkACL) CheckAccess(addr netip.Addr) Action { acl.mu.RLock() defer acl.mu.RUnlock() // Check rules in order for _, rule := range acl.rules { if rule.Network.Contains(addr) { return rule.Action } } return Deny // Default deny } func (acl *NetworkACL) OptimizeRules() { acl.mu.Lock() defer acl.mu.Unlock() // Group rules by action allowRules := make(map[netip.Prefix]bool) denyRules := make(map[netip.Prefix]bool) for _, rule := range acl.rules { if rule.Action == Allow { allowRules[rule.Network] = true } else { denyRules[rule.Network] = true } } // TODO: Implement rule optimization logic // This could include: // - Merging adjacent networks // - Removing redundant rules // - Detecting conflicts } 
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Validate Prefix Creation
 func validatePrefix(prefix string) error { p, err := netip.ParsePrefix(prefix) if err != nil { return fmt.Errorf("invalid prefix: %w", err) } // Ensure network address is properly masked if p.Addr().String() != p.Addr().Masking(p.Bits()).String() { return fmt.Errorf("prefix contains host bits") } return nil } 
Enter fullscreen mode Exit fullscreen mode
  1. Handle IPv4 and IPv6 Appropriately
 func getAddressFamily(p netip.Prefix) string { if p.Addr().Is4() { return fmt.Sprintf("IPv4/%d", p.Bits()) } return fmt.Sprintf("IPv6/%d", p.Bits()) } 
Enter fullscreen mode Exit fullscreen mode
  1. Use Contains Efficiently
 func isInRange(networks []netip.Prefix, addr netip.Addr) bool { for _, net := range networks { if net.Contains(addr) { return true } } return false } 
Enter fullscreen mode Exit fullscreen mode

Performance Tips

  1. Cache Parsed Prefixes
 type NetworkCache struct { prefixes map[string]netip.Prefix mu sync.RWMutex } func (nc *NetworkCache) GetPrefix(cidr string) (netip.Prefix, error) { nc.mu.RLock() if prefix, ok := nc.prefixes[cidr]; ok { nc.mu.RUnlock() return prefix, nil } nc.mu.RUnlock() prefix, err := netip.ParsePrefix(cidr) if err != nil { return netip.Prefix{}, err } nc.mu.Lock() nc.prefixes[cidr] = prefix nc.mu.Unlock() return prefix, nil } 
Enter fullscreen mode Exit fullscreen mode
  1. Efficient Network Checks
 // Bad: Converting to string unnecessarily if prefix.String() == "192.168.1.0/24" { // ... } // Good: Direct comparison if prefix == netip.MustParsePrefix("192.168.1.0/24") { // ... } 
Enter fullscreen mode Exit fullscreen mode
  1. Batch Operations
 func processNetworks(prefixes []netip.Prefix) { // Process in chunks for better performance const chunkSize = 100 for i := 0; i < len(prefixes); i += chunkSize { end := i + chunkSize if end > len(prefixes) { end = len(prefixes) } processChunk(prefixes[i:end]) } } 
Enter fullscreen mode Exit fullscreen mode

Series Conclusion

This concludes our deep dive into the net/netip package! We've covered:

  • Addr type and its methods
  • AddrPort for handling IP:port combinations
  • Prefix for working with CIDR networks

These types work together to provide a robust foundation for network programming in Go. The key benefits of using net/netip include:

  • Type safety
  • Memory efficiency
  • Clear semantics
  • Comprehensive functionality

Remember to check the Go documentation for updates and new features. The package continues to evolve with the language.

Keep exploring and building great networking applications with Go!

Top comments (0)