wgengine: make NewUserspaceEngine wait for TUN interface to be up on Windows

Updates #474

Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
pull/1302/head
Aleksandar Pesic 4 years ago committed by Brad Fitzpatrick
parent ea3715e3ce
commit daf6de4f14

@ -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
}

@ -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")
}
}
}

@ -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,

Loading…
Cancel
Save