Skip to content

Commit b01561f

Browse files
committed
fix: address code review findings from embedded wireguard-go PR
- Add explicit wgDevice.Up() call on darwin and windows (bug: device stayed in down state, preventing traffic flow) - Move UAPI accept goroutine after all init succeeds to prevent races - Fix Windows TOCTOU race: inline LUID lookup under lock in SetupRoutes - Use errors.Is(windows.ERROR_OBJECT_ALREADY_EXISTS) instead of string matching - Add context.Context to wgconfig.ApplyConfig for future cancellation - Remove dead BuildServerConfig and its tests - Migrate darwin addRouteViaInterface to netip.ParsePrefix, auto-detect IPv6 - Deduplicate wireguardMTU constant into shared util.go - Inline replacePeers variable, fix %%v to %%w error wrapping - Add explanatory comments for ifconfig shelling, time.Sleep, IPv6 filter - Replace empty NSIS _StartService_Init with PP_NoOp
1 parent 518c0e4 commit b01561f

8 files changed

Lines changed: 88 additions & 139 deletions

File tree

assets/windows/naisdevice.nsi

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,6 @@ Function _StartService_Abort
349349
FunctionEnd
350350

351351
; Function to initialize any needed state, PP_NoOp to skip
352-
Function _StartService_Init
353-
FunctionEnd
354352

355353
; Function called on every step. Should push 0 to the stack to leave the page, any other value to continue (required)
356354
Function _StartService_Step
@@ -382,6 +380,6 @@ ${ProgressPage} \
382380
The final step is to start the background service.$\n$\n\
383381
Have a nais day!" \
384382
_StartService_Abort \
385-
_StartService_Init \
383+
PP_NoOp \
386384
_StartService_Step \
387385
PP_NoOp

