diff --git a/cmd/hepto/hepto.go b/cmd/hepto/hepto.go
index da9991affc8b8c2e50e060b14934551ce4b31d9d..42b9a7555bfae9322ab346f287c9399ad0b47cb5 100644
--- a/cmd/hepto/hepto.go
+++ b/cmd/hepto/hepto.go
@@ -72,7 +72,7 @@ func Hepto() error {
 		})
 	}
 	return c.Run(func() error {
-		config.Node.PublicIP = config.Iface.Addresses[0].Addr().AsSlice()
+		config.Node.PublicIP = config.Iface.Addresses[0].Addr()
 		return manager.Run()
 	})
 }
diff --git a/services/apiserver.go b/services/apiserver.go
index 3f614b791dae69b49262f4c2eac1830aed18c424..00d8b2c597f75968ba4f95215c36a10f5e7552eb 100644
--- a/services/apiserver.go
+++ b/services/apiserver.go
@@ -180,7 +180,7 @@ func buildConfig(c *Cluster) (config *server.Config, clients *k8s.Clients, err e
 		Cert:     cert,
 		ClientCA: clientCA, // not setup upstream, might be an issue
 	}
-	config.PublicAddress = c.networking.NodeAddress.IP
+	config.PublicAddress = c.networking.NodeAddress.Addr().AsSlice()
 
 	// Setup loopback clients (no authorization at this point, handled later)
 	clients, err = k8s.NewTokenClients(c.masterUrl, c.pki.TLS, c.loopbackToken)
@@ -328,8 +328,11 @@ func buildApiConfig(c *Cluster, config server.Config, clients *k8s.Clients) (*co
 				ReadOnlyPort:          ports.KubeletReadOnlyPort,
 				PreferredAddressTypes: []string{string(core.NodeInternalIP), string(core.NodeExternalIP)},
 			},
-			ServiceIPRange:           *c.networking.ServiceNet,
-			APIServerServiceIP:       c.networking.APIAddress,
+			ServiceIPRange: net.IPNet{
+				IP:   c.networking.ServiceNet.Addr().AsSlice(),
+				Mask: net.CIDRMask(c.networking.ServiceNet.Bits(), c.networking.ServiceNet.Addr().BitLen()),
+			},
+			APIServerServiceIP:       c.networking.APIAddress.AsSlice(),
 			APIServerServicePort:     443,
 			ServiceNodePortRange:     utilnet.PortRange{Base: 30000, Size: 2768},
 			EndpointReconcilerType:   reconcilers.LeaseEndpointReconcilerType,
