Scope B (Windows agent): - probe.go split into shared core + probe_linux.go (verbatim Linux sampler, build-tagged) + probe_windows.go (Win32 sampler: GetSystemTimes CPU diff, GlobalMemoryStatusEx, fixed-drive df totals, GetIfTable2 counted set, MachineGuid boot identity, registry facts; load and TCP/UDP counts stay zero - x/sys v0.47.0 exports no wrapper) - update intents are acknowledged and skipped on Windows (the release assets are Linux binaries; Windows nodes update via install-agent.ps1) - cmd/xui-agent-windows: SCM service host (svc.Run + mgr install/ uninstall/start/stop) mirroring main.go's agent lifecycle wiring - install-agent.ps1: downloads the exe, writes probeOnly agent.json, registers and starts the service, rerun-safe Scope C (version display): - config.GetAgentDisplayVersion (and the master getter) compose from the bare number so a V-form injection no longer doubles the letter (AV2.1.0 -> A2.1.0) - node list view repairs stored AV-prefixed reports at assembly so deployed agents read uniformly without an update Scope D (production fix): - share-link reality sni: when the inbound has a sing-box fragment, sni is the fragment's tls.server_name - the only name the sing-box REALITY server serves. The previous rule picked a uniformly random serverNames entry per generation, which xray tolerated but sing-box refuses as an invalid connection. sid keeps its random pick: the converter carries the whole shortIds list into the fragment, so every listed sid is served.
100 lines
3.0 KiB
Go
100 lines
3.0 KiB
Go
//go:build windows
|
|
|
|
package agent
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// The Windows sampler's pure helpers: the Win32 calls themselves need
|
|
// a real machine, but the accounting math layered on their results is
|
|
// pinned here.
|
|
|
|
func TestFiletime100nsAndCPUSampleDiff(t *testing.T) {
|
|
lo := windows.Filetime{LowDateTime: 0x00000005, HighDateTime: 0x00000002}
|
|
if got := filetime100ns(lo); got != 0x200000005 {
|
|
t.Fatalf("filetime100ns = %#x, want 0x200000005", got)
|
|
}
|
|
// The shared differ over Windows-style counters (kernel includes
|
|
// idle): idle advanced by the full window -> 0% busy; then a
|
|
// window where only a quarter of the time is idle -> 75%.
|
|
c := &probeCollector{}
|
|
first := &cpuSample{total: 1000, idle: 400}
|
|
if got := c.cpuPercent(first); got != 0 {
|
|
t.Fatalf("first sample = %v, want 0 (baseline only)", got)
|
|
}
|
|
idleAll := &cpuSample{total: 2000, idle: 1400}
|
|
if got := c.cpuPercent(idleAll); got != 0 {
|
|
t.Fatalf("all-idle window = %v, want 0", got)
|
|
}
|
|
busy := &cpuSample{total: 3000, idle: 1650}
|
|
if got := c.cpuPercent(busy); got != 75 {
|
|
t.Fatalf("busy window = %v, want 75", got)
|
|
}
|
|
}
|
|
|
|
func TestUsedFromTotalAvail(t *testing.T) {
|
|
if got := usedFromTotalAvail(100, 30); got != 70 {
|
|
t.Fatalf("used = %d, want 70", got)
|
|
}
|
|
if got := usedFromTotalAvail(100, 100); got != 0 {
|
|
t.Fatalf("used = %d, want 0", got)
|
|
}
|
|
if got := usedFromTotalAvail(100, 130); got != 0 {
|
|
t.Fatalf("used saturates, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestSumDriveSpaceDfSemantics(t *testing.T) {
|
|
total, used := sumDriveSpace([]driveSpace{
|
|
{total: 1000, free: 250},
|
|
{total: 2000, free: 2000},
|
|
{total: 500, free: 900}, // inconsistent source clamps to full
|
|
})
|
|
if total != 3500 || used != 1250 {
|
|
t.Fatalf("sumDriveSpace = %d/%d, want 3500/1250", total, used)
|
|
}
|
|
}
|
|
|
|
func TestSplitDriveStrings(t *testing.T) {
|
|
mk := func(ss ...string) []uint16 {
|
|
var out []uint16
|
|
for _, s := range ss {
|
|
for _, r := range s {
|
|
out = append(out, uint16(r))
|
|
}
|
|
out = append(out, 0)
|
|
}
|
|
return append(out, 0)
|
|
}
|
|
got := splitDriveStrings(mk(`C:\`, `D:\`))
|
|
if len(got) != 2 || got[0] != `C:\` || got[1] != `D:\` {
|
|
t.Fatalf("splitDriveStrings = %q, want [C:\\ D:\\]", got)
|
|
}
|
|
if got := splitDriveStrings(mk()); len(got) != 0 {
|
|
t.Fatalf("empty buffer = %q, want none", got)
|
|
}
|
|
}
|
|
|
|
func TestWinIfaceCountsRule(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
row winIfaceRow
|
|
want bool
|
|
}{
|
|
{"up ethernet", winIfaceRow{ifType: windows.IF_TYPE_ETHERNET_CSMACD, operUp: true}, true},
|
|
{"up wifi", winIfaceRow{ifType: windows.IF_TYPE_IEEE80211, operUp: true}, true},
|
|
{"down ethernet", winIfaceRow{ifType: windows.IF_TYPE_ETHERNET_CSMACD, operUp: false}, false},
|
|
{"loopback", winIfaceRow{ifType: windows.IF_TYPE_SOFTWARE_LOOPBACK, operUp: true}, false},
|
|
{"tunnel", winIfaceRow{ifType: 131, operUp: true}, false},
|
|
{"ppp", winIfaceRow{ifType: 23, operUp: true}, false},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := winIfaceCounts(tc.row); got != tc.want {
|
|
t.Errorf("%s: winIfaceCounts = %v, want %v", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|