internal/helper/helper.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (dhs *DeviceHelperServer) Teardown(
5252
dhs.log.WithField("interface", dhs.config.Interface).Info("removing network interface and all routes")
5353
err := dhs.osConfigurator.TeardownInterface(ctx)
5454
if err != nil {
55-
return nil, fmt.Errorf("tearing down interface: %v", err)
55+
return nil, fmt.Errorf("tearing down interface: %w", err)
5656
}
5757

5858
return &pb.TeardownResponse{}, nil

internal/helper/helper_darwin.go

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
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-
2726
type DarwinConfigurator struct {
2827
helperConfig Config
2928

@@ -46,7 +45,7 @@ func (c *DarwinConfigurator) Prerequisites() error {
4645
}
4746

4847
func (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

5251
func (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

internal/helper/helper_linux.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313
"github.com/nais/device/pkg/pb"
1414
)
1515

16-
const wireguardMTU = 1360
17-
1816
func New(helperConfig Config) *LinuxConfigurator {
1917
return &LinuxConfigurator{
2018
helperConfig: helperConfig,
@@ -32,7 +30,7 @@ func (c *LinuxConfigurator) Prerequisites() error {
3230
}
3331

3432
func (c *LinuxConfigurator) SyncConf(ctx context.Context, cfg *pb.Configuration) error {
35-
return wgconfig.ApplyConfig(c.helperConfig.Interface, cfg)
33+
return wgconfig.ApplyConfig(ctx, c.helperConfig.Interface, cfg)
3634
}
3735

3836
func (c *LinuxConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Gateway) (int, error) {

internal/helper/helper_windows.go

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package helper
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"net"
78
"net/netip"
89
"os"
910
"strings"
1011
"sync"
1112

13+
"golang.org/x/sys/windows"
1214
"golang.zx2c4.com/wireguard/conn"
1315
"golang.zx2c4.com/wireguard/device"
1416
"golang.zx2c4.com/wireguard/ipc"
@@ -19,8 +21,6 @@ import (
1921
"github.com/nais/device/pkg/pb"
2022
)
2123

22-
const wireguardMTU = 1360
23-
2424
type WindowsConfigurator struct {
2525
helperConfig Config
2626

@@ -43,15 +43,23 @@ func (c *WindowsConfigurator) Prerequisites() error {
4343
}
4444

4545
func (c *WindowsConfigurator) SyncConf(ctx context.Context, cfg *pb.Configuration) error {
46-
return wgconfig.ApplyConfig(c.helperConfig.Interface, cfg)
46+
return wgconfig.ApplyConfig(ctx, c.helperConfig.Interface, cfg)
4747
}
4848

4949
func (c *WindowsConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Gateway) (int, error) {
50-
ifLUID, err := c.interfaceLUID()
51-
if err != nil {
52-
return 0, err
50+
c.mu.Lock()
51+
defer c.mu.Unlock()
52+
53+
if c.tunDev == nil {
54+
return 0, fmt.Errorf("TUN device not initialized")
5355
}
5456

57+
nativeTun, ok := c.tunDev.(*tun.NativeTun)
58+
if !ok {
59+
return 0, fmt.Errorf("unexpected TUN device type %T", c.tunDev)
60+
}
61+
ifLUID := winipcfg.LUID(nativeTun.LUID())
62+
5563
routesAdded := 0
5664
for _, gw := range gateways {
5765
for _, cidr := range append(gw.GetRoutesIPv4(), gw.GetRoutesIPv6()...) {
@@ -72,7 +80,7 @@ func (c *WindowsConfigurator) SetupRoutes(ctx context.Context, gateways []*pb.Ga
7280
}
7381

7482
if err := ifLUID.AddRoute(dst, nextHop, 0); err != nil {
75-
if strings.Contains(err.Error(), "already exists") {
83+
if errors.Is(err, windows.ERROR_OBJECT_ALREADY_EXISTS) {
7684
continue
7785
}
7886
return routesAdded, fmt.Errorf("add route %s: %w", cidr, err)
@@ -103,22 +111,17 @@ func (c *WindowsConfigurator) SetupInterface(ctx context.Context, cfg *pb.Config
103111
}
104112
wgDev := device.NewDevice(tunDev, conn.NewDefaultBind(), logger)
105113

114+
if err := wgDev.Up(); err != nil {
115+
wgDev.Close()
116+
return fmt.Errorf("bring up wireguard device: %w", err)
117+
}
118+
106119
uapi, err := ipc.UAPIListen(c.helperConfig.Interface)
107120
if err != nil {
108121
wgDev.Close()
109122
return fmt.Errorf("listen on UAPI named pipe: %w", err)
110123
}
111124

112-
go func() {
113-
for {
114-
uapiConn, err := uapi.Accept()
115-
if err != nil {
116-
return
117-
}
118-
go wgDev.IpcHandle(uapiConn)
119-
}
120-
}()
121-
122125
c.wgDevice = wgDev
123126
c.tunDev = tunDev
124127
c.uapi = uapi
@@ -154,6 +157,18 @@ func (c *WindowsConfigurator) SetupInterface(ctx context.Context, cfg *pb.Config
154157
}
155158
}
156159

160+
// Start accepting UAPI connections only after all initialization has succeeded.
161+
// This ensures wgctrl can't race against incomplete interface setup.
162+
go func() {
163+
for {
164+
uapiConn, err := uapi.Accept()
165+
if err != nil {
166+
return
167+
}
168+
go wgDev.IpcHandle(uapiConn)
169+
}
170+
}()
171+
157172
return nil
158173
}
159174

@@ -182,18 +197,3 @@ func (c *WindowsConfigurator) closeLocked() {
182197
}
183198
c.tunDev = nil
184199
}
185-
186-
func (c *WindowsConfigurator) interfaceLUID() (winipcfg.LUID, error) {
187-
c.mu.Lock()
188-
defer c.mu.Unlock()
189-
190-
if c.tunDev == nil {
191-
return 0, fmt.Errorf("TUN device not initialized")
192-
}
193-
194-
nativeTun, ok := c.tunDev.(*tun.NativeTun)
195-
if !ok {
196-
return 0, fmt.Errorf("unexpected TUN device type %T", c.tunDev)
197-
}
198-
return winipcfg.LUID(nativeTun.LUID()), nil
199-
}

internal/helper/util.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import (
1414

1515
const (
1616
TunnelNetworkPrefix = "10.255.24."
17+
18+
// wireguardMTU is the MTU used for WireGuard tunnel interfaces across all platforms.
19+
wireguardMTU = 1360
1720
)
1821

1922
func ZipLogFiles(files []string, log logrus.FieldLogger) (string, error) {
@@ -33,7 +36,7 @@ func ZipLogFiles(files []string, log logrus.FieldLogger) (string, error) {
3336
}
3437
logFile, err := os.Open(filename)
3538
if err != nil {
36-
return "nil", fmt.Errorf("%s %v", filename, err)
39+
return "nil", fmt.Errorf("%s: %w", filename, err)
3740
}
3841
zipEntryWiter, err := zipWriter.Create(filepath.Base(filename))
3942
if err != nil {

0 commit comments

Comments
 (0)