diff --git a/services/certs.go b/services/certs.go
index 0f189f1a969ed8b4ec2af86b8ea340ccde6d8804..4850f527762c04f7d3c92d43d93520c56625dd24 100644
--- a/services/certs.go
+++ b/services/certs.go
@@ -121,7 +121,7 @@ var pkiMaster = &Unit{
 		tlsCert, err := bundle.GetCertOrCSR("tls",
 			pekahi.NewServerTemplate(
 				[]string{"kube-apiserver", "kubernetes.default", "apiserver-loopback-client"},
-				[]net.IP{c.networking.NodeAddress.IP, c.networking.APIAddress, net.ParseIP("::1")},
+				[]net.IP{c.networking.NodeAddress.Addr().AsSlice(), c.networking.APIAddress.AsSlice(), net.ParseIP("::1")},
 			),
 		)
 		if err != nil {
@@ -146,7 +146,7 @@ var pkiMaster = &Unit{
 		}
 		// Controller manager certificate
 		controllersTLSCert, err := bundle.GetCertOrCSR("controllers-tls",
-			pekahi.NewServerTemplate([]string{"controllers"}, []net.IP{c.networking.NodeAddress.IP}),
+			pekahi.NewServerTemplate([]string{"controllers"}, []net.IP{c.networking.NodeAddress.Addr().AsSlice()}),
 		)
 		if err != nil {
 			return err
diff --git a/services/discovery.go b/services/discovery.go
index 4e9b3c3bff73fa322b70e54e9fd5d82c7c1f133b..861c1a8aa86a5e3f1ec185d80dac793fa1d038f4 100644
--- a/services/discovery.go
+++ b/services/discovery.go
@@ -13,7 +13,7 @@ var memberlist = &Unit{
 	Dependencies: []*Unit{},
 	Run: func(u *Unit, c *Cluster, ctx context.Context) error {
 		ml := sml.New[HeptoMeta, HeptoState](
-			c.thisNode.Name, c.thisNode.PublicIP,
+			c.thisNode.Name, c.thisNode.PublicIP.AsSlice(),
 			c.settings.DiscoveryPort, c.settings.Anchors, c.settings.Key, c.settings.Logger,
 		)
 		ml.Meta = c.thisNode
@@ -36,7 +36,7 @@ var memberlist = &Unit{
 					if node.Role == "master" || node.Role == "full" {
 						u.Manager.Logger.Info("found remote master", "name", node.Name)
 						c.masterNode = node
-						c.masterUrl = fmt.Sprintf("https://[%s]:%d", node.VpnIP.String(), apiserverPort)
+						c.masterUrl = fmt.Sprintf("https://[%s]:%d", node.VpnIP.Addr().String(), apiserverPort)
 						u.Markready()
 					}
 				}
diff --git a/services/kubelet.go b/services/kubelet.go
index 1f0c6681a8853b4977fc5edb9926aa11f5e241d7..8362fae7410172113d0171187c644c4c4d28d246 100644
--- a/services/kubelet.go
+++ b/services/kubelet.go
@@ -156,30 +156,30 @@ var kubeKubelet = &Unit{
 			&config.ContainerRuntimeOptions{
 				PodSandboxImage: "registry.k8s.io/pause:3.9",
 			},
-			c.thisNode.Name,                       // Hostname
-			false,                                 // Hostname overridden
-			types.NodeName(c.thisNode.Name),       // Node name
-			[]net.IP{c.networking.NodeAddress.IP}, // IP addresses
-			c.thisNode.Name,                       // Provider ID (unused)
-			"",                                    // Cloud provider
-			path.Join(kubeletRoot, "pki"),         // PKI path
-			kubeletRoot,                           // Root directory
-			"",                                    // Image creds config file
-			"",                                    // Image creds bin path
-			true,                                  // Register node
-			[]core.Taint{},                        // Taints
-			[]string{},                            // Unsafe sysctl
-			"",                                    // Mounter path
-			false,                                 // Kernel memcg notifications
-			false,                                 // Allocatable ignore eviction threshold
-			meta.Duration{Duration: 0},            // Max gc duration
-			1,                                     // Max per pod container count
-			-1,                                    // Max container count
-			true,                                  // Register schedulable
-			false,                                 // Keep terminated pod volumes
-			map[string]string{},                   // Node labels
-			-1,                                    // Node status max images
-			false,                                 // Seccomp default
+			c.thisNode.Name,                 // Hostname
+			false,                           // Hostname overridden
+			types.NodeName(c.thisNode.Name), // Node name
+			[]net.IP{c.networking.NodeAddress.Addr().AsSlice()}, // IP addresses
+			c.thisNode.Name,               // Provider ID (unused)
+			"",                            // Cloud provider
+			path.Join(kubeletRoot, "pki"), // PKI path
+			kubeletRoot,                   // Root directory
+			"",                            // Image creds config file
+			"",                            // Image creds bin path
+			true,                          // Register node
+			[]core.Taint{},                // Taints
+			[]string{},                    // Unsafe sysctl
+			"",                            // Mounter path
+			false,                         // Kernel memcg notifications
+			false,                         // Allocatable ignore eviction threshold
+			meta.Duration{Duration: 0},    // Max gc duration
+			1,                             // Max per pod container count
+			-1,                            // Max container count
+			true,                          // Register schedulable
+			false,                         // Keep terminated pod volumes
+			map[string]string{},           // Node labels
+			-1,                            // Node status max images
+			false,                         // Seccomp default
 		)
 		if err != nil {
 			return fmt.Errorf("could not instantiate kubelet: %w", err)
diff --git a/services/manager.go b/services/manager.go
index dbcefe7b5e7dd94dbade97fd97ac0aeb8d992406..9dad1cae7eb77d609765670c837ef24d9a97cacf 100644
--- a/services/manager.go
+++ b/services/manager.go
@@ -3,7 +3,6 @@
 package services
 
 import (
-	"net"
 	"net/netip"
 
 	"github.com/containerd/containerd/services/server"
@@ -38,12 +37,12 @@ type ClusterSettings struct {
 }
 
 type ClusterNetworking struct {
-	NodeNet     *net.IPNet
-	NodeAddress *net.IPNet
-	ServiceNet  *net.IPNet
-	PodNet      *net.IPNet
-	APIAddress  net.IP
-	DNSAddress  net.IP
+	NodeNet     netip.Prefix
+	NodeAddress netip.Prefix
+	ServiceNet  netip.Prefix
+	PodNet      netip.Prefix
+	APIAddress  netip.Addr
+	DNSAddress  netip.Addr
 	MTU         int
 }
 
@@ -74,7 +73,7 @@ type Unit = daeman.Unit[*Cluster]
 
 func NewManager(settings *ClusterSettings, node *HeptoMeta, logger logr.Logger) *daeman.Manager[*Cluster] {
 	networking := NewClusterNetworking(settings.Name, node.Name)
-	node.VpnIP = networking.NodeAddress.IP
+	node.VpnIP = networking.NodeAddress
 	cluster := &Cluster{
 		settings:      settings,
 		thisNode:      node,
@@ -116,9 +115,8 @@ func NewClusterNetworking(clusterName string, nodeName string) *ClusterNetworkin
 	podNet := utils.ULA(clusterName, 56, 2)
 	// Services are hosted on a /112 at :3, e.g. fd00:0:0:0:0:0:0::/112
 	serviceNet := utils.ULA(clusterName, 112, 3)
-	// API address is the first service address
-	apiAddress, _ := netip.AddrFromSlice(serviceNet.IP)
-	apiIP := net.IP(apiAddress.Next().AsSlice())
-	dnsIP := net.IP(apiAddress.Next().Next().AsSlice())
+	// API address is the first service address, dns is the next one
+	apiIP := serviceNet.Addr().Next()
+	dnsIP := apiIP.Next()
 	return &ClusterNetworking{nodeNet, nodeAddress, serviceNet, podNet, apiIP, dnsIP, 1500}
 }
diff --git a/services/meta.go b/services/meta.go
index 104425e0751fc2fba51ab5ebcf916086e52d560d..79836fd141a45bb53ce013c075cd2abf4bee6f41 100644
--- a/services/meta.go
+++ b/services/meta.go
@@ -2,7 +2,7 @@ package services
 
 import (
 	"encoding/json"
-	"net"
+	"net/netip"
 
 	"go.acides.org/pekahi"
 )
@@ -12,9 +12,9 @@ type HeptoMeta struct {
 	// Rencoding of node name
 	Name string `json:"name"`
 	// Public address of the node
-	PublicIP net.IP `json:"ip"`
+	PublicIP netip.Addr `json:"ip"`
 	// Address of the node over the VPN
-	VpnIP net.IP `json:"vpnIP"`
+	VpnIP netip.Prefix `json:"vpnIP"`
 	// Public key for the wireguard mesh VPN
 	VpnKey string `json:"vpnKey"`
 	// Node role inside the cluster
@@ -26,16 +26,16 @@ func (m *HeptoMeta) Hostname() string {
 	return m.Name
 }
 
-func (m *HeptoMeta) IP() net.IP {
+func (m *HeptoMeta) IP() netip.Addr {
 	return m.PublicIP
 }
 
-func (m *HeptoMeta) OverlayIP() net.IP {
+func (m *HeptoMeta) OverlayIP() netip.Prefix {
 	return m.VpnIP
 }
 
-func (m *HeptoMeta) Routes() []net.IPNet {
-	return []net.IPNet{}
+func (m *HeptoMeta) Routes() []netip.Prefix {
+	return []netip.Prefix{}
 }
 
 func (m *HeptoMeta) Key() string {
diff --git a/utils/net.go b/utils/net.go
index 926276bcf8ac8de69a8d44b94469923c725fc263..c2deb5eb7b4935b79136116849e4f320f79f1bc6 100644
--- a/utils/net.go
+++ b/utils/net.go
@@ -3,31 +3,27 @@ package utils
 import (
 	"crypto/sha256"
 	"encoding/binary"
-	"net"
+	"net/netip"
 )
 
 // Derive an address inside a pool network, based on a name
-func DeriveAddress(pool *net.IPNet, name string) *net.IPNet {
+func DeriveAddress(pool netip.Prefix, name string) netip.Prefix {
 	// Compute a hash from the node name
 	h := sha256.New()
 	h.Write([]byte(name))
 	hb := h.Sum(nil)
 
 	// Copy the hash to the address part of the IPNet
-	ones, _ := pool.Mask.Size()
-	bytes := ones / 8
-	ip := make(net.IP, len(pool.IP))
-	copy(ip, pool.IP)
+	bytes := pool.Bits() / 8
+	ip := pool.Addr().AsSlice()
 	copy(ip[bytes:], hb[:16-bytes])
-	return &net.IPNet{
-		IP:   ip,
-		Mask: pool.Mask,
-	}
+	addr, _ := netip.AddrFromSlice(ip)
+	return netip.PrefixFrom(addr, pool.Bits())
 }
 
 // Derive a fc::/7 network including a pseudo-random global id
 // generated from the network name and the provided local id
-func ULA(name string, length int, id uint16) *net.IPNet {
+func ULA(name string, length int, id uint16) netip.Prefix {
 	ip := make([]byte, 16)
 
 	// Compute a hash for the network name
@@ -44,8 +40,6 @@ func ULA(name string, length int, id uint16) *net.IPNet {
 	// global ID
 	binary.LittleEndian.PutUint16(ip[6:], id)
 
-	return &net.IPNet{
-		IP:   ip,
-		Mask: net.CIDRMask(length, 128),
-	}
+	addr, _ := netip.AddrFromSlice(ip)
+	return netip.PrefixFrom(addr, length)
 }
diff --git a/wg/peer.go b/wg/peer.go
index 908c8456dbb4e6abeb21384de5aa59ee0f6346db..67500239eb4057736a960c58206af7d00be7b01b 100644
--- a/wg/peer.go
+++ b/wg/peer.go
@@ -2,6 +2,7 @@ package wg
 
 import (
 	"net"
+	"net/netip"
 
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
@@ -9,28 +10,35 @@ import (
 type Peer interface {
 	Hostname() string
 	Key() string
-	IP() net.IP
-	OverlayIP() net.IP
-	Routes() []net.IPNet
+	IP() netip.Addr
+	OverlayIP() netip.Prefix
+	Routes() []netip.Prefix
 }
 
 func (w *Wireguard) peerToWgConfig(p Peer) (wgtypes.PeerConfig, error) {
-	bits := 8 * len(p.OverlayIP())
 	pubKey, err := wgtypes.ParseKey(p.Key())
 	if err != nil {
 		return wgtypes.PeerConfig{}, err
 	}
+	overlay := p.OverlayIP()
+	allowed := []net.IPNet{{
+		IP:   overlay.Addr().AsSlice(),
+		Mask: net.CIDRMask(overlay.Bits(), overlay.Addr().BitLen()),
+	}}
+	for _, route := range p.Routes() {
+		allowed = append(allowed, net.IPNet{
+			IP:   route.Addr().AsSlice(),
+			Mask: net.CIDRMask(route.Bits(), route.Addr().BitLen()),
+		})
+	}
 	return wgtypes.PeerConfig{
 		PublicKey:         pubKey,
 		ReplaceAllowedIPs: true,
 		Endpoint: &net.UDPAddr{
-			IP:   p.IP(),
+			IP:   p.IP().AsSlice(),
 			Port: w.port,
 		},
-		AllowedIPs: append(p.Routes(), net.IPNet{
-			IP:   p.OverlayIP(),
-			Mask: net.CIDRMask(bits, bits),
-		}),
+		AllowedIPs:                  allowed,
 		PersistentKeepaliveInterval: &w.keepalive,
 	}, nil
 }
diff --git a/wg/wireguard.go b/wg/wireguard.go
index 915d38d9c4b9142179e5e3e81b11927e7b54f0a5..bc0292803446d3df4cd5c6240a6eb2057eb83176 100644
--- a/wg/wireguard.go
+++ b/wg/wireguard.go
@@ -3,6 +3,7 @@ package wg
 import (
 	"fmt"
 	"net"
+	"net/netip"
 	"os"
 	"strings"
 	"time"
@@ -18,7 +19,7 @@ import (
 // Wireguard interface config
 type Wireguard struct {
 	iface     string
-	ipnet     *net.IPNet
+	ipnet     netip.Prefix
 	logger    logr.Logger
 	client    *wgctrl.Client
 	port      int
@@ -27,7 +28,7 @@ type Wireguard struct {
 	PubKey    wgtypes.Key
 }
 
-func New(iface string, port int, ipnet *net.IPNet, logger logr.Logger) (*Wireguard, error) {
+func New(iface string, port int, ipnet netip.Prefix, logger logr.Logger) (*Wireguard, error) {
 	client, err := wgctrl.New()
 	if err != nil {
 		return nil, errors.Wrap(err, "could not instantiate wireguard client")
@@ -88,7 +89,10 @@ func (w *Wireguard) Update(peers []Peer, mtu int) error {
 	}
 	w.logger.Info("adding VPN address", "address", w.ipnet.String())
 	err = netlink.AddrAdd(link, &netlink.Addr{
-		IPNet: w.ipnet,
+		IPNet: &net.IPNet{
+			IP:   w.ipnet.Addr().AsSlice(),
+			Mask: net.CIDRMask(w.ipnet.Bits(), w.ipnet.Addr().BitLen()),
+		},
 	})
 	if err != nil && !os.IsExist(err) {
 		return fmt.Errorf("could not setup wg address: %w", err)
@@ -102,15 +106,12 @@ func (w *Wireguard) Update(peers []Peer, mtu int) error {
 		return fmt.Errorf("could not enable wg iface: %w", err)
 	}
 	for _, peer := range configs {
-		overlay := net.IPNet{
-			IP:   w.ipnet.IP.Mask(w.ipnet.Mask),
-			Mask: w.ipnet.Mask,
-		}
-		var gw net.IP
 		for _, route := range peer.AllowedIPs {
+			var gw net.IP
 			w.logger.Info("new route", "dest", route.String())
 			scope := netlink.SCOPE_UNIVERSE
-			if overlay.Contains(route.IP) {
+			routeAddr, _ := netip.AddrFromSlice(route.IP)
+			if w.ipnet.Contains(routeAddr) {
 				gw = route.IP
 				scope = netlink.SCOPE_LINK
 			}