Files
ctrld/cmd/cli/net_windows.go
T

219 lines
6.8 KiB
Go

package cli
import (
"io"
"log"
"net"
"os"
"strings"
"sync"
"time"
"github.com/microsoft/wmi/pkg/base/host"
"github.com/microsoft/wmi/pkg/base/instance"
"github.com/microsoft/wmi/pkg/base/query"
"github.com/microsoft/wmi/pkg/constant"
"github.com/microsoft/wmi/pkg/hardware/network/netadapter"
)
// adapterInfo holds what one WMI query knows about a network adapter.
type adapterInfo struct {
Name string
Description string
Virtual bool
ConnectorPresent bool
Hardware bool
}
// Every network change reads the adapter set, so one query serves a window.
const adapterCacheTTL = time.Minute
var (
adapterCacheMu sync.Mutex
adapterCache []adapterInfo
// The set before the last read still names the adapter that went away,
// so a removed adapter keeps its class in the journal.
adapterCachePrevious []adapterInfo
adapterCacheReadAt time.Time
// Tests replace these seams to avoid a WMI query.
readAdaptersFn = readAdapters
adapterCacheNowFn = time.Now
)
func patchNetIfaceName(iface *net.Interface) (bool, error) {
return true, nil
}
// validInterface reports whether the *net.Interface is a valid one.
// On Windows, only physical interfaces are considered valid.
func validInterface(iface *net.Interface, validIfacesMap map[string]struct{}) bool {
_, ok := validIfacesMap[iface.Name]
return ok
}
// adapters returns the adapter set of the host, from the cache while it is
// younger than the window. The query runs under the mutex, so two callers
// start one query.
func adapters() []adapterInfo {
adapterCacheMu.Lock()
defer adapterCacheMu.Unlock()
now := adapterCacheNowFn()
if !adapterCacheReadAt.IsZero() && now.Sub(adapterCacheReadAt) < adapterCacheTTL {
return adapterCache
}
read := readAdaptersFn()
// A failing query waits for the window too, so it starts no query storm.
adapterCacheReadAt = now
if read == nil {
// Keep the last good read, so a failed query loses no adapter.
return adapterCache
}
adapterCachePrevious, adapterCache = adapterCache, read
return read
}
// refreshInterfaceMeta drops the read window. The cache still describes the
// adapter that appeared or went away, and the next lookup reads the set.
func refreshInterfaceMeta() {
adapterCacheMu.Lock()
defer adapterCacheMu.Unlock()
adapterCacheReadAt = time.Time{}
}
// tunnelAdapterWords name the VPN drivers of Windows. The description is the
// only signal, because WMI describes a tunnel adapter as an Ethernet adapter.
var tunnelAdapterWords = []string{"tap", "wintun", "wireguard", "vpn"}
// adapterClass names the kind of an adapter. WMI marks a software adapter
// virtual and gives it no connector.
func adapterClass(adapter adapterInfo) string {
switch {
case tunnelAdapter(adapter.Description):
return "tunnel"
case adapter.Virtual && !adapter.ConnectorPresent:
return "virtual"
}
return "hardware"
}
// tunnelAdapter reports a description that names a VPN driver.
func tunnelAdapter(description string) bool {
lower := strings.ToLower(description)
for _, word := range tunnelAdapterWords {
if strings.Contains(lower, word) {
return true
}
}
return false
}
// platformInterfaceMeta returns the Windows class and adapter description of
// an interface. Windows has no service name.
func platformInterfaceMeta(name string) (class, hardwarePort, service string) {
adapter, known := adapterByName(name)
if !known {
return "", "", ""
}
return adapterClass(adapter), adapter.Description, ""
}
// adapterByName finds an adapter in the current set, or in the set before the
// last read. An adapter that went away keeps its names that way.
func adapterByName(name string) (adapterInfo, bool) {
if adapter, known := findAdapter(adapters(), name); known {
return adapter, true
}
adapterCacheMu.Lock()
defer adapterCacheMu.Unlock()
return findAdapter(adapterCachePrevious, name)
}
func findAdapter(set []adapterInfo, name string) (adapterInfo, bool) {
for _, adapter := range set {
if adapter.Name == name {
return adapter, true
}
}
return adapterInfo{}, false
}
// platformVirtualInterfaces returns the adapters that carry no traffic of the
// host network.
func platformVirtualInterfaces() map[string]struct{} {
virtual := make(map[string]struct{})
for _, adapter := range adapters() {
if adapterClass(adapter) != "virtual" {
continue
}
virtual[adapter.Name] = struct{}{}
}
return virtual
}
// readAdapters runs one WMI query for every network adapter. It returns nil
// when the query fails.
func readAdapters() []adapterInfo {
log.SetOutput(io.Discard)
defer log.SetOutput(os.Stderr)
whost := host.NewWmiLocalHost()
q := query.NewWmiQuery("MSFT_NetAdapter")
instances, err := instance.GetWmiInstancesFromHost(whost, string(constant.StadardCimV2), q)
if instances != nil {
defer instances.Close()
}
if err != nil {
mainLog.Load().Warn().Err(err).Msg("failed to get wmi network adapter")
return nil
}
var read []adapterInfo
for _, i := range instances {
adapter, err := netadapter.NewNetworkAdapter(i)
if err != nil {
mainLog.Load().Warn().Err(err).Msg("failed to get network adapter")
continue
}
info, ok := readAdapterInfo(adapter)
if !ok {
continue
}
read = append(read, info)
}
return read
}
// readAdapterInfo reads the properties of one adapter. It reports false when a
// property that decides validity is unreadable.
func readAdapterInfo(adapter *netadapter.NetworkAdapter) (adapterInfo, bool) {
name, err := adapter.GetPropertyName()
if err != nil {
mainLog.Load().Warn().Err(err).Msg("failed to get interface name")
return adapterInfo{}, false
}
// From: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/hh968170(v=vs.85)
//
// "Indicates if a connector is present on the network adapter. This value is set to TRUE
// if this is a physical adapter or FALSE if this is not a physical adapter."
connectorPresent, err := adapter.GetPropertyConnectorPresent()
if err != nil {
mainLog.Load().Debug().Str("method", "readAdapterInfo").Str("interface", name).Msg("failed to get network adapter connector present property")
return adapterInfo{}, false
}
// Check if it's a hardware interface. Checking only for connector present is not enough
// because some interfaces are not physical but have a connector.
hardware, err := adapter.GetPropertyHardwareInterface()
if err != nil {
mainLog.Load().Debug().Str("method", "readAdapterInfo").Str("interface", name).Msg("failed to get network adapter hardware interface property")
return adapterInfo{}, false
}
info := adapterInfo{Name: name, ConnectorPresent: connectorPresent, Hardware: hardware}
// The class and the description describe the adapter only, so an
// unreadable one costs no validity.
info.Description, _ = adapter.GetPropertyInterfaceDescription()
info.Virtual, _ = adapter.GetPropertyVirtual()
return info, true
}