From 8a56389396a8eb853777820b35c68dd6137d1128 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 20 Mar 2023 22:53:42 +0700 Subject: [PATCH 01/11] cmd/ctrld: ensure both udp/tcp listener aborted So either one of them return an error, the other will be terminated. --- cmd/ctrld/dns_proxy.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 733ae637..5143659b 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -56,7 +56,7 @@ func (p *prog) serveDNS(listenerNum string) error { } }) - g := new(errgroup.Group) + g, ctx := errgroup.WithContext(context.Background()) for _, proto := range []string{"udp", "tcp"} { proto := proto // On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can @@ -68,6 +68,10 @@ func (p *prog) serveDNS(listenerNum string) error { Net: proto, Handler: handler, } + go func() { + <-ctx.Done() + _ = s.Shutdown() + }() if err := s.ListenAndServe(); err != nil { mainLog.Error().Err(err).Msg("could not serving on ::1") } @@ -80,6 +84,10 @@ func (p *prog) serveDNS(listenerNum string) error { Net: proto, Handler: handler, } + go func() { + <-ctx.Done() + _ = s.Shutdown() + }() if err := s.ListenAndServe(); err != nil { mainLog.Error().Err(err).Msgf("could not listen and serve on: %s", s.Addr) return err From f0c604a9f12a709158184ea91395541abc18dfda Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 20 Mar 2023 23:53:51 +0700 Subject: [PATCH 02/11] cmd/ctrld: only watch config when doing self-check Avoiding reading/writing global config, causing a data race. While at it, also guarding read/write access to cfg.Service.AllocateIP field, since when it is read/write by multiple goroutines. --- cmd/ctrld/cli.go | 26 +++++++++++++++++++------- cmd/ctrld/prog.go | 8 ++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index dc172d3a..d7223589 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -15,6 +15,7 @@ import ( "runtime" "strconv" "strings" + "sync" "time" "github.com/fsnotify/fsnotify" @@ -508,13 +509,6 @@ func readConfigFile(writeDefaultConfig bool) bool { if err == nil { fmt.Println("loading config file from:", v.ConfigFileUsed()) defaultConfigFile = v.ConfigFileUsed() - v.OnConfigChange(func(in fsnotify.Event) { - if err := v.UnmarshalKey("listener", &cfg.Listener); err != nil { - log.Printf("failed to unmarshal listener config: %v", err) - return - } - }) - v.WatchConfig() return true } @@ -727,8 +721,26 @@ func selfCheckStatus(status service.Status) service.Status { err := errors.New("query failed") maxAttempts := 20 mainLog.Debug().Msg("Performing self-check") + var ( + lcChanged map[string]*ctrld.ListenerConfig + mu sync.Mutex + ) + v.OnConfigChange(func(in fsnotify.Event) { + mu.Lock() + defer mu.Unlock() + if err := v.UnmarshalKey("listener", &lcChanged); err != nil { + log.Printf("failed to unmarshal listener config: %v", err) + return + } + }) + v.WatchConfig() for i := 0; i < maxAttempts; i++ { lc := cfg.Listener["0"] + mu.Lock() + if lcChanged != nil { + lc = lcChanged["0"] + } + mu.Unlock() m := new(dns.Msg) m.SetQuestion(selfCheckFQDN+".", dns.TypeA) m.RecursionDesired = true diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 52b9c85e..6f4abada 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -28,6 +28,8 @@ var svcConfig = &service.Config{ } type prog struct { + mu sync.Mutex + cfg *ctrld.Config cache dnscache.Cacher } @@ -106,7 +108,9 @@ func (p *prog) run() { } else { mainLog.Info().Msg("writing config file to: " + defaultConfigFile) } + p.mu.Lock() p.cfg.Service.AllocateIP = true + p.mu.Unlock() p.preRun() mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, net.JoinHostPort(ip, strconv.Itoa(port))) if err := p.serveDNS(listenerNum); err != nil { @@ -132,6 +136,8 @@ func (p *prog) Stop(s service.Service) error { } func (p *prog) allocateIP(ip string) error { + p.mu.Lock() + defer p.mu.Unlock() if !p.cfg.Service.AllocateIP { return nil } @@ -139,6 +145,8 @@ func (p *prog) allocateIP(ip string) error { } func (p *prog) deAllocateIP() error { + p.mu.Lock() + defer p.mu.Unlock() if !p.cfg.Service.AllocateIP { return nil } From 992780349731479d99da01925452f2f6d0d8ef37 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 27 Mar 2023 23:13:24 +0700 Subject: [PATCH 03/11] cmd/ctrld: response to OS service manager earlier When startup, ctrld waits for network up before calling s.Run to starts its logic. However, if network is down on startup, ctrld will hang on waiting for network up. That causes OS service manager unhappy, as ctrld do not response to it, marking ctrld as failure service and never start ctrld again. To fix this, we should call s.Run as soon as possible, and use a channel for waiting a signal that we can actual do our logic after network up. Update #34 --- cmd/ctrld/cli.go | 47 +++++++++++++++++++++++++++++++---------------- cmd/ctrld/prog.go | 7 ++++++- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index d7223589..f25cc637 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -90,6 +90,35 @@ func initCLI() { if daemon && runtime.GOOS == "windows" { log.Fatal("Cannot run in daemon mode. Please install a Windows service.") } + + waitCh := make(chan struct{}) + stopCh := make(chan struct{}) + if !daemon { + // We need to call s.Run() as soon as possible to response to the OS manager, so it + // can see ctrld is running and don't mark ctrld as failed service. + go func() { + p := &prog{ + waitCh: waitCh, + stopCh: stopCh, + } + s, err := service.New(p, svcConfig) + if err != nil { + mainLog.Fatal().Err(err).Msg("failed create new service") + } + serviceLogger, err := s.Logger(nil) + if err != nil { + mainLog.Error().Err(err).Msg("failed to get service logger") + return + } + + if err := s.Run(); err != nil { + if sErr := serviceLogger.Error(err); sErr != nil { + mainLog.Error().Err(sErr).Msg("failed to write service log") + } + mainLog.Error().Err(err).Msg("failed to start service") + } + }() + } noConfigStart := isNoConfigStart(cmd) writeDefaultConfig := !noConfigStart && configBase64 == "" configs := []struct { @@ -150,22 +179,8 @@ func initCLI() { os.Exit(0) } - s, err := service.New(&prog{}, svcConfig) - if err != nil { - mainLog.Fatal().Err(err).Msg("failed create new service") - } - serviceLogger, err := s.Logger(nil) - if err != nil { - mainLog.Error().Err(err).Msg("failed to get service logger") - return - } - - if err := s.Run(); err != nil { - if sErr := serviceLogger.Error(err); sErr != nil { - mainLog.Error().Err(sErr).Msg("failed to write service log") - } - mainLog.Error().Err(err).Msg("failed to start service") - } + close(waitCh) + <-stopCh }, } runCmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "Run as daemon") diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 6f4abada..0206402b 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -28,7 +28,9 @@ var svcConfig = &service.Config{ } type prog struct { - mu sync.Mutex + mu sync.Mutex + waitCh chan struct{} + stopCh chan struct{} cfg *ctrld.Config cache dnscache.Cacher @@ -41,6 +43,8 @@ func (p *prog) Start(s service.Service) error { } func (p *prog) run() { + // Wait the caller to signal that we can do our logic. + <-p.waitCh p.preRun() if p.cfg.Service.CacheEnable { cacher, err := dnscache.NewLRUCache(p.cfg.Service.CacheSize) @@ -132,6 +136,7 @@ func (p *prog) Stop(s service.Service) error { return err } mainLog.Info().Msg("Service stopped") + close(p.stopCh) return nil } From b3a342bc448506f578c2bfca956802e8b7a4bdc2 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 29 Mar 2023 12:36:19 +0700 Subject: [PATCH 04/11] all: some improvements for better troubleshooting - Include version/OS information when logging - Make time field human readable in log file - Force root privilege when running status command on darwin Updates #34 --- cmd/ctrld/cli.go | 42 ++++++++++++++++++++++++++++++++++-------- cmd/ctrld/main.go | 1 - go.mod | 1 + go.sum | 4 ++++ 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index f25cc637..48169082 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -20,6 +20,7 @@ import ( "github.com/fsnotify/fsnotify" + "github.com/cuonglm/osinfo" "github.com/go-playground/validator/v10" "github.com/kardianos/service" "github.com/miekg/dns" @@ -36,6 +37,11 @@ import ( const selfCheckFQDN = "verify.controld.com" +var ( + version = "dev" + commit = "none" +) + var ( v = viper.NewWithOptions(viper.KeyDelimiter("::")) defaultConfigWritten = false @@ -62,17 +68,28 @@ _/ ___\ __\_ __ \ | / __ | \/ dns forwarding proxy \/ ` +var rootCmd = &cobra.Command{ + Use: "ctrld", + Short: strings.TrimLeft(rootShortDesc, "\n"), + Version: curVersion(), +} + +func curVersion() string { + if version != "dev" { + version = "v" + version + } + if len(commit) > 7 { + commit = commit[:7] + } + return fmt.Sprintf("%s-%s", version, commit) +} + func initCLI() { // Enable opening via explorer.exe on Windows. // See: https://github.com/spf13/cobra/issues/844. cobra.MousetrapHelpText = "" cobra.EnableCommandSorting = false - rootCmd := &cobra.Command{ - Use: "ctrld", - Short: strings.TrimLeft(rootShortDesc, "\n"), - Version: "1.1.3", - } rootCmd.PersistentFlags().CountVarP( &verbose, "verbose", @@ -142,7 +159,12 @@ func initCLI() { if err := v.Unmarshal(&cfg); err != nil { log.Fatalf("failed to unmarshal config: %v", err) } - fmt.Println("starting ctrld...") + + log.Println("starting ctrld ...") + log.Printf("version: %s\n", curVersion()) + oi := osinfo.New() + log.Printf("os: %s\n", oi.String()) + // Wait for network up. if !ctrldnet.Up() { log.Fatal("network is not up yet") @@ -362,6 +384,10 @@ func initCLI() { } }, } + if runtime.GOOS == "darwin" { + // On darwin, running status command without privileges may return wrong information. + statusCmd.PreRun = checkHasElevatedPrivilege + } uninstallCmd := &cobra.Command{ PreRun: checkHasElevatedPrivilege, @@ -522,7 +548,7 @@ func readConfigFile(writeDefaultConfig bool) bool { // If err == nil, there's a config supplied via `--config`, no default config written. err := v.ReadInConfig() if err == nil { - fmt.Println("loading config file from:", v.ConfigFileUsed()) + log.Println("loading config file from:", v.ConfigFileUsed()) defaultConfigFile = v.ConfigFileUsed() return true } @@ -536,7 +562,7 @@ func readConfigFile(writeDefaultConfig bool) bool { if err := writeConfigFile(); err != nil { log.Fatalf("failed to write default config file: %v", err) } else { - fmt.Println("writing default config file to: " + defaultConfigFile) + log.Println("writing default config file to: " + defaultConfigFile) } defaultConfigWritten = true return false diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index e53f7fda..57ae02df 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -73,7 +73,6 @@ func initLogging() { } writers = append(writers, logFile) } - zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs consoleWriter := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { w.TimeFormat = time.StampMilli }) diff --git a/go.mod b/go.mod index 71933728..25f5cda4 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 + github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19 github.com/frankban/quicktest v1.14.3 github.com/fsnotify/fsnotify v1.6.0 github.com/go-playground/validator/v10 v10.11.1 diff --git a/go.sum b/go.sum index a1583546..57a40c04 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,10 @@ github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27 github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cuonglm/osinfo v0.0.0-20230329052356-117e0ee9d353 h1:PFKlvMrKAUendoPEiJxSMkYeGG/G/5k7vu2ldGBnq3I= +github.com/cuonglm/osinfo v0.0.0-20230329052356-117e0ee9d353/go.mod h1:G45410zMgmnSjLVKCq4f6GpbYAzoP2plX9rPwgx6C24= +github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19 h1:7P/f19Mr0oa3ug8BYt4JuRe/Zq3dF4Mrr4m8+Kw+Hcs= +github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19/go.mod h1:G45410zMgmnSjLVKCq4f6GpbYAzoP2plX9rPwgx6C24= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From ba48ff5965938879202dcbd9678ab8519ac9cf8f Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 30 Mar 2023 02:43:37 +0700 Subject: [PATCH 05/11] all: fix os resolver hangs when all server failed For os resolver, ctrld queries against all servers concurrently, and get the first success result back. However, if all server failed, the result channel is not closed, causing ctrld hang. Fixing this by closing the result channel once getting back all response from servers. While at it, also shorten the backoff time when waiting for network up, ctrld should serve as fast as possible after network is available. Updates #34 --- cmd/ctrld/cli.go | 3 +-- internal/net/net.go | 2 +- resolver.go | 8 ++++++++ resolver_test.go | 29 +++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 resolver_test.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 48169082..938860a7 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -160,8 +160,7 @@ func initCLI() { log.Fatalf("failed to unmarshal config: %v", err) } - log.Println("starting ctrld ...") - log.Printf("version: %s\n", curVersion()) + log.Printf("starting ctrld %s\n", curVersion()) oi := osinfo.New() log.Printf("os: %s\n", oi.String()) diff --git a/internal/net/net.go b/internal/net/net.go index 888a2d6f..4e712069 100644 --- a/internal/net/net.go +++ b/internal/net/net.go @@ -66,7 +66,7 @@ func supportListenIPv6Local() bool { } func probeStack() { - b := backoff.NewBackoff("probeStack", func(format string, args ...any) {}, time.Minute) + b := backoff.NewBackoff("probeStack", func(format string, args ...any) {}, 5*time.Second) for { if _, err := probeStackDialer.Dial("udp", bootstrapDNS); err == nil { hasNetworkUp = true diff --git a/resolver.go b/resolver.go index a12c7001..45537fa3 100644 --- a/resolver.go +++ b/resolver.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "sync" "github.com/miekg/dns" ) @@ -69,8 +70,15 @@ func (o *osResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error dnsClient := &dns.Client{Net: "udp"} ch := make(chan *osResolverResult, numServers) + var wg sync.WaitGroup + wg.Add(len(o.nameservers)) + go func() { + wg.Wait() + close(ch) + }() for _, server := range o.nameservers { go func(server string) { + defer wg.Done() answer, _, err := dnsClient.ExchangeContext(ctx, msg, server) ch <- &osResolverResult{answer: answer, err: err} }(server) diff --git a/resolver_test.go b/resolver_test.go new file mode 100644 index 00000000..a5a93c4b --- /dev/null +++ b/resolver_test.go @@ -0,0 +1,29 @@ +package ctrld + +import ( + "context" + "testing" + "time" + + "github.com/miekg/dns" +) + +func Test_osResolver_Resolve(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { + defer cancel() + resolver := &osResolver{nameservers: []string{"127.0.0.127:5353"}} + m := new(dns.Msg) + m.SetQuestion("controld.com.", dns.TypeA) + m.RecursionDesired = true + _, _ = resolver.Resolve(context.Background(), m) + }() + + select { + case <-time.After(10 * time.Second): + t.Error("os resolver hangs") + case <-ctx.Done(): + } +} From b65a5ac2839660676b66aa510978749f0fa1765c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 30 Mar 2023 18:25:36 +0700 Subject: [PATCH 06/11] all: fix bug that causes ctrld stop working if bootstrap failed The bootstrap process has two issues that can make ctrld stop resolving after restarting machine host. ctrld uses bootstrap DNS and os nameservers for resolving upstream. On unix, /etc/resolv.conf content is used to get available nameservers. This works well when installing ctrld. However, after being installed, ctrld may modify the content of /etc/resolv.conf itself, to make other apps use its listener as DNS resolver. So when ctrld starts after OS restart, it ends up using [bootstrap DNS + ctrld's listener], for resolving upstream. At this moment, if ctrld could not contact bootstrap DNS for any reason, upstream domain will not be resolved. For above reason, an upstream may not have bootstrap IPs after ctrld starts. When re-bootstrapping, if there's no bootstrap IPs, ctrld should call the setup bootstrap process again. Currently, it does not, causing all queries failed. This commit fixes above issue by adding mechanism for retrieving OS nameservers properly, by querying routing table information: - Parsing /proc/net subsystem on Linux. - For BSD variants, just fetching routing information base from OS. - On Windows, just include the gateway information when reading iface. The fixing for second issue is trivial, just kickoff a bootstrap process if there's no bootstrap IPs when re-boostrapping. While at it, also ensure that fetching resolver information from ControlD API is also used the same approach. Fixes #34 --- config.go | 16 ++++++- config_internal_test.go | 20 +++++++++ go.mod | 2 +- go.sum | 2 - internal/controld/config.go | 48 ++++++++++++++++++++ nameservers_bsd.go | 58 ++++++++++++++++++++++++ nameservers_linux.go | 88 +++++++++++++++++++++++++++++++++++++ nameservers_test.go | 11 +++++ nameservers_unix.go | 8 +--- nameservers_windows.go | 87 +++++++++++++++--------------------- 10 files changed, 278 insertions(+), 62 deletions(-) create mode 100644 config_internal_test.go create mode 100644 nameservers_bsd.go create mode 100644 nameservers_linux.go create mode 100644 nameservers_test.go diff --git a/config.go b/config.go index 47c23154..2cb0e38f 100644 --- a/config.go +++ b/config.go @@ -155,6 +155,12 @@ func (uc *UpstreamConfig) Init() { // SetupBootstrapIP manually find all available IPs of the upstream. // The first usable IP will be used as bootstrap IP of the upstream. func (uc *UpstreamConfig) SetupBootstrapIP() { + uc.setupBootstrapIP(true) +} + +// SetupBootstrapIP manually find all available IPs of the upstream. +// The first usable IP will be used as bootstrap IP of the upstream. +func (uc *UpstreamConfig) setupBootstrapIP(withBootstrapDNS bool) { bootstrapIP := func(record dns.RR) string { switch ar := record.(type) { case *dns.A: @@ -166,7 +172,9 @@ func (uc *UpstreamConfig) SetupBootstrapIP() { } resolver := &osResolver{nameservers: availableNameservers()} - resolver.nameservers = append([]string{net.JoinHostPort(bootstrapDNS, "53")}, resolver.nameservers...) + if withBootstrapDNS { + resolver.nameservers = append([]string{net.JoinHostPort(bootstrapDNS, "53")}, resolver.nameservers...) + } ProxyLog.Debug().Msgf("Resolving %q using bootstrap DNS %q", uc.Domain, resolver.nameservers) timeoutMs := 2000 if uc.Timeout > 0 && uc.Timeout < timeoutMs { @@ -228,9 +236,13 @@ func (uc *UpstreamConfig) ReBootstrap() { default: return } - _, _, _ = uc.g.Do("rebootstrap", func() (any, error) { + _, _, _ = uc.g.Do("ReBootstrap", func() (any, error) { ProxyLog.Debug().Msg("re-bootstrapping upstream ip") n := uint32(len(uc.bootstrapIPs)) + if n == 0 { + uc.SetupBootstrapIP() + uc.setupTransportWithoutPingUpstream() + } timeoutMs := 1000 if uc.Timeout > 0 && uc.Timeout < timeoutMs { diff --git a/config_internal_test.go b/config_internal_test.go new file mode 100644 index 00000000..608b0ec6 --- /dev/null +++ b/config_internal_test.go @@ -0,0 +1,20 @@ +package ctrld + +import ( + "testing" +) + +func TestUpstreamConfig_SetupBootstrapIP(t *testing.T) { + uc := &UpstreamConfig{ + Name: "test", + Type: ResolverTypeDOH, + Endpoint: "https://freedns.controld.com/p2", + Timeout: 5000, + } + uc.Init() + uc.setupBootstrapIP(false) + if uc.BootstrapIP == "" { + t.Fatal("could not bootstrap ip without bootstrap DNS") + } + t.Log(uc) +} diff --git a/go.mod b/go.mod index 25f5cda4..da86f6d2 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.1 + golang.org/x/net v0.7.0 golang.org/x/sync v0.1.0 golang.org/x/sys v0.5.0 golang.zx2c4.com/wireguard/windows v0.5.3 @@ -68,7 +69,6 @@ require ( golang.org/x/crypto v0.4.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.6.0 // indirect - golang.org/x/net v0.7.0 // indirect golang.org/x/text v0.7.0 // indirect golang.org/x/tools v0.2.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 57a40c04..5b863e74 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,6 @@ github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27 github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cuonglm/osinfo v0.0.0-20230329052356-117e0ee9d353 h1:PFKlvMrKAUendoPEiJxSMkYeGG/G/5k7vu2ldGBnq3I= -github.com/cuonglm/osinfo v0.0.0-20230329052356-117e0ee9d353/go.mod h1:G45410zMgmnSjLVKCq4f6GpbYAzoP2plX9rPwgx6C24= github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19 h1:7P/f19Mr0oa3ug8BYt4JuRe/Zq3dF4Mrr4m8+Kw+Hcs= github.com/cuonglm/osinfo v0.0.0-20230329055532-c513f836da19/go.mod h1:G45410zMgmnSjLVKCq4f6GpbYAzoP2plX9rPwgx6C24= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/controld/config.go b/internal/controld/config.go index 39948376..f323f990 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -7,16 +7,26 @@ import ( "fmt" "net" "net/http" + "sync" "time" + "github.com/miekg/dns" + + "github.com/Control-D-Inc/ctrld" ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" ) const ( + apiDomain = "api.controld.com" resolverDataURL = "https://api.controld.com/utility" InvalidConfigCode = 40401 ) +var ( + resolveAPIDomainOnce sync.Once + apiDomainIP string +) + // ResolverConfig represents Control D resolver data. type ResolverConfig struct { DOH string `json:"doh"` @@ -64,6 +74,44 @@ func FetchResolverConfig(uid string) (*ResolverConfig, error) { if ctrldnet.SupportsIPv4() { proto = "tcp4" } + resolveAPIDomainOnce.Do(func() { + r, err := ctrld.NewResolver(&ctrld.UpstreamConfig{Type: ctrld.ResolverTypeOS}) + if err != nil { + return + } + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + msg := new(dns.Msg) + dnsType := dns.TypeAAAA + if proto == "tcp4" { + dnsType = dns.TypeA + } + msg.SetQuestion(apiDomain+".", dnsType) + msg.RecursionDesired = true + answer, err := r.Resolve(ctx, msg) + if err != nil { + return + } + if answer.Rcode != dns.RcodeSuccess || len(answer.Answer) == 0 { + return + } + for _, record := range answer.Answer { + switch ar := record.(type) { + case *dns.A: + apiDomainIP = ar.A.String() + return + case *dns.AAAA: + apiDomainIP = ar.AAAA.String() + return + } + } + }) + if apiDomainIP != "" { + if _, port, _ := net.SplitHostPort(addr); port != "" { + return ctrldnet.Dialer.DialContext(ctx, proto, net.JoinHostPort(apiDomainIP, port)) + } + } return ctrldnet.Dialer.DialContext(ctx, proto, addr) } client := http.Client{ diff --git a/nameservers_bsd.go b/nameservers_bsd.go new file mode 100644 index 00000000..5ecc5e6c --- /dev/null +++ b/nameservers_bsd.go @@ -0,0 +1,58 @@ +//go:build darwin || dragonfly || freebsd || netbsd || openbsd + +package ctrld + +import ( + "net" + "syscall" + + "golang.org/x/net/route" +) + +func osNameservers() []string { + var dns []string + seen := make(map[string]bool) + rib, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0) + if err != nil { + return nil + } + messages, err := route.ParseRIB(route.RIBTypeRoute, rib) + if err != nil { + return nil + } + for _, message := range messages { + message, ok := message.(*route.RouteMessage) + if !ok { + continue + } + addresses := message.Addrs + if len(addresses) < 2 { + continue + } + dst, gw := toNetIP(addresses[0]), toNetIP(addresses[1]) + if dst == nil || gw == nil { + continue + } + if gw.IsLoopback() || seen[gw.String()] { + continue + } + if dst.Equal(net.IPv4zero) || dst.Equal(net.IPv6zero) { + seen[gw.String()] = true + dns = append(dns, net.JoinHostPort(gw.String(), "53")) + } + } + return dns +} + +func toNetIP(addr route.Addr) net.IP { + switch t := addr.(type) { + case *route.Inet4Addr: + return net.IPv4(t.IP[0], t.IP[1], t.IP[2], t.IP[3]) + case *route.Inet6Addr: + ip := make(net.IP, net.IPv6len) + copy(ip, t.IP[:]) + return ip + default: + return nil + } +} diff --git a/nameservers_linux.go b/nameservers_linux.go new file mode 100644 index 00000000..deeff7e7 --- /dev/null +++ b/nameservers_linux.go @@ -0,0 +1,88 @@ +package ctrld + +import ( + "bufio" + "bytes" + "encoding/hex" + "net" + "os" +) + +const ( + v4RouteFile = "/proc/net/route" + v6RouteFile = "/proc/net/ipv6_route" +) + +func osNameservers() []string { + ns4 := dns4() + ns6 := dns6() + ns := make([]string, len(ns4)+len(ns6)) + ns = append(ns, ns4...) + ns = append(ns, ns6...) + return ns +} + +func dns4() []string { + f, err := os.Open(v4RouteFile) + if err != nil { + return nil + } + defer f.Close() + + var dns []string + seen := make(map[string]bool) + s := bufio.NewScanner(f) + first := true + for s.Scan() { + if first { + first = false + continue + } + fields := bytes.Fields(s.Bytes()) + if len(fields) < 2 { + continue + } + + gw := make([]byte, net.IPv4len) + // Third fields is gateway. + if _, err := hex.Decode(gw, fields[2]); err != nil { + continue + } + ip := net.IPv4(gw[3], gw[2], gw[1], gw[0]) + if ip.Equal(net.IPv4zero) || seen[ip.String()] { + continue + } + seen[ip.String()] = true + dns = append(dns, net.JoinHostPort(ip.String(), "53")) + } + return dns +} + +func dns6() []string { + f, err := os.Open(v6RouteFile) + if err != nil { + return nil + } + defer f.Close() + + var dns []string + s := bufio.NewScanner(f) + for s.Scan() { + fields := bytes.Fields(s.Bytes()) + if len(fields) < 4 { + continue + } + + gw := make([]byte, net.IPv6len) + // Fifth fields is gateway. + if _, err := hex.Decode(gw, fields[4]); err != nil { + continue + } + ip := net.IP(gw) + if ip.Equal(net.IPv6zero) { + continue + } + dns = append(dns, net.JoinHostPort(ip.String(), "53")) + } + return dns +} diff --git a/nameservers_test.go b/nameservers_test.go new file mode 100644 index 00000000..166cced6 --- /dev/null +++ b/nameservers_test.go @@ -0,0 +1,11 @@ +package ctrld + +import "testing" + +func TestNameservers(t *testing.T) { + ns := nameservers() + if len(ns) == 0 { + t.Fatal("failed to get nameservers") + } + t.Log(ns) +} diff --git a/nameservers_unix.go b/nameservers_unix.go index 5c765d37..fd9ebfcf 100644 --- a/nameservers_unix.go +++ b/nameservers_unix.go @@ -1,11 +1,7 @@ -//go:build !js && !windows +//go:build unix package ctrld -import ( - "github.com/Control-D-Inc/ctrld/internal/resolvconffile" -) - func nameservers() []string { - return resolvconffile.NameServersWithPort() + return osNameservers() } diff --git a/nameservers_windows.go b/nameservers_windows.go index 7812f2a1..1863a6e1 100644 --- a/nameservers_windows.go +++ b/nameservers_windows.go @@ -2,70 +2,55 @@ package ctrld import ( "net" - "os" "syscall" - "unsafe" + + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "golang.org/x/sys/windows" ) func nameservers() []string { - aas, err := adapterAddresses() + aas, err := winipcfg.GetAdaptersAddresses(syscall.AF_UNSPEC, winipcfg.GAAFlagIncludeGateways|winipcfg.GAAFlagIncludePrefix) if err != nil { return nil } - ns := make([]string, 0, len(aas)) - for _, aa := range aas { - for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next { - sa, err := dns.Address.Sockaddr.Sockaddr() - if err != nil { - continue - } - var ip net.IP - switch sa := sa.(type) { - case *syscall.SockaddrInet4: - ip = net.IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]) - case *syscall.SockaddrInet6: - ip = make(net.IP, net.IPv6len) - copy(ip, sa.Addr[:]) - if ip[0] == 0xfe && ip[1] == 0xc0 { - // Ignore these fec0/10 ones. Windows seems to - // populate them as defaults on its misc rando - // interfaces. - continue - } - default: - // Unexpected type. - continue - } - ns = append(ns, net.JoinHostPort(ip.String(), "53")) + ns := make([]string, 0, len(aas)*2) + seen := make(map[string]bool) + do := func(addr windows.SocketAddress) { + sa, err := addr.Sockaddr.Sockaddr() + if err != nil { + return } - } - return ns -} - -func adapterAddresses() ([]*windows.IpAdapterAddresses, error) { - var b []byte - l := uint32(15000) // recommended initial size - for { - b = make([]byte, l) - err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l) - if err == nil { - if l == 0 { - return nil, nil + var ip net.IP + switch sa := sa.(type) { + case *syscall.SockaddrInet4: + ip = net.IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]) + case *syscall.SockaddrInet6: + ip = make(net.IP, net.IPv6len) + copy(ip, sa.Addr[:]) + if ip[0] == 0xfe && ip[1] == 0xc0 { + // Ignore these fec0/10 ones. Windows seems to + // populate them as defaults on its misc rando + // interfaces. + return } - break - } - if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW { - return nil, os.NewSyscallError("getadaptersaddresses", err) + default: + return + } - if l <= uint32(len(b)) { - return nil, os.NewSyscallError("getadaptersaddresses", err) + if ip.IsLoopback() || seen[ip.String()] { + return } + seen[ip.String()] = true + ns = append(ns, net.JoinHostPort(ip.String(), "53")) } - var aas []*windows.IpAdapterAddresses - for aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next { - aas = append(aas, aa) + for _, aa := range aas { + for dns := aa.FirstDNSServerAddress; dns != nil; dns = dns.Next { + do(dns.Address) + } + for gw := aa.FirstGatewayAddress; gw != nil; gw = gw.Next { + do(gw.Address) + } } - return aas, nil + return ns } From 42d29b626b3329dd4698ae622590a736b14ff037 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 31 Mar 2023 12:29:57 +0700 Subject: [PATCH 07/11] Adding more source for getting available DNS On some platforms, the gateway may not be a usable DNS. So extending the current approach to allow retrieving DNS from many sources. --- config_internal_test.go | 1 + nameservers.go | 29 +++++++++++++++++++++++++++++ nameservers_bsd.go | 27 ++++++++++++++++++++++----- nameservers_linux.go | 27 ++++++++++++++++++--------- nameservers_unix.go | 7 ------- nameservers_windows.go | 8 ++++++-- 6 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 nameservers.go delete mode 100644 nameservers_unix.go diff --git a/config_internal_test.go b/config_internal_test.go index 608b0ec6..0a457d37 100644 --- a/config_internal_test.go +++ b/config_internal_test.go @@ -14,6 +14,7 @@ func TestUpstreamConfig_SetupBootstrapIP(t *testing.T) { uc.Init() uc.setupBootstrapIP(false) if uc.BootstrapIP == "" { + t.Log(availableNameservers()) t.Fatal("could not bootstrap ip without bootstrap DNS") } t.Log(uc) diff --git a/nameservers.go b/nameservers.go new file mode 100644 index 00000000..ce99a3ba --- /dev/null +++ b/nameservers.go @@ -0,0 +1,29 @@ +package ctrld + +import "net" + +type dnsFn func() []string + +func nameservers() []string { + var dns []string + seen := make(map[string]bool) + ch := make(chan []string) + fns := dnsFns() + + for _, fn := range fns { + go func(fn dnsFn) { + ch <- fn() + }(fn) + } + for range fns { + for _, ns := range <-ch { + if seen[ns] { + continue + } + seen[ns] = true + dns = append(dns, net.JoinHostPort(ns, "53")) + } + } + + return dns +} diff --git a/nameservers_bsd.go b/nameservers_bsd.go index 5ecc5e6c..2beebd0c 100644 --- a/nameservers_bsd.go +++ b/nameservers_bsd.go @@ -4,14 +4,20 @@ package ctrld import ( "net" + "os/exec" + "runtime" + "strings" "syscall" "golang.org/x/net/route" ) -func osNameservers() []string { +func dnsFns() []dnsFn { + return []dnsFn{dnsFromRIB, dnsFromIPConfig} +} + +func dnsFromRIB() []string { var dns []string - seen := make(map[string]bool) rib, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0) if err != nil { return nil @@ -33,17 +39,28 @@ func osNameservers() []string { if dst == nil || gw == nil { continue } - if gw.IsLoopback() || seen[gw.String()] { + if gw.IsLoopback() { continue } if dst.Equal(net.IPv4zero) || dst.Equal(net.IPv6zero) { - seen[gw.String()] = true - dns = append(dns, net.JoinHostPort(gw.String(), "53")) + dns = append(dns, gw.String()) } } return dns } +func dnsFromIPConfig() []string { + if runtime.GOOS != "darwin" { + return nil + } + cmd := exec.Command("ipconfig", "getoption", "", "domain_name_server") + out, _ := cmd.Output() + if ip := net.ParseIP(strings.TrimSpace(string(out))); ip != nil { + return []string{ip.String()} + } + return nil +} + func toNetIP(addr route.Addr) net.IP { switch t := addr.(type) { case *route.Inet4Addr: diff --git a/nameservers_linux.go b/nameservers_linux.go index deeff7e7..8859ea5a 100644 --- a/nameservers_linux.go +++ b/nameservers_linux.go @@ -6,6 +6,8 @@ import ( "encoding/hex" "net" "os" + + "github.com/Control-D-Inc/ctrld/internal/dns/resolvconffile" ) const ( @@ -13,13 +15,8 @@ const ( v6RouteFile = "/proc/net/ipv6_route" ) -func osNameservers() []string { - ns4 := dns4() - ns6 := dns6() - ns := make([]string, len(ns4)+len(ns6)) - ns = append(ns, ns4...) - ns = append(ns, ns6...) - return ns +func dnsFns() []dnsFn { + return []dnsFn{dns4, dns6, dnsFromSystemdResolver} } func dns4() []string { @@ -53,7 +50,7 @@ func dns4() []string { continue } seen[ip.String()] = true - dns = append(dns, net.JoinHostPort(ip.String(), "53")) + dns = append(dns, ip.String()) } return dns } @@ -82,7 +79,19 @@ func dns6() []string { if ip.Equal(net.IPv6zero) { continue } - dns = append(dns, net.JoinHostPort(ip.String(), "53")) + dns = append(dns, ip.String()) } return dns } + +func dnsFromSystemdResolver() []string { + c, err := resolvconffile.ParseFile("/run/systemd/resolve/resolv.conf") + if err != nil { + return nil + } + ns := make([]string, 0, len(c.Nameservers)) + for _, nameserver := range c.Nameservers { + ns = append(ns, nameserver.String()) + } + return ns +} diff --git a/nameservers_unix.go b/nameservers_unix.go deleted file mode 100644 index fd9ebfcf..00000000 --- a/nameservers_unix.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build unix - -package ctrld - -func nameservers() []string { - return osNameservers() -} diff --git a/nameservers_windows.go b/nameservers_windows.go index 1863a6e1..5cd78116 100644 --- a/nameservers_windows.go +++ b/nameservers_windows.go @@ -9,7 +9,11 @@ import ( "golang.org/x/sys/windows" ) -func nameservers() []string { +func dnsFns() []dnsFn { + return []dnsFn{dnsFromAdapter} +} + +func dnsFromAdapter() []string { aas, err := winipcfg.GetAdaptersAddresses(syscall.AF_UNSPEC, winipcfg.GAAFlagIncludeGateways|winipcfg.GAAFlagIncludePrefix) if err != nil { return nil @@ -42,7 +46,7 @@ func nameservers() []string { return } seen[ip.String()] = true - ns = append(ns, net.JoinHostPort(ip.String(), "53")) + ns = append(ns, ip.String()) } for _, aa := range aas { for dns := aa.FirstDNSServerAddress; dns != nil; dns = dns.Next { From c94e1b02d2bcf3552f4375f10295562f87015def Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 25 Mar 2023 00:44:27 +0700 Subject: [PATCH 08/11] all: supports multiple protocols for no config mode Updates #78 --- cmd/ctrld/cli.go | 18 ++++++++++++------ config.go | 24 ++++++++++++++++++++++++ resolver_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 938860a7..4123e285 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -593,18 +593,24 @@ func processNoConfigFlags(noConfigStart bool) { } processListenFlag() + endpointAndTyp := func(endpoint string) (string, string) { + typ := ctrld.ResolverTypeFromEndpoint(endpoint) + return strings.TrimPrefix(endpoint, "quic://"), typ + } + pEndpoint, pType := endpointAndTyp(primaryUpstream) upstream := map[string]*ctrld.UpstreamConfig{ "0": { - Name: primaryUpstream, - Endpoint: primaryUpstream, - Type: ctrld.ResolverTypeDOH, + Name: pEndpoint, + Endpoint: pEndpoint, + Type: pType, }, } if secondaryUpstream != "" { + sEndpoint, sType := endpointAndTyp(secondaryUpstream) upstream["1"] = &ctrld.UpstreamConfig{ - Name: secondaryUpstream, - Endpoint: secondaryUpstream, - Type: ctrld.ResolverTypeLegacy, + Name: sEndpoint, + Endpoint: sEndpoint, + Type: sType, } rules := make([]ctrld.Rule, 0, len(domains)) for _, domain := range domains { diff --git a/config.go b/config.go index 2cb0e38f..fb979017 100644 --- a/config.go +++ b/config.go @@ -380,3 +380,27 @@ func availableNameservers() []string { } return nss[:n] } + +// ResolverTypeFromEndpoint tries guessing the resolver type with a given endpoint +// using following rules: +// +// - If endpoint is an IP address -> ResolverTypeLegacy +// - If endpoint starts with "https://" -> ResolverTypeDOH +// - If endpoint starts with "quic://" -> ResolverTypeDOQ +// - For anything else -> ResolverTypeDOT +func ResolverTypeFromEndpoint(endpoint string) string { + switch { + case strings.HasPrefix(endpoint, "https://"): + return ResolverTypeDOH + case strings.HasPrefix(endpoint, "quic://"): + return ResolverTypeDOQ + } + host := endpoint + if strings.Contains(endpoint, ":") { + host, _, _ = net.SplitHostPort(host) + } + if ip := net.ParseIP(host); ip != nil { + return ResolverTypeLegacy + } + return ResolverTypeDOT +} diff --git a/resolver_test.go b/resolver_test.go index a5a93c4b..531570bc 100644 --- a/resolver_test.go +++ b/resolver_test.go @@ -27,3 +27,27 @@ func Test_osResolver_Resolve(t *testing.T) { case <-ctx.Done(): } } + +func Test_upstreamTypeFromEndpoint(t *testing.T) { + tests := []struct { + name string + endpoint string + resolverType string + }{ + {"doh", "https://freedns.controld.com/p2", ResolverTypeDOH}, + {"doq", "quic://p2.freedns.controld.com", ResolverTypeDOQ}, + {"dot", "p2.freedns.controld.com", ResolverTypeDOT}, + {"legacy", "8.8.8.8:53", ResolverTypeLegacy}, + {"legacy ipv6", "[2404:6800:4005:809::200e]:53", ResolverTypeLegacy}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if rt := ResolverTypeFromEndpoint(tc.endpoint); rt != tc.resolverType { + t.Errorf("mismatch, want: %s, got: %s", tc.resolverType, rt) + } + }) + } +} From 8869e33a20cdd5b7f32ebe3fff6fa4d4650cea36 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 31 Mar 2023 10:25:37 +0700 Subject: [PATCH 09/11] Inject version and commit during goreleaser build --- .goreleaser-darwin.yaml | 2 ++ .goreleaser-qf.yaml | 2 ++ .goreleaser.yaml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.goreleaser-darwin.yaml b/.goreleaser-darwin.yaml index 57b84c06..5fa9605c 100644 --- a/.goreleaser-darwin.yaml +++ b/.goreleaser-darwin.yaml @@ -9,6 +9,8 @@ builds: - -trimpath ldflags: - -s -w + - -X main.version={{.Version}} + - -X main.commit={{.Commit}} goos: - darwin goarch: diff --git a/.goreleaser-qf.yaml b/.goreleaser-qf.yaml index 9e72ed40..5fc359e0 100644 --- a/.goreleaser-qf.yaml +++ b/.goreleaser-qf.yaml @@ -9,6 +9,8 @@ builds: - -trimpath ldflags: - -s -w + - -X main.version={{.Version}} + - -X main.commit={{.Commit}} goos: - darwin - linux diff --git a/.goreleaser.yaml b/.goreleaser.yaml index f010ff95..a08f8d15 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -9,6 +9,8 @@ builds: - -trimpath ldflags: - -s -w + - -X main.version={{.Version}} + - -X main.commit={{.Commit}} goos: - linux - freebsd From 95dbf71939332527a454ad6b8a4c58a7ec9e3dff Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 31 Mar 2023 10:38:16 +0700 Subject: [PATCH 10/11] Upgrage tailscale.com for fixing security issue --- go.mod | 18 ++++----- go.sum | 39 ++++++++++--------- internal/dns/nm.go | 4 +- internal/dns/resolvconffile/resolvconffile.go | 5 +-- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index da86f6d2..56918c7a 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,8 @@ require ( github.com/godbus/dbus/v5 v5.0.6 github.com/hashicorp/golang-lru/v2 v2.0.1 github.com/illarion/gonotify v1.0.1 - github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e + github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 + github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 github.com/kardianos/service v1.2.1 github.com/miekg/dns v1.1.50 github.com/pelletier/go-toml/v2 v2.0.6 @@ -24,7 +25,7 @@ require ( golang.org/x/sync v0.1.0 golang.org/x/sys v0.5.0 golang.zx2c4.com/wireguard/windows v0.5.3 - tailscale.com v1.34.1 + tailscale.com v1.38.3 ) require ( @@ -38,7 +39,6 @@ require ( github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/josharian/native v1.0.0 // indirect github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b // indirect github.com/kr/pretty v0.3.0 // indirect github.com/kr/text v0.2.0 // indirect @@ -47,9 +47,9 @@ require ( github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 // indirect - github.com/mdlayher/netlink v1.6.0 // indirect + github.com/mdlayher/netlink v1.7.1 // indirect github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 // indirect - github.com/mdlayher/socket v0.2.3 // indirect + github.com/mdlayher/socket v0.4.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/onsi/ginkgo/v2 v2.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect @@ -64,13 +64,13 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.1 // indirect - github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect + github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect - golang.org/x/crypto v0.4.0 // indirect + golang.org/x/crypto v0.6.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect - golang.org/x/mod v0.6.0 // indirect + golang.org/x/mod v0.7.0 // indirect golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.2.0 // indirect + golang.org/x/tools v0.4.1-0.20221208213631-3f74d914ae6d // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 5b863e74..c3cb4be2 100644 --- a/go.sum +++ b/go.sum @@ -165,10 +165,12 @@ github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwso github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e h1:IQpunlq7T+NiJJMO7ODYV2YWBiv/KnObR3gofX0mWOo= -github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= +github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg= +github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE= github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +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= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= @@ -204,14 +206,15 @@ github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= -github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0= github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= +github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg= +github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= -github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= -github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= +github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw= +github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -277,9 +280,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= -github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME= -github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= +github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww= +github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -301,8 +303,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= -golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -339,8 +341,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -450,7 +452,6 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -458,7 +459,9 @@ golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -526,8 +529,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.1-0.20221208213631-3f74d914ae6d h1:9ZNWAi4CYhNv60mXGgAncgq7SGc5qa7C8VZV8Tg7Ggs= +golang.org/x/tools v0.4.1-0.20221208213631-3f74d914ae6d/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -647,5 +650,5 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 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= -tailscale.com v1.34.1 h1:tqm9Ww4ltyYp3IPe7vCGch6tT6j5G/WXPQ6BrVZ6pdI= -tailscale.com v1.34.1/go.mod h1:ZsBP7rjzzB2rp+UCOumr9DAe0EQ6OPivwSXcz/BrekQ= +tailscale.com v1.38.3 h1:2aX3+u0Re8QcN6nq7zf9Aa4ZCR2Nf6Imv3isqdQrb58= +tailscale.com v1.38.3/go.mod h1:UWLQxcd8dz+lds2I+HpfXSruHrvXM1j4zd4zdx86t7w= diff --git a/internal/dns/nm.go b/internal/dns/nm.go index 03e6f4a5..a8ea9231 100644 --- a/internal/dns/nm.go +++ b/internal/dns/nm.go @@ -14,8 +14,8 @@ import ( "time" "github.com/godbus/dbus/v5" + "github.com/josharian/native" "tailscale.com/util/dnsname" - "tailscale.com/util/endian" ) const ( @@ -131,7 +131,7 @@ func (m *nmManager) trySet(ctx context.Context, config OSConfig) error { for _, ip := range config.Nameservers { b := ip.As16() if ip.Is4() { - dnsv4 = append(dnsv4, endian.Native.Uint32(b[12:])) + dnsv4 = append(dnsv4, native.Endian.Uint32(b[12:])) } else { dnsv6 = append(dnsv6, b[:]) } diff --git a/internal/dns/resolvconffile/resolvconffile.go b/internal/dns/resolvconffile/resolvconffile.go index 55728919..b91354da 100644 --- a/internal/dns/resolvconffile/resolvconffile.go +++ b/internal/dns/resolvconffile/resolvconffile.go @@ -15,7 +15,6 @@ import ( "strings" "tailscale.com/util/dnsname" - "tailscale.com/util/strs" ) // Path is the canonical location of resolv.conf. @@ -63,7 +62,7 @@ func Parse(r io.Reader) (*Config, error) { line, _, _ = strings.Cut(line, "#") // remove any comments line = strings.TrimSpace(line) - if s, ok := strs.CutPrefix(line, "nameserver"); ok { + if s, ok := strings.CutPrefix(line, "nameserver"); ok { nameserver := strings.TrimSpace(s) if len(nameserver) == len(s) { return nil, fmt.Errorf("missing space after \"nameserver\" in %q", line) @@ -76,7 +75,7 @@ func Parse(r io.Reader) (*Config, error) { continue } - if s, ok := strs.CutPrefix(line, "search"); ok { + if s, ok := strings.CutPrefix(line, "search"); ok { domains := strings.TrimSpace(s) if len(domains) == len(s) { // No leading space?! From a77a924320ec81140f78e70c609d23c941c44773 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 31 Mar 2023 10:38:53 +0700 Subject: [PATCH 11/11] Require go1.20 for building ctrld --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 56918c7a..16242602 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Control-D-Inc/ctrld -go 1.19 +go 1.20 require ( github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534