diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bd4d27..b4b44d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: ["windows-latest", "ubuntu-latest", "macOS-latest"] - go: ["1.24.x"] + go: ["1.23.x"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 @@ -21,6 +21,6 @@ jobs: - run: "go test -race ./..." - uses: dominikh/staticcheck-action@v1.3.1 with: - version: "2025.1" + version: "2024.1.1" install-go: false cache-key: ${{ matrix.go }} diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 1984f70..b99c48f 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -178,15 +178,7 @@ func RunMobile(appConfig *AppConfig, appCallback *AppCallback, stopCh chan struc noConfigStart = false homedir = appConfig.HomeDir verbose = appConfig.Verbose - if appConfig.ProvisionID != "" { - cdOrg = appConfig.ProvisionID - } - if appConfig.CustomHostname != "" { - customHostname = appConfig.CustomHostname - } - if appConfig.CdUID != "" { - cdUID = appConfig.CdUID - } + cdUID = appConfig.CdUID cdUpstreamProto = appConfig.UpstreamProto logPath = appConfig.LogPath run(appCallback, stopCh) diff --git a/cmd/cli/commands.go b/cmd/cli/commands.go index a1074f2..048212a 100644 --- a/cmd/cli/commands.go +++ b/cmd/cli/commands.go @@ -13,7 +13,6 @@ import ( "os/exec" "path/filepath" "runtime" - "slices" "sort" "strconv" "strings" @@ -189,7 +188,6 @@ func initRunCmd() *cobra.Command { runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) _ = runCmd.Flags().MarkHidden("iface") runCmd.Flags().StringVarP(&cdUpstreamProto, "proto", "", ctrld.ResolverTypeDOH, `Control D upstream type, either "doh" or "doh3"`) - runCmd.Flags().BoolVarP(&rfc1918, "rfc1918", "", false, "Listen on RFC1918 addresses when 127.0.0.1 is the only listener") runCmd.FParseErrWhitelist = cobra.FParseErrWhitelist{UnknownFlags: true} rootCmd.AddCommand(runCmd) @@ -208,7 +206,6 @@ func initStartCmd() *cobra.Command { NOTE: running "ctrld start" without any arguments will start already installed ctrld service.`, Args: func(cmd *cobra.Command, args []string) error { - args = filterEmptyStrings(args) if len(args) > 0 { return fmt.Errorf("'ctrld start' doesn't accept positional arguments\n" + "Use flags instead (e.g. --cd, --iface) or see 'ctrld start --help' for all options") @@ -222,7 +219,6 @@ NOTE: running "ctrld start" without any arguments will start already installed c sc := &service.Config{} *sc = *svcConfig osArgs := os.Args[2:] - osArgs = filterEmptyStrings(osArgs) if os.Args[1] == "service" { osArgs = os.Args[3:] } @@ -532,7 +528,6 @@ NOTE: running "ctrld start" without any arguments will start already installed c startCmd.Flags().BoolVarP(&skipSelfChecks, "skip_self_checks", "", false, `Skip self checks after installing ctrld service`) startCmd.Flags().BoolVarP(&startOnly, "start_only", "", false, "Do not install new service") _ = startCmd.Flags().MarkHidden("start_only") - startCmd.Flags().BoolVarP(&rfc1918, "rfc1918", "", false, "Listen on RFC1918 addresses when 127.0.0.1 is the only listener") routerCmd := &cobra.Command{ Use: "setup", @@ -571,7 +566,6 @@ NOTE: running "ctrld start" without any arguments will start already installed c NOTE: running "ctrld start" without any arguments will start already installed ctrld service.`, Args: func(cmd *cobra.Command, args []string) error { - args = filterEmptyStrings(args) if len(args) > 0 { return fmt.Errorf("'ctrld start' doesn't accept positional arguments\n" + "Use flags instead (e.g. --cd, --iface) or see 'ctrld start --help' for all options") @@ -1387,11 +1381,3 @@ func initServicesCmd(commands ...*cobra.Command) *cobra.Command { return serviceCmd } - -// filterEmptyStrings removes empty strings from a slice of strings. -// It returns a new slice containing only non-empty strings. -func filterEmptyStrings(slice []string) []string { - return slices.DeleteFunc(slice, func(s string) bool { - return s == "" - }) -} diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index 994741b..33012fa 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -84,7 +84,13 @@ type upstreamForResult struct { srcAddr string } -func (p *prog) serveDNS(listenerNum string) error { +func (p *prog) serveDNS(mainCtx context.Context, listenerNum string) error { + // Start network monitoring + if err := p.monitorNetworkChanges(mainCtx); err != nil { + mainLog.Load().Error().Err(err).Msg("Failed to start network monitoring") + // Don't return here as we still want DNS service to run + } + listenerConfig := p.cfg.Listener[listenerNum] // make sure ip is allocated if allocErr := p.allocateIP(listenerConfig.IP); allocErr != nil { @@ -207,8 +213,8 @@ func (p *prog) serveDNS(listenerNum string) error { return nil }) } - // When we spawn a listener on 127.0.0.1, also spawn listeners on the RFC1918 addresses of the machine - // if explicitly set via setting rfc1918 flag, so ctrld could receive queries from LAN clients. + // When we spawn a listener on 127.0.0.1, also spawn listeners on the RFC1918 + // addresses of the machine. So ctrld could receive queries from LAN clients. if needRFC1918Listeners(listenerConfig) { g.Go(func() error { for _, addr := range ctrld.Rfc1918Addresses() { @@ -1039,7 +1045,7 @@ func (p *prog) queryFromSelf(ip string) bool { // needRFC1918Listeners reports whether ctrld need to spawn listener for RFC 1918 addresses. // This is helpful for non-desktop platforms to receive queries from LAN clients. func needRFC1918Listeners(lc *ctrld.ListenerConfig) bool { - return rfc1918 && lc.IP == "127.0.0.1" && lc.Port == 53 + return lc.IP == "127.0.0.1" && lc.Port == 53 && !ctrld.IsDesktopPlatform() } // ipFromARPA parses a FQDN arpa domain and return the IP address if valid. @@ -1181,7 +1187,7 @@ func FlushDNSCache() error { } // monitorNetworkChanges starts monitoring for network interface changes -func (p *prog) monitorNetworkChanges() error { +func (p *prog) monitorNetworkChanges(ctx context.Context) error { mon, err := netmon.New(func(format string, args ...any) { // Always fetch the latest logger (and inject the prefix) mainLog.Load().Printf("netmon: "+format, args...) @@ -1400,6 +1406,9 @@ func (p *prog) checkUpstreamOnce(upstream string, uc *ctrld.UpstreamConfig) erro return err } + msg := new(dns.Msg) + msg.SetQuestion(".", dns.TypeNS) + timeout := 1000 * time.Millisecond if uc.Timeout > 0 { timeout = time.Millisecond * time.Duration(uc.Timeout) @@ -1413,7 +1422,6 @@ func (p *prog) checkUpstreamOnce(upstream string, uc *ctrld.UpstreamConfig) erro mainLog.Load().Debug().Msgf("Rebootstrapping resolver for upstream: %s", upstream) start := time.Now() - msg := uc.VerifyMsg() _, err = resolver.Resolve(ctx, msg) duration := time.Since(start) diff --git a/cmd/cli/library.go b/cmd/cli/library.go index 7847dd7..3c1db1b 100644 --- a/cmd/cli/library.go +++ b/cmd/cli/library.go @@ -18,13 +18,11 @@ type AppCallback struct { // AppConfig allows overwriting ctrld cli flags from mobile platforms. type AppConfig struct { - CdUID string - ProvisionID string - CustomHostname string - HomeDir string - UpstreamProto string - Verbose int - LogPath string + CdUID string + HomeDir string + UpstreamProto string + Verbose int + LogPath string } const ( diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 0783975..6a8cb62 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -39,7 +39,6 @@ var ( skipSelfChecks bool cleanup bool startOnly bool - rfc1918 bool mainLog atomic.Pointer[zerolog.Logger] consoleWriter zerolog.ConsoleWriter diff --git a/net_darwin_test.go b/cmd/cli/net_darwin_test.go similarity index 99% rename from net_darwin_test.go rename to cmd/cli/net_darwin_test.go index 8f9734f..9ef1906 100644 --- a/net_darwin_test.go +++ b/cmd/cli/net_darwin_test.go @@ -1,4 +1,4 @@ -package ctrld +package cli import ( "maps" diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 76f7c36..dd8de9f 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -35,7 +35,6 @@ import ( "github.com/Control-D-Inc/ctrld/internal/controld" "github.com/Control-D-Inc/ctrld/internal/dnscache" "github.com/Control-D-Inc/ctrld/internal/router" - "github.com/Control-D-Inc/ctrld/internal/router/dnsmasq" ) const ( @@ -329,7 +328,7 @@ func (p *prog) apiConfigReload() { // Performing self-upgrade check for production version. if isStable { - _ = selfUpgradeCheck(resolverConfig.Ctrld.VersionTarget, curVer, &logger) + selfUpgradeCheck(resolverConfig.Ctrld.VersionTarget, curVer, &logger) } if resolverConfig.DeactivationPin != nil { @@ -530,15 +529,6 @@ func (p *prog) run(reload bool, reloadCh chan struct{}) { go p.watchLinkState(ctx) } - if !reload { - go func() { - // Start network monitoring - if err := p.monitorNetworkChanges(); err != nil { - mainLog.Load().Error().Err(err).Msg("Failed to start network monitoring") - } - }() - } - for listenerNum := range p.cfg.Listener { p.cfg.Listener[listenerNum].Init() if !reload { @@ -550,7 +540,7 @@ func (p *prog) run(reload bool, reloadCh chan struct{}) { } addr := net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port)) mainLog.Load().Info().Msgf("starting DNS server on listener.%s: %s", listenerNum, addr) - if err := p.serveDNS(listenerNum); err != nil { + if err := p.serveDNS(ctx, listenerNum); err != nil { mainLog.Load().Fatal().Err(err).Msgf("unable to start dns proxy on listener.%s", listenerNum) } mainLog.Load().Debug().Msgf("end of serveDNS listener.%s: %s", listenerNum, addr) @@ -617,12 +607,6 @@ func (p *prog) setupClientInfoDiscover(selfIP string) { format := ctrld.LeaseFileFormat(p.cfg.Service.DHCPLeaseFileFormat) p.ciTable.AddLeaseFile(leaseFile, format) } - if leaseFiles := dnsmasq.AdditionalLeaseFiles(); len(leaseFiles) > 0 { - mainLog.Load().Debug().Msgf("watching additional lease files: %v", leaseFiles) - for _, leaseFile := range leaseFiles { - p.ciTable.AddLeaseFile(leaseFile, ctrld.Dnsmasq) - } - } } // runClientInfoDiscover runs the client info discover. @@ -1483,15 +1467,14 @@ func selfUninstallCheck(uninstallErr error, p *prog, logger zerolog.Logger) { } } -// shouldUpgrade checks if the version target vt is greater than the current one cv. -// Major version upgrades are not allowed to prevent breaking changes. +// selfUpgradeCheck checks if the version target vt is greater +// than the current one cv, perform self-upgrade then. // // The callers must ensure curVer and logger are non-nil. -// Returns true if upgrade is allowed, false otherwise. -func shouldUpgrade(vt string, cv *semver.Version, logger *zerolog.Logger) bool { +func selfUpgradeCheck(vt string, cv *semver.Version, logger *zerolog.Logger) { if vt == "" { logger.Debug().Msg("no version target set, skipped checking self-upgrade") - return false + return } vts := vt if !strings.HasPrefix(vts, "v") { @@ -1500,58 +1483,28 @@ func shouldUpgrade(vt string, cv *semver.Version, logger *zerolog.Logger) bool { targetVer, err := semver.NewVersion(vts) if err != nil { logger.Warn().Err(err).Msgf("invalid target version, skipped self-upgrade: %s", vt) - return false + return } - - // Prevent major version upgrades to avoid breaking changes - if targetVer.Major() != cv.Major() { - logger.Warn(). - Str("target", vt). - Str("current", cv.String()). - Msgf("major version upgrade not allowed (target: %d, current: %d), skipped self-upgrade", targetVer.Major(), cv.Major()) - return false - } - if !targetVer.GreaterThan(cv) { logger.Debug(). Str("target", vt). Str("current", cv.String()). Msgf("target version is not greater than current one, skipped self-upgrade") - return false + return } - return true -} - -// performUpgrade executes the self-upgrade command. -// Returns true if upgrade was initiated successfully, false otherwise. -func performUpgrade(vt string) bool { exe, err := os.Executable() if err != nil { mainLog.Load().Error().Err(err).Msg("failed to get executable path, skipped self-upgrade") - return false + return } cmd := exec.Command(exe, "upgrade", "prod", "-vv") cmd.SysProcAttr = sysProcAttrForDetachedChildProcess() if err := cmd.Start(); err != nil { mainLog.Load().Error().Err(err).Msg("failed to start self-upgrade") - return false + return } - mainLog.Load().Debug().Msgf("self-upgrade triggered, version target: %s", vt) - return true -} - -// selfUpgradeCheck checks if the version target vt is greater -// than the current one cv, perform self-upgrade then. -// Major version upgrades are not allowed to prevent breaking changes. -// -// The callers must ensure curVer and logger are non-nil. -// Returns true if upgrade is allowed and should proceed, false otherwise. -func selfUpgradeCheck(vt string, cv *semver.Version, logger *zerolog.Logger) bool { - if shouldUpgrade(vt, cv, logger) { - return performUpgrade(vt) - } - return false + mainLog.Load().Debug().Msgf("self-upgrade triggered, version target: %s", vts) } // leakOnUpstreamFailure reports whether ctrld should initiate a recovery flow diff --git a/cmd/cli/prog_test.go b/cmd/cli/prog_test.go index c4ef5c3..5f2f8e1 100644 --- a/cmd/cli/prog_test.go +++ b/cmd/cli/prog_test.go @@ -1,15 +1,11 @@ package cli import ( - "runtime" "testing" "time" - "github.com/Masterminds/semver/v3" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/Control-D-Inc/ctrld" + "github.com/stretchr/testify/assert" ) func Test_prog_dnsWatchdogEnabled(t *testing.T) { @@ -59,215 +55,3 @@ func Test_prog_dnsWatchdogInterval(t *testing.T) { }) } } - -func Test_shouldUpgrade(t *testing.T) { - // Helper function to create a version - makeVersion := func(v string) *semver.Version { - ver, err := semver.NewVersion(v) - if err != nil { - t.Fatalf("failed to create version %s: %v", v, err) - } - return ver - } - - tests := []struct { - name string - versionTarget string - currentVersion *semver.Version - shouldUpgrade bool - description string - }{ - { - name: "empty version target", - versionTarget: "", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: false, - description: "should skip upgrade when version target is empty", - }, - { - name: "invalid version target", - versionTarget: "invalid-version", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: false, - description: "should skip upgrade when version target is invalid", - }, - { - name: "same version", - versionTarget: "v1.0.0", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: false, - description: "should skip upgrade when target version equals current version", - }, - { - name: "older version", - versionTarget: "v1.0.0", - currentVersion: makeVersion("v1.1.0"), - shouldUpgrade: false, - description: "should skip upgrade when target version is older than current version", - }, - { - name: "patch upgrade allowed", - versionTarget: "v1.0.1", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: true, - description: "should allow patch version upgrade within same major version", - }, - { - name: "minor upgrade allowed", - versionTarget: "v1.1.0", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: true, - description: "should allow minor version upgrade within same major version", - }, - { - name: "major upgrade blocked", - versionTarget: "v2.0.0", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: false, - description: "should block major version upgrade", - }, - { - name: "major downgrade blocked", - versionTarget: "v1.0.0", - currentVersion: makeVersion("v2.0.0"), - shouldUpgrade: false, - description: "should block major version downgrade", - }, - { - name: "version without v prefix", - versionTarget: "1.0.1", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: true, - description: "should handle version target without v prefix", - }, - { - name: "complex version upgrade allowed", - versionTarget: "v1.5.3", - currentVersion: makeVersion("v1.4.2"), - shouldUpgrade: true, - description: "should allow complex version upgrade within same major version", - }, - { - name: "complex major upgrade blocked", - versionTarget: "v3.1.0", - currentVersion: makeVersion("v2.5.3"), - shouldUpgrade: false, - description: "should block complex major version upgrade", - }, - { - name: "pre-release version upgrade allowed", - versionTarget: "v1.0.1-beta.1", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: true, - description: "should allow pre-release version upgrade within same major version", - }, - { - name: "pre-release major upgrade blocked", - versionTarget: "v2.0.0-alpha.1", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: false, - description: "should block pre-release major version upgrade", - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - // Create test logger - testLogger := zerolog.New(zerolog.NewTestWriter(t)).With().Logger() - - // Call the function and capture the result - result := shouldUpgrade(tc.versionTarget, tc.currentVersion, &testLogger) - - // Assert the expected result - assert.Equal(t, tc.shouldUpgrade, result, tc.description) - }) - } -} - -func Test_selfUpgradeCheck(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("skipped due to Windows file locking issue on Github Action runners") - } - - // Helper function to create a version - makeVersion := func(v string) *semver.Version { - ver, err := semver.NewVersion(v) - if err != nil { - t.Fatalf("failed to create version %s: %v", v, err) - } - return ver - } - - tests := []struct { - name string - versionTarget string - currentVersion *semver.Version - shouldUpgrade bool - description string - }{ - { - name: "upgrade allowed", - versionTarget: "v1.0.1", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: true, - description: "should allow upgrade and attempt to perform it", - }, - { - name: "upgrade blocked", - versionTarget: "v2.0.0", - currentVersion: makeVersion("v1.0.0"), - shouldUpgrade: false, - description: "should block upgrade and not attempt to perform it", - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - // Create test logger - testLogger := zerolog.New(zerolog.NewTestWriter(t)).With().Logger() - - // Call the function and capture the result - result := selfUpgradeCheck(tc.versionTarget, tc.currentVersion, &testLogger) - - // Assert the expected result - assert.Equal(t, tc.shouldUpgrade, result, tc.description) - }) - } -} - -func Test_performUpgrade(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("skipped due to Windows file locking issue on Github Action runners") - } - - tests := []struct { - name string - versionTarget string - expectedResult bool - description string - }{ - { - name: "valid version target", - versionTarget: "v1.0.1", - expectedResult: true, - description: "should attempt to perform upgrade with valid version target", - }, - { - name: "empty version target", - versionTarget: "", - expectedResult: true, - description: "should attempt to perform upgrade even with empty version target", - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - // Call the function and capture the result - result := performUpgrade(tc.versionTarget) - assert.Equal(t, tc.expectedResult, result, tc.description) - }) - } -} diff --git a/cmd/ctrld_library/main.go b/cmd/ctrld_library/main.go index b2e643d..49f5b26 100644 --- a/cmd/ctrld_library/main.go +++ b/cmd/ctrld_library/main.go @@ -28,17 +28,15 @@ type AppCallback interface { // Start configures utility with config.toml from provided directory. // This function will block until Stop is called // Check port availability prior to calling it. -func (c *Controller) Start(CdUID string, ProvisionID string, CustomHostname string, HomeDir string, UpstreamProto string, logLevel int, logPath string) { +func (c *Controller) Start(CdUID string, HomeDir string, UpstreamProto string, logLevel int, logPath string) { if c.stopCh == nil { c.stopCh = make(chan struct{}) c.Config = cli.AppConfig{ - CdUID: CdUID, - ProvisionID: ProvisionID, - CustomHostname: CustomHostname, - HomeDir: HomeDir, - UpstreamProto: UpstreamProto, - Verbose: logLevel, - LogPath: logPath, + CdUID: CdUID, + HomeDir: HomeDir, + UpstreamProto: UpstreamProto, + Verbose: logLevel, + LogPath: logPath, } appCallback := mapCallback(c.AppCallback) cli.RunMobile(&c.Config, &appCallback, c.stopCh) diff --git a/config.go b/config.go index 73484d7..96f6686 100644 --- a/config.go +++ b/config.go @@ -358,15 +358,6 @@ func (uc *UpstreamConfig) Init() { } } -// VerifyMsg creates and returns a new DNS message could be used for testing upstream health. -func (uc *UpstreamConfig) VerifyMsg() *dns.Msg { - msg := new(dns.Msg) - msg.RecursionDesired = true - msg.SetQuestion(".", dns.TypeNS) - msg.SetEdns0(4096, false) // ensure handling of large DNS response - return msg -} - // VerifyDomain returns the domain name that could be resolved by the upstream endpoint. // It returns empty for non-ControlD upstream endpoint. func (uc *UpstreamConfig) VerifyDomain() string { diff --git a/config_quic.go b/config_quic.go index 33f56b9..cadcb6b 100644 --- a/config_quic.go +++ b/config_quic.go @@ -36,7 +36,7 @@ func (uc *UpstreamConfig) setupDOH3Transport() { func (uc *UpstreamConfig) newDOH3Transport(addrs []string) http.RoundTripper { rt := &http3.Transport{} rt.TLSClientConfig = &tls.Config{RootCAs: uc.certPool} - rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { + rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { _, port, _ := net.SplitHostPort(addr) // if we have a bootstrap ip set, use it to avoid DNS lookup if uc.BootstrapIP != "" { @@ -96,14 +96,14 @@ func (uc *UpstreamConfig) doh3Transport(dnsType uint16) http.RoundTripper { // - quic dialer is different with net.Dialer // - simplification for quic free version type parallelDialerResult struct { - conn *quic.Conn + conn quic.EarlyConnection err error } type quicParallelDialer struct{} // Dial performs parallel dialing to the given address list. -func (d *quicParallelDialer) Dial(ctx context.Context, addrs []string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { +func (d *quicParallelDialer) Dial(ctx context.Context, addrs []string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { if len(addrs) == 0 { return nil, errors.New("empty addresses") } diff --git a/docs/known-issues.md b/docs/known-issues.md deleted file mode 100644 index 0d13bcc..0000000 --- a/docs/known-issues.md +++ /dev/null @@ -1,42 +0,0 @@ -# Known Issues - -This document outlines known issues with ctrld and their current status, workarounds, and recommendations. - -## macOS (Darwin) Issues - -### Self-Upgrade Issue on Darwin 15.5 - -**Issue**: ctrld self-upgrading functionality may not work on macOS Darwin 15.5. - -**Status**: Under investigation - -**Description**: Users on macOS Darwin 15.5 may experience issues when ctrld attempts to perform automatic self-upgrades. The upgrade process would be triggered, but ctrld won't be upgraded. - -**Workarounds**: -1. **Recommended**: Upgrade your macOS system to Darwin 15.6 or later, which has been tested and verified to work correctly with ctrld self-upgrade functionality. -2. **Alternative**: Run `ctrld upgrade prod` directly to manually upgrade ctrld to the latest version on Darwin 15.5. - -**Affected Versions**: ctrld v1.4.2 and later on macOS Darwin 15.5 - -**Last Updated**: 05/09/2025 - ---- - -## Contributing to Known Issues - -If you encounter an issue not listed here, please: - -1. Check the [GitHub Issues](https://github.com/Control-D-Inc/ctrld/issues) to see if it's already reported -2. If not reported, create a new issue with: - - Detailed description of the problem - - Steps to reproduce - - Expected vs actual behavior - - System information (OS, version, architecture) - - ctrld version - -## Issue Status Legend - -- **Under investigation**: Issue is confirmed and being analyzed -- **Workaround available**: Temporary solution exists while permanent fix is developed -- **Fixed**: Issue has been resolved in a specific version -- **Won't fix**: Issue is acknowledged but will not be addressed due to technical limitations or design decisions diff --git a/doq_test.go b/doq_test.go index 14055dd..430a22a 100644 --- a/doq_test.go +++ b/doq_test.go @@ -142,7 +142,7 @@ func (s *testQUICServer) serve(t *testing.T) { } // handleConnection manages an individual QUIC connection by accepting and handling incoming streams in separate goroutines. -func (s *testQUICServer) handleConnection(t *testing.T, conn *quic.Conn) { +func (s *testQUICServer) handleConnection(t *testing.T, conn quic.Connection) { for { stream, err := conn.AcceptStream(context.Background()) if err != nil { @@ -154,7 +154,7 @@ func (s *testQUICServer) handleConnection(t *testing.T, conn *quic.Conn) { } // handleStream processes a single QUIC stream, reads DNS messages, generates a response, and sends it back to the client. -func (s *testQUICServer) handleStream(t *testing.T, stream *quic.Stream) { +func (s *testQUICServer) handleStream(t *testing.T, stream quic.Stream) { defer stream.Close() // Read length (2 bytes) diff --git a/go.mod b/go.mod index 2280eb6..1d94a07 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ 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.54.0 + github.com/quic-go/quic-go v0.48.2 github.com/rs/zerolog v1.28.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 @@ -54,8 +54,10 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -72,6 +74,7 @@ require ( github.com/mdlayher/packet v1.1.2 // indirect github.com/mdlayher/socket v0.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -86,7 +89,7 @@ require ( 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 - go.uber.org/mock v0.5.0 // indirect + go.uber.org/mock v0.4.0 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/crypto v0.36.0 // indirect diff --git a/go.sum b/go.sum index 56a71e1..25af133 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ 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-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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= @@ -101,6 +103,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= @@ -158,6 +162,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -236,6 +242,10 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -261,8 +271,8 @@ github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcET 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.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= 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= @@ -320,8 +330,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= @@ -495,6 +505,8 @@ golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/internal/clientinfo/dhcp_lease_files.go b/internal/clientinfo/dhcp_lease_files.go index 34aabf3..1b5d829 100644 --- a/internal/clientinfo/dhcp_lease_files.go +++ b/internal/clientinfo/dhcp_lease_files.go @@ -16,5 +16,4 @@ var clientInfoFiles = map[string]ctrld.LeaseFileFormat{ "/var/dhcpd/var/db/dhcpd.leases": ctrld.IscDhcpd, // Pfsense "/home/pi/.router/run/dhcp/dnsmasq.leases": ctrld.Dnsmasq, // Firewalla "/var/lib/kea/dhcp4.leases": ctrld.KeaDHCP4, // Pfsense - "/var/db/dnsmasq.leases": ctrld.Dnsmasq, // OPNsense } diff --git a/internal/clientinfo/mdns.go b/internal/clientinfo/mdns.go index a09d729..e009e01 100644 --- a/internal/clientinfo/mdns.go +++ b/internal/clientinfo/mdns.go @@ -74,6 +74,7 @@ func (m *mdns) lookupIPByHostname(name string, v6 bool) string { if value == name { if addr, err := netip.ParseAddr(key.(string)); err == nil && addr.Is6() == v6 { ip = addr.String() + //lint:ignore S1008 This is used for readable. if addr.IsLoopback() { // Continue searching if this is loopback address. return true } diff --git a/internal/clientinfo/ptr_lookup.go b/internal/clientinfo/ptr_lookup.go index 9a1d10c..8e6b3f7 100644 --- a/internal/clientinfo/ptr_lookup.go +++ b/internal/clientinfo/ptr_lookup.go @@ -104,6 +104,7 @@ func (p *ptrDiscover) lookupIPByHostname(name string, v6 bool) string { if value == name { if addr, err := netip.ParseAddr(key.(string)); err == nil && addr.Is6() == v6 { ip = addr.String() + //lint:ignore S1008 This is used for readable. if addr.IsLoopback() { // Continue searching if this is loopback address. return true } @@ -119,7 +120,8 @@ func (p *ptrDiscover) lookupIPByHostname(name string, v6 bool) string { // is reachable, set p.serverDown to false, so p.lookupHostname can continue working. func (p *ptrDiscover) checkServer() { bo := backoff.NewBackoff("ptrDiscover", func(format string, args ...any) {}, time.Minute*5) - m := (&ctrld.UpstreamConfig{}).VerifyMsg() + m := new(dns.Msg) + m.SetQuestion(".", dns.TypeNS) ping := func() error { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() diff --git a/internal/router/dnsmasq/conf.go b/internal/router/dnsmasq/conf.go index bb81d60..b168042 100644 --- a/internal/router/dnsmasq/conf.go +++ b/internal/router/dnsmasq/conf.go @@ -6,7 +6,6 @@ import ( "errors" "io" "os" - "path/filepath" "strings" ) @@ -29,62 +28,3 @@ func interfaceNameFromReader(r io.Reader) (string, error) { } return "", errors.New("not found") } - -// AdditionalConfigFiles returns a list of Dnsmasq configuration files found in the "/tmp/etc" directory. -func AdditionalConfigFiles() []string { - if paths, err := filepath.Glob("/tmp/etc/dnsmasq-*.conf"); err == nil { - return paths - } - return nil -} - -// AdditionalLeaseFiles returns a list of lease file paths corresponding to the Dnsmasq configuration files. -func AdditionalLeaseFiles() []string { - cfgFiles := AdditionalConfigFiles() - if len(cfgFiles) == 0 { - return nil - } - leaseFiles := make([]string, 0, len(cfgFiles)) - for _, cfgFile := range cfgFiles { - if leaseFile := leaseFileFromConfigFileName(cfgFile); leaseFile != "" { - leaseFiles = append(leaseFiles, leaseFile) - - } else { - leaseFiles = append(leaseFiles, defaultLeaseFileFromConfigPath(cfgFile)) - } - } - return leaseFiles -} - -// leaseFileFromConfigFileName retrieves the DHCP lease file path by reading and parsing the provided configuration file. -func leaseFileFromConfigFileName(cfgFile string) string { - if f, err := os.Open(cfgFile); err == nil { - return leaseFileFromReader(f) - } - return "" -} - -// leaseFileFromReader parses the given io.Reader for the "dhcp-leasefile" configuration and returns its value as a string. -func leaseFileFromReader(r io.Reader) string { - scanner := bufio.NewScanner(r) - for scanner.Scan() { - line := scanner.Text() - if strings.HasPrefix(line, "#") { - continue - } - before, after, found := strings.Cut(line, "=") - if !found { - continue - } - if before == "dhcp-leasefile" { - return after - } - } - return "" -} - -// defaultLeaseFileFromConfigPath generates the default lease file path based on the provided configuration file path. -func defaultLeaseFileFromConfigPath(path string) string { - name := filepath.Base(path) - return filepath.Join("/var/lib/misc", strings.TrimSuffix(name, ".conf")+".leases") -} diff --git a/internal/router/dnsmasq/conf_test.go b/internal/router/dnsmasq/conf_test.go index 9ca672b..99a0710 100644 --- a/internal/router/dnsmasq/conf_test.go +++ b/internal/router/dnsmasq/conf_test.go @@ -1,7 +1,6 @@ package dnsmasq import ( - "io" "strings" "testing" ) @@ -45,49 +44,3 @@ interface=eth0 }) } } - -func Test_leaseFileFromReader(t *testing.T) { - tests := []struct { - name string - in io.Reader - expected string - }{ - { - "default", - strings.NewReader(` -dhcp-script=/sbin/dhcpc_lease -dhcp-leasefile=/var/lib/misc/dnsmasq-1.leases -script-arp -`), - "/var/lib/misc/dnsmasq-1.leases", - }, - { - "non-default", - strings.NewReader(` -dhcp-script=/sbin/dhcpc_lease -dhcp-leasefile=/tmp/var/lib/misc/dnsmasq-1.leases -script-arp -`), - "/tmp/var/lib/misc/dnsmasq-1.leases", - }, - { - "missing", - strings.NewReader(` -dhcp-script=/sbin/dhcpc_lease -script-arp -`), - "", - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - if got := leaseFileFromReader(tc.in); got != tc.expected { - t.Errorf("leaseFileFromReader() = %v, want %v", got, tc.expected) - } - }) - } - -} diff --git a/internal/router/dnsmasq/dnsmasq.go b/internal/router/dnsmasq/dnsmasq.go index 058b0b5..819bd59 100644 --- a/internal/router/dnsmasq/dnsmasq.go +++ b/internal/router/dnsmasq/dnsmasq.go @@ -4,7 +4,6 @@ import ( "errors" "html/template" "net" - "os" "path/filepath" "strings" @@ -27,13 +26,9 @@ max-cache-ttl=0 {{- end}} ` -const ( - MerlinConfPath = "/tmp/etc/dnsmasq.conf" - MerlinJffsConfDir = "/jffs/configs" - MerlinJffsConfPath = "/jffs/configs/dnsmasq.conf" - MerlinPostConfPath = "/jffs/scripts/dnsmasq.postconf" -) - +const MerlinConfPath = "/tmp/etc/dnsmasq.conf" +const MerlinJffsConfPath = "/jffs/configs/dnsmasq.conf" +const MerlinPostConfPath = "/jffs/scripts/dnsmasq.postconf" const MerlinPostConfMarker = `# GENERATED BY ctrld - EOF` const MerlinPostConfTmpl = `# GENERATED BY ctrld - DO NOT MODIFY @@ -164,27 +159,3 @@ func FirewallaSelfInterfaces() []*net.Interface { } return ifaces } - -const ( - ubios43ConfPath = "/run/dnsmasq.dhcp.conf.d" - ubios42ConfPath = "/run/dnsmasq.conf.d" - ubios43PidFile = "/run/dnsmasq-main.pid" - ubios42PidFile = "/run/dnsmasq.pid" - UbiosConfName = "zzzctrld.conf" -) - -// UbiosConfPath returns the appropriate configuration path based on the system's directory structure. -func UbiosConfPath() string { - if st, _ := os.Stat(ubios43ConfPath); st != nil && st.IsDir() { - return ubios43ConfPath - } - return ubios42ConfPath -} - -// UbiosPidFile returns the appropriate dnsmasq pid file based on the system's directory structure. -func UbiosPidFile() string { - if st, _ := os.Stat(ubios43PidFile); st != nil && !st.IsDir() { - return ubios43PidFile - } - return ubios42PidFile -} diff --git a/internal/router/edgeos/edgeos.go b/internal/router/edgeos/edgeos.go index 7364ac1..2e229ac 100644 --- a/internal/router/edgeos/edgeos.go +++ b/internal/router/edgeos/edgeos.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "os/exec" - "path/filepath" "strings" "github.com/kardianos/service" @@ -182,7 +181,7 @@ func ContentFilteringEnabled() bool { // DnsShieldEnabled reports whether DNS Shield is enabled. // See: https://community.ui.com/releases/UniFi-OS-Dream-Machines-3-2-7/251dfc1e-f4dd-4264-a080-3be9d8b9e02b func DnsShieldEnabled() bool { - buf, err := os.ReadFile(filepath.Join(dnsmasq.UbiosConfPath(), "dns.conf")) + buf, err := os.ReadFile("/var/run/dnsmasq.conf.d/dns.conf") if err != nil { return false } diff --git a/internal/router/merlin/merlin.go b/internal/router/merlin/merlin.go index c1c6821..cacc508 100644 --- a/internal/router/merlin/merlin.go +++ b/internal/router/merlin/merlin.go @@ -6,7 +6,6 @@ import ( "io" "os" "os/exec" - "path/filepath" "strings" "time" "unicode" @@ -21,18 +20,10 @@ import ( const Name = "merlin" -// nvramKvMap is a map of NVRAM key-value pairs used to configure and manage Merlin-specific settings. var nvramKvMap = map[string]string{ "dnspriv_enable": "0", // Ensure Merlin native DoT disabled. } -// dnsmasqConfig represents configuration paths for dnsmasq operations in Merlin firmware. -type dnsmasqConfig struct { - confPath string - jffsConfPath string -} - -// Merlin represents a configuration handler for setting up and managing ctrld on Merlin routers. type Merlin struct { cfg *ctrld.Config } @@ -42,22 +33,18 @@ func New(cfg *ctrld.Config) *Merlin { return &Merlin{cfg: cfg} } -// ConfigureService configures the service based on the provided configuration. It returns an error if the configuration fails. func (m *Merlin) ConfigureService(config *service.Config) error { return nil } -// Install sets up the necessary configurations and services required for the Merlin instance to function properly. func (m *Merlin) Install(_ *service.Config) error { return nil } -// Uninstall removes the ctrld-related configurations and services from the Merlin router and reverts to the original state. func (m *Merlin) Uninstall(_ *service.Config) error { return nil } -// PreRun prepares the Merlin instance for operation by waiting for essential services and directories to become available. func (m *Merlin) PreRun() error { // Wait NTP ready. _ = m.Cleanup() @@ -79,7 +66,6 @@ func (m *Merlin) PreRun() error { return nil } -// Setup initializes and configures the Merlin instance for use, including setting up dnsmasq and necessary nvram settings. func (m *Merlin) Setup() error { if m.cfg.FirstListener().IsDirectDnsListener() { return nil @@ -93,10 +79,35 @@ func (m *Merlin) Setup() error { return err } - for _, cfg := range getDnsmasqConfigs() { - if err := m.setupDnsmasq(cfg); err != nil { - return fmt.Errorf("failed to setup dnsmasq: config: %s, error: %w", cfg.confPath, err) - } + // Copy current dnsmasq config to /jffs/configs/dnsmasq.conf, + // Then we will run postconf script on this file. + // + // Normally, adding postconf script is enough. However, we see + // reports on some Merlin devices that postconf scripts does not + // work, but manipulating the config directly via /jffs/configs does. + src, err := os.Open(dnsmasq.MerlinConfPath) + if err != nil { + return fmt.Errorf("failed to open dnsmasq config: %w", err) + } + defer src.Close() + + dst, err := os.Create(dnsmasq.MerlinJffsConfPath) + if err != nil { + return fmt.Errorf("failed to create %s: %w", dnsmasq.MerlinJffsConfPath, err) + } + defer dst.Close() + + if _, err := io.Copy(dst, src); err != nil { + return fmt.Errorf("failed to copy current dnsmasq config: %w", err) + } + if err := dst.Close(); err != nil { + return fmt.Errorf("failed to save %s: %w", dnsmasq.MerlinJffsConfPath, err) + } + + // Run postconf script on /jffs/configs/dnsmasq.conf directly. + cmd := exec.Command("/bin/sh", dnsmasq.MerlinPostConfPath, dnsmasq.MerlinJffsConfPath) + if out, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to run post conf: %s: %w", string(out), err) } // Restart dnsmasq service. @@ -111,7 +122,6 @@ func (m *Merlin) Setup() error { return nil } -// Cleanup restores the original dnsmasq and nvram configurations and restarts dnsmasq if necessary. func (m *Merlin) Cleanup() error { if m.cfg.FirstListener().IsDirectDnsListener() { return nil @@ -133,11 +143,9 @@ func (m *Merlin) Cleanup() error { if err := os.WriteFile(dnsmasq.MerlinPostConfPath, merlinParsePostConf(buf), 0750); err != nil { return err } - - for _, cfg := range getDnsmasqConfigs() { - if err := m.cleanupDnsmasqJffs(cfg); err != nil { - return fmt.Errorf("failed to cleanup jffs dnsmasq: config: %s, error: %w", cfg.confPath, err) - } + // Remove /jffs/configs/dnsmasq.conf file. + if err := os.Remove(dnsmasq.MerlinJffsConfPath); err != nil && !os.IsNotExist(err) { + return err } // Restart dnsmasq service. if err := restartDNSMasq(); err != nil { @@ -146,54 +154,6 @@ func (m *Merlin) Cleanup() error { return nil } -// setupDnsmasq sets up dnsmasq configuration by writing postconf, copying configuration, and running a postconf script. -func (m *Merlin) setupDnsmasq(cfg *dnsmasqConfig) error { - src, err := os.Open(cfg.confPath) - if os.IsNotExist(err) { - return nil // nothing to do if conf file does not exist. - } - if err != nil { - return fmt.Errorf("failed to open dnsmasq config: %w", err) - } - defer src.Close() - - // Copy current dnsmasq config to cfg.jffsConfPath, - // Then we will run postconf script on this file. - // - // Normally, adding postconf script is enough. However, we see - // reports on some Merlin devices that postconf scripts does not - // work, but manipulating the config directly via /jffs/configs does. - dst, err := os.Create(cfg.jffsConfPath) - if err != nil { - return fmt.Errorf("failed to create %s: %w", cfg.jffsConfPath, err) - } - defer dst.Close() - - if _, err := io.Copy(dst, src); err != nil { - return fmt.Errorf("failed to copy current dnsmasq config: %w", err) - } - if err := dst.Close(); err != nil { - return fmt.Errorf("failed to save %s: %w", cfg.jffsConfPath, err) - } - - // Run postconf script on cfg.jffsConfPath directly. - cmd := exec.Command("/bin/sh", dnsmasq.MerlinPostConfPath, cfg.jffsConfPath) - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("failed to run post conf: %s: %w", string(out), err) - } - return nil -} - -// cleanupDnsmasqJffs removes the JFFS configuration file specified in the given dnsmasqConfig, if it exists. -func (m *Merlin) cleanupDnsmasqJffs(cfg *dnsmasqConfig) error { - // Remove cfg.jffsConfPath file. - if err := os.Remove(cfg.jffsConfPath); err != nil && !os.IsNotExist(err) { - return err - } - return nil -} - -// writeDnsmasqPostconf writes the requireddnsmasqConfigs post-configuration for dnsmasq to enable custom DNS settings with ctrld. func (m *Merlin) writeDnsmasqPostconf() error { buf, err := os.ReadFile(dnsmasq.MerlinPostConfPath) // Already setup. @@ -219,8 +179,6 @@ func (m *Merlin) writeDnsmasqPostconf() error { return os.WriteFile(dnsmasq.MerlinPostConfPath, []byte(data), 0750) } -// restartDNSMasq restarts the dnsmasq service by executing the appropriate system command using "service". -// Returns an error if the command fails or if there is an issue processing the command output. func restartDNSMasq() error { if out, err := exec.Command("service", "restart_dnsmasq").CombinedOutput(); err != nil { return fmt.Errorf("restart_dnsmasq: %s, %w", string(out), err) @@ -228,22 +186,6 @@ func restartDNSMasq() error { return nil } -// getDnsmasqConfigs retrieves a list of dnsmasqConfig containing configuration and JFFS paths for dnsmasq operations. -func getDnsmasqConfigs() []*dnsmasqConfig { - cfgs := []*dnsmasqConfig{ - {dnsmasq.MerlinConfPath, dnsmasq.MerlinJffsConfPath}, - } - for _, path := range dnsmasq.AdditionalConfigFiles() { - jffsConfPath := filepath.Join(dnsmasq.MerlinJffsConfDir, filepath.Base(path)) - cfgs = append(cfgs, &dnsmasqConfig{path, jffsConfPath}) - } - - return cfgs -} - -// merlinParsePostConf parses the dnsmasq post configuration by removing content after the MerlinPostConfMarker, if present. -// If no marker is found, the original buffer is returned unmodified. -// Returns nil if the input buffer is empty. func merlinParsePostConf(buf []byte) []byte { if len(buf) == 0 { return nil @@ -255,7 +197,6 @@ func merlinParsePostConf(buf []byte) []byte { return buf } -// waitDirExists waits until the specified directory exists, polling its existence every second. func waitDirExists(dir string) { for { if _, err := os.Stat(dir); !os.IsNotExist(err) { diff --git a/internal/router/service_ubios.go b/internal/router/service_ubios.go index 9ad971d..8077c07 100644 --- a/internal/router/service_ubios.go +++ b/internal/router/service_ubios.go @@ -13,13 +13,14 @@ import ( "time" "github.com/kardianos/service" - - "github.com/Control-D-Inc/ctrld/internal/router/dnsmasq" ) // This is a copy of https://github.com/kardianos/service/blob/v1.2.1/service_sysv_linux.go, // with modification for supporting ubios v1 init system. +// Keep in sync with ubios.ubiosDNSMasqConfigPath +const ubiosDNSMasqConfigPath = "/run/dnsmasq.conf.d/zzzctrld.conf" + type ubiosSvc struct { i service.Interface platform string @@ -85,7 +86,7 @@ func (s *ubiosSvc) Install() error { }{ s.Config, path, - filepath.Join(dnsmasq.UbiosConfPath(), dnsmasq.UbiosConfName), + ubiosDNSMasqConfigPath, } if err := s.template().Execute(f, to); err != nil { diff --git a/internal/router/ubios/ubios.go b/internal/router/ubios/ubios.go index cba6842..a1f0b6c 100644 --- a/internal/router/ubios/ubios.go +++ b/internal/router/ubios/ubios.go @@ -3,7 +3,6 @@ package ubios import ( "bytes" "os" - "path/filepath" "strconv" "github.com/kardianos/service" @@ -13,19 +12,19 @@ import ( "github.com/Control-D-Inc/ctrld/internal/router/edgeos" ) -const Name = "ubios" +const ( + Name = "ubios" + ubiosDNSMasqConfigPath = "/run/dnsmasq.conf.d/zzzctrld.conf" + ubiosDNSMasqDnsConfigPath = "/run/dnsmasq.conf.d/dns.conf" +) type Ubios struct { - cfg *ctrld.Config - dnsmasqConfPath string + cfg *ctrld.Config } // New returns a router.Router for configuring/setup/run ctrld on Ubios routers. func New(cfg *ctrld.Config) *Ubios { - return &Ubios{ - cfg: cfg, - dnsmasqConfPath: filepath.Join(dnsmasq.UbiosConfPath(), dnsmasq.UbiosConfName), - } + return &Ubios{cfg: cfg} } func (u *Ubios) ConfigureService(config *service.Config) error { @@ -60,7 +59,7 @@ func (u *Ubios) Setup() error { if err != nil { return err } - if err := os.WriteFile(u.dnsmasqConfPath, []byte(data), 0600); err != nil { + if err := os.WriteFile(ubiosDNSMasqConfigPath, []byte(data), 0600); err != nil { return err } // Restart dnsmasq service. @@ -75,7 +74,7 @@ func (u *Ubios) Cleanup() error { return nil } // Remove the custom dnsmasq config - if err := os.Remove(u.dnsmasqConfPath); err != nil { + if err := os.Remove(ubiosDNSMasqConfigPath); err != nil { return err } // Restart dnsmasq service. @@ -86,7 +85,7 @@ func (u *Ubios) Cleanup() error { } func restartDNSMasq() error { - buf, err := os.ReadFile(dnsmasq.UbiosPidFile()) + buf, err := os.ReadFile("/run/dnsmasq.pid") if err != nil { return err } diff --git a/nameservers_linux.go b/nameservers_linux.go index 37a9ed2..13a5507 100644 --- a/nameservers_linux.go +++ b/nameservers_linux.go @@ -5,12 +5,9 @@ import ( "bytes" "encoding/hex" "net" - "net/netip" "os" "strings" - "tailscale.com/net/netmon" - "github.com/Control-D-Inc/ctrld/internal/dns/resolvconffile" ) @@ -131,25 +128,3 @@ func virtualInterfaces() set { } return s } - -// validInterfacesMap returns a set containing non virtual interfaces. -// TODO: deduplicated with cmd/cli/net_linux.go in v2. -func validInterfaces() set { - m := make(map[string]struct{}) - vis := virtualInterfaces() - netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) { - if _, existed := vis[i.Name]; existed { - return - } - m[i.Name] = struct{}{} - }) - // Fallback to default route interface if found nothing. - if len(m) == 0 { - defaultRoute, err := netmon.DefaultRoute() - if err != nil { - return m - } - m[defaultRoute.InterfaceName] = struct{}{} - } - return m -} diff --git a/nameservers_windows.go b/nameservers_windows.go index 7b16e8e..eb4f2b5 100644 --- a/nameservers_windows.go +++ b/nameservers_windows.go @@ -23,17 +23,20 @@ import ( ) const ( - maxDNSAdapterRetries = 5 - retryDelayDNSAdapter = 1 * time.Second - defaultDNSAdapterTimeout = 10 * time.Second - minDNSServers = 1 // Minimum number of DNS servers we want to find - - DS_FORCE_REDISCOVERY = 0x00000001 - DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010 - DS_BACKGROUND_ONLY = 0x00000100 - DS_IP_REQUIRED = 0x00000200 - DS_IS_DNS_NAME = 0x00020000 - DS_RETURN_DNS_NAME = 0x40000000 + maxDNSAdapterRetries = 5 + retryDelayDNSAdapter = 1 * time.Second + defaultDNSAdapterTimeout = 10 * time.Second + minDNSServers = 1 // Minimum number of DNS servers we want to find + NetSetupUnknown uint32 = 0 + NetSetupWorkgroup uint32 = 1 + NetSetupDomain uint32 = 2 + NetSetupCloudDomain uint32 = 3 + DS_FORCE_REDISCOVERY = 0x00000001 + DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010 + DS_BACKGROUND_ONLY = 0x00000100 + DS_IP_REQUIRED = 0x00000200 + DS_IS_DNS_NAME = 0x00020000 + DS_RETURN_DNS_NAME = 0x40000000 ) type DomainControllerInfo struct { @@ -155,7 +158,7 @@ func getDNSServers(ctx context.Context) ([]string, error) { 0, // DomainGuid - not needed 0, // SiteName - not needed uintptr(flags), // Flags - uintptr(unsafe.Pointer(&info))) // DomainControllerInfo - output + uintptr(unsafe.Pointer(&info))) // DomainControllerInfo - output if ret != 0 { switch ret { @@ -340,28 +343,27 @@ func checkDomainJoined() bool { 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) + err := windows.NetGetJoinInformation(nil, &domain, &status) + if err != nil { + Log(context.Background(), logger.Debug(), + "Failed to get domain join 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)", + "Domain join status: domain=%s status=%d (Unknown=0, Workgroup=1, Domain=2, CloudDomain=3)", domainName, status) - isDomain := status == syscall.NetSetupDomainName - Log(context.Background(), logger.Debug(), "Is domain joined? status=%d, result=%v", status, isDomain) + // Consider domain or cloud domain as domain-joined + isDomain := status == NetSetupDomain || status == NetSetupCloudDomain + Log(context.Background(), logger.Debug(), + "Is domain joined? status=%d, traditional=%v, cloud=%v, result=%v", + status, + status == NetSetupDomain, + status == NetSetupCloudDomain, + isDomain) return isDomain } diff --git a/net_darwin.go b/net_darwin.go deleted file mode 100644 index 5b01e9f..0000000 --- a/net_darwin.go +++ /dev/null @@ -1,35 +0,0 @@ -package ctrld - -import ( - "bufio" - "bytes" - "io" - "os/exec" - "strings" -) - -// validInterfaces returns a set of all valid hardware ports. -// TODO: deduplicated with cmd/cli/net_darwin.go in v2. -func validInterfaces() map[string]struct{} { - b, err := exec.Command("networksetup", "-listallhardwareports").Output() - if err != nil { - return nil - } - return parseListAllHardwarePorts(bytes.NewReader(b)) -} - -// parseListAllHardwarePorts parses output of "networksetup -listallhardwareports" -// and returns map presents all hardware ports. -func parseListAllHardwarePorts(r io.Reader) map[string]struct{} { - m := make(map[string]struct{}) - scanner := bufio.NewScanner(r) - for scanner.Scan() { - line := scanner.Text() - after, ok := strings.CutPrefix(line, "Device: ") - if !ok { - continue - } - m[after] = struct{}{} - } - return m -} diff --git a/net_others.go b/net_others.go deleted file mode 100644 index ae7ab8e..0000000 --- a/net_others.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !darwin && !windows && !linux - -package ctrld - -import "tailscale.com/net/netmon" - -// validInterfaces returns a set containing only default route interfaces. -// TODO: deuplicated with cmd/cli/net_others.go in v2. -func validInterfaces() map[string]struct{} { - defaultRoute, err := netmon.DefaultRoute() - if err != nil { - return nil - } - return map[string]struct{}{defaultRoute.InterfaceName: {}} -} diff --git a/resolver.go b/resolver.go index 3aeddd0..27c0108 100644 --- a/resolver.go +++ b/resolver.go @@ -729,15 +729,10 @@ func newResolverWithNameserver(nameservers []string) *osResolver { return r } -// Rfc1918Addresses returns the list of local physical interfaces private IP addresses +// Rfc1918Addresses returns the list of local interfaces private IP addresses func Rfc1918Addresses() []string { - vis := validInterfaces() var res []string netmon.ForeachInterface(func(i netmon.Interface, prefixes []netip.Prefix) { - // Skip virtual interfaces. - if _, existed := vis[i.Name]; !existed { - return - } addrs, _ := i.Addrs() for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) diff --git a/resolver_test.go b/resolver_test.go index f030739..ebcad16 100644 --- a/resolver_test.go +++ b/resolver_test.go @@ -282,35 +282,6 @@ func Test_Edns0_CacheReply(t *testing.T) { } } -// https://github.com/Control-D-Inc/ctrld/issues/255 -func Test_legacyResolverWithBigExtraSection(t *testing.T) { - lanPC, err := net.ListenPacket("udp", "127.0.0.1:0") // 127.0.0.1 is considered LAN (loopback) - if err != nil { - t.Fatalf("failed to listen on LAN address: %v", err) - } - lanServer, lanAddr, err := runLocalPacketConnTestServer(t, lanPC, bigExtraSectionHandler()) - if err != nil { - t.Fatalf("failed to run LAN test server: %v", err) - } - defer lanServer.Shutdown() - - uc := &UpstreamConfig{ - Name: "Legacy", - Type: ResolverTypeLegacy, - Endpoint: lanAddr, - } - uc.Init() - r, err := NewResolver(uc) - if err != nil { - t.Fatal(err) - } - - _, err = r.Resolve(context.Background(), uc.VerifyMsg()) - if err != nil { - t.Fatal(err) - } -} - func Test_upstreamTypeFromEndpoint(t *testing.T) { tests := []struct { name string @@ -399,68 +370,6 @@ func countHandler(call *atomic.Int64) dns.HandlerFunc { } } -func mustRR(s string) dns.RR { - r, err := dns.NewRR(s) - if err != nil { - panic(err) - } - return r -} - -func bigExtraSectionHandler() dns.HandlerFunc { - return func(w dns.ResponseWriter, msg *dns.Msg) { - m := &dns.Msg{ - Answer: []dns.RR{ - mustRR(". 7149 IN NS m.root-servers.net."), - mustRR(". 7149 IN NS c.root-servers.net."), - mustRR(". 7149 IN NS e.root-servers.net."), - mustRR(". 7149 IN NS j.root-servers.net."), - mustRR(". 7149 IN NS g.root-servers.net."), - mustRR(". 7149 IN NS k.root-servers.net."), - mustRR(". 7149 IN NS l.root-servers.net."), - mustRR(". 7149 IN NS d.root-servers.net."), - mustRR(". 7149 IN NS h.root-servers.net."), - mustRR(". 7149 IN NS b.root-servers.net."), - mustRR(". 7149 IN NS a.root-servers.net."), - mustRR(". 7149 IN NS f.root-servers.net."), - mustRR(". 7149 IN NS i.root-servers.net."), - }, - Extra: []dns.RR{ - mustRR("m.root-servers.net. 656 IN A 202.12.27.33"), - mustRR("m.root-servers.net. 656 IN AAAA 2001:dc3::35"), - mustRR("c.root-servers.net. 656 IN A 192.33.4.12"), - mustRR("c.root-servers.net. 656 IN AAAA 2001:500:2::c"), - mustRR("e.root-servers.net. 656 IN A 192.203.230.10"), - mustRR("e.root-servers.net. 656 IN AAAA 2001:500:a8::e"), - mustRR("j.root-servers.net. 656 IN A 192.58.128.30"), - mustRR("j.root-servers.net. 656 IN AAAA 2001:503:c27::2:30"), - mustRR("g.root-servers.net. 656 IN A 192.112.36.4"), - mustRR("g.root-servers.net. 656 IN AAAA 2001:500:12::d0d"), - mustRR("k.root-servers.net. 656 IN A 193.0.14.129"), - mustRR("k.root-servers.net. 656 IN AAAA 2001:7fd::1"), - mustRR("l.root-servers.net. 656 IN A 199.7.83.42"), - mustRR("l.root-servers.net. 656 IN AAAA 2001:500:9f::42"), - mustRR("d.root-servers.net. 656 IN A 199.7.91.13"), - mustRR("d.root-servers.net. 656 IN AAAA 2001:500:2d::d"), - mustRR("h.root-servers.net. 656 IN A 198.97.190.53"), - mustRR("h.root-servers.net. 656 IN AAAA 2001:500:1::53"), - mustRR("b.root-servers.net. 656 IN A 170.247.170.2"), - mustRR("b.root-servers.net. 656 IN AAAA 2801:1b8:10::b"), - mustRR("a.root-servers.net. 656 IN A 198.41.0.4"), - mustRR("a.root-servers.net. 656 IN AAAA 2001:503:ba3e::2:30"), - mustRR("f.root-servers.net. 656 IN A 192.5.5.241"), - mustRR("f.root-servers.net. 656 IN AAAA 2001:500:2f::f"), - mustRR("i.root-servers.net. 656 IN A 192.36.148.17"), - mustRR("i.root-servers.net. 656 IN AAAA 2001:7fe::53"), - }, - } - - m.Compress = true - m.SetReply(msg) - w.WriteMsg(m) - } -} - func generateEdns0ClientCookie() string { cookie := make([]byte, 8) if _, err := rand.Read(cookie); err != nil {