From daf6de4f14cc898bb59932e927e946fd8b9ca4bf Mon Sep 17 00:00:00 2001 From: Aleksandar Pesic Date: Tue, 23 Feb 2021 04:29:54 +0100 Subject: [PATCH] wgengine: make NewUserspaceEngine wait for TUN interface to be up on Windows Updates #474 Signed-off-by: Aleksandar Pesic --- wgengine/ifstatus_noop.go | 19 ++++++ wgengine/ifstatus_windows.go | 111 +++++++++++++++++++++++++++++++++++ wgengine/userspace.go | 4 ++ 3 files changed, 134 insertions(+) create mode 100644 wgengine/ifstatus_noop.go create mode 100644 wgengine/ifstatus_windows.go diff --git a/wgengine/ifstatus_noop.go b/wgengine/ifstatus_noop.go new file mode 100644 index 000000000..7564d67ec --- /dev/null +++ b/wgengine/ifstatus_noop.go @@ -0,0 +1,19 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !windows + +package wgengine + +import ( + "time" + + "github.com/tailscale/wireguard-go/tun" + "tailscale.com/types/logger" +) + +// Dummy implementation that does nothing. +func waitInterfaceUp(iface tun.Device, timeout time.Duration, logf logger.Logf) error { + return nil +} diff --git a/wgengine/ifstatus_windows.go b/wgengine/ifstatus_windows.go new file mode 100644 index 000000000..840b6cf39 --- /dev/null +++ b/wgengine/ifstatus_windows.go @@ -0,0 +1,111 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package wgengine + +import ( + "fmt" + "sync" + "time" + + "github.com/tailscale/wireguard-go/tun" + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" + "tailscale.com/types/logger" +) + +// ifaceWatcher waits for an interface to be up. +type ifaceWatcher struct { + logf logger.Logf + luid winipcfg.LUID + + mu sync.Mutex // guards following + done bool + sig chan bool +} + +// callback is the callback we register with Windows to call when IP interface changes. +func (iw *ifaceWatcher) callback(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) { + // Probably should check only when MibParameterNotification, but just in case included MibAddInstance also. + if notificationType == winipcfg.MibParameterNotification || notificationType == winipcfg.MibAddInstance { + // Out of paranoia, start a goroutine to finish our work, to return to Windows out of this callback. + go iw.isUp() + } +} + +func (iw *ifaceWatcher) isUp() bool { + iw.mu.Lock() + defer iw.mu.Unlock() + + if iw.done { + // We already know that it's up + return true + } + + if iw.getOperStatus() != winipcfg.IfOperStatusUp { + return false + } + + iw.done = true + iw.sig <- true + return true +} + +func (iw *ifaceWatcher) getOperStatus() winipcfg.IfOperStatus { + ifc, err := iw.luid.Interface() + if err != nil { + iw.logf("iw.luid.Interface error: %v", err) + return 0 + } + return ifc.OperStatus +} + +func waitInterfaceUp(iface tun.Device, timeout time.Duration, logf logger.Logf) error { + iw := &ifaceWatcher{ + luid: winipcfg.LUID(iface.(*tun.NativeTun).LUID()), + logf: logger.WithPrefix(logf, "waitInterfaceUp: "), + } + + // Just in case check the status first + if iw.getOperStatus() == winipcfg.IfOperStatusUp { + iw.logf("TUN interface already up; no need to wait") + return nil + } + + iw.sig = make(chan bool, 1) + cb, err := winipcfg.RegisterInterfaceChangeCallback(iw.callback) + if err != nil { + iw.logf("RegisterInterfaceChangeCallback error: %v", err) + return err + } + defer cb.Unregister() + + t0 := time.Now() + expires := t0.Add(timeout) + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + + for { + iw.logf("waiting for TUN interface to come up...") + + select { + case <-iw.sig: + iw.logf("TUN interface is up after %v", time.Since(t0)) + return nil + case <-ticker.C: + break + } + + if iw.isUp() { + // Very unlikely to happen - either NotifyIpInterfaceChange doesn't work + // or it came up in the same moment as tick. Indicate this in the log message. + iw.logf("TUN interface is up after %v (on poll, without notification)", time.Since(t0)) + return nil + } + + if expires.Before(time.Now()) { + iw.logf("timeout waiting %v for TUN interface to come up", timeout) + return fmt.Errorf("timeout waiting for TUN interface to come up") + } + } +} diff --git a/wgengine/userspace.go b/wgengine/userspace.go index bcb4025c7..952abfc09 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -187,6 +187,10 @@ func NewUserspaceEngine(logf logger.Logf, tunName string, listenPort uint16) (En } logf("CreateTUN ok.") + if err := waitInterfaceUp(tun, 90*time.Second, logf); err != nil { + return nil, err + } + conf := EngineConfig{ Logf: logf, TUN: tun,