mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-10-12 01:58:46 +02:00
feat(cli): put the network state of the host in the log journal
The journal records the network state of the host and each change of that state. The support team reads one file to see the interfaces, the routes, the resolvers, and the recoveries around an incident. The same file shows the health of the query path. A change that touches only AirDrop or virtual interfaces runs no DNS work.
This commit is contained in:
1 parent
8934e09428
commit
8ae09ac98e
68 files changed
+10458
-442
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -40,3 +41,143 @@ func validInterfacesPowershell() []string {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func Test_adapterClass(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
adapter adapterInfo
|
||||
want string
|
||||
}{
|
||||
{"virtual without a connector", adapterInfo{Virtual: true}, "virtual"},
|
||||
{"virtual with a connector", adapterInfo{Virtual: true, ConnectorPresent: true}, "hardware"},
|
||||
{"physical adapter", adapterInfo{ConnectorPresent: true, Hardware: true}, "hardware"},
|
||||
{"unknown adapter", adapterInfo{}, "hardware"},
|
||||
{"tap driver", adapterInfo{Description: "TAP-Windows Adapter V9", Virtual: true}, "tunnel"},
|
||||
{"wintun driver", adapterInfo{Description: "Wintun Userspace Tunnel", Virtual: true}, "tunnel"},
|
||||
{"wireguard driver", adapterInfo{Description: "WireGuard Tunnel #1", Virtual: true}, "tunnel"},
|
||||
{"vpn description", adapterInfo{Description: "Cisco AnyConnect VPN Adapter", ConnectorPresent: true}, "tunnel"},
|
||||
{"lower case description", adapterInfo{Description: "openvpn tap-windows6"}, "tunnel"},
|
||||
{"ethernet adapter", adapterInfo{Description: "Intel(R) Ethernet Connection I219-V", ConnectorPresent: true, Hardware: true}, "hardware"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := adapterClass(tc.adapter); got != tc.want {
|
||||
t.Errorf("adapterClass(%+v) = %q, want %q", tc.adapter, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// stubAdapters replaces the WMI and clock seams of the adapter cache, and
|
||||
// restores them with the cache itself.
|
||||
func stubAdapters(t *testing.T, read func() []adapterInfo, now func() time.Time) {
|
||||
t.Helper()
|
||||
readBefore, nowBefore := readAdaptersFn, adapterCacheNowFn
|
||||
adapterCacheMu.Lock()
|
||||
cacheBefore, readAtBefore := adapterCache, adapterCacheReadAt
|
||||
adapterCache, adapterCacheReadAt = nil, time.Time{}
|
||||
adapterCacheMu.Unlock()
|
||||
t.Cleanup(func() {
|
||||
readAdaptersFn, adapterCacheNowFn = readBefore, nowBefore
|
||||
adapterCacheMu.Lock()
|
||||
adapterCache, adapterCacheReadAt = cacheBefore, readAtBefore
|
||||
adapterCacheMu.Unlock()
|
||||
})
|
||||
readAdaptersFn, adapterCacheNowFn = read, now
|
||||
}
|
||||
|
||||
func Test_adapterCache(t *testing.T) {
|
||||
now := time.Unix(1700000000, 0)
|
||||
reads := 0
|
||||
stubAdapters(t, func() []adapterInfo {
|
||||
reads++
|
||||
return []adapterInfo{
|
||||
{Name: "Ethernet", Description: "Intel I219-V", ConnectorPresent: true, Hardware: true},
|
||||
{Name: "Hyper-V Switch", Description: "Hyper-V Virtual Switch", Virtual: true},
|
||||
}
|
||||
}, func() time.Time { return now })
|
||||
|
||||
if got := validInterfaces(); !slices.Equal(got, []string{"Ethernet"}) {
|
||||
t.Fatalf("validInterfaces = %v, want [Ethernet]", got)
|
||||
}
|
||||
now = now.Add(59 * time.Second)
|
||||
validInterfacesMap()
|
||||
if reads != 1 {
|
||||
t.Fatalf("reads within the cache window = %d, want 1", reads)
|
||||
}
|
||||
now = now.Add(2 * time.Second)
|
||||
adapters()
|
||||
if reads != 2 {
|
||||
t.Fatalf("reads after the cache window = %d, want 2", reads)
|
||||
}
|
||||
refreshInterfaceMeta()
|
||||
if reads != 2 {
|
||||
t.Fatalf("reads for a refresh alone = %d, want 2", reads)
|
||||
}
|
||||
adapters()
|
||||
if reads != 3 {
|
||||
t.Fatalf("reads after a refresh = %d, want 3", reads)
|
||||
}
|
||||
|
||||
virtual := platformVirtualInterfaces()
|
||||
if _, ok := virtual["Hyper-V Switch"]; !ok || len(virtual) != 1 {
|
||||
t.Fatalf("platformVirtualInterfaces = %v, want the virtual switch only", virtual)
|
||||
}
|
||||
class, hardwarePort, service := platformInterfaceMeta("Ethernet")
|
||||
if class != "hardware" || hardwarePort != "Intel I219-V" || service != "" {
|
||||
t.Fatalf("platformInterfaceMeta = %q, %q, %q, want hardware, Intel I219-V, empty", class, hardwarePort, service)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapterCacheReadsOnceForTwoCallers(t *testing.T) {
|
||||
now := time.Unix(1700000000, 0)
|
||||
var mu sync.Mutex
|
||||
reads := 0
|
||||
stubAdapters(t, func() []adapterInfo {
|
||||
mu.Lock()
|
||||
reads++
|
||||
mu.Unlock()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
return []adapterInfo{{Name: "Ethernet", Description: "Intel I219-V", ConnectorPresent: true, Hardware: true}}
|
||||
}, func() time.Time { return now })
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range 4 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
adapters()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if reads != 1 {
|
||||
t.Fatalf("queries for four callers = %d, want 1", reads)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapterCacheKeepsTheRemovedAdapter(t *testing.T) {
|
||||
now := time.Unix(1700000000, 0)
|
||||
removed := false
|
||||
stubAdapters(t, func() []adapterInfo {
|
||||
read := []adapterInfo{{Name: "Ethernet", Description: "Intel I219-V", ConnectorPresent: true, Hardware: true}}
|
||||
if removed {
|
||||
return read
|
||||
}
|
||||
return append(read, adapterInfo{Name: "WireGuard", Description: "WireGuard Tunnel #1", Virtual: true})
|
||||
}, func() time.Time { return now })
|
||||
|
||||
adapters()
|
||||
removed = true
|
||||
refreshInterfaceMeta()
|
||||
|
||||
class, hardwarePort, _ := platformInterfaceMeta("WireGuard")
|
||||
if class != "tunnel" || hardwarePort != "WireGuard Tunnel #1" {
|
||||
t.Fatalf("meta of the removed adapter = %q, %q, want tunnel, WireGuard Tunnel #1", class, hardwarePort)
|
||||
}
|
||||
if class, _, _ := platformInterfaceMeta("Absent"); class != "" {
|
||||
t.Fatalf("meta of an adapter that no read named = %q, want empty", class)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user