tailscale/net/tstun/ifstatus_windows.go

111 lines
2.8 KiB
Go

// 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 tstun
import (
"fmt"
"sync"
"time"
"golang.zx2c4.com/wireguard/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:
}
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")
}
}
}