diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go index d6be0824a..2d71d4d4e 100644 --- a/net/interfaces/interfaces.go +++ b/net/interfaces/interfaces.go @@ -43,7 +43,8 @@ func Tailscale() (net.IP, *net.Interface, error) { // maybeTailscaleInterfaceName reports whether s is an interface // name that might be used by Tailscale. func maybeTailscaleInterfaceName(s string) bool { - return strings.HasPrefix(s, "wg") || + return s == "Tailscale" || + strings.HasPrefix(s, "wg") || strings.HasPrefix(s, "ts") || strings.HasPrefix(s, "tailscale") || strings.HasPrefix(s, "utun") @@ -179,7 +180,8 @@ func (s *State) Equal(s2 *State) bool { // /^tailscale/) func (s *State) RemoveTailscaleInterfaces() { for name := range s.InterfaceIPs { - if strings.HasPrefix(name, "tailscale") { // TODO: use --tun flag value, etc; see TODO in method doc + if name == "Tailscale" || // as it is on Windows + strings.HasPrefix(name, "tailscale") { // TODO: use --tun flag value, etc; see TODO in method doc delete(s.InterfaceIPs, name) delete(s.InterfaceUp, name) } diff --git a/wgengine/monitor/monitor_unsupported.go b/wgengine/monitor/monitor_unsupported.go index 03eee07b6..a54990c02 100644 --- a/wgengine/monitor/monitor_unsupported.go +++ b/wgengine/monitor/monitor_unsupported.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux,!freebsd android +// +build !linux,!freebsd,!windows android package monitor diff --git a/wgengine/monitor/monitor_windows.go b/wgengine/monitor/monitor_windows.go new file mode 100644 index 000000000..35f2756cf --- /dev/null +++ b/wgengine/monitor/monitor_windows.go @@ -0,0 +1,131 @@ +// Copyright (c) 2020 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 monitor + +import ( + "context" + "errors" + "sync" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/windows" + "tailscale.com/types/logger" +) + +var ( + iphlpapi = syscall.NewLazyDLL("iphlpapi.dll") + notifyAddrChangeProc = iphlpapi.NewProc("NotifyAddrChange") + notifyRouteChangeProc = iphlpapi.NewProc("NotifyRouteChange") +) + +type unspecifiedMessage struct{} + +func (unspecifiedMessage) ignore() bool { return false } + +type winMon struct { + ctx context.Context + cancel context.CancelFunc + + logf logger.Logf + + mu sync.Mutex + event windows.Handle +} + +func newOSMon(logf logger.Logf) (osMon, error) { + ctx, cancel := context.WithCancel(context.Background()) + return &winMon{ + logf: logf, + ctx: ctx, + cancel: cancel, + }, nil +} + +func (m *winMon) Close() error { + m.cancel() + + m.mu.Lock() + defer m.mu.Unlock() + if h := m.event; h != 0 { + // Wake up any reader blocked in Receive. + windows.SetEvent(h) + } + + return nil +} + +var errClosed = errors.New("closed") + +func (m *winMon) Receive() (message, error) { + if m.ctx.Err() != nil { + return nil, errClosed + } + + var o windows.Overlapped + h, err := windows.CreateEvent(nil, 1 /* true*/, 0 /* unsignaled */, nil /* no name */) + if err != nil { + m.logf("CreateEvent: %v", err) + return nil, err + } + defer windows.CloseHandle(h) + + m.mu.Lock() + m.event = h + m.mu.Unlock() + + o.HEvent = h + + err = notifyAddrChange(&h, &o) + if err != nil { + m.logf("notifyAddrChange: %v", err) + return nil, err + } + err = notifyRouteChange(&h, &o) + if err != nil { + m.logf("notifyRouteChange: %v", err) + return nil, err + } + + t0 := time.Now() + evt, err := windows.WaitForSingleObject(o.HEvent, windows.INFINITE) + if m.ctx.Err() != nil { + return nil, errClosed + } + if err != nil { + m.logf("notifyRouteChange: %v", err) + return nil, err + } + d := time.Since(t0) + m.logf("got windows change event after %v: %+v", d, evt) + + m.mu.Lock() + m.event = 0 + m.mu.Unlock() + + return unspecifiedMessage{}, nil +} + +func notifyAddrChange(h *windows.Handle, o *windows.Overlapped) error { + return callNotifyProc(notifyAddrChangeProc, h, o) +} + +func notifyRouteChange(h *windows.Handle, o *windows.Overlapped) error { + return callNotifyProc(notifyAddrChangeProc, h, o) +} + +func callNotifyProc(p *syscall.LazyProc, h *windows.Handle, o *windows.Overlapped) error { + r1, _, e1 := p.Call(uintptr(unsafe.Pointer(h)), uintptr(unsafe.Pointer(o))) + expect := uintptr(0) + if h != nil || o != nil { + const ERROR_IO_PENDING = 997 + expect = ERROR_IO_PENDING + } + if r1 == expect { + return nil + } + return e1 +}