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)