From 22122c45b2dd462b00bba09380cfda9f385d607a Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 10 Dec 2025 17:43:57 +0700 Subject: [PATCH 1/6] Including system metadata when posting to utility API --- cmd/cli/ad_windows.go | 3 +- cmd/cli/ad_windows_test.go | 6 ++- cmd/cli/cli.go | 24 +++++++-- cmd/cli/control_server.go | 7 ++- cmd/cli/dns_proxy.go | 8 ++- cmd/cli/prog.go | 7 ++- go.mod | 11 +++- go.sum | 27 ++++++++-- internal/controld/config.go | 43 ++++++++++------ internal/controld/controld_test.go | 12 ++++- internal/system/chassis_darwin.go | 25 +++++++++ internal/system/chassis_others.go | 18 +++++++ internal/system/metadata.go | 7 +++ internal/system/metadata_others.go | 8 +++ internal/system/metadata_windows.go | 74 +++++++++++++++++++++++++++ metadata.go | 78 +++++++++++++++++++++++++++++ metadata_others.go | 10 ++++ metadata_test.go | 11 ++++ metadata_windows.go | 22 ++++++++ nameservers_windows.go | 71 +++----------------------- 20 files changed, 376 insertions(+), 96 deletions(-) create mode 100644 internal/system/chassis_darwin.go create mode 100644 internal/system/chassis_others.go create mode 100644 internal/system/metadata.go create mode 100644 internal/system/metadata_others.go create mode 100644 internal/system/metadata_windows.go create mode 100644 metadata.go create mode 100644 metadata_others.go create mode 100644 metadata_test.go create mode 100644 metadata_windows.go diff --git a/cmd/cli/ad_windows.go b/cmd/cli/ad_windows.go index 66180a90..c477b4eb 100644 --- a/cmd/cli/ad_windows.go +++ b/cmd/cli/ad_windows.go @@ -10,11 +10,12 @@ import ( hh "github.com/microsoft/wmi/pkg/hardware/host" "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/system" ) // addExtraSplitDnsRule adds split DNS rule for domain if it's part of active directory. func addExtraSplitDnsRule(cfg *ctrld.Config) bool { - domain, err := getActiveDirectoryDomain() + domain, err := system.GetActiveDirectoryDomain() if err != nil { mainLog.Load().Debug().Msgf("unable to get active directory domain: %v", err) return false diff --git a/cmd/cli/ad_windows_test.go b/cmd/cli/ad_windows_test.go index 6abd25f9..c987fe13 100644 --- a/cmd/cli/ad_windows_test.go +++ b/cmd/cli/ad_windows_test.go @@ -5,14 +5,16 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/system" "github.com/Control-D-Inc/ctrld/testhelper" - "github.com/stretchr/testify/assert" ) func Test_getActiveDirectoryDomain(t *testing.T) { start := time.Now() - domain, err := getActiveDirectoryDomain() + domain, err := system.GetActiveDirectoryDomain() if err != nil { t.Fatal(err) } diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 7d70b78b..49d8b674 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -642,13 +642,19 @@ func processCDFlags(cfg *ctrld.Config) (*controld.ResolverConfig, error) { logger.Info().Msgf("fetching Controld D configuration from API: %s", cdUID) bo := backoff.NewBackoff("processCDFlags", logf, 30*time.Second) bo.LogLongerThan = 30 * time.Second + ctx := context.Background() - resolverConfig, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + req := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(ctx), + } + resolverConfig, err := controld.FetchResolverConfig(req, cdDev) for { if errUrlNetworkError(err) { bo.BackOff(ctx, err) logger.Warn().Msg("could not fetch resolver using bootstrap DNS, retrying...") - resolverConfig, err = controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + resolverConfig, err = controld.FetchResolverConfig(req, cdDev) continue } break @@ -1516,7 +1522,12 @@ func cdUIDFromProvToken() string { if customHostname != "" && !validHostname(customHostname) { mainLog.Load().Fatal().Msgf("invalid custom hostname: %q", customHostname) } - req := &controld.UtilityOrgRequest{ProvToken: cdOrg, Hostname: customHostname} + + req := &controld.UtilityOrgRequest{ + ProvToken: cdOrg, + Hostname: customHostname, + Metadata: ctrld.SystemMetadata(context.Background()), + } // Process provision token if provided. resolverConfig, err := controld.FetchResolverUID(req, rootCmd.Version, cdDev) if err != nil { @@ -1857,7 +1868,12 @@ func runningIface(s service.Service) *ifaceResponse { // doValidateCdRemoteConfig fetches and validates custom config for cdUID. func doValidateCdRemoteConfig(cdUID string, fatal bool) error { - rc, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + req := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(context.Background()), + } + rc, err := controld.FetchResolverConfig(req, cdDev) if err != nil { logger := mainLog.Load().Fatal() if !fatal { diff --git a/cmd/cli/control_server.go b/cmd/cli/control_server.go index 9281b904..3db444e2 100644 --- a/cmd/cli/control_server.go +++ b/cmd/cli/control_server.go @@ -217,7 +217,12 @@ func (p *prog) registerControlServerHandler() { } // Re-fetch pin code from API. - if rc, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev); rc != nil { + rcReq := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(context.Background()), + } + if rc, err := controld.FetchResolverConfig(rcReq, cdDev); rc != nil { if rc.DeactivationPin != nil { cdDeactivationPin.Store(*rc.DeactivationPin) } else { diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index 994741b1..ba9d5af5 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -954,7 +954,13 @@ func (p *prog) doSelfUninstall(answer *dns.Msg) { logger := mainLog.Load().With().Str("mode", "self-uninstall").Logger() if p.refusedQueryCount > selfUninstallMaxQueries { p.checkingSelfUninstall = true - _, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + + req := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(context.Background()), + } + _, err := controld.FetchResolverConfig(req, cdDev) logger.Debug().Msg("maximum number of refused queries reached, checking device status") selfUninstallCheck(err, p, logger) diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index cf8c6804..42e2efe0 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -325,7 +325,12 @@ func (p *prog) apiConfigReload() { } doReloadApiConfig := func(forced bool, logger zerolog.Logger) { - resolverConfig, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev) + req := &controld.ResolverConfigRequest{ + RawUID: cdUID, + Version: rootCmd.Version, + Metadata: ctrld.SystemMetadata(context.Background()), + } + resolverConfig, err := controld.FetchResolverConfig(req, cdDev) selfUninstallCheck(err, p, logger) if err != nil { logger.Warn().Err(err).Msg("could not fetch resolver config") diff --git a/go.mod b/go.mod index a171a51c..daf46c30 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24 require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/ameshkov/dnsstamps v1.0.3 + github.com/brunogui0812/sysprofiler v0.5.0 github.com/coreos/go-systemd/v22 v22.5.0 github.com/cuonglm/osinfo v0.0.0-20230921071424-e0e1b1e0bbbf github.com/docker/go-units v0.5.0 @@ -15,6 +16,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.1 github.com/illarion/gonotify/v2 v2.0.3 github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 + github.com/jaypipes/ghw v0.21.0 github.com/jaytaylor/go-hostsfile v0.0.0-20220426042432-61485ac1fa6c github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 github.com/kardianos/service v1.2.1 @@ -29,8 +31,8 @@ require ( github.com/prometheus/prom2json v1.3.3 github.com/quic-go/quic-go v0.56.0 github.com/rs/zerolog v1.28.0 - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.5 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.9.0 github.com/vishvananda/netlink v1.2.1-beta.2 @@ -55,8 +57,10 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/groob/plist v0.0.0-20200425180238-0f631f258c01 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jaypipes/pcidb v1.1.1 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -78,12 +82,14 @@ require ( github.com/quic-go/qpack v0.5.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/spakin/awk v1.0.0 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect github.com/vishvananda/netns v0.0.4 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/crypto v0.41.0 // indirect @@ -94,6 +100,7 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 // indirect ) replace github.com/mr-karan/doggo => github.com/Windscribe/doggo v0.0.0-20220919152748-2c118fc391f8 diff --git a/go.sum b/go.sum index 2f13cf37..1ee8ca82 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1O github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/brunogui0812/sysprofiler v0.5.0 h1:AUekplOKG/VKH6sPSBRxsKOA9Uv5OsI8qolXM73dXPU= +github.com/brunogui0812/sysprofiler v0.5.0/go.mod h1:lLd7gvylgd4nsTSC8exq1YY6qhLWXkgnalxjVzdlbEM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -64,7 +66,7 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cuonglm/osinfo v0.0.0-20230921071424-e0e1b1e0bbbf h1:40DHYsri+d1bnroFDU2FQAeq68f3kAlOzlQ93kCf26Q= github.com/cuonglm/osinfo v0.0.0-20230921071424-e0e1b1e0bbbf/go.mod h1:G45410zMgmnSjLVKCq4f6GpbYAzoP2plX9rPwgx6C24= @@ -91,6 +93,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -165,6 +168,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/groob/plist v0.0.0-20200425180238-0f631f258c01 h1:0T3XGXebqLj7zSVLng9wX9axQzTEnvj/h6eT7iLfUas= +github.com/groob/plist v0.0.0-20200425180238-0f631f258c01/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= @@ -181,8 +186,13 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= +github.com/jaypipes/ghw v0.21.0 h1:ClG2xWtYY0c1ud9jZYwVGdSgfCI7AbmZmZyw3S5HHz8= +github.com/jaypipes/ghw v0.21.0/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE= +github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro= +github.com/jaypipes/pcidb v1.1.1/go.mod h1:x27LT2krrUgjf875KxQXKB0Ha/YXLdZRVmw6hH0G7g8= github.com/jaytaylor/go-hostsfile v0.0.0-20220426042432-61485ac1fa6c h1:kbTQ8oGf+BVFvt/fM+ECI+NbZDCqoi0vtZTfB2p2hrI= github.com/jaytaylor/go-hostsfile v0.0.0-20220426042432-61485ac1fa6c/go.mod h1:k6+89xKz7BSMJ+DzIerBdtpEUeTlBMugO/hcVSzahog= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= @@ -274,16 +284,18 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spakin/awk v1.0.0 h1:5ulBVgJhdN3XoFGNVv/MOHOIUfPVPvMCIlLH6O6ZqU4= +github.com/spakin/awk v1.0.0/go.mod h1:e7FnxcIEcRqdKwStPYWonox4n9DpharWk+3nnn1IqJs= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -314,6 +326,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -439,6 +453,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -661,6 +676,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 h1:eeH1AIcPvSc0Z25ThsYF+Xoqbn0CI/YnXVYoTLFdGQw= +howett.net/plist v1.0.2-0.20250314012144-ee69052608d9/go.mod h1:fyFX5Hj5tP1Mpk8obqA9MZgXT416Q5711SDT7dQLTLk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/controld/config.go b/internal/controld/config.go index 595e758e..b6e7047f 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -71,14 +71,23 @@ func (u ErrorResponse) Error() string { } type utilityRequest struct { - UID string `json:"uid"` - ClientID string `json:"client_id,omitempty"` + UID string `json:"uid"` + ClientID string `json:"client_id,omitempty"` + Metadata map[string]string `json:"metadata"` } // UtilityOrgRequest contains request data for calling Org API. type UtilityOrgRequest struct { - ProvToken string `json:"prov_token"` - Hostname string `json:"hostname"` + ProvToken string `json:"prov_token"` + Hostname string `json:"hostname"` + Metadata map[string]string `json:"metadata"` +} + +// ResolverConfigRequest contains request data for fetching resolver config. +type ResolverConfigRequest struct { + RawUID string + Version string + Metadata map[string]string } // LogsRequest contains request data for sending runtime logs to API. @@ -88,26 +97,30 @@ type LogsRequest struct { } // FetchResolverConfig fetch Control D config for given uid. -func FetchResolverConfig(rawUID, version string, cdDev bool) (*ResolverConfig, error) { - uid, clientID := ParseRawUID(rawUID) - req := utilityRequest{UID: uid} +func FetchResolverConfig(req *ResolverConfigRequest, cdDev bool) (*ResolverConfig, error) { + uid, clientID := ParseRawUID(req.RawUID) + uReq := utilityRequest{ + UID: uid, + Metadata: req.Metadata, + } if clientID != "" { - req.ClientID = clientID + uReq.ClientID = clientID } - body, _ := json.Marshal(req) - return postUtilityAPI(version, cdDev, false, bytes.NewReader(body)) + body, _ := json.Marshal(uReq) + return postUtilityAPI(req.Version, cdDev, false, bytes.NewReader(body)) } -// FetchResolverUID fetch resolver uid from provision token. +// FetchResolverUID fetch resolver uid from a given request. func FetchResolverUID(req *UtilityOrgRequest, version string, cdDev bool) (*ResolverConfig, error) { if req == nil { return nil, errors.New("invalid request") } - hostname := req.Hostname - if hostname == "" { - hostname, _ = os.Hostname() + if req.Hostname == "" { + hostname, _ := os.Hostname() + req.Hostname = hostname } - body, _ := json.Marshal(UtilityOrgRequest{ProvToken: req.ProvToken, Hostname: hostname}) + + body, _ := json.Marshal(req) return postUtilityAPI(version, cdDev, false, bytes.NewReader(body)) } diff --git a/internal/controld/controld_test.go b/internal/controld/controld_test.go index 2c00247d..80762e36 100644 --- a/internal/controld/controld_test.go +++ b/internal/controld/controld_test.go @@ -3,10 +3,13 @@ package controld import ( + "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/Control-D-Inc/ctrld" ) func TestFetchResolverConfig(t *testing.T) { @@ -20,11 +23,18 @@ func TestFetchResolverConfig(t *testing.T) { {"valid dev", "p2", true, false}, {"invalid uid", "abcd1234", false, true}, } + + ctx := context.Background() for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() - got, err := FetchResolverConfig(tc.uid, "dev-test", tc.dev) + req := &ResolverConfigRequest{ + RawUID: tc.uid, + Version: "dev-test", + Metadata: ctrld.SystemMetadata(ctx), + } + got, err := FetchResolverConfig(ctx, req, tc.dev) require.False(t, (err != nil) != tc.wantErr, err) if !tc.wantErr { assert.NotEmpty(t, got.DOH) diff --git a/internal/system/chassis_darwin.go b/internal/system/chassis_darwin.go new file mode 100644 index 00000000..49a7317b --- /dev/null +++ b/internal/system/chassis_darwin.go @@ -0,0 +1,25 @@ +package system + +import ( + "errors" + "fmt" + + "github.com/brunogui0812/sysprofiler" +) + +// GetChassisInfo retrieves hardware information including machine model type and vendor from the system profiler. +func GetChassisInfo() (*ChassisInfo, error) { + hardwares, err := sysprofiler.Hardware() + if err != nil { + return nil, fmt.Errorf("failed to get hardware info: %w", err) + } + if len(hardwares) == 0 { + return nil, errors.New("no hardware info found") + } + hardware := hardwares[0] + info := &ChassisInfo{ + Type: hardware.MachineModel, + Vendor: "Apple Inc.", + } + return info, nil +} diff --git a/internal/system/chassis_others.go b/internal/system/chassis_others.go new file mode 100644 index 00000000..cbfefa50 --- /dev/null +++ b/internal/system/chassis_others.go @@ -0,0 +1,18 @@ +//go:build !darwin + +package system + +import "github.com/jaypipes/ghw" + +// GetChassisInfo retrieves hardware information including machine model type and vendor from the system profiler. +func GetChassisInfo() (*ChassisInfo, error) { + chassis, err := ghw.Chassis() + if err != nil { + return nil, err + } + info := &ChassisInfo{ + Type: chassis.TypeDescription, + Vendor: chassis.Vendor, + } + return info, nil +} diff --git a/internal/system/metadata.go b/internal/system/metadata.go new file mode 100644 index 00000000..cfe02e3c --- /dev/null +++ b/internal/system/metadata.go @@ -0,0 +1,7 @@ +package system + +// ChassisInfo represents the structural framework of a device, specifying its type and manufacturer information. +type ChassisInfo struct { + Type string + Vendor string +} diff --git a/internal/system/metadata_others.go b/internal/system/metadata_others.go new file mode 100644 index 00000000..f20a1508 --- /dev/null +++ b/internal/system/metadata_others.go @@ -0,0 +1,8 @@ +//go:build !windows + +package system + +// GetActiveDirectoryDomain returns AD domain name of this computer. +func GetActiveDirectoryDomain() (string, error) { + return "", nil +} diff --git a/internal/system/metadata_windows.go b/internal/system/metadata_windows.go new file mode 100644 index 00000000..40f137fb --- /dev/null +++ b/internal/system/metadata_windows.go @@ -0,0 +1,74 @@ +package system + +import ( + "errors" + "fmt" + "io" + "log" + "os" + "strings" + "unsafe" + + "github.com/microsoft/wmi/pkg/base/host" + hh "github.com/microsoft/wmi/pkg/hardware/host" + "golang.org/x/sys/windows" +) + +// GetActiveDirectoryDomain returns AD domain name of this computer. +func GetActiveDirectoryDomain() (string, error) { + log.SetOutput(io.Discard) + defer log.SetOutput(os.Stderr) + + // 1) Check environment variable + envDomain := os.Getenv("USERDNSDOMAIN") + if envDomain != "" { + return strings.TrimSpace(envDomain), nil + } + + // 2) Query WMI via the microsoft/wmi library + whost := host.NewWmiLocalHost() + cs, err := hh.GetComputerSystem(whost) + if cs != nil { + defer cs.Close() + } + if err != nil { + return "", err + } + pod, err := cs.GetPropertyPartOfDomain() + if err != nil { + return "", err + } + if pod { + domainVal, err := cs.GetPropertyDomain() + if err != nil { + return "", fmt.Errorf("failed to get domain property: %w", err) + } + domainName := strings.TrimSpace(fmt.Sprintf("%v", domainVal)) + if domainName == "" { + return "", errors.New("machine does not appear to have a domain set") + } + return domainName, nil + } + return "", nil +} + +// DomainJoinedStatus returns the domain joined status of the current computer. +// +// NETSETUP_JOIN_STATUS constants from Microsoft Windows API +// See: https://learn.microsoft.com/en-us/windows/win32/api/lmjoin/ne-lmjoin-netsetup_join_status +// +// NetSetupUnknownStatus uint32 = 0 // The status is unknown +// NetSetupUnjoined uint32 = 1 // The computer is not joined to a domain or workgroup +// NetSetupWorkgroupName uint32 = 2 // The computer is joined to a workgroup +// NetSetupDomainName uint32 = 3 // The computer is joined to a domain +func DomainJoinedStatus() (uint32, error) { + var domain *uint16 + var status uint32 + + if err := windows.NetGetJoinInformation(nil, &domain, &status); err != nil { + return 0, fmt.Errorf("failed to get domain join status: %w", err) + } + defer windows.NetApiBufferFree((*byte)(unsafe.Pointer(domain))) + + return status, nil +} diff --git a/metadata.go b/metadata.go new file mode 100644 index 00000000..11e8fb51 --- /dev/null +++ b/metadata.go @@ -0,0 +1,78 @@ +package ctrld + +import ( + "context" + "os" + "os/user" + + "github.com/cuonglm/osinfo" + + "github.com/Control-D-Inc/ctrld/internal/system" +) + +const ( + metadataOsKey = "os" + metadataChassisTypeKey = "chassis_type" + metadataChassisVendorKey = "chassis_vendor" + metadataUsernameKey = "username" + metadataDomainOrWorkgroupKey = "domain_or_workgroup" + metadataDomainKey = "domain" +) + +var ( + chassisType string + chassisVendor string +) + +// SystemMetadata collects system and user-related SystemMetadata and returns it as a map. +func SystemMetadata(ctx context.Context) map[string]string { + m := make(map[string]string) + oi := osinfo.New() + m[metadataOsKey] = oi.String() + if chassisType == "" && chassisVendor == "" { + if ci, err := system.GetChassisInfo(); err == nil { + chassisType, chassisVendor = ci.Type, ci.Vendor + } + } + m[metadataChassisTypeKey] = chassisType + m[metadataChassisVendorKey] = chassisVendor + m[metadataUsernameKey] = currentLoginUser(ctx) + m[metadataDomainOrWorkgroupKey] = partOfDomainOrWorkgroup(ctx) + domain, err := system.GetActiveDirectoryDomain() + if err != nil { + ProxyLogger.Load().Debug().Err(err).Msg("Failed to get active directory domain name") + } + m[metadataDomainKey] = domain + + return m +} + +// currentLoginUser attempts to find the actual login user, even if the process is running as root. +func currentLoginUser(ctx context.Context) string { + // 1. Check SUDO_USER: This is the most reliable way to find the original user + // when a script is run via 'sudo'. + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + return sudoUser + } + + // 2. Check general user login variables. LOGNAME is often preferred over USER. + if logName := os.Getenv("LOGNAME"); logName != "" { + return logName + } + + // 3. Fallback to USER variable. + if userEnv := os.Getenv("USER"); userEnv != "" { + return userEnv + } + + // 4. Final fallback: Use the standard library function to get the *effective* user. + // This will return "root" if the process is running as root. + currentUser, err := user.Current() + if err != nil { + // Handle error gracefully, returning a placeholder + ProxyLogger.Load().Debug().Err(err).Msg("Failed to get current user") + return "unknown" + } + + return currentUser.Username +} diff --git a/metadata_others.go b/metadata_others.go new file mode 100644 index 00000000..2b060ac3 --- /dev/null +++ b/metadata_others.go @@ -0,0 +1,10 @@ +//go:build !windows + +package ctrld + +import "context" + +// partOfDomainOrWorkgroup checks if the computer is part of a domain or workgroup and returns "true" or "false". +func partOfDomainOrWorkgroup(ctx context.Context) string { + return "false" +} diff --git a/metadata_test.go b/metadata_test.go new file mode 100644 index 00000000..b832c7e8 --- /dev/null +++ b/metadata_test.go @@ -0,0 +1,11 @@ +package ctrld + +import ( + "context" + "testing" +) + +func Test_metadata(t *testing.T) { + m := SystemMetadata(context.Background()) + t.Logf("metadata: %v", m) +} diff --git a/metadata_windows.go b/metadata_windows.go new file mode 100644 index 00000000..e00dfe48 --- /dev/null +++ b/metadata_windows.go @@ -0,0 +1,22 @@ +package ctrld + +import ( + "context" + + "github.com/Control-D-Inc/ctrld/internal/system" +) + +// partOfDomainOrWorkgroup checks if the computer is part of a domain or workgroup and returns "true" or "false". +func partOfDomainOrWorkgroup(ctx context.Context) string { + status, err := system.DomainJoinedStatus() + if err != nil { + ProxyLogger.Load().Debug().Err(err).Msg("Failed to get domain join status") + return "false" + } + switch status { + case 2, 3: + return "true" + default: + return "false" + } +} diff --git a/nameservers_windows.go b/nameservers_windows.go index 7b16e8e1..68173931 100644 --- a/nameservers_windows.go +++ b/nameservers_windows.go @@ -20,6 +20,8 @@ import ( "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "tailscale.com/net/netmon" + + "github.com/Control-D-Inc/ctrld/internal/system" ) const ( @@ -128,7 +130,7 @@ func getDNSServers(ctx context.Context) ([]string, error) { var dcServers []string isDomain := checkDomainJoined() if isDomain { - domainName, err := getLocalADDomain() + domainName, err := system.GetActiveDirectoryDomain() if err != nil { Log(context.Background(), logger.Debug(), "Failed to get local AD domain: %v", err) @@ -337,75 +339,18 @@ func currentNameserversFromResolvconf() []string { func checkDomainJoined() bool { logger := *ProxyLogger.Load() - var domain *uint16 - var status uint32 - - if err := windows.NetGetJoinInformation(nil, &domain, &status); err != nil { - Log(context.Background(), logger.Debug(), "Failed to get domain join status: %v", err) + status, err := system.DomainJoinedStatus() + if err != nil { + logger.Debug().Msgf("Failed to get domain joined status: %v", err) return false } - defer windows.NetApiBufferFree((*byte)(unsafe.Pointer(domain))) - - // NETSETUP_JOIN_STATUS constants from Microsoft Windows API - // See: https://learn.microsoft.com/en-us/windows/win32/api/lmjoin/ne-lmjoin-netsetup_join_status - // - // NetSetupUnknownStatus uint32 = 0 // The status is unknown - // NetSetupUnjoined uint32 = 1 // The computer is not joined to a domain or workgroup - // NetSetupWorkgroupName uint32 = 2 // The computer is joined to a workgroup - // NetSetupDomainName uint32 = 3 // The computer is joined to a domain - // - // We only care about NetSetupDomainName. - domainName := windows.UTF16PtrToString(domain) - Log(context.Background(), logger.Debug(), - "Domain join status: domain=%s status=%d (UnknownStatus=0, Unjoined=1, WorkgroupName=2, DomainName=3)", - domainName, status) - isDomain := status == syscall.NetSetupDomainName - Log(context.Background(), logger.Debug(), "Is domain joined? status=%d, result=%v", status, isDomain) + logger.Debug().Msg("Domain join status: (UnknownStatus=0, Unjoined=1, WorkgroupName=2, DomainName=3)") + logger.Debug().Msgf("Is domain joined? status=%d, result=%v", status, isDomain) return isDomain } -// getLocalADDomain uses Microsoft's WMI wrappers (github.com/microsoft/wmi/pkg/*) -// to query the Domain field from Win32_ComputerSystem instead of a direct go-ole call. -func getLocalADDomain() (string, error) { - log.SetOutput(io.Discard) - defer log.SetOutput(os.Stderr) - // 1) Check environment variable - envDomain := os.Getenv("USERDNSDOMAIN") - if envDomain != "" { - return strings.TrimSpace(envDomain), nil - } - - // 2) Query WMI via the microsoft/wmi library - whost := host.NewWmiLocalHost() - q := query.NewWmiQuery("Win32_ComputerSystem") - instances, err := instance.GetWmiInstancesFromHost(whost, string(constant.CimV2), q) - if instances != nil { - defer instances.Close() - } - if err != nil { - return "", fmt.Errorf("WMI query failed: %v", err) - } - - // If no results, return an error - if len(instances) == 0 { - return "", fmt.Errorf("no rows returned from Win32_ComputerSystem") - } - - // We only care about the first row - domainVal, err := instances[0].GetProperty("Domain") - if err != nil { - return "", fmt.Errorf("machine does not appear to have a domain set: %v", err) - } - - domainName := strings.TrimSpace(fmt.Sprintf("%v", domainVal)) - if domainName == "" { - return "", fmt.Errorf("machine does not appear to have a domain set") - } - return domainName, nil -} - // validInterfaces returns a list of all physical interfaces. // this is a duplicate of what is in net_windows.go, we should // clean this up so there is only one version From 705df721104c8962261316f6202949b0826024dd Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 12 Dec 2025 15:37:41 +0700 Subject: [PATCH 2/6] fix: remove incorrect transport close on DoH3 error Remove the transport Close() call from DoH3 error handling path. The transport is shared and reused across requests, and closing it on error would break subsequent requests. The transport lifecycle is already properly managed by the http.Client and the finalizer set in newDOH3Transport(). --- doh.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/doh.go b/doh.go index 3459cb8a..58aaf165 100644 --- a/doh.go +++ b/doh.go @@ -122,11 +122,6 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro } if err != nil { err = wrapUrlError(err) - if r.isDoH3 { - if closer, ok := c.Transport.(io.Closer); ok { - closer.Close() - } - } return nil, fmt.Errorf("could not perform request: %w", err) } defer resp.Body.Close() From a92e1ca0244e137c859acadbd2d4cb3bfd54163a Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 16 Dec 2025 15:51:40 +0700 Subject: [PATCH 3/6] Upgrade quic-go to v0.57.1 --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index daf46c30..56015734 100644 --- a/go.mod +++ b/go.mod @@ -29,12 +29,12 @@ require ( github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.5.0 github.com/prometheus/prom2json v1.3.3 - github.com/quic-go/quic-go v0.56.0 + github.com/quic-go/quic-go v0.57.1 github.com/rs/zerolog v1.28.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.16.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.11.1 github.com/vishvananda/netlink v1.2.1-beta.2 golang.org/x/net v0.43.0 golang.org/x/sync v0.16.0 @@ -79,7 +79,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/qpack v0.6.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/spakin/awk v1.0.0 // indirect diff --git a/go.sum b/go.sum index 1ee8ca82..266a916a 100644 --- a/go.sum +++ b/go.sum @@ -269,10 +269,10 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcETyaUgo= github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY= -github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= +github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -311,8 +311,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= From 1f9c58644457dd22cb7c517ab31b5d647baf89d9 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 16 Dec 2025 15:40:57 +0700 Subject: [PATCH 4/6] docs: add documentation for runtime internal logging --- docs/runtime-internal-logging.md | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/runtime-internal-logging.md diff --git a/docs/runtime-internal-logging.md b/docs/runtime-internal-logging.md new file mode 100644 index 00000000..982632cb --- /dev/null +++ b/docs/runtime-internal-logging.md @@ -0,0 +1,46 @@ +# Runtime Internal Logging + +When no logging is configured (i.e., `log_path` is not set), ctrld automatically enables an internal logging system. This system stores logs in memory to provide troubleshooting information when problems occur. + +## Purpose + +The runtime internal logging system is designed primarily for **ctrld developers**, not end users. It captures detailed diagnostic information that can be useful for troubleshooting issues when they arise, especially in production environments where explicit logging may not be configured. + +## When It's Enabled + +Internal logging is automatically enabled when: + +- ctrld is running in Control D mode (i.e., `--cd` flag is provided) +- No log file is configured (i.e., `log_path` is empty or not set) + +If a log file is explicitly configured via `log_path`, internal logging will **not** be enabled, as the configured log file serves the logging purpose. + +## How It Works + +The internal logging system: + +- Stores logs in **in-memory buffers** (not written to disk) +- Captures logs at **debug level** for normal operations and **warn level** for warnings +- Maintains separate buffers for normal logs and warning logs +- Automatically manages buffer size to prevent unbounded memory growth +- Preserves initialization logs even when buffers overflow + +## Configuration + +**Important**: The `log_level` configuration option does **not** affect the internal logging system. Internal logging always operates at debug level for normal logs and warn level for warnings, regardless of the `log_level` setting in the configuration file. + +The `log_level` setting only affects: +- Console output (when running interactively) +- File-based logging (when `log_path` is configured) + +## Accessing Internal Logs + +Internal logs can be accessed through the control server API endpoints. This functionality is intended for developers and support personnel who need to diagnose issues. + +## Notes + +- Internal logging is **not** a replacement for proper log file configuration in production environments +- For production deployments, it is recommended to configure `log_path` to enable persistent file-based logging +- Internal logs are stored in memory and will be lost if the process terminates unexpectedly +- The internal logging system is automatically disabled when explicit logging is configured + From 3beffd0dc8701a0d3eba4030dbcb27fe919b0360 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 17 Dec 2025 15:14:25 +0700 Subject: [PATCH 5/6] .github/workflows: temporary use actions/setup-go Since WillAbides/setup-go-faster failed with macOS-latest. See: https://github.com/WillAbides/setup-go-faster/issues/37 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 551241f5..93be810c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 1 - - uses: WillAbides/setup-go-faster@v1.8.0 + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} - run: "go test -race ./..." From 27c5be43c2f263cede210424305de461bd2ccf05 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 8 Jan 2026 22:20:32 +0700 Subject: [PATCH 6/6] fix(system): disable ghw warnings to reduce log noise Disable warnings from ghw library when retrieving chassis information. These warnings are undesirable but recoverable errors that emit unnecessary log messages. Using WithDisableWarnings() suppresses them while maintaining functionality. --- internal/system/chassis_others.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/system/chassis_others.go b/internal/system/chassis_others.go index cbfefa50..49e38aaa 100644 --- a/internal/system/chassis_others.go +++ b/internal/system/chassis_others.go @@ -6,7 +6,9 @@ import "github.com/jaypipes/ghw" // GetChassisInfo retrieves hardware information including machine model type and vendor from the system profiler. func GetChassisInfo() (*ChassisInfo, error) { - chassis, err := ghw.Chassis() + // Disable warnings from ghw, since these are undesirable but recoverable errors. + // With warnings enabled, ghw will emit unnecessary log messages. + chassis, err := ghw.Chassis(ghw.WithDisableWarnings()) if err != nil { return nil, err }