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) } } }
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())) }
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 }
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 }
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 }
Best Practices
- 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 }
- 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()) }
- Use Contains Efficiently
func isInRange(networks []netip.Prefix, addr netip.Addr) bool { for _, net := range networks { if net.Contains(addr) { return true } } return false }
Performance Tips
- 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 }
- 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") { // ... }
- 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]) } }
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)