44 "context"
55 "fmt"
66 "net"
7+ "net/netip"
78 "os"
89 "os/exec"
910 "strings"
@@ -22,8 +23,6 @@ import (
2223 "github.com/nais/device/pkg/pb"
2324)
2425
25- const wireguardMTU = 1360
26-
2726type DarwinConfigurator struct {
2827 helperConfig Config
2928
@@ -46,7 +45,7 @@ func (c *DarwinConfigurator) Prerequisites() error {
4645}
4746
4847func (c * DarwinConfigurator ) SyncConf (ctx context.Context , cfg * pb.Configuration ) error {
49- return wgconfig .ApplyConfig (c .helperConfig .Interface , cfg )
48+ return wgconfig .ApplyConfig (ctx , c .helperConfig .Interface , cfg )
5049}
5150
5251func (c * DarwinConfigurator ) SetupRoutes (ctx context.Context , gateways []* pb.Gateway ) (int , error ) {
@@ -61,17 +60,20 @@ func (c *DarwinConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Gat
6160 if strings .HasPrefix (cidr , TunnelNetworkPrefix ) {
6261 continue
6362 }
64- if err := addRouteViaInterface (cidr , iface , false ); err != nil {
63+ if err := addRouteViaInterface (cidr , iface ); err != nil {
6564 return routesAdded , fmt .Errorf ("add IPv4 route %s: %w" , cidr , err )
6665 }
6766 routesAdded ++
6867 }
6968
7069 for _ , cidr := range gw .GetRoutesIPv6 () {
70+ // TunnelNetworkPrefix is an IPv4 prefix ("10.255.24.") so this check
71+ // is a no-op for IPv6 CIDRs. It's kept for consistency with the IPv4
72+ // loop and as a safeguard in case the prefix changes in the future.
7173 if strings .HasPrefix (cidr , TunnelNetworkPrefix ) {
7274 continue
7375 }
74- if err := addRouteViaInterface (cidr , iface , true ); err != nil {
76+ if err := addRouteViaInterface (cidr , iface ); err != nil {
7577 return routesAdded , fmt .Errorf ("add IPv6 route %s: %w" , cidr , err )
7678 }
7779 routesAdded ++
@@ -106,6 +108,11 @@ func (c *DarwinConfigurator) SetupInterface(ctx context.Context, cfg *pb.Configu
106108 }
107109 wgDev := device .NewDevice (tunDev , conn .NewDefaultBind (), logger )
108110
111+ if err := wgDev .Up (); err != nil {
112+ wgDev .Close ()
113+ return fmt .Errorf ("bring up wireguard device: %w" , err )
114+ }
115+
109116 // UAPI socket allows wgctrl to configure the device
110117 fileUAPI , err := ipc .UAPIOpen (ifaceName )
111118 if err != nil {
@@ -120,21 +127,15 @@ func (c *DarwinConfigurator) SetupInterface(ctx context.Context, cfg *pb.Configu
120127 return fmt .Errorf ("listen on UAPI socket: %w" , err )
121128 }
122129
123- go func () {
124- for {
125- uapiConn , err := uapi .Accept ()
126- if err != nil {
127- return
128- }
129- go wgDev .IpcHandle (uapiConn )
130- }
131- }()
132-
133130 c .wgDevice = wgDev
134131 c .tunDev = tunDev
135132 c .uapi = uapi
136133
137- // Configure IP addresses and bring the interface up
134+ // Configure IP addresses and bring the interface up.
135+ // We shell out to ifconfig because macOS has no stable public API for assigning
136+ // addresses to utun interfaces — the required SIOCAIFADDR_IN6 / SIOCSIFADDR
137+ // ioctls are undocumented and vary across OS versions. ifconfig is the standard
138+ // tool used by the macOS networking stack itself.
138139 commands := [][]string {
139140 {"ifconfig" , ifaceName , "inet" , cfg .GetDeviceIPv4 () + "/21" , cfg .GetDeviceIPv4 (), "alias" },
140141 {"ifconfig" , ifaceName , "inet6" , cfg .GetDeviceIPv6 () + "/64" , "alias" },
@@ -149,7 +150,9 @@ func (c *DarwinConfigurator) SetupInterface(ctx context.Context, cfg *pb.Configu
149150 return fmt .Errorf ("running %v: %w: %v" , cmd , err , string (out ))
150151 }
151152
152- time .Sleep (100 * time .Millisecond ) // avoid serializable race conditions with kernel
153+ // Small delay between ifconfig calls to avoid kernel ENOMEM / EBUSY
154+ // errors when rapidly configuring addresses on a freshly-created utun.
155+ time .Sleep (100 * time .Millisecond )
153156 }
154157
155158 // Add /21 route for the tunnel network
@@ -159,11 +162,23 @@ func (c *DarwinConfigurator) SetupInterface(ctx context.Context, cfg *pb.Configu
159162 return fmt .Errorf ("lookup interface %q: %w" , ifaceName , err )
160163 }
161164
162- if err := addRouteViaInterface (cfg .GetDeviceIPv4 ()+ "/21" , iface , false ); err != nil {
165+ if err := addRouteViaInterface (cfg .GetDeviceIPv4 ()+ "/21" , iface ); err != nil {
163166 c .closeLocked ()
164167 return fmt .Errorf ("add tunnel network route: %w" , err )
165168 }
166169
170+ // Start accepting UAPI connections only after all initialization has succeeded.
171+ // This ensures wgctrl can't race against incomplete interface setup.
172+ go func () {
173+ for {
174+ uapiConn , err := uapi .Accept ()
175+ if err != nil {
176+ return
177+ }
178+ go wgDev .IpcHandle (uapiConn )
179+ }
180+ }()
181+
167182 return nil
168183}
169184
@@ -196,8 +211,8 @@ func (c *DarwinConfigurator) closeLocked() {
196211
197212// addRouteViaInterface adds a route for the given CIDR through the specified
198213// network interface using BSD routing sockets.
199- func addRouteViaInterface (cidr string , iface * net.Interface , ipv6 bool ) error {
200- _ , ipNet , err := net . ParseCIDR (cidr )
214+ func addRouteViaInterface (cidr string , iface * net.Interface ) error {
215+ prefix , err := netip . ParsePrefix (cidr )
201216 if err != nil {
202217 return fmt .Errorf ("parse CIDR %q: %w" , cidr , err )
203218 }
@@ -210,21 +225,25 @@ func addRouteViaInterface(cidr string, iface *net.Interface, ipv6 bool) error {
210225
211226 addrs := make ([]route.Addr , syscall .RTAX_MAX )
212227
213- if ipv6 {
214- var dst [16 ]byte
215- copy (dst [:], ipNet .IP .To16 ())
228+ if prefix .Addr ().Is6 () {
229+ dst := prefix .Addr ().As16 ()
216230 addrs [syscall .RTAX_DST ] = & route.Inet6Addr {IP : dst }
217231
218232 var mask [16 ]byte
219- copy (mask [:], ipNet .Mask )
233+ ones := prefix .Bits ()
234+ for i := range ones {
235+ mask [i / 8 ] |= 1 << (7 - i % 8 )
236+ }
220237 addrs [syscall .RTAX_NETMASK ] = & route.Inet6Addr {IP : mask }
221238 } else {
222- var dst [4 ]byte
223- copy (dst [:], ipNet .IP .To4 ())
239+ dst := prefix .Addr ().As4 ()
224240 addrs [syscall .RTAX_DST ] = & route.Inet4Addr {IP : dst }
225241
226242 var mask [4 ]byte
227- copy (mask [:], ipNet .Mask )
243+ ones := prefix .Bits ()
244+ for i := range ones {
245+ mask [i / 8 ] |= 1 << (7 - i % 8 )
246+ }
228247 addrs [syscall .RTAX_NETMASK ] = & route.Inet4Addr {IP : mask }
229248 }
230249
0 commit comments