From ebcc5455477361545fa1e13235bb5e1e97578008 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 17 Dec 2022 23:03:48 +0700 Subject: [PATCH 01/69] all: improving DoH query performance Previously, for each DoH query, we use the net/http default transport with DialContext function re-assigned. This has some problems: - The first query to server will be slow. - Using the default transport for all upstreams can have race condition in case of multiple queries to multiple DoH upstreams This commit fixes those issues, by initializing a separate transport for each DoH upstream, the warming up the transport by doing a test query. Later queries can take the advantage and re-use the connection. --- cmd/ctrld/prog.go | 2 +- config.go | 53 +++++++++++++++++++++++++++++++++++++++++------ doh.go | 21 ++++++------------- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index e67e02c..76af611 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -48,7 +48,6 @@ func (p *prog) run() { for n := range p.cfg.Upstream { uc := p.cfg.Upstream[n] uc.Init() - if uc.BootstrapIP == "" { // resolve it manually and set the bootstrap ip c := new(dns.Client) @@ -71,6 +70,7 @@ func (p *prog) run() { } } } + uc.SetupTransport() } for listenerNum := range p.cfg.Listener { diff --git a/config.go b/config.go index f660006..faf8720 100644 --- a/config.go +++ b/config.go @@ -1,12 +1,17 @@ package ctrld import ( + "context" + "fmt" "net" + "net/http" "net/url" "strings" + "time" "github.com/Control-D-Inc/ctrld/internal/dnsrcode" "github.com/go-playground/validator/v10" + "github.com/miekg/dns" "github.com/spf13/viper" ) @@ -75,12 +80,13 @@ type NetworkConfig struct { // UpstreamConfig specifies configuration for upstreams that ctrld will forward requests to. type UpstreamConfig struct { - Name string `mapstructure:"name" toml:"name"` - Type string `mapstructure:"type" toml:"type" validate:"oneof=doh doh3 dot doq os legacy"` - Endpoint string `mapstructure:"endpoint" toml:"endpoint" validate:"required_unless=Type os"` - BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip"` - Domain string `mapstructure:"-" toml:"-"` - Timeout int `mapstructure:"timeout" toml:"timeout" validate:"gte=0"` + Name string `mapstructure:"name" toml:"name"` + Type string `mapstructure:"type" toml:"type" validate:"oneof=doh doh3 dot doq os legacy"` + Endpoint string `mapstructure:"endpoint" toml:"endpoint" validate:"required_unless=Type os"` + BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip"` + Domain string `mapstructure:"-" toml:"-"` + Timeout int `mapstructure:"timeout" toml:"timeout" validate:"gte=0"` + transport *http.Transport `mapstructure:"-" toml:"-"` } // ListenerConfig specifies the networks configuration that ctrld will run on. @@ -125,6 +131,41 @@ func (uc *UpstreamConfig) Init() { } } +// SetupTransport initializes the network transport used to connect to upstream server. +// For now, only DoH upstream is supported. +func (uc *UpstreamConfig) SetupTransport() { + if uc.Type != resolverTypeDOH { + return + } + uc.transport = http.DefaultTransport.(*http.Transport).Clone() + uc.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + dialer := &net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 10 * time.Second, + } + Log(ctx, ProxyLog.Debug(), "debug dial context %s - %s - %s", addr, network, bootstrapDNS) + // if we have a bootstrap ip set, use it to avoid DNS lookup + if uc.BootstrapIP != "" && addr == fmt.Sprintf("%s:443", uc.Domain) { + addr = fmt.Sprintf("%s:443", uc.BootstrapIP) + Log(ctx, ProxyLog.Debug(), "sending doh request to: %s", addr) + } + return dialer.DialContext(ctx, network, addr) + } + + // Warming up the transport by querying a test packet. + dnsResolver, err := NewResolver(uc) + if err != nil { + ProxyLog.Error().Err(err).Msgf("failed to create resolver for upstream: %s", uc.Name) + return + } + msg := new(dns.Msg) + msg.SetQuestion(".", dns.TypeNS) + msg.MsgHdr.RecursionDesired = true + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _, _ = dnsResolver.Resolve(ctx, msg) +} + // Init initialized necessary values for an ListenerConfig. func (lc *ListenerConfig) Init() { if lc.Policy != nil { diff --git a/doh.go b/doh.go index f3e3810..78adbfa 100644 --- a/doh.go +++ b/doh.go @@ -8,7 +8,6 @@ import ( "io" "net" "net/http" - "time" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/http3" @@ -16,20 +15,11 @@ import ( ) func newDohResolver(uc *UpstreamConfig) *dohResolver { - http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - dialer := &net.Dialer{ - Timeout: 10 * time.Second, - KeepAlive: 10 * time.Second, - } - Log(ctx, ProxyLog.Debug(), "debug dial context %s - %s - %s", addr, network, bootstrapDNS) - // if we have a bootstrap ip set, use it to avoid DNS lookup - if uc.BootstrapIP != "" && addr == fmt.Sprintf("%s:443", uc.Domain) { - addr = fmt.Sprintf("%s:443", uc.BootstrapIP) - Log(ctx, ProxyLog.Debug(), "sending doh request to: %s", addr) - } - return dialer.DialContext(ctx, network, addr) + r := &dohResolver{ + endpoint: uc.Endpoint, + isDoH3: uc.Type == resolverTypeDOH3, + transport: uc.transport, } - r := &dohResolver{endpoint: uc.Endpoint, isDoH3: uc.Type == resolverTypeDOH3} if r.isDoH3 { r.doh3DialFunc = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { host := addr @@ -57,6 +47,7 @@ type dohResolver struct { endpoint string isDoH3 bool doh3DialFunc func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) + transport *http.Transport } func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { @@ -73,7 +64,7 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro req.Header.Set("Content-Type", "application/dns-message") req.Header.Set("Accept", "application/dns-message") - c := http.Client{} + c := http.Client{Transport: r.transport} if r.isDoH3 { c.Transport = &http3.RoundTripper{} c.Transport.(*http3.RoundTripper).Dial = r.doh3DialFunc From a7ae6c98531db14efd405283e41acaf00f2b9033 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sun, 18 Dec 2022 00:51:34 +0700 Subject: [PATCH 02/69] all: support ipv6 for upstream bootstrap ip --- cmd/ctrld/net.go | 23 +++++++++++++++++++++++ cmd/ctrld/prog.go | 45 +++++++++++++++++++++++++++++++-------------- config.go | 7 ++++--- 3 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 cmd/ctrld/net.go diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go new file mode 100644 index 0000000..76265d8 --- /dev/null +++ b/cmd/ctrld/net.go @@ -0,0 +1,23 @@ +package main + +import ( + "net" + "sync" +) + +var ( + stackOnce sync.Once + ipv6Enabled bool +) + +func probeStack() { + if ln, err := net.Listen("tcp6", "[::]:0"); err == nil { + ln.Close() + ipv6Enabled = true + } +} + +func supportsIPv6() bool { + stackOnce.Do(probeStack) + return ipv6Enabled +} diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 76af611..c7dce9d 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -51,23 +51,40 @@ func (p *prog) run() { if uc.BootstrapIP == "" { // resolve it manually and set the bootstrap ip c := new(dns.Client) - m := new(dns.Msg) - m.SetQuestion(uc.Domain+".", dns.TypeA) - m.RecursionDesired = true - r, _, err := c.Exchange(m, net.JoinHostPort(bootstrapDNS, "53")) - if err != nil { - proxyLog.Error().Err(err).Msgf("could not resolve domain %s for upstream.%s", uc.Domain, n) - } else { + for _, dnsType := range []uint16{dns.TypeAAAA, dns.TypeA} { + if !supportsIPv6() && dnsType == dns.TypeAAAA { + continue + } + m := new(dns.Msg) + m.SetQuestion(uc.Domain+".", dnsType) + m.RecursionDesired = true + r, _, err := c.Exchange(m, net.JoinHostPort(bootstrapDNS, "53")) + if err != nil { + proxyLog.Error().Err(err).Msgf("could not resolve domain %s for upstream.%s", uc.Domain, n) + continue + } if r.Rcode != dns.RcodeSuccess { proxyLog.Error().Msgf("could not resolve domain return code: %d, upstream.%s", r.Rcode, n) - } else { - for _, a := range r.Answer { - if ar, ok := a.(*dns.A); ok { - uc.BootstrapIP = ar.A.String() - proxyLog.Info().Str("bootstrap_ip", uc.BootstrapIP).Msgf("Setting bootstrap IP for upstream.%s", n) - } - } + continue } + if len(r.Answer) == 0 { + continue + } + for _, a := range r.Answer { + switch ar := a.(type) { + case *dns.A: + uc.BootstrapIP = ar.A.String() + case *dns.AAAA: + uc.BootstrapIP = ar.AAAA.String() + default: + continue + } + proxyLog.Info().Str("bootstrap_ip", uc.BootstrapIP).Msgf("Setting bootstrap IP for upstream.%s", n) + // Stop if we reached here, because we got the bootstrap IP from r.Answer. + break + } + // If we reached here, uc.BootstrapIP was set, nothing to do anymore. + break } } uc.SetupTransport() diff --git a/config.go b/config.go index faf8720..1d2fd20 100644 --- a/config.go +++ b/config.go @@ -2,7 +2,6 @@ package ctrld import ( "context" - "fmt" "net" "net/http" "net/url" @@ -145,8 +144,10 @@ func (uc *UpstreamConfig) SetupTransport() { } Log(ctx, ProxyLog.Debug(), "debug dial context %s - %s - %s", addr, network, bootstrapDNS) // if we have a bootstrap ip set, use it to avoid DNS lookup - if uc.BootstrapIP != "" && addr == fmt.Sprintf("%s:443", uc.Domain) { - addr = fmt.Sprintf("%s:443", uc.BootstrapIP) + if uc.BootstrapIP != "" { + if _, port, _ := net.SplitHostPort(addr); port != "" { + addr = net.JoinHostPort(uc.BootstrapIP, port) + } Log(ctx, ProxyLog.Debug(), "sending doh request to: %s", addr) } return dialer.DialContext(ctx, network, addr) From 837d3195ca6407d27f24a71391db654c112e31a6 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 19 Dec 2022 20:48:12 +0700 Subject: [PATCH 03/69] cmd/ctrld: rework "verbose" flag This commit changes "verbose" flag from boolean to count flag, so we can specify the flag multiple times to indicate different logging output: - No "-v": no query logging except startup/listeners - "-v" : query logging enabled - "-vv" : debug level logging enabled --- cmd/ctrld/cli.go | 7 ++++++- cmd/ctrld/main.go | 26 ++++++++++++++++---------- cmd/ctrld/prog.go | 6 +++--- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 8ef9b5d..6eb65f6 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -31,7 +31,12 @@ func initCLI() { Short: "Running Control-D DNS proxy server", Version: "1.0.1", } - rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose log output") + rootCmd.PersistentFlags().CountVarP( + &verbose, + "verbose", + "v", + `verbose log output, "-v" means query logging enabled, "-vv" means debug level logging enabled`, + ) runCmd := &cobra.Command{ Use: "run", diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 7b539eb..b916ee7 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -15,7 +15,7 @@ var ( configPath string daemon bool cfg ctrld.Config - verbose bool + verbose int bootstrapDNS = "76.76.2.0" @@ -42,21 +42,27 @@ func initLogging() { writers = append(writers, logFile) } zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs - if verbose || isLog { - consoleWriter := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { - w.TimeFormat = time.StampMilli - }) - writers = append(writers, consoleWriter) - multi := zerolog.MultiLevelWriter(writers...) - mainLog = mainLog.Output(multi).With().Timestamp().Str("prefix", "main").Logger() + consoleWriter := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { + w.TimeFormat = time.StampMilli + }) + writers = append(writers, consoleWriter) + multi := zerolog.MultiLevelWriter(writers...) + mainLog = mainLog.Output(multi).With().Timestamp().Str("prefix", "main").Logger() + if verbose > 0 || isLog { proxyLog = proxyLog.Output(multi).With().Timestamp().Logger() // TODO: find a better way. ctrld.ProxyLog = proxyLog } - if cfg.Service.LogLevel == "" { + + zerolog.SetGlobalLevel(zerolog.InfoLevel) + logLevel := cfg.Service.LogLevel + if verbose > 1 { + logLevel = "debug" + } + if logLevel == "" { return } - level, err := zerolog.ParseLevel(cfg.Service.LogLevel) + level, err := zerolog.ParseLevel(logLevel) if err != nil { mainLog.Warn().Err(err).Msg("could not set log level") return diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index c7dce9d..49b6c0e 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -79,7 +79,7 @@ func (p *prog) run() { default: continue } - proxyLog.Info().Str("bootstrap_ip", uc.BootstrapIP).Msgf("Setting bootstrap IP for upstream.%s", n) + mainLog.Info().Str("bootstrap_ip", uc.BootstrapIP).Msgf("Setting bootstrap IP for upstream.%s", n) // Stop if we reached here, because we got the bootstrap IP from r.Answer. break } @@ -101,7 +101,7 @@ func (p *prog) run() { return } addr := net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port)) - proxyLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, addr) + mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, addr) err := p.serveUDP(listenerNum) if err != nil && !defaultConfigWritten { proxyLog.Error().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum) @@ -130,7 +130,7 @@ func (p *prog) run() { }, }) writeConfigFile() - proxyLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, pc.LocalAddr()) + mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, pc.LocalAddr()) // There can be a race between closing the listener and start our own UDP server, but it's // rare, and we only do this once, so let conservative here. if err := pc.Close(); err != nil { From b03aa39b8314194ad79ccab3f31c02c8c5fcdaed Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 20 Dec 2022 01:25:52 +0700 Subject: [PATCH 04/69] all: support ipv6 for doh3 upstream bootstrap ip We did it for doh, but the doh3 transport also needs to be changed. --- config.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++-------- doh.go | 42 ++++++++----------------------------- 2 files changed, 62 insertions(+), 42 deletions(-) diff --git a/config.go b/config.go index 1d2fd20..95d05ec 100644 --- a/config.go +++ b/config.go @@ -2,6 +2,7 @@ package ctrld import ( "context" + "crypto/tls" "net" "net/http" "net/url" @@ -10,6 +11,8 @@ import ( "github.com/Control-D-Inc/ctrld/internal/dnsrcode" "github.com/go-playground/validator/v10" + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/http3" "github.com/miekg/dns" "github.com/spf13/viper" ) @@ -79,13 +82,14 @@ type NetworkConfig struct { // UpstreamConfig specifies configuration for upstreams that ctrld will forward requests to. type UpstreamConfig struct { - Name string `mapstructure:"name" toml:"name"` - Type string `mapstructure:"type" toml:"type" validate:"oneof=doh doh3 dot doq os legacy"` - Endpoint string `mapstructure:"endpoint" toml:"endpoint" validate:"required_unless=Type os"` - BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip"` - Domain string `mapstructure:"-" toml:"-"` - Timeout int `mapstructure:"timeout" toml:"timeout" validate:"gte=0"` - transport *http.Transport `mapstructure:"-" toml:"-"` + Name string `mapstructure:"name" toml:"name"` + Type string `mapstructure:"type" toml:"type" validate:"oneof=doh doh3 dot doq os legacy"` + Endpoint string `mapstructure:"endpoint" toml:"endpoint" validate:"required_unless=Type os"` + BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip"` + Domain string `mapstructure:"-" toml:"-"` + Timeout int `mapstructure:"timeout" toml:"timeout" validate:"gte=0"` + transport *http.Transport `mapstructure:"-" toml:"-"` + http3RoundTripper *http3.RoundTripper `mapstructure:"-" toml:"-"` } // ListenerConfig specifies the networks configuration that ctrld will run on. @@ -133,9 +137,15 @@ func (uc *UpstreamConfig) Init() { // SetupTransport initializes the network transport used to connect to upstream server. // For now, only DoH upstream is supported. func (uc *UpstreamConfig) SetupTransport() { - if uc.Type != resolverTypeDOH { - return + switch uc.Type { + case resolverTypeDOH: + uc.setupDOHTransport() + case resolverTypeDOH3: + uc.setupDOH3Transport() } +} + +func (uc *UpstreamConfig) setupDOHTransport() { uc.transport = http.DefaultTransport.(*http.Transport).Clone() uc.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { dialer := &net.Dialer{ @@ -153,6 +163,40 @@ func (uc *UpstreamConfig) SetupTransport() { return dialer.DialContext(ctx, network, addr) } + uc.pingUpstream() +} + +func (uc *UpstreamConfig) setupDOH3Transport() { + uc.http3RoundTripper = &http3.RoundTripper{} + uc.http3RoundTripper.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + host := addr + ProxyLog.Debug().Msgf("debug dial context D0H3 %s - %s", addr, bootstrapDNS) + // if we have a bootstrap ip set, use it to avoid DNS lookup + if uc.BootstrapIP != "" { + if _, port, _ := net.SplitHostPort(addr); port != "" { + addr = net.JoinHostPort(uc.BootstrapIP, port) + } + ProxyLog.Debug().Msgf("sending doh3 request to: %s", addr) + } + remoteAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + localAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0} + if strings.Index(uc.BootstrapIP, ":") > -1 { + localAddr = &net.UDPAddr{IP: net.IPv6zero, Port: 0} + } + udpConn, err := net.ListenUDP("udp", localAddr) + if err != nil { + return nil, err + } + return quic.DialEarlyContext(ctx, udpConn, remoteAddr, host, tlsCfg, cfg) + } + + uc.pingUpstream() +} + +func (uc *UpstreamConfig) pingUpstream() { // Warming up the transport by querying a test packet. dnsResolver, err := NewResolver(uc) if err != nil { diff --git a/doh.go b/doh.go index 78adbfa..511e28c 100644 --- a/doh.go +++ b/doh.go @@ -2,52 +2,30 @@ package ctrld import ( "context" - "crypto/tls" "encoding/base64" "fmt" "io" - "net" "net/http" - "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/http3" "github.com/miekg/dns" ) func newDohResolver(uc *UpstreamConfig) *dohResolver { r := &dohResolver{ - endpoint: uc.Endpoint, - isDoH3: uc.Type == resolverTypeDOH3, - transport: uc.transport, - } - if r.isDoH3 { - r.doh3DialFunc = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { - host := addr - Log(ctx, ProxyLog.Debug(), "debug dial context D0H3 %s - %s", addr, bootstrapDNS) - // if we have a bootstrap ip set, use it to avoid DNS lookup - if uc.BootstrapIP != "" && addr == fmt.Sprintf("%s:443", uc.Domain) { - addr = fmt.Sprintf("%s:443", uc.BootstrapIP) - Log(ctx, ProxyLog.Debug(), "sending doh3 request to: %s", addr) - } - remoteAddr, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, err - } - udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) - if err != nil { - return nil, err - } - return quic.DialEarlyContext(ctx, udpConn, remoteAddr, host, tlsCfg, cfg) - } + endpoint: uc.Endpoint, + isDoH3: uc.Type == resolverTypeDOH3, + transport: uc.transport, + http3RoundTripper: uc.http3RoundTripper, } return r } type dohResolver struct { - endpoint string - isDoH3 bool - doh3DialFunc func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) - transport *http.Transport + endpoint string + isDoH3 bool + transport *http.Transport + http3RoundTripper *http3.RoundTripper } func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { @@ -66,9 +44,7 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro c := http.Client{Transport: r.transport} if r.isDoH3 { - c.Transport = &http3.RoundTripper{} - c.Transport.(*http3.RoundTripper).Dial = r.doh3DialFunc - defer c.Transport.(*http3.RoundTripper).Close() + c.Transport = r.http3RoundTripper } resp, err := c.Do(req) if err != nil { From a6b3c4a7570fc78daf29380da905d5e8e03a8462 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 20 Dec 2022 03:15:43 +0700 Subject: [PATCH 05/69] Don't set default log level in config file --- config.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/config.go b/config.go index 95d05ec..c8ba10d 100644 --- a/config.go +++ b/config.go @@ -24,9 +24,6 @@ func InitConfig(v *viper.Viper, name string) { v.AddConfigPath("$HOME/.ctrld") v.AddConfigPath(".") - v.SetDefault("service", ServiceConfig{ - LogLevel: "info", - }) v.SetDefault("listener", map[string]*ListenerConfig{ "0": { IP: "127.0.0.1", From fa3c3e8a2977841ca7263b5dff3db96e995536d2 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 21 Dec 2022 00:10:14 +0700 Subject: [PATCH 06/69] Close http3 roundtripper when error occurred For http3, if the network were down, the quic transport needs to be closed, so the transport can create new connection when network up. --- doh.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doh.go b/doh.go index 511e28c..546f8c7 100644 --- a/doh.go +++ b/doh.go @@ -48,6 +48,9 @@ func (r *dohResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro } resp, err := c.Do(req) if err != nil { + if r.isDoH3 { + r.http3RoundTripper.Close() + } return nil, fmt.Errorf("could not perform request: %w", err) } defer resp.Body.Close() From 30fefe7ab93bd17f2ed578a3eec6eb945891dc47 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 21 Dec 2022 00:29:04 +0700 Subject: [PATCH 07/69] all: add local caching This commit adds config params to enable local DNS response caching and control its behavior, allow tweaking the cache size, ttl override and serving stale response. --- cmd/ctrld/dns_proxy.go | 65 ++++++++++++++++++++++++++++++++ cmd/ctrld/prog.go | 12 +++++- config.go | 19 +++++----- docs/config.md | 27 ++++++++++++++ go.mod | 1 + go.sum | 2 + internal/dnscache/cache.go | 76 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 internal/dnscache/cache.go diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 9d14c66..b1df992 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -13,8 +13,11 @@ import ( "github.com/miekg/dns" "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/dnscache" ) +const staleTTL = 60 * time.Second + func (p *prog) serveUDP(listenerNum string) error { listenerConfig := p.cfg.Listener[listenerNum] // make sure ip is allocated @@ -123,6 +126,22 @@ func (p *prog) upstreamFor(ctx context.Context, defaultUpstreamNum string, lc *c } func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []int, msg *dns.Msg) *dns.Msg { + var staleAnswer *dns.Msg + serveStaleCache := p.cache != nil && p.cfg.Service.CacheServeStale + // Inverse query should not be cached: https://www.rfc-editor.org/rfc/rfc1035#section-7.4 + if p.cache != nil && msg.Question[0].Qtype != dns.TypePTR { + if cachedValue := p.cache.Get(dnscache.NewKey(msg)); cachedValue != nil { + answer := cachedValue.Msg.Copy() + answer.SetReply(msg) + now := time.Now() + if cachedValue.Expire.After(now) { + ctrld.Log(ctx, proxyLog.Debug(), "hit cached response") + setCachedAnswerTTL(answer, now, cachedValue.Expire) + return answer + } + staleAnswer = answer + } + } upstreamConfigs := p.upstreamConfigsFromUpstreamNumbers(upstreams) resolve := func(n int, upstreamConfig *ctrld.UpstreamConfig, msg *dns.Msg) *dns.Msg { ctrld.Log(ctx, proxyLog.Debug(), "sending query to %s: %s", upstreams[n], upstreamConfig.Name) @@ -148,12 +167,29 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i for n, upstreamConfig := range upstreamConfigs { answer := resolve(n, upstreamConfig, msg) if answer == nil { + if serveStaleCache && staleAnswer != nil { + ctrld.Log(ctx, proxyLog.Debug(), "serving stale cached response") + now := time.Now() + setCachedAnswerTTL(staleAnswer, now, now.Add(staleTTL)) + return staleAnswer + } continue } if answer.Rcode != dns.RcodeSuccess && len(upstreamConfigs) > 1 && containRcode(failoverRcodes, answer.Rcode) { ctrld.Log(ctx, proxyLog.Debug(), "failover rcode matched, process to next upstream") continue } + if p.cache != nil { + ttl := ttlFromMsg(answer) + now := time.Now() + expired := now.Add(time.Duration(ttl) * time.Second) + if cachedTTL := p.cfg.Service.CacheTTLOverride; cachedTTL > 0 { + expired = now.Add(time.Duration(cachedTTL) * time.Second) + } + setCachedAnswerTTL(answer, now, expired) + p.cache.Add(dnscache.NewKey(msg), dnscache.NewValue(answer, expired)) + ctrld.Log(ctx, proxyLog.Debug(), "add cached response") + } return answer } ctrld.Log(ctx, proxyLog.Error(), "all upstreams failed") @@ -229,6 +265,35 @@ func containRcode(rcodes []int, rcode int) bool { return false } +func setCachedAnswerTTL(answer *dns.Msg, now, expiredTime time.Time) { + ttl := uint32(expiredTime.Sub(now).Seconds()) + if ttl < 0 { + return + } + + for _, rr := range answer.Answer { + rr.Header().Ttl = ttl + } + for _, rr := range answer.Ns { + rr.Header().Ttl = ttl + } + for _, rr := range answer.Extra { + if rr.Header().Rrtype != dns.TypeOPT { + rr.Header().Ttl = ttl + } + } +} + +func ttlFromMsg(msg *dns.Msg) uint32 { + for _, rr := range msg.Answer { + return rr.Header().Ttl + } + for _, rr := range msg.Ns { + return rr.Header().Ttl + } + return 0 +} + var osUpstreamConfig = &ctrld.UpstreamConfig{ Name: "OS resolver", Type: "os", diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 49b6c0e..9a889be 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -12,6 +12,7 @@ import ( "github.com/miekg/dns" "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/dnscache" ) var errWindowsAddrInUse = syscall.Errno(0x2740) @@ -22,7 +23,8 @@ var svcConfig = &service.Config{ } type prog struct { - cfg *ctrld.Config + cfg *ctrld.Config + cache dnscache.Cacher } func (p *prog) Start(s service.Service) error { @@ -32,6 +34,14 @@ func (p *prog) Start(s service.Service) error { } func (p *prog) run() { + if p.cfg.Service.CacheEnable { + cacher, err := dnscache.NewLRUCache(p.cfg.Service.CacheSize) + if err != nil { + mainLog.Error().Err(err).Msg("failed to create cacher, caching is disabled") + } else { + p.cache = cacher + } + } var wg sync.WaitGroup wg.Add(len(p.cfg.Listener)) diff --git a/config.go b/config.go index c8ba10d..650b371 100644 --- a/config.go +++ b/config.go @@ -64,10 +64,14 @@ type Config struct { // ServiceConfig specifies the general ctrld config. type ServiceConfig struct { - LogLevel string `mapstructure:"log_level" toml:"log_level"` - LogPath string `mapstructure:"log_path" toml:"log_path"` - Daemon bool `mapstructure:"-" toml:"-"` - AllocateIP bool `mapstructure:"-" toml:"-"` + LogLevel string `mapstructure:"log_level" toml:"log_level"` + LogPath string `mapstructure:"log_path" toml:"log_path"` + CacheEnable bool `mapstructure:"cache_enable" toml:"cache_enable"` + CacheSize int `mapstructure:"cache_size" toml:"cache_size"` + CacheTTLOverride int `mapstructure:"cache_ttl_override" toml:"cache_ttl_override"` + CacheServeStale bool `mapstructure:"cache_serve_stale" toml:"cache_serve_stale"` + Daemon bool `mapstructure:"-" toml:"-"` + AllocateIP bool `mapstructure:"-" toml:"-"` } // NetworkConfig specifies configuration for networks where ctrld will handle requests. @@ -179,11 +183,8 @@ func (uc *UpstreamConfig) setupDOH3Transport() { if err != nil { return nil, err } - localAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0} - if strings.Index(uc.BootstrapIP, ":") > -1 { - localAddr = &net.UDPAddr{IP: net.IPv6zero, Port: 0} - } - udpConn, err := net.ListenUDP("udp", localAddr) + + udpConn, err := net.ListenUDP("udp", nil) if err != nil { return nil, err } diff --git a/docs/config.md b/docs/config.md index 44c125e..483f0d1 100644 --- a/docs/config.md +++ b/docs/config.md @@ -33,6 +33,8 @@ If no configuration files found, a default `config.toml` file will be created in [service] log_level = "info" log_path = "" + cache_enable = true + cache_size = 4096 [network.0] cidrs = ["0.0.0.0/0"] @@ -109,6 +111,31 @@ Relative or absolute path of the log file. - Type: string - Required: no +### cache_enable +When `cache_enable = true`, all resolved DNS query responses will be cached for duration of the upstream record TTLs. + +- Type: boolean +- Required: no + +### cache_size +The number of cached records, must be a positive integer. Tweaking this value with care depends on your available RAM. +A minimum value `4096` should be enough for most use cases. + +An invalid `cache_size` value will disable the cache, regardless of `cache_enable` value. + +- Type: int +- Required: no + +### cache_ttl_override +When `cache_ttl_override` is set to a positive value (in seconds), TTLs are overridden to this value and cached for this long. + +- Type: int +- Required: no + +### cache_serve_stale +When `cache_serve_stale = true`, in cases of upstream failures (upstreams not reachable), `ctrld` will keep serving +stale cached records (regardless of their TTLs) until upstream comes online. + The above config will look like this at query time. ``` diff --git a/go.mod b/go.mod index 67ec79a..c3b636b 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/go-playground/validator/v10 v10.11.1 + github.com/hashicorp/golang-lru/v2 v2.0.1 github.com/kardianos/service v1.2.1 github.com/lucas-clemente/quic-go v0.29.1 github.com/miekg/dns v1.1.50 diff --git a/go.sum b/go.sum index d971225..e123e72 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= +github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= diff --git a/internal/dnscache/cache.go b/internal/dnscache/cache.go new file mode 100644 index 0000000..efbd8e3 --- /dev/null +++ b/internal/dnscache/cache.go @@ -0,0 +1,76 @@ +package dnscache + +import ( + "strings" + "time" + + lru "github.com/hashicorp/golang-lru/v2" + "github.com/miekg/dns" +) + +// Cacher is the interface for caching DNS response. +type Cacher interface { + Get(Key) *Value + Add(Key, *Value) +} + +// Key is the caching key for DNS message. +type Key struct { + Qtype uint16 + Qclass uint16 + Name string +} + +type Value struct { + Expire time.Time + Msg *dns.Msg +} + +var _ Cacher = (*LRUCache)(nil) + +// LRUCache implements Cacher interface. +type LRUCache struct { + cacher *lru.ARCCache[Key, *Value] +} + +func (l *LRUCache) Get(key Key) *Value { + v, _ := l.cacher.Get(key) + return v +} + +func (l *LRUCache) Add(key Key, value *Value) { + l.cacher.Add(key, value) +} + +// NewLRUCache creates a new LRUCache instance with given size. +func NewLRUCache(size int) (*LRUCache, error) { + cacher, err := lru.NewARC[Key, *Value](size) + return &LRUCache{cacher: cacher}, err +} + +// NewKey creates a new cache key for given DNS message. +func NewKey(msg *dns.Msg) Key { + q := msg.Question[0] + return Key{Qtype: q.Qtype, Qclass: q.Qclass, Name: normalizeQname(q.Name)} +} + +// NewValue creates a new cache value for given DNS message. +func NewValue(msg *dns.Msg, expire time.Time) *Value { + return &Value{ + Expire: expire, + Msg: msg, + } +} + +func normalizeQname(name string) string { + var b strings.Builder + b.Grow(len(name)) + for i := 0; i < len(name); i++ { + c := name[i] + if 'A' <= c && c <= 'Z' { + c += 'a' - 'A' + } + b.WriteByte(c) + } + return b.String() +} From b93970ccfd891718e1f1e8deed258eefedb14112 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 21 Dec 2022 19:08:19 +0700 Subject: [PATCH 08/69] all: add CLI flags for no config start This commit adds the ability to start `ctrld` without config file. All necessary information can be provided via command line flags, either in base64 encoded config or launch arguments. --- cmd/ctrld/cli.go | 104 ++++++++++++++++++++++++++++++++++++++++++--- cmd/ctrld/main.go | 14 ++++-- config.go | 14 +++--- docs/basic_mode.md | 79 ++++++++++++++++++++++++++++++++++ doh.go | 2 +- resolver.go | 22 +++++----- 6 files changed, 205 insertions(+), 30 deletions(-) create mode 100644 docs/basic_mode.md diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 6eb65f6..602e3f5 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -1,11 +1,15 @@ package main import ( + "bytes" + "encoding/base64" "fmt" "log" + "net" "os" "os/exec" "runtime" + "strconv" "github.com/go-playground/validator/v10" "github.com/kardianos/service" @@ -38,6 +42,7 @@ func initCLI() { `verbose log output, "-v" means query logging enabled, "-vv" means debug level logging enabled`, ) + basicModeFlags := []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log"} runCmd := &cobra.Command{ Use: "run", Short: "Run the DNS proxy server", @@ -49,14 +54,18 @@ func initCLI() { if configPath != "" { v.SetConfigFile(configPath) } - if err := v.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - writeConfigFile() - defaultConfigWritten = true - } else { - log.Fatalf("failed to decode config file: %v", err) + noConfigStart := func() bool { + for _, flagName := range basicModeFlags { + if cmd.Flags().Lookup(flagName).Changed { + return true + } } - } + return false + }() + + readConfigFile(!noConfigStart && configBase64 == "") + readBase64Config() + processNoConfigFlags(noConfigStart) if err := v.Unmarshal(&cfg); err != nil { log.Fatalf("failed to unmarshal config: %v", err) } @@ -106,6 +115,12 @@ func initCLI() { } runCmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "Run as daemon") runCmd.Flags().StringVarP(&configPath, "config", "c", "", "Path to config file") + runCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "base64 encoded config") + runCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "listener address and port, in format: address:port") + runCmd.Flags().StringVarP(&primaryUpstream, "primary_upstream", "", "", "primary upstream endpoint") + runCmd.Flags().StringVarP(&secondaryUpstream, "secondary_upstream", "", "", "secondary upstream endpoint") + runCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "list of domain to apply in a split DNS policy") + runCmd.Flags().StringVarP(&logPath, "log", "", "", "path to log file") rootCmd.AddCommand(runCmd) @@ -125,3 +140,78 @@ func writeConfigFile() { log.Printf("failed to write config file: %v\n", err) } } + +func readConfigFile(configWritten bool) { + err := v.ReadInConfig() + if err == nil || !configWritten { + return + } + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + writeConfigFile() + defaultConfigWritten = true + return + } + log.Fatalf("failed to decode config file: %v", err) +} + +func readBase64Config() { + if configBase64 == "" { + return + } + configStr, err := base64.StdEncoding.DecodeString(configBase64) + if err != nil { + log.Fatalf("invalid base64 config: %v", err) + } + if err := v.ReadConfig(bytes.NewReader(configStr)); err != nil { + log.Fatalf("failed to read base64 config: %v", err) + } +} + +func processNoConfigFlags(noConfigStart bool) { + if !noConfigStart { + return + } + if listenAddress == "" || primaryUpstream == "" { + log.Fatal(`"listen" and "primary_upstream" flags must be set in no config mode`) + } + host, portStr, err := net.SplitHostPort(listenAddress) + if err != nil { + log.Fatalf("invalid listener address: %v", err) + } + port, err := strconv.Atoi(portStr) + if err != nil { + log.Fatalf("invalid port number: %v", err) + } + lc := &ctrld.ListenerConfig{ + IP: host, + Port: port, + } + v.Set("listener", map[string]*ctrld.ListenerConfig{ + "0": lc, + }) + + upstream := map[string]*ctrld.UpstreamConfig{ + "0": { + Name: primaryUpstream, + Endpoint: primaryUpstream, + Type: ctrld.ResolverTypeDOH, + }, + } + if secondaryUpstream != "" { + upstream["1"] = &ctrld.UpstreamConfig{ + Name: secondaryUpstream, + Endpoint: secondaryUpstream, + Type: ctrld.ResolverTypeLegacy, + } + rules := make([]ctrld.Rule, 0, len(domains)) + for _, domain := range domains { + rules = append(rules, ctrld.Rule{domain: []string{"upstream.1"}}) + } + lc.Policy = &ctrld.ListenerPolicyConfig{Name: "My Policy", Rules: rules} + } + v.Set("upstream", upstream) + + if logPath != "" { + v.Set("service", ctrld.ServiceConfig{LogLevel: "debug", LogPath: logPath}) + } +} diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index b916ee7..1fa5c22 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -12,10 +12,16 @@ import ( ) var ( - configPath string - daemon bool - cfg ctrld.Config - verbose int + configPath string + configBase64 string + daemon bool + listenAddress string + primaryUpstream string + secondaryUpstream string + domains []string + logPath string + cfg ctrld.Config + verbose int bootstrapDNS = "76.76.2.0" diff --git a/config.go b/config.go index 650b371..a88fdaa 100644 --- a/config.go +++ b/config.go @@ -40,14 +40,14 @@ func InitConfig(v *viper.Viper, name string) { "0": { BootstrapIP: "76.76.2.11", Name: "Control D - Anti-Malware", - Type: "doh", + Type: ResolverTypeDOH, Endpoint: "https://freedns.controld.com/p1", Timeout: 5000, }, "1": { BootstrapIP: "76.76.2.11", Name: "Control D - No Ads", - Type: "doq", + Type: ResolverTypeDOQ, Endpoint: "p2.freedns.controld.com", Timeout: 3000, }, @@ -139,9 +139,9 @@ func (uc *UpstreamConfig) Init() { // For now, only DoH upstream is supported. func (uc *UpstreamConfig) SetupTransport() { switch uc.Type { - case resolverTypeDOH: + case ResolverTypeDOH: uc.setupDOHTransport() - case resolverTypeDOH3: + case ResolverTypeDOH3: uc.setupDOH3Transport() } } @@ -231,11 +231,11 @@ func validateDnsRcode(fl validator.FieldLevel) bool { func defaultPortFor(typ string) string { switch typ { - case resolverTypeDOH, resolverTypeDOH3: + case ResolverTypeDOH, ResolverTypeDOH3: return "443" - case resolverTypeDOQ, resolverTypeDOT: + case ResolverTypeDOQ, ResolverTypeDOT: return "853" - case resolverTypeLegacy: + case ResolverTypeLegacy: return "53" } return "53" diff --git a/docs/basic_mode.md b/docs/basic_mode.md new file mode 100644 index 0000000..dda3801 --- /dev/null +++ b/docs/basic_mode.md @@ -0,0 +1,79 @@ +# basic mode + +`ctrld` can operate in `basic` mode, which requires no configuration file. All necessary information is provided +via command line flags, and be translated to corresponding config. `ctrld` will start with that config but do not +write anything to disk. + +## Base64 encoded config + +`ctrld` can read a base64 encoded config via command line flag: + +```shell +ctrld run --base64_config="CltsaXN0ZW5lcl0KCiAgW2xpc3RlbmVyLjBdCiAgICBpcCA9ICIxMjcuMC4wLjEiCiAgICBwb3J0ID0gNTMKICAgIHJlc3RyaWN0ZWQgPSBmYWxzZQoKW25ldHdvcmtdCgogIFtuZXR3b3JrLjBdCiAgICBjaWRycyA9IFsiMC4wLjAuMC8wIl0KICAgIG5hbWUgPSAiTmV0d29yayAwIgoKW3Vwc3RyZWFtXQoKICBbdXBzdHJlYW0uMF0KICAgIGJvb3RzdHJhcF9pcCA9ICI3Ni43Ni4yLjExIgogICAgZW5kcG9pbnQgPSAiaHR0cHM6Ly9mcmVlZG5zLmNvbnRyb2xkLmNvbS9wMSIKICAgIG5hbWUgPSAiQ29udHJvbCBEIC0gQW50aS1NYWx3YXJlIgogICAgdGltZW91dCA9IDUwMDAKICAgIHR5cGUgPSAiZG9oIgoKICBbdXBzdHJlYW0uMV0KICAgIGJvb3RzdHJhcF9pcCA9ICI3Ni43Ni4yLjExIgogICAgZW5kcG9pbnQgPSAicDIuZnJlZWRucy5jb250cm9sZC5jb20iCiAgICBuYW1lID0gIkNvbnRyb2wgRCAtIE5vIEFkcyIKICAgIHRpbWVvdXQgPSAzMDAwCiAgICB0eXBlID0gImRvcSIK" +``` + +## Launch arguments + +A set of arguments can be provided via command line flags. + +```shell +$ ctrld run --help +Run the DNS proxy server + +Usage: + ctrld run [flags] + +Flags: + --base64_config string base64 encoded config + -c, --config string Path to config file + -d, --daemon Run as daemon + --domains strings list of domain to apply in a split DNS policy + -h, --help help for run + --listen string listener address and port, in format: address:port + --log string path to log file + --primary_upstream string primary upstream endpoint + --secondary_upstream string secondary upstream endpoint + +Global Flags: + -v, --verbose count verbose log output, "-v" means query logging enabled, "-vv" means debug level logging enabled +``` + +For example: + +```shell +ctrld run --listen=127.0.0.1:53 --primary_upstream=https://freedns.controld.com/p2 --secondary_upstream=8.8.8.8:53 --domains=*.company.int,*.net --log /path/to/log.log +``` + +Above command will be translated roughly to this config: + +```toml +[service] + log_level = "debug" + log_path = "/path/to/log.log" + +[network.0] + name = "Network 0" + cidrs = ["0.0.0.0/0"] + +[upstream.0] + name = "https://freedns.controld.com/p2" + endpoint = "https://freedns.controld.com/p2" + type = "doh" + +[upstream.1] + name = "8.8.8.8:53" + endpoint = "8.8.8.8:53" + type = "legacy" + +[listener.0] + ip = "127.0.0.1" + port = 53 + + [listener.0.policy] + rules = [ + {"*.company.int" = ["upstream.1"]}, + {"*.net" = ["upstream.1"]}, + ] +``` + +`secondary_upstream`, `domains`, and `log` flags are optional. diff --git a/doh.go b/doh.go index 546f8c7..2c68512 100644 --- a/doh.go +++ b/doh.go @@ -14,7 +14,7 @@ import ( func newDohResolver(uc *UpstreamConfig) *dohResolver { r := &dohResolver{ endpoint: uc.Endpoint, - isDoH3: uc.Type == resolverTypeDOH3, + isDoH3: uc.Type == ResolverTypeDOH3, transport: uc.transport, http3RoundTripper: uc.http3RoundTripper, } diff --git a/resolver.go b/resolver.go index ac065ce..72f2177 100644 --- a/resolver.go +++ b/resolver.go @@ -11,12 +11,12 @@ import ( ) const ( - resolverTypeDOH = "doh" - resolverTypeDOH3 = "doh3" - resolverTypeDOT = "dot" - resolverTypeDOQ = "doq" - resolverTypeOS = "os" - resolverTypeLegacy = "legacy" + ResolverTypeDOH = "doh" + ResolverTypeDOH3 = "doh3" + ResolverTypeDOT = "dot" + ResolverTypeDOQ = "doq" + ResolverTypeOS = "os" + ResolverTypeLegacy = "legacy" ) var bootstrapDNS = "76.76.2.0" @@ -34,15 +34,15 @@ var errUnknownResolver = errors.New("unknown resolver") func NewResolver(uc *UpstreamConfig) (Resolver, error) { typ, endpoint := uc.Type, uc.Endpoint switch typ { - case resolverTypeDOH, resolverTypeDOH3: + case ResolverTypeDOH, ResolverTypeDOH3: return newDohResolver(uc), nil - case resolverTypeDOT: + case ResolverTypeDOT: return &dotResolver{uc: uc}, nil - case resolverTypeDOQ: + case ResolverTypeDOQ: return &doqResolver{uc: uc}, nil - case resolverTypeOS: + case ResolverTypeOS: return &osResolver{}, nil - case resolverTypeLegacy: + case ResolverTypeLegacy: return &legacyResolver{endpoint: endpoint}, nil } return nil, fmt.Errorf("%w: %s", errUnknownResolver, typ) From e6d77e2586f97aa9f2c91d32dcad7c1ff1260bbb Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 22 Dec 2022 10:20:44 +0700 Subject: [PATCH 09/69] cmd/ctrld: add default value and CLI flag for cache size --- cmd/ctrld/cli.go | 14 ++++++++++++-- cmd/ctrld/main.go | 10 ++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 602e3f5..25979eb 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -42,7 +42,7 @@ func initCLI() { `verbose log output, "-v" means query logging enabled, "-vv" means debug level logging enabled`, ) - basicModeFlags := []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log"} + basicModeFlags := []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log", "cache_size"} runCmd := &cobra.Command{ Use: "run", Short: "Run the DNS proxy server", @@ -73,6 +73,7 @@ func initCLI() { log.Fatalf("invalid config: %v", err) } initLogging() + initCache() if daemon { exe, err := os.Executable() if err != nil { @@ -121,6 +122,7 @@ func initCLI() { runCmd.Flags().StringVarP(&secondaryUpstream, "secondary_upstream", "", "", "secondary upstream endpoint") runCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "list of domain to apply in a split DNS policy") runCmd.Flags().StringVarP(&logPath, "log", "", "", "path to log file") + runCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") rootCmd.AddCommand(runCmd) @@ -211,7 +213,15 @@ func processNoConfigFlags(noConfigStart bool) { } v.Set("upstream", upstream) + sc := ctrld.ServiceConfig{} if logPath != "" { - v.Set("service", ctrld.ServiceConfig{LogLevel: "debug", LogPath: logPath}) + sc.LogLevel = "debug" + sc.LogPath = logPath } + + if cacheSize != 0 { + sc.CacheEnable = true + sc.CacheSize = cacheSize + } + v.Set("service", sc) } diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 1fa5c22..c52c5d0 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -20,6 +20,7 @@ var ( secondaryUpstream string domains []string logPath string + cacheSize int cfg ctrld.Config verbose int @@ -75,3 +76,12 @@ func initLogging() { } zerolog.SetGlobalLevel(level) } + +func initCache() { + if !cfg.Service.CacheEnable { + return + } + if cfg.Service.CacheSize == 0 { + cfg.Service.CacheSize = 4096 + } +} From e331a4113acaee6eca51e13a0825db74408702b1 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 22 Dec 2022 02:22:35 +0700 Subject: [PATCH 10/69] Rework os resolver Currently, os resolver not only handle A and AAAA records, but also does it wrongly, since when it packs AAAA record to a dns.A record. This commit reworks os resolver to make it works with all supported record types. --- cmd/ctrld/dns_proxy.go | 2 +- go.mod | 25 +- go.sum | 539 ++++++++++++++++++++++++++++++--------- nameservers_unix.go | 23 ++ nameservers_unix_test.go | 13 + nameservers_windows.go | 71 ++++++ resolver.go | 38 ++- 7 files changed, 561 insertions(+), 150 deletions(-) create mode 100644 nameservers_unix.go create mode 100644 nameservers_unix_test.go create mode 100644 nameservers_windows.go diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index b1df992..b203668 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -296,5 +296,5 @@ func ttlFromMsg(msg *dns.Msg) uint32 { var osUpstreamConfig = &ctrld.UpstreamConfig{ Name: "OS resolver", - Type: "os", + Type: ctrld.ResolverTypeOS, } diff --git a/go.mod b/go.mod index c3b636b..668925e 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,11 @@ require ( github.com/miekg/dns v1.1.50 github.com/pelletier/go-toml v1.9.5 github.com/rs/zerolog v1.28.0 - github.com/spf13/cobra v1.1.1 - github.com/spf13/viper v1.7.0 - github.com/stretchr/testify v1.7.1 + github.com/spf13/cobra v1.4.0 + github.com/spf13/viper v1.9.0 + github.com/stretchr/testify v1.8.0 + golang.org/x/sys v0.1.0 + tailscale.com v1.34.1 ) require ( @@ -25,7 +27,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/magiconair/properties v1.8.1 // indirect + github.com/magiconair/properties v1.8.5 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect @@ -35,20 +37,19 @@ require ( github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/afero v1.1.2 // indirect - github.com/spf13/cast v1.3.0 // indirect - github.com/spf13/jwalterweatherman v1.0.0 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.2.0 // indirect golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect - golang.org/x/sys v0.0.0-20221010170243-090e33056c14 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/net v0.1.0 // indirect + golang.org/x/text v0.4.0 // indirect golang.org/x/tools v0.1.12 // indirect - gopkg.in/ini.v1 v1.51.0 // indirect + gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e123e72..74bd024 100644 --- a/go.sum +++ b/go.sum @@ -5,49 +5,89 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +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/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= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -56,60 +96,97 @@ 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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +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-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= @@ -117,25 +194,24 @@ github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kardianos/service v1.2.1 h1:AYndMsehS+ywIS6RB9KOlcXzteWUzxgMgBymJD7+BYk= github.com/kardianos/service v1.2.1/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -147,8 +223,8 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lucas-clemente/quic-go v0.29.1 h1:Z+WMJ++qMLhvpFkRZA+jl3BTxUjm415YBmWanXB8zP0= github.com/lucas-clemente/quic-go v0.29.1/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= @@ -156,32 +232,35 @@ github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU= github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 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/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= @@ -189,87 +268,86 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= +github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= +github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +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= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +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.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/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.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -278,6 +356,11 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= @@ -289,11 +372,21 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -301,9 +394,6 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -312,88 +402,200 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= -golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/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-20210616094352-59db8d763f22/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= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -406,10 +608,35 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -419,41 +646,121 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 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= diff --git a/nameservers_unix.go b/nameservers_unix.go new file mode 100644 index 0000000..0fe74d9 --- /dev/null +++ b/nameservers_unix.go @@ -0,0 +1,23 @@ +//go:build !js && !windows + +package ctrld + +import ( + "net" + + "tailscale.com/net/dns/resolvconffile" +) + +const resolvconfPath = "/etc/resolv.conf" + +func nameservers() []string { + c, err := resolvconffile.ParseFile(resolvconfPath) + if err != nil { + return nil + } + ns := make([]string, 0, len(c.Nameservers)) + for _, nameserver := range c.Nameservers { + ns = append(ns, net.JoinHostPort(nameserver.String(), "53")) + } + return ns +} diff --git a/nameservers_unix_test.go b/nameservers_unix_test.go new file mode 100644 index 0000000..790e8ee --- /dev/null +++ b/nameservers_unix_test.go @@ -0,0 +1,13 @@ +package ctrld + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_nameservers(t *testing.T) { + ns := nameservers() + require.NotNil(t, ns) + t.Log(ns) +} diff --git a/nameservers_windows.go b/nameservers_windows.go new file mode 100644 index 0000000..7812f2a --- /dev/null +++ b/nameservers_windows.go @@ -0,0 +1,71 @@ +package ctrld + +import ( + "net" + "os" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +func nameservers() []string { + aas, err := adapterAddresses() + 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")) + } + } + 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 + } + break + } + if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW { + return nil, os.NewSyscallError("getadaptersaddresses", err) + } + if l <= uint32(len(b)) { + return nil, os.NewSyscallError("getadaptersaddresses", err) + } + } + var aas []*windows.IpAdapterAddresses + for aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next { + aas = append(aas, aa) + } + return aas, nil +} diff --git a/resolver.go b/resolver.go index 72f2177..af153d1 100644 --- a/resolver.go +++ b/resolver.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "strings" + "sync/atomic" "github.com/miekg/dns" ) @@ -20,6 +21,7 @@ const ( ) var bootstrapDNS = "76.76.2.0" +var or = &osResolver{nameservers: nameservers()} // Resolver is the interface that wraps the basic DNS operations. // @@ -41,37 +43,31 @@ func NewResolver(uc *UpstreamConfig) (Resolver, error) { case ResolverTypeDOQ: return &doqResolver{uc: uc}, nil case ResolverTypeOS: - return &osResolver{}, nil + return or, nil case ResolverTypeLegacy: return &legacyResolver{endpoint: endpoint}, nil } return nil, fmt.Errorf("%w: %s", errUnknownResolver, typ) } -type osResolver struct{} +type osResolver struct { + nameservers []string + next atomic.Uint32 +} +// Resolve performs DNS resolvers using OS default nameservers. Nameserver is chosen from +// available nameservers with a roundrobin algorithm. func (o *osResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) { - domain := canonicalName(msg.Question[0].Name) - addrs, err := net.DefaultResolver.LookupHost(ctx, domain) - if err != nil { - return nil, err - } - if len(addrs) == 0 { - return nil, errors.New("no answer") - } - answer := new(dns.Msg) - answer.SetReply(msg) - ip := net.ParseIP(addrs[0]) - a := &dns.A{ - A: ip, - Hdr: dns.RR_Header{Name: msg.Question[0].Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 10}, - } - if ip.To4() != nil { - a.Hdr.Rrtype = dns.TypeA + numServers := uint32(len(o.nameservers)) + if numServers == 0 { + return nil, errors.New("no nameservers available") } + next := o.next.Add(1) + server := o.nameservers[(next-1)%numServers] + dnsClient := &dns.Client{Net: "udp"} + answer, _, err := dnsClient.ExchangeContext(ctx, msg, server) - msg.Answer = append(msg.Answer, a) - return msg, nil + return answer, err } func newDialer(dnsAddress string) *net.Dialer { From 9e7578fb294628bca41a37884e4b1297c2371058 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 23 Dec 2022 01:49:01 +0700 Subject: [PATCH 11/69] cmd/ctrld: use better approach for checking IPv6 available Some operating systems may throw a confirmation dialog when attempting to listen on any interface other than loopback. A better approach is checking for any interface which is up and can be routed IP traffic. --- cmd/ctrld/net.go | 6 ++++-- go.mod | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go index 76265d8..ce98405 100644 --- a/cmd/ctrld/net.go +++ b/cmd/ctrld/net.go @@ -3,6 +3,8 @@ package main import ( "net" "sync" + + "golang.org/x/net/nettest" ) var ( @@ -11,8 +13,8 @@ var ( ) func probeStack() { - if ln, err := net.Listen("tcp6", "[::]:0"); err == nil { - ln.Close() + // TODO(cuonglm): use nettest.SupportsIPv6 once https://github.com/golang/go/issues/57386 fixed. + if _, err := nettest.RoutedInterface("ip6", net.FlagUp); err == nil { ipv6Enabled = true } } diff --git a/go.mod b/go.mod index 668925e..f6d12e4 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.8.0 + golang.org/x/net v0.1.0 golang.org/x/sys v0.1.0 tailscale.com v1.34.1 ) @@ -45,7 +46,6 @@ require ( golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.1.0 // indirect golang.org/x/text v0.4.0 // indirect golang.org/x/tools v0.1.12 // indirect gopkg.in/ini.v1 v1.66.2 // indirect From ec72af19162aa65fb037dc1f693544c0421c7323 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 23 Dec 2022 21:35:26 +0700 Subject: [PATCH 12/69] cmd/ctrld: add commands to control ctrld as a system service Supported actions: - start: install and start ctrld as a system service - stop: stop the ctrld service - restart: restart ctrld service - status: show status of ctrld service - uninstall: remove ctrld from system service --- cmd/ctrld/cli.go | 211 +++++++++++++++++++++++++++++++++++++++---- cmd/ctrld/main.go | 26 +++++- cmd/ctrld/service.go | 31 +++++++ config.go | 20 +++- docs/config.md | 5 +- go.mod | 21 +++-- go.sum | 39 ++++++++ testhelper/config.go | 1 + 8 files changed, 315 insertions(+), 39 deletions(-) create mode 100644 cmd/ctrld/service.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 25979eb..f62cb87 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -3,7 +3,6 @@ package main import ( "bytes" "encoding/base64" - "fmt" "log" "net" "os" @@ -25,6 +24,17 @@ var ( defaultConfigWritten = false ) +var basicModeFlags = []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log", "cache_size"} + +func isNoConfigStart(cmd *cobra.Command) bool { + for _, flagName := range basicModeFlags { + if cmd.Flags().Lookup(flagName).Changed { + return true + } + } + return false +} + func initCLI() { // Enable opening via explorer.exe on Windows. // See: https://github.com/spf13/cobra/issues/844. @@ -42,7 +52,6 @@ func initCLI() { `verbose log output, "-v" means query logging enabled, "-vv" means debug level logging enabled`, ) - basicModeFlags := []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log", "cache_size"} runCmd := &cobra.Command{ Use: "run", Short: "Run the DNS proxy server", @@ -51,19 +60,25 @@ func initCLI() { if daemon && runtime.GOOS == "windows" { log.Fatal("Cannot run in daemon mode. Please install a Windows service.") } - if configPath != "" { - v.SetConfigFile(configPath) - } - noConfigStart := func() bool { - for _, flagName := range basicModeFlags { - if cmd.Flags().Lookup(flagName).Changed { - return true - } - } - return false - }() - readConfigFile(!noConfigStart && configBase64 == "") + noConfigStart := isNoConfigStart(cmd) + + configs := []struct { + name string + written bool + }{ + // For compatibility, we check for config.toml first, but only read it if exists. + {"config", false}, + {"ctrld", !noConfigStart && configBase64 == ""}, + } + for _, config := range configs { + ctrld.SetConfigName(v, config.name) + v.SetConfigFile(configPath) + if readConfigFile(config.written) { + break + } + } + readBase64Config() processNoConfigFlags(noConfigStart) if err := v.Unmarshal(&cfg); err != nil { @@ -126,8 +141,158 @@ func initCLI() { rootCmd.AddCommand(runCmd) + startCmd := &cobra.Command{ + Use: "start", + Short: "Start the ctrld service", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + cfg := &service.Config{} + *cfg = *svcConfig + cfg.Arguments = append([]string{"run"}, os.Args[3:]...) + if dir, err := os.UserHomeDir(); err == nil { + // WorkingDirectory is not supported on Windows. + cfg.WorkingDirectory = dir + // No config path, generating config in HOME directory. + if configPath == "" && !isNoConfigStart(cmd) && configBase64 == "" { + readConfigFile(true) + } + } + s, err := service.New(&prog{}, cfg) + if err != nil { + stderrMsg(err.Error()) + return + } + tasks := []task{ + {s.Stop, false}, + {s.Uninstall, false}, + {s.Install, false}, + {s.Start, true}, + } + if doTasks(tasks) { + stdoutMsg("Service started") + return + } + }, + } + // Keep these flags in sync with runCmd above, except for "-d". + startCmd.Flags().StringVarP(&configPath, "config", "c", "", "Path to config file") + startCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "base64 encoded config") + startCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "listener address and port, in format: address:port") + startCmd.Flags().StringVarP(&primaryUpstream, "primary_upstream", "", "", "primary upstream endpoint") + startCmd.Flags().StringVarP(&secondaryUpstream, "secondary_upstream", "", "", "secondary upstream endpoint") + startCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "list of domain to apply in a split DNS policy") + startCmd.Flags().StringVarP(&logPath, "log", "", "", "path to log file") + startCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") + + stopCmd := &cobra.Command{ + Use: "stop", + Short: "Stop the ctrld service", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + s, err := service.New(&prog{}, svcConfig) + if err != nil { + stderrMsg(err.Error()) + return + } + if doTasks([]task{{s.Stop, true}}) { + stdoutMsg("Service stopped") + } + }, + } + + restartCmd := &cobra.Command{ + Use: "restart", + Short: "Restart the ctrld service", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + s, err := service.New(&prog{}, svcConfig) + if err != nil { + stderrMsg(err.Error()) + return + } + if doTasks([]task{{s.Restart, true}}) { + stdoutMsg("Service restarted") + } + }, + } + + statusCmd := &cobra.Command{ + Use: "status", + Short: "Show status of the ctrld service", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + s, err := service.New(&prog{}, svcConfig) + if err != nil { + stderrMsg(err.Error()) + return + } + status, err := s.Status() + if err != nil { + stderrMsg(err.Error()) + return + } + switch status { + case service.StatusUnknown: + stdoutMsg("Unknown status") + case service.StatusRunning: + stdoutMsg("Service is running") + case service.StatusStopped: + stdoutMsg("Service is stopped") + } + }, + } + + uninstallCmd := &cobra.Command{ + Use: "uninstall", + Short: "Uninstall the ctrld service", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + s, err := service.New(&prog{}, svcConfig) + if err != nil { + stderrMsg(err.Error()) + return + } + tasks := []task{ + {s.Stop, false}, + {s.Uninstall, true}, + } + if doTasks(tasks) { + stdoutMsg("Service uninstalled") + return + } + }, + } + + serviceCmd := &cobra.Command{ + Use: "service", + Short: "Manage ctrld service", + Args: cobra.OnlyValidArgs, + ValidArgs: []string{ + statusCmd.Use, + stopCmd.Use, + restartCmd.Use, + statusCmd.Use, + uninstallCmd.Use, + }, + } + serviceCmd.AddCommand(startCmd) + serviceCmd.AddCommand(stopCmd) + serviceCmd.AddCommand(restartCmd) + serviceCmd.AddCommand(statusCmd) + serviceCmd.AddCommand(uninstallCmd) + rootCmd.AddCommand(serviceCmd) + startCmdAlias := &cobra.Command{ + Use: "start", + Short: "Alias for service start", + Run: func(cmd *cobra.Command, args []string) { + startCmd.Run(cmd, args) + }, + } + startCmdAlias.Flags().AddFlagSet(startCmd.Flags()) + rootCmd.AddCommand(startCmdAlias) + if err := rootCmd.Execute(); err != nil { - fmt.Println(err) + stderrMsg(err.Error()) os.Exit(1) } } @@ -138,22 +303,28 @@ func writeConfigFile() { if err != nil { log.Fatalf("unable to marshal config to toml: %v", err) } - if err := os.WriteFile("config.toml", bs, 0600); err != nil { + if err := os.WriteFile("ctrld.toml", bs, 0600); err != nil { log.Printf("failed to write config file: %v\n", err) } } -func readConfigFile(configWritten bool) { +func readConfigFile(configWritten bool) bool { err := v.ReadInConfig() - if err == nil || !configWritten { - return + if err == nil { + return true } + + if !configWritten { + return false + } + if _, ok := err.(viper.ConfigFileNotFoundError); ok { writeConfigFile() defaultConfigWritten = true - return + return false } log.Fatalf("failed to decode config file: %v", err) + return false } func readBase64Config() { diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index c52c5d0..4918020 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -4,8 +4,10 @@ import ( "fmt" "io" "os" + "path/filepath" "time" + "github.com/kardianos/service" "github.com/rs/zerolog" "github.com/Control-D-Inc/ctrld" @@ -32,17 +34,33 @@ var ( ) func main() { - ctrld.InitConfig(v, "config") + ctrld.InitConfig(v, "ctrld") initCLI() } +func normalizeLogFilePath(logFilePath string) string { + if logFilePath == "" || filepath.IsAbs(logFilePath) || service.Interactive() { + return logFilePath + } + dir, _ := os.UserHomeDir() + if dir == "" { + return logFilePath + } + return filepath.Join(dir, logFilePath) +} + func initLogging() { writers := []io.Writer{io.Discard} isLog := cfg.Service.LogLevel != "" - if logPath := cfg.Service.LogPath; logPath != "" { - logFile, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600) + if logFilePath := normalizeLogFilePath(cfg.Service.LogPath); logFilePath != "" { + // Create parent directory if necessary. + if err := os.MkdirAll(filepath.Dir(logFilePath), 0750); err != nil { + fmt.Fprintf(os.Stderr, "failed to create log path: %v", err) + os.Exit(1) + } + logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600) if err != nil { - fmt.Fprintf(os.Stderr, "failed to creating log file: %v", err) + fmt.Fprintf(os.Stderr, "failed to create log file: %v", err) os.Exit(1) } isLog = true diff --git a/cmd/ctrld/service.go b/cmd/ctrld/service.go new file mode 100644 index 0000000..7307a89 --- /dev/null +++ b/cmd/ctrld/service.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "os" +) + +func stderrMsg(msg string) { + _, _ = fmt.Fprintln(os.Stderr, msg) +} + +func stdoutMsg(msg string) { + _, _ = fmt.Fprintln(os.Stdout, msg) +} + +type task struct { + f func() error + abortOnError bool +} + +func doTasks(tasks []task) bool { + for _, task := range tasks { + if err := task.f(); err != nil { + if task.abortOnError { + stderrMsg(err.Error()) + return false + } + } + } + return true +} diff --git a/config.go b/config.go index a88fdaa..a5514ea 100644 --- a/config.go +++ b/config.go @@ -6,6 +6,7 @@ import ( "net" "net/http" "net/url" + "os" "strings" "time" @@ -17,12 +18,23 @@ import ( "github.com/spf13/viper" ) +// SetConfigName set the config name that ctrld will look for. +func SetConfigName(v *viper.Viper, name string) { + v.SetConfigName(name) + + configPath := "$HOME" + // viper has its own way to get user home directory: https://github.com/spf13/viper/blob/v1.14.0/util.go#L134 + // To be consistent, we prefer os.UserHomeDir instead. + if homeDir, err := os.UserHomeDir(); err == nil { + configPath = homeDir + } + v.AddConfigPath(configPath) + v.AddConfigPath(".") +} + // InitConfig initializes default config values for given *viper.Viper instance. func InitConfig(v *viper.Viper, name string) { - v.SetConfigName(name) - v.SetConfigType("toml") - v.AddConfigPath("$HOME/.ctrld") - v.AddConfigPath(".") + SetConfigName(v, name) v.SetDefault("listener", map[string]*ListenerConfig{ "0": { diff --git a/docs/config.md b/docs/config.md index 483f0d1..e8b30e8 100644 --- a/docs/config.md +++ b/docs/config.md @@ -25,7 +25,10 @@ The user can choose to override default value using command line `--config` or ` ctrld run --config /path/to/myconfig.toml ``` -If no configuration files found, a default `config.toml` file will be created in the current directory. +If no configuration files found, a default `ctrld.toml` file will be created in the current directory. + +In pre v1.1.0, `config.toml` file was used, so for compatibility, `ctrld` will still read `config.toml` +if it's existed. # Example Config diff --git a/go.mod b/go.mod index f6d12e4..6529554 100644 --- a/go.mod +++ b/go.mod @@ -11,16 +11,16 @@ require ( github.com/pelletier/go-toml v1.9.5 github.com/rs/zerolog v1.28.0 github.com/spf13/cobra v1.4.0 - github.com/spf13/viper v1.9.0 - github.com/stretchr/testify v1.8.0 + github.com/spf13/viper v1.14.0 + github.com/stretchr/testify v1.8.1 golang.org/x/net v0.1.0 - golang.org/x/sys v0.1.0 + golang.org/x/sys v0.3.0 tailscale.com v1.34.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fsnotify/fsnotify v1.6.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-20210107165309-348f09dbbbc0 // indirect @@ -28,7 +28,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/magiconair/properties v1.8.5 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect @@ -37,18 +37,19 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/afero v1.6.0 // indirect - github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.2.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/text v0.5.0 // indirect golang.org/x/tools v0.1.12 // indirect - gopkg.in/ini.v1 v1.66.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // 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 74bd024..66033f3 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,6 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -41,6 +43,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -84,6 +87,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -162,6 +167,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 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-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -172,6 +178,7 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= @@ -225,6 +232,8 @@ github.com/lucas-clemente/quic-go v0.29.1 h1:Z+WMJ++qMLhvpFkRZA+jl3BTxUjm415YBmW github.com/lucas-clemente/quic-go v0.29.1/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= @@ -273,10 +282,13 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -297,8 +309,12 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -307,8 +323,11 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= +github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= +github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -318,8 +337,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +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/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= @@ -347,7 +370,9 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -424,6 +449,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= @@ -460,6 +486,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -508,12 +535,14 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -524,8 +553,11 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -538,6 +570,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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= @@ -590,6 +624,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -672,7 +707,9 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -739,6 +776,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/testhelper/config.go b/testhelper/config.go index 06f1bbb..6d646be 100644 --- a/testhelper/config.go +++ b/testhelper/config.go @@ -12,6 +12,7 @@ import ( func SampleConfig(t *testing.T) *ctrld.Config { v := viper.NewWithOptions(viper.KeyDelimiter("::")) ctrld.InitConfig(v, "test_load_config") + v.SetConfigType("toml") require.NoError(t, v.ReadConfig(strings.NewReader(sampleConfigContent))) var cfg ctrld.Config require.NoError(t, v.Unmarshal(&cfg)) From 114ef9aad62d3e6fa4d524182d7b1440a6d13326 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 22 Dec 2022 23:27:45 +0700 Subject: [PATCH 13/69] all: add starting service with Control D config --- cmd/ctrld/cli.go | 90 +++++++++++++++++++++++++------- cmd/ctrld/dns_proxy.go | 7 +-- cmd/ctrld/main.go | 2 + docs/basic_mode.md | 2 + docs/controld_config.md | 15 ++++++ internal/controld/config.go | 90 ++++++++++++++++++++++++++++++++ internal/controld/config_test.go | 33 ++++++++++++ 7 files changed, 216 insertions(+), 23 deletions(-) create mode 100644 docs/controld_config.md create mode 100644 internal/controld/config.go create mode 100644 internal/controld/config_test.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index f62cb87..a63b3cc 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -17,6 +17,7 @@ import ( "github.com/spf13/viper" "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/controld" ) var ( @@ -61,15 +62,15 @@ func initCLI() { log.Fatal("Cannot run in daemon mode. Please install a Windows service.") } - noConfigStart := isNoConfigStart(cmd) - + noConfigStart := isNoConfigStart(cmd) && cdUID != "" + writeDefaultConfig := !noConfigStart && configBase64 == "" configs := []struct { name string written bool }{ // For compatibility, we check for config.toml first, but only read it if exists. {"config", false}, - {"ctrld", !noConfigStart && configBase64 == ""}, + {"ctrld", writeDefaultConfig}, } for _, config := range configs { ctrld.SetConfigName(v, config.name) @@ -81,6 +82,7 @@ func initCLI() { readBase64Config() processNoConfigFlags(noConfigStart) + processCDFlags() if err := v.Unmarshal(&cfg); err != nil { log.Fatalf("failed to unmarshal config: %v", err) } @@ -138,6 +140,7 @@ func initCLI() { runCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "list of domain to apply in a split DNS policy") runCmd.Flags().StringVarP(&logPath, "log", "", "", "path to log file") runCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") + runCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") rootCmd.AddCommand(runCmd) @@ -183,6 +186,7 @@ func initCLI() { startCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "list of domain to apply in a split DNS policy") startCmd.Flags().StringVarP(&logPath, "log", "", "", "path to log file") startCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") + startCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") stopCmd := &cobra.Command{ Use: "stop", @@ -308,21 +312,24 @@ func writeConfigFile() { } } -func readConfigFile(configWritten bool) bool { +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 { return true } - if !configWritten { + if !writeDefaultConfig { return false } + // If error is viper.ConfigFileNotFoundError, write default config. if _, ok := err.(viper.ConfigFileNotFoundError); ok { writeConfigFile() defaultConfigWritten = true return false } + // Otherwise, report fatal error and exit. log.Fatalf("failed to decode config file: %v", err) return false } @@ -347,21 +354,7 @@ func processNoConfigFlags(noConfigStart bool) { if listenAddress == "" || primaryUpstream == "" { log.Fatal(`"listen" and "primary_upstream" flags must be set in no config mode`) } - host, portStr, err := net.SplitHostPort(listenAddress) - if err != nil { - log.Fatalf("invalid listener address: %v", err) - } - port, err := strconv.Atoi(portStr) - if err != nil { - log.Fatalf("invalid port number: %v", err) - } - lc := &ctrld.ListenerConfig{ - IP: host, - Port: port, - } - v.Set("listener", map[string]*ctrld.ListenerConfig{ - "0": lc, - }) + processListenFlag() upstream := map[string]*ctrld.UpstreamConfig{ "0": { @@ -380,10 +373,67 @@ func processNoConfigFlags(noConfigStart bool) { for _, domain := range domains { rules = append(rules, ctrld.Rule{domain: []string{"upstream.1"}}) } + lc := v.Get("listener").(map[string]*ctrld.ListenerConfig)["0"] lc.Policy = &ctrld.ListenerPolicyConfig{Name: "My Policy", Rules: rules} } v.Set("upstream", upstream) + processLogAndCacheFlags() +} + +func processCDFlags() { + if cdUID == "" { + return + } + resolverConfig, err := controld.FetchResolverConfig(cdUID) + if err != nil { + log.Fatalf("failed to fetch resolver config: %v", err) + } + + upstream := map[string]*ctrld.UpstreamConfig{ + "0": { + BootstrapIP: resolverConfig.IP(supportsIPv6()), + Name: resolverConfig.DOH, + Endpoint: resolverConfig.DOH, + Type: ctrld.ResolverTypeDOH, + }, + } + v.Set("upstream", upstream) + + processListenFlag() + + rules := make([]ctrld.Rule, 0, len(resolverConfig.Exclude)) + for _, domain := range resolverConfig.Exclude { + rules = append(rules, ctrld.Rule{domain: []string{}}) + } + lc := v.Get("listener").(map[string]*ctrld.ListenerConfig)["0"] + lc.Policy = &ctrld.ListenerPolicyConfig{Name: "My Policy", Rules: rules} + + processLogAndCacheFlags() +} + +func processListenFlag() { + if listenAddress == "" { + return + } + host, portStr, err := net.SplitHostPort(listenAddress) + if err != nil { + log.Fatalf("invalid listener address: %v", err) + } + port, err := strconv.Atoi(portStr) + if err != nil { + log.Fatalf("invalid port number: %v", err) + } + lc := &ctrld.ListenerConfig{ + IP: host, + Port: port, + } + v.Set("listener", map[string]*ctrld.ListenerConfig{ + "0": lc, + }) +} + +func processLogAndCacheFlags() { sc := ctrld.ServiceConfig{} if logPath != "" { sc.LogLevel = "debug" diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index b203668..679d292 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -143,6 +143,10 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i } } upstreamConfigs := p.upstreamConfigsFromUpstreamNumbers(upstreams) + if len(upstreamConfigs) == 0 { + upstreamConfigs = []*ctrld.UpstreamConfig{osUpstreamConfig} + upstreams = []string{"upstream.os"} + } resolve := func(n int, upstreamConfig *ctrld.UpstreamConfig, msg *dns.Msg) *dns.Msg { ctrld.Log(ctx, proxyLog.Debug(), "sending query to %s: %s", upstreams[n], upstreamConfig.Name) dnsResolver, err := ctrld.NewResolver(upstreamConfig) @@ -204,9 +208,6 @@ func (p *prog) upstreamConfigsFromUpstreamNumbers(upstreams []string) []*ctrld.U upstreamNum := strings.TrimPrefix(upstream, "upstream.") upstreamConfigs = append(upstreamConfigs, p.cfg.Upstream[upstreamNum]) } - if len(upstreamConfigs) == 0 { - upstreamConfigs = []*ctrld.UpstreamConfig{osUpstreamConfig} - } return upstreamConfigs } diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 4918020..d4362ba 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -31,6 +31,8 @@ var ( rootLogger = zerolog.New(io.Discard) mainLog = rootLogger proxyLog = rootLogger + + cdUID string ) func main() { diff --git a/docs/basic_mode.md b/docs/basic_mode.md index dda3801..5e2bcda 100644 --- a/docs/basic_mode.md +++ b/docs/basic_mode.md @@ -25,6 +25,8 @@ Usage: Flags: --base64_config string base64 encoded config + --cache_size int Enable cache with size items + --cd string Control D resolver uid -c, --config string Path to config file -d, --daemon Run as daemon --domains strings list of domain to apply in a split DNS policy diff --git a/docs/controld_config.md b/docs/controld_config.md new file mode 100644 index 0000000..20c9c5b --- /dev/null +++ b/docs/controld_config.md @@ -0,0 +1,15 @@ +# Control D config + +`ctrld` can build a Control D config and run with the specific resolver data. + +For example: + +```shell +ctrld run --cd p2 +``` + +Above command will fetch the `p2` resolver data from Control D API and use that data for running `ctrld`: + + - The resolver `doh` endpoint will be used as the primary upstream. + - The resolver `exclude` list will be used to create a rule policy which will steer them to the default OS resolver. +``` diff --git a/internal/controld/config.go b/internal/controld/config.go new file mode 100644 index 0000000..69b41a8 --- /dev/null +++ b/internal/controld/config.go @@ -0,0 +1,90 @@ +package controld + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "time" +) + +const resolverDataURL = "https://api.controld.com/utility" + +// ResolverConfig represents Control D resolver data. +type ResolverConfig struct { + V4 []string `json:"v4"` + V6 []string `json:"v6"` + DOH string `json:"doh"` + Exclude []string `json:"exclude"` +} + +func (r *ResolverConfig) IP(v6 bool) string { + ip4 := r.v4() + ip6 := r.v6() + if v6 && ip6 != "" { + return ip6 + } + return ip4 +} + +func (r *ResolverConfig) v4() string { + for _, ip := range r.V4 { + return ip + } + return "" +} + +func (r *ResolverConfig) v6() string { + for _, ip := range r.V6 { + return ip + } + return "" +} + +type utilityResponse struct { + Success bool `json:"success"` + Body struct { + Resolver ResolverConfig `json:"resolver"` + } `json:"body"` +} + +type utilityErrorResponse struct { + Error struct { + Message string `json:"message"` + } `json:"error"` +} + +type utilityRequest struct { + UID string `json:"uid"` +} + +// FetchResolverConfig fetch Control D config for given uid. +func FetchResolverConfig(uid string) (*ResolverConfig, error) { + body, _ := json.Marshal(utilityRequest{UID: uid}) + req, err := http.NewRequest("POST", resolverDataURL, bytes.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("http.NewRequest: %w", err) + } + req.Header.Add("Content-Type", "application/json") + client := http.Client{Timeout: 5 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("client.Do: %w", err) + } + defer resp.Body.Close() + d := json.NewDecoder(resp.Body) + if resp.StatusCode != http.StatusOK { + errResp := &utilityErrorResponse{} + if err := d.Decode(errResp); err != nil { + return nil, err + } + return nil, errors.New(errResp.Error.Message) + } + + ur := &utilityResponse{} + if err := d.Decode(ur); err != nil { + return nil, err + } + return &ur.Body.Resolver, nil +} diff --git a/internal/controld/config_test.go b/internal/controld/config_test.go new file mode 100644 index 0000000..3c09ed7 --- /dev/null +++ b/internal/controld/config_test.go @@ -0,0 +1,33 @@ +//go:build controld + +package controld + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +const utilityURL = "https://api.controld.com/utility" + +func TestFetchResolverConfig(t *testing.T) { + tests := []struct { + name string + uid string + wantErr bool + }{ + {"valid", "p2", false}, + {"invalid uid", "abcd1234", true}, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got, err := FetchResolverConfig(tc.uid) + assert.False(t, (err != nil) != tc.wantErr) + if !tc.wantErr { + assert.NotEmpty(t, got.DOH) + } + }) + } +} From 7b13fd862d4d6c91cf2928be572c0c317f1ff70e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 5 Jan 2023 18:19:05 +0700 Subject: [PATCH 14/69] cmd/ctrld: fix mis-handling of start alias For "ctrld start", os.Args length is just 2, so we we could not shorten it from 3. --- cmd/ctrld/cli.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index a63b3cc..530ca59 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -151,7 +151,11 @@ func initCLI() { Run: func(cmd *cobra.Command, args []string) { cfg := &service.Config{} *cfg = *svcConfig - cfg.Arguments = append([]string{"run"}, os.Args[3:]...) + osArgs := os.Args[2:] + if os.Args[1] == "service" { + osArgs = os.Args[3:] + } + cfg.Arguments = append([]string{"run"}, osArgs...) if dir, err := os.UserHomeDir(); err == nil { // WorkingDirectory is not supported on Windows. cfg.WorkingDirectory = dir From b021833ed6f9dbb19bd01326f6aa561260128e8f Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 5 Jan 2023 18:58:33 +0700 Subject: [PATCH 15/69] cmd/ctrld: correct the write default config condition When "--cd" is supplied, we don't want to write default config. This happens due to faulty in resolving conflicts in !70. --- cmd/ctrld/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 530ca59..8c108e5 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -63,7 +63,7 @@ func initCLI() { } noConfigStart := isNoConfigStart(cmd) && cdUID != "" - writeDefaultConfig := !noConfigStart && configBase64 == "" + writeDefaultConfig := !noConfigStart && configBase64 == "" && cdUID == "" configs := []struct { name string written bool From 3014556f2d4f6625b49006c728602fe04ff371ae Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 5 Jan 2023 19:09:58 +0700 Subject: [PATCH 16/69] cmd/ctrld,internal/controld: do not set bootstrap IP --- cmd/ctrld/cli.go | 7 +++---- internal/controld/config.go | 25 ------------------------- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 8c108e5..d653af4 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -396,10 +396,9 @@ func processCDFlags() { upstream := map[string]*ctrld.UpstreamConfig{ "0": { - BootstrapIP: resolverConfig.IP(supportsIPv6()), - Name: resolverConfig.DOH, - Endpoint: resolverConfig.DOH, - Type: ctrld.ResolverTypeDOH, + Name: resolverConfig.DOH, + Endpoint: resolverConfig.DOH, + Type: ctrld.ResolverTypeDOH, }, } v.Set("upstream", upstream) diff --git a/internal/controld/config.go b/internal/controld/config.go index 69b41a8..c4ed22a 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -13,35 +13,10 @@ const resolverDataURL = "https://api.controld.com/utility" // ResolverConfig represents Control D resolver data. type ResolverConfig struct { - V4 []string `json:"v4"` - V6 []string `json:"v6"` DOH string `json:"doh"` Exclude []string `json:"exclude"` } -func (r *ResolverConfig) IP(v6 bool) string { - ip4 := r.v4() - ip6 := r.v6() - if v6 && ip6 != "" { - return ip6 - } - return ip4 -} - -func (r *ResolverConfig) v4() string { - for _, ip := range r.V4 { - return ip - } - return "" -} - -func (r *ResolverConfig) v6() string { - for _, ip := range r.V6 { - return ip - } - return "" -} - type utilityResponse struct { Success bool `json:"success"` Body struct { From 8e91123dbfba56b324f1a1f6312af794f4c1b96a Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 6 Jan 2023 01:29:51 +0700 Subject: [PATCH 17/69] cmd/ctrld: write default config to home dir when start This piece was missing in last resolving conflicts. --- cmd/ctrld/cli.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index d653af4..b3ed3a9 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -7,6 +7,7 @@ import ( "net" "os" "os/exec" + "path/filepath" "runtime" "strconv" @@ -23,6 +24,7 @@ import ( var ( v = viper.NewWithOptions(viper.KeyDelimiter("::")) defaultConfigWritten = false + defaultConfigFile = "ctrld.toml" ) var basicModeFlags = []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log", "cache_size"} @@ -161,6 +163,7 @@ func initCLI() { cfg.WorkingDirectory = dir // No config path, generating config in HOME directory. if configPath == "" && !isNoConfigStart(cmd) && configBase64 == "" { + defaultConfigFile = filepath.Join(dir, defaultConfigFile) readConfigFile(true) } } @@ -311,7 +314,7 @@ func writeConfigFile() { if err != nil { log.Fatalf("unable to marshal config to toml: %v", err) } - if err := os.WriteFile("ctrld.toml", bs, 0600); err != nil { + if err := os.WriteFile(defaultConfigFile, bs, 0600); err != nil { log.Printf("failed to write config file: %v\n", err) } } From 6edd42629e705c8a634cf9302832b03dfbcb8f15 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 6 Jan 2023 01:41:00 +0700 Subject: [PATCH 18/69] cmd/ctrld: correct write default config condition when start !76 was merged without rebasing on latest master, so it missed the condition of "--cd" when checking for writing default config. --- cmd/ctrld/cli.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index b3ed3a9..293d30e 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -162,7 +162,9 @@ func initCLI() { // WorkingDirectory is not supported on Windows. cfg.WorkingDirectory = dir // No config path, generating config in HOME directory. - if configPath == "" && !isNoConfigStart(cmd) && configBase64 == "" { + noConfigStart := isNoConfigStart(cmd) && cdUID != "" + writeDefaultConfig := !noConfigStart && configBase64 == "" && cdUID == "" + if configPath == "" && writeDefaultConfig { defaultConfigFile = filepath.Join(dir, defaultConfigFile) readConfigFile(true) } From 9f908115679315e35de23daa2072ba81b2f3994c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 6 Jan 2023 23:56:26 +0700 Subject: [PATCH 19/69] cmd/ctrld: update config when "--cd" present --- cmd/ctrld/cli.go | 66 ++++++++++++++++++++----------------- docs/config.md | 11 +++++++ internal/controld/config.go | 3 ++ 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 293d30e..47002dc 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -3,6 +3,7 @@ package main import ( "bytes" "encoding/base64" + "fmt" "log" "net" "os" @@ -13,7 +14,6 @@ import ( "github.com/go-playground/validator/v10" "github.com/kardianos/service" - "github.com/pelletier/go-toml" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -64,8 +64,8 @@ func initCLI() { log.Fatal("Cannot run in daemon mode. Please install a Windows service.") } - noConfigStart := isNoConfigStart(cmd) && cdUID != "" - writeDefaultConfig := !noConfigStart && configBase64 == "" && cdUID == "" + noConfigStart := isNoConfigStart(cmd) + writeDefaultConfig := !noConfigStart && configBase64 == "" configs := []struct { name string written bool @@ -84,10 +84,10 @@ func initCLI() { readBase64Config() processNoConfigFlags(noConfigStart) - processCDFlags() if err := v.Unmarshal(&cfg); err != nil { log.Fatalf("failed to unmarshal config: %v", err) } + processCDFlags(writeDefaultConfig) if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil { log.Fatalf("invalid config: %v", err) } @@ -151,25 +151,35 @@ func initCLI() { Short: "Start the ctrld service", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - cfg := &service.Config{} - *cfg = *svcConfig + sc := &service.Config{} + *sc = *svcConfig osArgs := os.Args[2:] if os.Args[1] == "service" { osArgs = os.Args[3:] } - cfg.Arguments = append([]string{"run"}, osArgs...) + sc.Arguments = append([]string{"run"}, osArgs...) if dir, err := os.UserHomeDir(); err == nil { // WorkingDirectory is not supported on Windows. - cfg.WorkingDirectory = dir + sc.WorkingDirectory = dir // No config path, generating config in HOME directory. - noConfigStart := isNoConfigStart(cmd) && cdUID != "" - writeDefaultConfig := !noConfigStart && configBase64 == "" && cdUID == "" + noConfigStart := isNoConfigStart(cmd) + writeDefaultConfig := !noConfigStart && configBase64 == "" if configPath == "" && writeDefaultConfig { defaultConfigFile = filepath.Join(dir, defaultConfigFile) readConfigFile(true) } + + // On Windows, the service will be run as SYSTEM, so if ctrld start as Admin, + // the written config won't be writable by SYSTEM account, we have to update + // the config here when "--cd" is supplied. + if runtime.GOOS == "windows" && cdUID != "" { + if err := v.Unmarshal(&cfg); err != nil { + log.Fatalf("failed to unmarshal config: %v", err) + } + processCDFlags(writeDefaultConfig) + } } - s, err := service.New(&prog{}, cfg) + s, err := service.New(&prog{}, sc) if err != nil { stderrMsg(err.Error()) return @@ -311,12 +321,10 @@ func initCLI() { } func writeConfigFile() { - c := v.AllSettings() - bs, err := toml.Marshal(c) - if err != nil { - log.Fatalf("unable to marshal config to toml: %v", err) + if cfu := v.ConfigFileUsed(); cfu != "" { + defaultConfigFile = cfu } - if err := os.WriteFile(defaultConfigFile, bs, 0600); err != nil { + if err := v.WriteConfigAs(defaultConfigFile); err != nil { log.Printf("failed to write config file: %v\n", err) } } @@ -325,6 +333,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()) return true } @@ -390,7 +399,7 @@ func processNoConfigFlags(noConfigStart bool) { processLogAndCacheFlags() } -func processCDFlags() { +func processCDFlags(writeConfig bool) { if cdUID == "" { return } @@ -399,25 +408,22 @@ func processCDFlags() { log.Fatalf("failed to fetch resolver config: %v", err) } - upstream := map[string]*ctrld.UpstreamConfig{ - "0": { - Name: resolverConfig.DOH, - Endpoint: resolverConfig.DOH, - Type: ctrld.ResolverTypeDOH, - }, - } - v.Set("upstream", upstream) - - processListenFlag() + u0 := cfg.Upstream["0"] + u0.Name = resolverConfig.DOH + u0.Endpoint = resolverConfig.DOH + u0.Type = ctrld.ResolverTypeDOH rules := make([]ctrld.Rule, 0, len(resolverConfig.Exclude)) for _, domain := range resolverConfig.Exclude { rules = append(rules, ctrld.Rule{domain: []string{}}) } - lc := v.Get("listener").(map[string]*ctrld.ListenerConfig)["0"] - lc.Policy = &ctrld.ListenerPolicyConfig{Name: "My Policy", Rules: rules} + cfg.Listener["0"].Policy = &ctrld.ListenerPolicyConfig{Name: "My Policy", Rules: rules} - processLogAndCacheFlags() + if writeConfig { + v.Set("listener", cfg.Listener) + v.Set("upstream", cfg.Upstream) + writeConfigFile() + } } func processListenFlag() { diff --git a/docs/config.md b/docs/config.md index e8b30e8..4f12736 100644 --- a/docs/config.md +++ b/docs/config.md @@ -314,6 +314,17 @@ Above policy will: - Forward requests on `listener.0` for `test.com` to `upstream.2`. If timeout is reached, retry on `upstream.1`. - All other requests on `listener.0` that do not match above conditions will be forwarded to `upstream.0`. +An empty upstream would not route the request to any defined upstreams, and use the OS default resolver. + +```toml +[listener.0.policy] +name = "OS Resolver" + +rules = [ + {"*.local" = []}, +] +``` + #### name `name` is the name for the policy. diff --git a/internal/controld/config.go b/internal/controld/config.go index c4ed22a..ca6303b 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -41,6 +41,9 @@ func FetchResolverConfig(uid string) (*ResolverConfig, error) { if err != nil { return nil, fmt.Errorf("http.NewRequest: %w", err) } + q := req.URL.Query() + q.Set("platform", "ctrld") + req.URL.RawQuery = q.Encode() req.Header.Add("Content-Type", "application/json") client := http.Client{Timeout: 5 * time.Second} resp, err := client.Do(req) From 279e938b2a7d2ee7d5c3cddb4105cdea2e7e9046 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 10 Jan 2023 10:17:49 +0700 Subject: [PATCH 20/69] cmd/ctrld: make "--cd" always owerwrites the config While at it, also make the toml encoded config format nicer. --- cmd/ctrld/cli.go | 65 ++++++++++++++++++++++++++++++++++-------------- cmd/ctrld/net.go | 7 +++--- config.go | 44 ++++++++++++++++---------------- 3 files changed, 72 insertions(+), 44 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 47002dc..af42c25 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -14,6 +14,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/kardianos/service" + "github.com/pelletier/go-toml/v2" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -87,7 +88,7 @@ func initCLI() { if err := v.Unmarshal(&cfg); err != nil { log.Fatalf("failed to unmarshal config: %v", err) } - processCDFlags(writeDefaultConfig) + processCDFlags() if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil { log.Fatalf("invalid config: %v", err) } @@ -173,10 +174,7 @@ func initCLI() { // the written config won't be writable by SYSTEM account, we have to update // the config here when "--cd" is supplied. if runtime.GOOS == "windows" && cdUID != "" { - if err := v.Unmarshal(&cfg); err != nil { - log.Fatalf("failed to unmarshal config: %v", err) - } - processCDFlags(writeDefaultConfig) + processCDFlags() } } s, err := service.New(&prog{}, sc) @@ -324,7 +322,24 @@ func writeConfigFile() { if cfu := v.ConfigFileUsed(); cfu != "" { defaultConfigFile = cfu } - if err := v.WriteConfigAs(defaultConfigFile); err != nil { + f, err := os.OpenFile(defaultConfigFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(0o644)) + if err != nil { + log.Printf("failed to open config file: %v\n", err) + os.Exit(1) + } + defer f.Close() + if cdUID != "" { + if _, err := f.WriteString("# AUTO-GENERATED VIA CD FLAG - DO NOT MODIFY\n\n"); err != nil { + log.Printf("failed to write header to config file: %v\n", err) + os.Exit(1) + } + } + enc := toml.NewEncoder(f).SetIndentTables(true) + if err := enc.Encode(v.AllSettings()); err != nil { + log.Printf("failed to encode config file: %v\n", err) + os.Exit(1) + } + if err := f.Close(); err != nil { log.Printf("failed to write config file: %v\n", err) } } @@ -399,7 +414,7 @@ func processNoConfigFlags(noConfigStart bool) { processLogAndCacheFlags() } -func processCDFlags(writeConfig bool) { +func processCDFlags() { if cdUID == "" { return } @@ -408,22 +423,36 @@ func processCDFlags(writeConfig bool) { log.Fatalf("failed to fetch resolver config: %v", err) } - u0 := cfg.Upstream["0"] - u0.Name = resolverConfig.DOH - u0.Endpoint = resolverConfig.DOH - u0.Type = ctrld.ResolverTypeDOH - + cfg = ctrld.Config{} + cfg.Network = make(map[string]*ctrld.NetworkConfig) + cfg.Network["0"] = &ctrld.NetworkConfig{ + Name: "Netowrk 0", + Cidrs: []string{"0.0.0.0/0"}, + } + cfg.Upstream = make(map[string]*ctrld.UpstreamConfig) + cfg.Upstream["0"] = &ctrld.UpstreamConfig{ + Endpoint: resolverConfig.DOH, + Type: ctrld.ResolverTypeDOH, + Timeout: 5000, + } rules := make([]ctrld.Rule, 0, len(resolverConfig.Exclude)) for _, domain := range resolverConfig.Exclude { rules = append(rules, ctrld.Rule{domain: []string{}}) } - cfg.Listener["0"].Policy = &ctrld.ListenerPolicyConfig{Name: "My Policy", Rules: rules} - - if writeConfig { - v.Set("listener", cfg.Listener) - v.Set("upstream", cfg.Upstream) - writeConfigFile() + cfg.Listener = make(map[string]*ctrld.ListenerConfig) + cfg.Listener["0"] = &ctrld.ListenerConfig{ + IP: "127.0.0.1", + Port: 53, + Policy: &ctrld.ListenerPolicyConfig{ + Name: "My Policy", + Rules: rules, + }, } + + v.Set("network", cfg.Network) + v.Set("upstream", cfg.Upstream) + v.Set("listener", cfg.Listener) + writeConfigFile() } func processListenFlag() { diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go index ce98405..ef2d47d 100644 --- a/cmd/ctrld/net.go +++ b/cmd/ctrld/net.go @@ -3,18 +3,17 @@ package main import ( "net" "sync" - - "golang.org/x/net/nettest" ) +const controldIPv6Test = "ipv6.controld.io" + var ( stackOnce sync.Once ipv6Enabled bool ) func probeStack() { - // TODO(cuonglm): use nettest.SupportsIPv6 once https://github.com/golang/go/issues/57386 fixed. - if _, err := nettest.RoutedInterface("ip6", net.FlagUp); err == nil { + if _, err := net.Dial("tcp6", controldIPv6Test); err == nil { ipv6Enabled = true } } diff --git a/config.go b/config.go index a5514ea..8916cb4 100644 --- a/config.go +++ b/config.go @@ -68,7 +68,7 @@ func InitConfig(v *viper.Viper, name string) { // Config represents ctrld supported configuration. type Config struct { - Service ServiceConfig `mapstructure:"service"` + Service ServiceConfig `mapstructure:"service" toml:"service,omitempty"` Network map[string]*NetworkConfig `mapstructure:"network" toml:"network" validate:"min=1,dive"` Upstream map[string]*UpstreamConfig `mapstructure:"upstream" toml:"upstream" validate:"min=1,dive"` Listener map[string]*ListenerConfig `mapstructure:"listener" toml:"listener" validate:"min=1,dive"` @@ -76,49 +76,49 @@ type Config struct { // ServiceConfig specifies the general ctrld config. type ServiceConfig struct { - LogLevel string `mapstructure:"log_level" toml:"log_level"` - LogPath string `mapstructure:"log_path" toml:"log_path"` - CacheEnable bool `mapstructure:"cache_enable" toml:"cache_enable"` - CacheSize int `mapstructure:"cache_size" toml:"cache_size"` - CacheTTLOverride int `mapstructure:"cache_ttl_override" toml:"cache_ttl_override"` - CacheServeStale bool `mapstructure:"cache_serve_stale" toml:"cache_serve_stale"` + LogLevel string `mapstructure:"log_level" toml:"log_level,omitempty"` + LogPath string `mapstructure:"log_path" toml:"log_path,omitempty"` + CacheEnable bool `mapstructure:"cache_enable" toml:"cache_enable,omitempty"` + CacheSize int `mapstructure:"cache_size" toml:"cache_size,omitempty"` + CacheTTLOverride int `mapstructure:"cache_ttl_override" toml:"cache_ttl_override,omitempty"` + CacheServeStale bool `mapstructure:"cache_serve_stale" toml:"cache_serve_stale,omitempty"` Daemon bool `mapstructure:"-" toml:"-"` AllocateIP bool `mapstructure:"-" toml:"-"` } // NetworkConfig specifies configuration for networks where ctrld will handle requests. type NetworkConfig struct { - Name string `mapstructure:"name" toml:"name"` - Cidrs []string `mapstructure:"cidrs" toml:"cidrs" validate:"dive,cidr"` + Name string `mapstructure:"name" toml:"name,omitempty"` + Cidrs []string `mapstructure:"cidrs" toml:"cidrs,omitempty" validate:"dive,cidr"` IPNets []*net.IPNet `mapstructure:"-" toml:"-"` } // UpstreamConfig specifies configuration for upstreams that ctrld will forward requests to. type UpstreamConfig struct { - Name string `mapstructure:"name" toml:"name"` - Type string `mapstructure:"type" toml:"type" validate:"oneof=doh doh3 dot doq os legacy"` - Endpoint string `mapstructure:"endpoint" toml:"endpoint" validate:"required_unless=Type os"` - BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip"` + Name string `mapstructure:"name" toml:"name,omitempty"` + Type string `mapstructure:"type" toml:"type,omitempty" validate:"oneof=doh doh3 dot doq os legacy"` + Endpoint string `mapstructure:"endpoint" toml:"endpoint,omitempty" validate:"required_unless=Type os"` + BootstrapIP string `mapstructure:"bootstrap_ip" toml:"bootstrap_ip,omitempty"` Domain string `mapstructure:"-" toml:"-"` - Timeout int `mapstructure:"timeout" toml:"timeout" validate:"gte=0"` + Timeout int `mapstructure:"timeout" toml:"timeout,omitempty" validate:"gte=0"` transport *http.Transport `mapstructure:"-" toml:"-"` http3RoundTripper *http3.RoundTripper `mapstructure:"-" toml:"-"` } // ListenerConfig specifies the networks configuration that ctrld will run on. type ListenerConfig struct { - IP string `mapstructure:"ip" toml:"ip" validate:"ip"` - Port int `mapstructure:"port" toml:"port" validate:"gt=0"` - Restricted bool `mapstructure:"restricted" toml:"restricted"` - Policy *ListenerPolicyConfig `mapstructure:"policy" toml:"policy"` + IP string `mapstructure:"ip" toml:"ip,omitempty" validate:"ip"` + Port int `mapstructure:"port" toml:"port,omitempty" validate:"gt=0"` + Restricted bool `mapstructure:"restricted" toml:"restricted,omitempty"` + Policy *ListenerPolicyConfig `mapstructure:"policy" toml:"policy,omitempty"` } // ListenerPolicyConfig specifies the policy rules for ctrld to filter incoming requests. type ListenerPolicyConfig struct { - Name string `mapstructure:"name" toml:"name"` - Networks []Rule `mapstructure:"networks" toml:"networks" validate:"dive,len=1"` - Rules []Rule `mapstructure:"rules" toml:"rules" validate:"dive,len=1"` - FailoverRcodes []string `mapstructure:"failover_rcodes" toml:"failover_rcodes" validate:"dive,dnsrcode"` + Name string `mapstructure:"name" toml:"name,omitempty"` + Networks []Rule `mapstructure:"networks" toml:"networks,omitempty,inline,multiline" validate:"dive,len=1"` + Rules []Rule `mapstructure:"rules" toml:"rules,omitempty,inline,multiline" validate:"dive,len=1"` + FailoverRcodes []string `mapstructure:"failover_rcodes" toml:"failover_rcodes,omitempty" validate:"dive,dnsrcode"` FailoverRcodeNumbers []int `mapstructure:"-" toml:"-"` } From 3a5c71514c48079725afeda9ee02921bad8ce3c8 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 10 Jan 2023 18:56:06 +0700 Subject: [PATCH 21/69] cmd/ctrld: ensure viper is re-new in --cd mode --- cmd/ctrld/cli.go | 1 + cmd/ctrld/main.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index af42c25..d9deab8 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -449,6 +449,7 @@ func processCDFlags() { }, } + v = viper.NewWithOptions(viper.KeyDelimiter("::")) v.Set("network", cfg.Network) v.Set("upstream", cfg.Upstream) v.Set("listener", cfg.Listener) diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index d4362ba..f82ee2f 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -60,7 +60,7 @@ func initLogging() { fmt.Fprintf(os.Stderr, "failed to create log path: %v", err) os.Exit(1) } - logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600) + logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_RDWR, os.FileMode(0o600)) if err != nil { fmt.Fprintf(os.Stderr, "failed to create log file: %v", err) os.Exit(1) From 53306235dc3b0c76f4b38d80315b69628e1f59d1 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 11 Jan 2023 22:31:00 +0700 Subject: [PATCH 22/69] all: uninstall service if got invalid config from API --- cmd/ctrld/cli.go | 17 +++++++++++++++-- internal/controld/config.go | 19 +++++++++++++------ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index d9deab8..6549f39 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -348,7 +348,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()) + fmt.Println("loading config file from:", v.ConfigFileUsed()) return true } @@ -419,8 +419,21 @@ func processCDFlags() { return } resolverConfig, err := controld.FetchResolverConfig(cdUID) + if uer, ok := err.(*controld.UtilityErrorResponse); ok && uer.ErrorField.Code == controld.InvalidConfigCode { + s, err := service.New(&prog{}, svcConfig) + if err != nil { + stderrMsg(err.Error()) + return + } + tasks := []task{{s.Uninstall, true}} + if doTasks(tasks) { + log.Println("uninstalled service") + } + log.Fatalf("failed to fetch resolver config: %v", uer) + } if err != nil { - log.Fatalf("failed to fetch resolver config: %v", err) + log.Printf("could not fetch resolver config: %v", err) + return } cfg = ctrld.Config{} diff --git a/internal/controld/config.go b/internal/controld/config.go index ca6303b..3ca1c5b 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -3,13 +3,15 @@ package controld import ( "bytes" "encoding/json" - "errors" "fmt" "net/http" "time" ) -const resolverDataURL = "https://api.controld.com/utility" +const ( + resolverDataURL = "https://api.controld.com/utility" + InvalidConfigCode = 40401 +) // ResolverConfig represents Control D resolver data. type ResolverConfig struct { @@ -24,12 +26,17 @@ type utilityResponse struct { } `json:"body"` } -type utilityErrorResponse struct { - Error struct { +type UtilityErrorResponse struct { + ErrorField struct { Message string `json:"message"` + Code int `json:"code"` } `json:"error"` } +func (u UtilityErrorResponse) Error() string { + return u.ErrorField.Message +} + type utilityRequest struct { UID string `json:"uid"` } @@ -53,11 +60,11 @@ func FetchResolverConfig(uid string) (*ResolverConfig, error) { defer resp.Body.Close() d := json.NewDecoder(resp.Body) if resp.StatusCode != http.StatusOK { - errResp := &utilityErrorResponse{} + errResp := &UtilityErrorResponse{} if err := d.Decode(errResp); err != nil { return nil, err } - return nil, errors.New(errResp.Error.Message) + return nil, errResp } ur := &utilityResponse{} From 8e164185b900cd63ab802a7e9202c9937e1fa5a3 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 12 Jan 2023 08:58:29 +0700 Subject: [PATCH 23/69] cmd/ctrld: always pass config file on windows start mode On windows, the SYSTEM user is used to run ctrld service. This user has different environment with the user that run the `ctrld` binary via CLI. That causes the mismatch issue in config file path, log path, or more generally, everything that involve with home directory. To circumvent this pain, just always passing the config path and the original home dir in start mode. So `ctrld run` command can setup things correctly. --- cmd/ctrld/cli.go | 16 +++++++++++----- cmd/ctrld/main.go | 4 ++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 6549f39..b84cc06 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -144,6 +144,8 @@ func initCLI() { runCmd.Flags().StringVarP(&logPath, "log", "", "", "path to log file") runCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") runCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") + runCmd.Flags().StringVarP(&homedir, "homedir", "", "", "") + _ = runCmd.Flags().MarkHidden("homedir") rootCmd.AddCommand(runCmd) @@ -169,14 +171,18 @@ func initCLI() { defaultConfigFile = filepath.Join(dir, defaultConfigFile) readConfigFile(true) } + sc.Arguments = append(sc.Arguments, "--homedir="+dir) + } - // On Windows, the service will be run as SYSTEM, so if ctrld start as Admin, - // the written config won't be writable by SYSTEM account, we have to update - // the config here when "--cd" is supplied. - if runtime.GOOS == "windows" && cdUID != "" { - processCDFlags() + // On Windows, the service will be run as SYSTEM, so if ctrld start as Admin, + // the user home dir is different, so pass specific arguments that relevant here. + if runtime.GOOS == "windows" { + processCDFlags() + if configPath == "" { + sc.Arguments = append(sc.Arguments, "--config="+defaultConfigFile) } } + s, err := service.New(&prog{}, sc) if err != nil { stderrMsg(err.Error()) diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index f82ee2f..5185e28 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -22,6 +22,7 @@ var ( secondaryUpstream string domains []string logPath string + homedir string cacheSize int cfg ctrld.Config verbose int @@ -44,6 +45,9 @@ func normalizeLogFilePath(logFilePath string) string { if logFilePath == "" || filepath.IsAbs(logFilePath) || service.Interactive() { return logFilePath } + if homedir != "" { + return filepath.Join(homedir, logFilePath) + } dir, _ := os.UserHomeDir() if dir == "" { return logFilePath From d5344aea5226b04d88686be62454a06e8bbb2b1e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 5 Jan 2023 23:27:39 +0700 Subject: [PATCH 24/69] cmd/ctrld: add list interfaces command --- cmd/ctrld/cli.go | 37 ++++++++ go.mod | 9 ++ go.sum | 242 ++++++----------------------------------------- 3 files changed, 76 insertions(+), 212 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index b84cc06..45bcc0d 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "net" + "net/netip" "os" "os/exec" "path/filepath" @@ -17,6 +18,7 @@ import ( "github.com/pelletier/go-toml/v2" "github.com/spf13/cobra" "github.com/spf13/viper" + "tailscale.com/net/interfaces" "github.com/Control-D-Inc/ctrld" "github.com/Control-D-Inc/ctrld/internal/controld" @@ -290,6 +292,39 @@ func initCLI() { }, } + listIfacesCmd := &cobra.Command{ + Use: "list", + Short: "List network interfaces of the host", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + err := interfaces.ForeachInterface(func(i interfaces.Interface, prefixes []netip.Prefix) { + fmt.Printf("Index : %d\n", i.Index) + fmt.Printf("Name : %s\n", i.Name) + addrs, _ := i.Addrs() + for i, ipaddr := range addrs { + if i == 0 { + fmt.Printf("Addrs : %v\n", ipaddr) + continue + } + fmt.Printf(" %v\n", ipaddr) + } + println() + }) + if err != nil { + stderrMsg(err.Error()) + } + }, + } + interfacesCmd := &cobra.Command{ + Use: "interfaces", + Short: "Manage network interfaces", + Args: cobra.OnlyValidArgs, + ValidArgs: []string{ + listIfacesCmd.Use, + }, + } + interfacesCmd.AddCommand(listIfacesCmd) + serviceCmd := &cobra.Command{ Use: "service", Short: "Manage ctrld service", @@ -300,6 +335,7 @@ func initCLI() { restartCmd.Use, statusCmd.Use, uninstallCmd.Use, + interfacesCmd.Use, }, } serviceCmd.AddCommand(startCmd) @@ -307,6 +343,7 @@ func initCLI() { serviceCmd.AddCommand(restartCmd) serviceCmd.AddCommand(statusCmd) serviceCmd.AddCommand(uninstallCmd) + serviceCmd.AddCommand(interfacesCmd) rootCmd.AddCommand(serviceCmd) startCmdAlias := &cobra.Command{ Use: "start", diff --git a/go.mod b/go.mod index 6529554..8e2a390 100644 --- a/go.mod +++ b/go.mod @@ -19,14 +19,18 @@ require ( ) require ( + github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.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-20210107165309-348f09dbbbc0 // indirect github.com/golang/mock v1.6.0 // indirect + github.com/google/go-cmp v0.5.9 // 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/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect @@ -34,6 +38,8 @@ require ( github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mdlayher/netlink v1.6.0 // indirect + github.com/mdlayher/socket v0.2.3 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.5 // indirect @@ -44,11 +50,14 @@ 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 + go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/text v0.5.0 // indirect golang.org/x/tools v0.1.12 // indirect + golang.zx2c4.com/wireguard/windows v0.5.3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 66033f3..7113bb3 100644 --- a/go.sum +++ b/go.sum @@ -17,14 +17,6 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -33,7 +25,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -47,25 +38,18 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao= +github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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= @@ -77,19 +61,13 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -104,7 +82,6 @@ github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -116,7 +93,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -133,11 +109,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -148,16 +120,14 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -168,57 +138,33 @@ 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-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= +github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b h1:Yws7RV6kZr2O7PPdT+RkbSmmOponA8i/1DuGHe8BRsM= +github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b/go.mod h1:TzDCVOZKUa79z6iXbbXqhtAflVgUKaFkZ21M5tK5tzY= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kardianos/service v1.2.1 h1:AYndMsehS+ywIS6RB9KOlcXzteWUzxgMgBymJD7+BYk= github.com/kardianos/service v1.2.1/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -230,8 +176,6 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lucas-clemente/quic-go v0.29.1 h1:Z+WMJ++qMLhvpFkRZA+jl3BTxUjm415YBmWanXB8zP0= github.com/lucas-clemente/quic-go v0.29.1/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= @@ -240,33 +184,19 @@ github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKA github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU= github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +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/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/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -278,23 +208,16 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= @@ -303,16 +226,8 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= @@ -321,26 +236,20 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= -github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -348,30 +257,20 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 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.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go4.org/mem v0.0.0-20210711025021-927187094b94 h1:OAAkygi2Js191AJP1Ds42MhJRgeofeKGjuoUqNp1QC4= +go4.org/mem v0.0.0-20210711025021-927187094b94/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= @@ -401,7 +300,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -418,7 +316,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -429,7 +326,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -447,16 +343,14 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -468,12 +362,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -485,14 +373,11 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -501,19 +386,13 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -534,42 +413,31 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/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-20210616094352-59db8d763f22/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= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -588,7 +456,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -613,7 +480,6 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -623,14 +489,9 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -638,6 +499,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -657,14 +520,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -695,7 +550,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -708,24 +562,7 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -739,19 +576,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -762,28 +589,19 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= -gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= From b00a7c34eed8be7ca27afaaf01e3b172c4a59ee4 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 6 Jan 2023 00:55:21 +0700 Subject: [PATCH 25/69] cmd/ctrld: add --iface for setting DNS on specific interface --- cmd/ctrld/cli.go | 92 ++++++++++++++--- cmd/ctrld/dns.go | 3 + cmd/ctrld/main.go | 1 + cmd/ctrld/net.go | 6 ++ cmd/ctrld/os_linux.go | 92 +++++++++++++++++ cmd/ctrld/os_mac.go | 32 ++++++ cmd/ctrld/os_windows.go | 99 +++++++++++++++++++ cmd/ctrld/prog.go | 32 +++++- go.mod | 26 ++++- go.sum | 78 +++++++++++++-- internal/resolvconffile/dns.go | 35 +++++++ .../resolvconffile/dns_test.go | 6 +- nameservers_unix.go | 16 +-- 13 files changed, 473 insertions(+), 45 deletions(-) create mode 100644 cmd/ctrld/dns.go create mode 100644 internal/resolvconffile/dns.go rename nameservers_unix_test.go => internal/resolvconffile/dns_test.go (54%) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 45bcc0d..1db8b61 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -3,6 +3,7 @@ package main import ( "bytes" "encoding/base64" + "errors" "fmt" "log" "net" @@ -12,6 +13,7 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "github.com/go-playground/validator/v10" "github.com/kardianos/service" @@ -24,10 +26,16 @@ import ( "github.com/Control-D-Inc/ctrld/internal/controld" ) +const ( + tailscaleDevName = "tailscale0" + tailscaleDNS = "100.100.100.100" +) + var ( v = viper.NewWithOptions(viper.KeyDelimiter("::")) defaultConfigWritten = false defaultConfigFile = "ctrld.toml" + tailscaleIface *net.Interface ) var basicModeFlags = []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log", "cache_size"} @@ -41,6 +49,15 @@ func isNoConfigStart(cmd *cobra.Command) bool { return false } +const rootShortDesc = ` + __ .__ .___ + _____/ |________| | __| _/ +_/ ___\ __\_ __ \ | / __ | +\ \___| | | | \/ |__/ /_/ | + \___ >__| |__| |____/\____ | + \/ dns forwarding proxy \/ +` + func initCLI() { // Enable opening via explorer.exe on Windows. // See: https://github.com/spf13/cobra/issues/844. @@ -48,15 +65,17 @@ func initCLI() { rootCmd := &cobra.Command{ Use: "ctrld", - Short: "Running Control-D DNS proxy server", + Short: strings.TrimLeft(rootShortDesc, "\n"), Version: "1.0.1", } rootCmd.PersistentFlags().CountVarP( &verbose, "verbose", "v", - `verbose log output, "-v" means query logging enabled, "-vv" means debug level logging enabled`, + `verbose log output, "-v" basic logging, "-vv" debug level logging`, ) + rootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) + rootCmd.CompletionOptions.HiddenDefaultCmd = true runCmd := &cobra.Command{ Use: "run", @@ -96,6 +115,15 @@ func initCLI() { } initLogging() initCache() + + if iface == "auto" { + dri, err := interfaces.DefaultRouteInterface() + if err != nil { + mainLog.Error().Err(err).Msg("failed to get default route interface") + } + iface = dri + } + if daemon { exe, err := os.Executable() if err != nil { @@ -138,16 +166,18 @@ func initCLI() { } runCmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "Run as daemon") runCmd.Flags().StringVarP(&configPath, "config", "c", "", "Path to config file") - runCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "base64 encoded config") - runCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "listener address and port, in format: address:port") - runCmd.Flags().StringVarP(&primaryUpstream, "primary_upstream", "", "", "primary upstream endpoint") - runCmd.Flags().StringVarP(&secondaryUpstream, "secondary_upstream", "", "", "secondary upstream endpoint") - runCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "list of domain to apply in a split DNS policy") - runCmd.Flags().StringVarP(&logPath, "log", "", "", "path to log file") + runCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "Base64 encoded config") + runCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "Listener address and port, in format: address:port") + runCmd.Flags().StringVarP(&primaryUpstream, "primary_upstream", "", "", "Primary upstream endpoint") + runCmd.Flags().StringVarP(&secondaryUpstream, "secondary_upstream", "", "", "Secondary upstream endpoint") + runCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "List of domain to apply in a split DNS policy") + runCmd.Flags().StringVarP(&logPath, "log", "", "", "Path to log file") runCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") runCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") runCmd.Flags().StringVarP(&homedir, "homedir", "", "", "") _ = runCmd.Flags().MarkHidden("homedir") + runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) + _ = runCmd.Flags().MarkHidden("iface") rootCmd.AddCommand(runCmd) @@ -204,14 +234,15 @@ func initCLI() { } // Keep these flags in sync with runCmd above, except for "-d". startCmd.Flags().StringVarP(&configPath, "config", "c", "", "Path to config file") - startCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "base64 encoded config") - startCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "listener address and port, in format: address:port") - startCmd.Flags().StringVarP(&primaryUpstream, "primary_upstream", "", "", "primary upstream endpoint") - startCmd.Flags().StringVarP(&secondaryUpstream, "secondary_upstream", "", "", "secondary upstream endpoint") - startCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "list of domain to apply in a split DNS policy") - startCmd.Flags().StringVarP(&logPath, "log", "", "", "path to log file") + startCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "Base64 encoded config") + startCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "Listener address and port, in format: address:port") + startCmd.Flags().StringVarP(&primaryUpstream, "primary_upstream", "", "", "Primary upstream endpoint") + startCmd.Flags().StringVarP(&secondaryUpstream, "secondary_upstream", "", "", "Secondary upstream endpoint") + startCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "List of domain to apply in a split DNS policy") + startCmd.Flags().StringVarP(&logPath, "log", "", "", "Path to log file") startCmd.Flags().IntVarP(&cacheSize, "cache_size", "", 0, "Enable cache with size items") startCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") + startCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) stopCmd := &cobra.Command{ Use: "stop", @@ -228,6 +259,7 @@ func initCLI() { } }, } + stopCmd.Flags().StringVarP(&iface, "iface", "", "", `Reset DNS setting for iface, "auto" means the default interface gateway`) restartCmd := &cobra.Command{ Use: "restart", @@ -347,13 +379,24 @@ func initCLI() { rootCmd.AddCommand(serviceCmd) startCmdAlias := &cobra.Command{ Use: "start", - Short: "Alias for service start", + Short: "Quick start service and configure DNS on interface", Run: func(cmd *cobra.Command, args []string) { startCmd.Run(cmd, args) }, } + startCmdAlias.Flags().StringVarP(&iface, "iface", "", "auto", `Update DNS setting for iface, "auto" means the default interface gateway`) startCmdAlias.Flags().AddFlagSet(startCmd.Flags()) rootCmd.AddCommand(startCmdAlias) + stopCmdAlias := &cobra.Command{ + Use: "stop", + Short: "Quick stop service and remove DNS from interface", + Run: func(cmd *cobra.Command, args []string) { + startCmd.Run(cmd, args) + }, + } + stopCmdAlias.Flags().StringVarP(&iface, "iface", "", "auto", `Reset DNS setting for iface, "auto" means the default interface gateway`) + stopCmdAlias.Flags().AddFlagSet(stopCmd.Flags()) + rootCmd.AddCommand(stopCmdAlias) if err := rootCmd.Execute(); err != nil { stderrMsg(err.Error()) @@ -461,6 +504,9 @@ func processCDFlags() { if cdUID == "" { return } + if iface == "" { + iface = "auto" + } resolverConfig, err := controld.FetchResolverConfig(cdUID) if uer, ok := err.(*controld.UtilityErrorResponse); ok && uer.ErrorField.Code == controld.InvalidConfigCode { s, err := service.New(&prog{}, svcConfig) @@ -546,3 +592,19 @@ func processLogAndCacheFlags() { } v.Set("service", sc) } + +func netIfaceFromName(ifaceName string) (*net.Interface, error) { + var iface *net.Interface + err := interfaces.ForeachInterface(func(i interfaces.Interface, prefixes []netip.Prefix) { + if i.Name == ifaceName { + iface = i.Interface + } + if i.Name == tailscaleDevName { + tailscaleIface = i.Interface + } + }) + if iface == nil { + return nil, errors.New("interface not found") + } + return iface, err +} diff --git a/cmd/ctrld/dns.go b/cmd/ctrld/dns.go new file mode 100644 index 0000000..6d42867 --- /dev/null +++ b/cmd/ctrld/dns.go @@ -0,0 +1,3 @@ +package main + +type getDNS func(iface string) []string diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index 5185e28..db6b852 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -34,6 +34,7 @@ var ( proxyLog = rootLogger cdUID string + iface string ) func main() { diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go index ef2d47d..257c8b4 100644 --- a/cmd/ctrld/net.go +++ b/cmd/ctrld/net.go @@ -22,3 +22,9 @@ func supportsIPv6() bool { stackOnce.Do(probeStack) return ipv6Enabled } + +// isIPv6 checks if the provided IP is v6. +func isIPv6(ip string) bool { + parsedIP := net.ParseIP(ip) + return parsedIP != nil && parsedIP.To4() == nil && parsedIP.To16() != nil +} diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 6d952c9..5f63d17 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -1,7 +1,18 @@ package main import ( + "bufio" + "bytes" + "fmt" + "net" + "net/netip" "os/exec" + "strings" + + "tailscale.com/net/dns" + "tailscale.com/util/dnsname" + + "github.com/Control-D-Inc/ctrld/internal/resolvconffile" ) // allocate loopback ip @@ -23,3 +34,84 @@ func deAllocateIP(ip string) error { } return nil } + +// set the dns server for the provided network interface +func setDNS(iface *net.Interface, nameservers []string) error { + logf := func(format string, args ...any) { + mainLog.Debug().Msgf(format, args...) + } + + r, err := dns.NewOSConfigurator(logf, iface.Name) + if err != nil { + mainLog.Error().Err(err).Msg("failed to create DNS OS configurator") + return err + } + + ns := make([]netip.Addr, 0, len(nameservers)) + for _, nameserver := range nameservers { + ns = append(ns, netip.MustParseAddr(nameserver)) + } + return r.SetDNS(dns.OSConfig{ + Nameservers: ns, + SearchDomains: []dnsname.FQDN{}, + }) +} + +func resetDNS(iface *net.Interface, nameservers []string) error { + if err := setDNS(iface, nameservers); err != nil { + mainLog.Error().Err(err).Msg("resetDNS failed.") + return err + } + return nil +} + +func currentDNS(iface *net.Interface) []string { + for _, fn := range []getDNS{getDNSByResolvectl, getDNSByNmcli, resolvconffile.NameServers} { + if ns := fn(iface.Name); len(ns) > 0 { + return ns + } + } + return nil +} + +func getDNSByResolvectl(iface string) []string { + b, err := exec.Command("resolvectl", "dns", "-i", iface).Output() + if err != nil { + return nil + } + parts := strings.SplitN(string(b), "%", 2) + if len(parts) != 2 { + return nil + } + parts = strings.Fields(parts[0]) + if len(parts) > 2 { + fmt.Println(parts) + return parts[3:] + } + return nil +} + +func getDNSByNmcli(iface string) []string { + b, err := exec.Command("nmcli", "dev", "show", iface).Output() + if err != nil { + return nil + } + s := bufio.NewScanner(bytes.NewReader(b)) + var dns []string + do := func(line string) { + parts := strings.SplitN(line, ":", 2) + if len(parts) > 1 { + dns = append(dns, strings.TrimSpace(parts[1])) + } + } + for s.Scan() { + line := s.Text() + switch { + case strings.HasPrefix(line, "IP4.DNS"): + fallthrough + case strings.HasPrefix(line, "IP6.DNS"): + do(line) + } + } + return dns +} diff --git a/cmd/ctrld/os_mac.go b/cmd/ctrld/os_mac.go index b635ea5..99d7f91 100644 --- a/cmd/ctrld/os_mac.go +++ b/cmd/ctrld/os_mac.go @@ -4,6 +4,7 @@ package main import ( + "net" "os/exec" ) @@ -26,3 +27,34 @@ func deAllocateIP(ip string) error { } return nil } + +// set the dns server for the provided network interface +// networksetup -setdnsservers Wi-Fi 8.8.8.8 1.1.1.1 +// TODO(cuonglm): use system API +func setDNS(iface *net.Interface, nameservers []string) error { + cmd := "networksetup" + args := []string{"-setdnsservers", iface.Name} + args = append(args, nameservers...) + + if err := exec.Command(cmd, args...).Run(); err != nil { + mainLog.Error().Err(err).Msgf("setDNS failed, ips = %q", nameservers) + return err + } + return nil +} + +// TODO(cuonglm): use system API +func resetDNS(iface *net.Interface, _ []string) error { + cmd := "networksetup" + args := []string{"-setdnsservers", iface.Name, "empty"} + + if err := exec.Command(cmd, args...).Run(); err != nil { + mainLog.Error().Err(err).Msgf("resetDNS failed") + return err + } + return nil +} + +func currentDNS(_ *net.Interface) []string { + return nil +} diff --git a/cmd/ctrld/os_windows.go b/cmd/ctrld/os_windows.go index e7c2c56..373479e 100644 --- a/cmd/ctrld/os_windows.go +++ b/cmd/ctrld/os_windows.go @@ -3,6 +3,15 @@ package main +import ( + "errors" + "net" + "os/exec" + "strconv" + + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" +) + // TODO(cuonglm): implement. func allocateIP(ip string) error { return nil @@ -12,3 +21,93 @@ func allocateIP(ip string) error { func deAllocateIP(ip string) error { return nil } + +func setDNS(iface *net.Interface, nameservers []string) error { + if len(nameservers) == 0 { + return errors.New("empty DNS nameservers") + } + primaryDNS := nameservers[0] + if err := setPrimaryDNS(iface, primaryDNS); err != nil { + return err + } + if len(nameservers) > 1 { + secondaryDNS := nameservers[1] + _ = addSecondaryDNS(iface, secondaryDNS) + } + return nil +} + +// TODO(cuonglm): should we use system API? +func resetDNS(iface *net.Interface, nameservers []string) error { + if err := resetDNSUseDHCP(iface); err != nil { + mainLog.Debug().Err(err).Msg("could not reset DNS using DHCP") + } + return setDNS(iface, nameservers) +} + +func setPrimaryDNS(iface *net.Interface, dns string) error { + ipVer := "ipv4" + if isIPv6(dns) { + ipVer = "ipv6" + } + idx := strconv.Itoa(iface.Index) + output, err := netsh("interface", ipVer, "set", "dnsserver", idx, "static", dns) + if err != nil { + mainLog.Error().Err(err).Msgf("failed to set primary DNS: %s", string(output)) + return err + } + if ipVer == "ipv4" { + // Disable IPv6 DNS, so the query will be fallback to IPv4. + _, _ = netsh("interface", "ipv6", "set", "dnsserver", idx, "static", "::1", "primary") + } + + return nil +} + +func addSecondaryDNS(iface *net.Interface, dns string) error { + ipVer := "ipv4" + if isIPv6(dns) { + ipVer = "ipv6" + } + output, err := netsh("interface", ipVer, "add", "dns", strconv.Itoa(iface.Index), dns, "index=2") + if err != nil { + mainLog.Warn().Err(err).Msgf("failed to add secondary DNS: %s", string(output)) + } + return nil +} + +func resetDNSUseDHCP(iface *net.Interface) error { + if supportsIPv6() { + if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil { + mainLog.Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output)) + } + } + output, err := netsh("interface", "ipv4", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp") + if err != nil { + mainLog.Error().Err(err).Msgf("failed to reset ipv4 DNS: %s", string(output)) + return err + } + return nil +} + +func netsh(args ...string) ([]byte, error) { + return exec.Command("netsh", args...).Output() +} + +func currentDNS(iface *net.Interface) []string { + luid, err := winipcfg.LUIDFromIndex(uint32(iface.Index)) + if err != nil { + mainLog.Error().Err(err).Msg("failed to get interface LUID") + return nil + } + nameservers, err := luid.DNS() + if err != nil { + mainLog.Error().Err(err).Msg("failed to get interface DNS") + return nil + } + ns := make([]string, 0, len(nameservers)) + for _, nameserver := range nameservers { + ns = append(ns, nameserver.String()) + } + return ns +} diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 9a889be..d6bcbca 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -23,8 +23,9 @@ var svcConfig = &service.Config{ } type prog struct { - cfg *ctrld.Config - cache dnscache.Cacher + cfg *ctrld.Config + cache dnscache.Cacher + origDNS []string } func (p *prog) Start(s service.Service) error { @@ -34,6 +35,24 @@ func (p *prog) Start(s service.Service) error { } func (p *prog) run() { + if iface != "" { + netIface, err := netIfaceFromName(iface) + if err != nil { + mainLog.Error().Err(err).Msg("could not get interface") + } else { + p.origDNS = currentDNS(netIface) + if err := setDNS(netIface, []string{cfg.Listener["0"].IP}); err != nil { + mainLog.Error().Err(err).Str("iface", iface).Msgf("could not set DNS for interface") + } + } + } + // Sorry, tailscale! + if tailscaleIface != nil { + if err := setDNS(tailscaleIface, []string{cfg.Listener["0"].IP}); err != nil { + mainLog.Warn().Err(err).Msg("could not set DNS for tailscale interface") + } + } + if p.cfg.Service.CacheEnable { cacher, err := dnscache.NewLRUCache(p.cfg.Service.CacheSize) if err != nil { @@ -164,6 +183,15 @@ func (p *prog) Stop(s service.Service) error { mainLog.Error().Err(err).Msg("de-allocate ip failed") return err } + if iface != "" { + if netIface, err := netIfaceFromName(iface); err == nil { + if err := resetDNS(netIface, p.origDNS); err != nil { + mainLog.Error().Err(err).Str("iface", iface).Msgf("could not reset DNS") + } + } else { + mainLog.Error().Err(err).Msg("could not get interface") + } + } return nil } diff --git a/go.mod b/go.mod index 8e2a390..f2d5684 100644 --- a/go.mod +++ b/go.mod @@ -8,27 +8,34 @@ require ( github.com/kardianos/service v1.2.1 github.com/lucas-clemente/quic-go v0.29.1 github.com/miekg/dns v1.1.50 - github.com/pelletier/go-toml v1.9.5 + github.com/pelletier/go-toml/v2 v2.0.6 github.com/rs/zerolog v1.28.0 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.1.0 - golang.org/x/sys v0.3.0 + golang.org/x/sys v0.4.0 tailscale.com v1.34.1 ) require ( + filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fxamacker/cbor/v2 v2.4.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-20210107165309-348f09dbbbc0 // indirect + github.com/godbus/dbus/v5 v5.0.6 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.6.0 // indirect + github.com/google/btree v1.0.1 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect + github.com/illarion/gonotify v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e // indirect github.com/josharian/native v1.0.0 // indirect github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b // indirect github.com/leodido/go-urn v1.2.1 // indirect @@ -38,28 +45,37 @@ require ( github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mdlayher/genetlink v1.2.0 // indirect github.com/mdlayher/netlink v1.6.0 // indirect github.com/mdlayher/socket v0.2.3 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect 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/x448/float16 v0.8.4 // indirect go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect + go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf // indirect golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.5.1-0.20230105164244-f8411da775a6 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/text v0.6.0 // indirect + golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect golang.org/x/tools v0.1.12 // indirect + golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect + golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 // indirect ) diff --git a/go.sum b/go.sum index 7113bb3..c965e25 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= @@ -62,12 +64,15 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= +github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -82,10 +87,14 @@ github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -112,6 +121,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -143,23 +154,36 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= +github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= +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/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +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= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b h1:Yws7RV6kZr2O7PPdT+RkbSmmOponA8i/1DuGHe8BRsM= github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b/go.mod h1:TzDCVOZKUa79z6iXbbXqhtAflVgUKaFkZ21M5tK5tzY= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kardianos/service v1.2.1 h1:AYndMsehS+ywIS6RB9KOlcXzteWUzxgMgBymJD7+BYk= github.com/kardianos/service v1.2.1/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -188,8 +212,17 @@ github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZb github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU= +github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +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/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +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= @@ -226,6 +259,8 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -252,6 +287,11 @@ 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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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= @@ -263,8 +303,12 @@ 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= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= go4.org/mem v0.0.0-20210711025021-927187094b94 h1:OAAkygi2Js191AJP1Ds42MhJRgeofeKGjuoUqNp1QC4= go4.org/mem v0.0.0-20210711025021-927187094b94/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= +go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf h1:IdwJUzqoIo5lkr2EOyKoe5qipUaEjbOKKY5+fzPBZ3A= +go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf/go.mod h1:+QXzaoURFd0rGDIjDNpyIkv+F9R7EmeKorvlKRnhqgA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -320,12 +364,15 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -341,8 +388,10 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -351,8 +400,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.5.1-0.20230105164244-f8411da775a6 h1:pKt/LWZC6+FwNujj5E7DdVyWcbtQvKqPuN0GPKWMyB8= +golang.org/x/net v0.5.1-0.20230105164244-f8411da775a6/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -379,15 +428,20 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -407,7 +461,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -418,6 +474,7 @@ 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= @@ -426,8 +483,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -438,17 +495,20 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -499,6 +559,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c h1:Okh6a1xpnJslG9Mn84pId1Mn+Q8cvpo4HCeeFWHo0cA= +golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c/go.mod h1:enML0deDxY1ux+B6ANGiwtg0yAJi1rctkTpcHNAVPyg= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -609,6 +673,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 h1:cv/zaNV0nr1mJzaeo4S5mHIm5va1W0/9J3/5prlsuRM= +gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/resolvconffile/dns.go b/internal/resolvconffile/dns.go new file mode 100644 index 0000000..3ce0f91 --- /dev/null +++ b/internal/resolvconffile/dns.go @@ -0,0 +1,35 @@ +//go:build !js && !windows + +package resolvconffile + +import ( + "net" + + "tailscale.com/net/dns/resolvconffile" +) + +const resolvconfPath = "/etc/resolv.conf" + +func NameServersWithPort() []string { + c, err := resolvconffile.ParseFile(resolvconfPath) + if err != nil { + return nil + } + ns := make([]string, 0, len(c.Nameservers)) + for _, nameserver := range c.Nameservers { + ns = append(ns, net.JoinHostPort(nameserver.String(), "53")) + } + return ns +} + +func NameServers(_ string) []string { + c, err := resolvconffile.ParseFile(resolvconfPath) + 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_test.go b/internal/resolvconffile/dns_test.go similarity index 54% rename from nameservers_unix_test.go rename to internal/resolvconffile/dns_test.go index 790e8ee..1c21b58 100644 --- a/nameservers_unix_test.go +++ b/internal/resolvconffile/dns_test.go @@ -1,4 +1,4 @@ -package ctrld +package resolvconffile import ( "testing" @@ -6,8 +6,8 @@ import ( "github.com/stretchr/testify/require" ) -func Test_nameservers(t *testing.T) { - ns := nameservers() +func TestNameServers(t *testing.T) { + ns := NameServers("") require.NotNil(t, ns) t.Log(ns) } diff --git a/nameservers_unix.go b/nameservers_unix.go index 0fe74d9..5c765d3 100644 --- a/nameservers_unix.go +++ b/nameservers_unix.go @@ -3,21 +3,9 @@ package ctrld import ( - "net" - - "tailscale.com/net/dns/resolvconffile" + "github.com/Control-D-Inc/ctrld/internal/resolvconffile" ) -const resolvconfPath = "/etc/resolv.conf" - func nameservers() []string { - c, err := resolvconffile.ParseFile(resolvconfPath) - if err != nil { - return nil - } - ns := make([]string, 0, len(c.Nameservers)) - for _, nameserver := range c.Nameservers { - ns = append(ns, net.JoinHostPort(nameserver.String(), "53")) - } - return ns + return resolvconffile.NameServersWithPort() } From a318e19e330320445cb36ec99335fc87a25a8dfc Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 18 Jan 2023 09:22:16 +0700 Subject: [PATCH 26/69] Workaround quic-go DoQ server issue Reported in https://feedback.controld.com/posts/1739 --- doq.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/doq.go b/doq.go index 4ac3f43..9f498f4 100644 --- a/doq.go +++ b/doq.go @@ -27,6 +27,22 @@ func (r *doqResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, erro } func resolve(ctx context.Context, msg *dns.Msg, endpoint string, tlsConfig *tls.Config) (*dns.Msg, error) { + // DoQ quic-go server returns io.EOF error after running for a long time, + // even for a good stream. So retrying the query for 5 times before giving up. + for i := 0; i < 5; i++ { + answer, err := doResolve(ctx, msg, endpoint, tlsConfig) + if err == io.EOF { + continue + } + if err != nil { + return nil, err + } + return answer, nil + } + return nil, &quic.ApplicationError{ErrorCode: quic.ApplicationErrorCode(quic.InternalError), ErrorMessage: quic.InternalError.Message()} +} + +func doResolve(ctx context.Context, msg *dns.Msg, endpoint string, tlsConfig *tls.Config) (*dns.Msg, error) { session, err := quic.DialAddr(endpoint, tlsConfig, nil) if err != nil { return nil, err @@ -66,6 +82,14 @@ func resolve(ctx context.Context, msg *dns.Msg, endpoint string, tlsConfig *tls. _ = stream.Close() + // io.ReadAll hide the io.EOF error returned by quic-go server. + // Once we figure out why quic-go server sends io.EOF after running + // for a long time, we can have a better way to handle this. For now, + // make sure io.EOF error returned, so the caller can handle it cleanly. + if len(buf) == 0 { + return nil, io.EOF + } + answer := new(dns.Msg) if err := answer.Unpack(buf[2:]); err != nil { return nil, err From 1f2bd90308ae2349f2673fe2b41e2bfd1a0d5c85 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 19 Jan 2023 10:34:14 +0700 Subject: [PATCH 27/69] cmd/ctrld: fix wrong stop command alias It's "stopCmd", not "startCmd" --- cmd/ctrld/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 1db8b61..f1f8429 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -391,7 +391,7 @@ func initCLI() { Use: "stop", Short: "Quick stop service and remove DNS from interface", Run: func(cmd *cobra.Command, args []string) { - startCmd.Run(cmd, args) + stopCmd.Run(cmd, args) }, } stopCmdAlias.Flags().StringVarP(&iface, "iface", "", "auto", `Reset DNS setting for iface, "auto" means the default interface gateway`) From 99b0cbedc372b469ac4a35ad20f845a77d8d7082 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 19 Jan 2023 11:01:10 +0700 Subject: [PATCH 28/69] cmd/ctrld: include DNS in interface list --- cmd/ctrld/cli.go | 7 +++++++ cmd/ctrld/os_linux.go | 2 -- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index f1f8429..da0a3c1 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -340,6 +340,13 @@ func initCLI() { } fmt.Printf(" %v\n", ipaddr) } + for i, dns := range currentDNS(i.Interface) { + if i == 0 { + fmt.Printf("DNS : %s\n", dns) + continue + } + fmt.Printf(" : %s\n", dns) + } println() }) if err != nil { diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 5f63d17..ac94b04 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -3,7 +3,6 @@ package main import ( "bufio" "bytes" - "fmt" "net" "net/netip" "os/exec" @@ -85,7 +84,6 @@ func getDNSByResolvectl(iface string) []string { } parts = strings.Fields(parts[0]) if len(parts) > 2 { - fmt.Println(parts) return parts[3:] } return nil From 05cfb9b66163001c3b2bb3a945fc8c48e8d3f99c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 19 Jan 2023 11:12:05 +0700 Subject: [PATCH 29/69] cmd/ctrld: fix typo in Network name --- cmd/ctrld/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index da0a3c1..42bde24 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -535,7 +535,7 @@ func processCDFlags() { cfg = ctrld.Config{} cfg.Network = make(map[string]*ctrld.NetworkConfig) cfg.Network["0"] = &ctrld.NetworkConfig{ - Name: "Netowrk 0", + Name: "Network 0", Cidrs: []string{"0.0.0.0/0"}, } cfg.Upstream = make(map[string]*ctrld.UpstreamConfig) From 47c280cf1d07f08e14c144486d34bea37ce80f5d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 19 Jan 2023 12:24:21 +0700 Subject: [PATCH 30/69] cmd/ctrld: use network service on darwin --- cmd/ctrld/cli.go | 3 +++ cmd/ctrld/net_darwin.go | 34 ++++++++++++++++++++++++++++++++++ cmd/ctrld/net_others.go | 7 +++++++ 3 files changed, 44 insertions(+) create mode 100644 cmd/ctrld/net_darwin.go create mode 100644 cmd/ctrld/net_others.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 42bde24..97faa91 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -613,5 +613,8 @@ func netIfaceFromName(ifaceName string) (*net.Interface, error) { if iface == nil { return nil, errors.New("interface not found") } + if err := patchNetIfaceName(iface); err != nil { + return nil, err + } return iface, err } diff --git a/cmd/ctrld/net_darwin.go b/cmd/ctrld/net_darwin.go new file mode 100644 index 0000000..223cc75 --- /dev/null +++ b/cmd/ctrld/net_darwin.go @@ -0,0 +1,34 @@ +package main + +import ( + "bufio" + "bytes" + "net" + "os/exec" + "strings" +) + +func patchNetIfaceName(iface *net.Interface) error { + b, err := exec.Command("networksetup", "-listnetworkserviceorder").Output() + if err != nil { + return err + } + + scanner := bufio.NewScanner(bytes.NewReader(b)) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, "*") { + // Network services is disabled. + continue + } + if !strings.Contains(line, "Device: "+iface.Name) { + continue + } + parts := strings.Split(line, ",") + if _, networkServiceName, ok := strings.Cut(parts[0], "(Hardware Port: "); ok { + mainLog.Debug().Str("network_service", networkServiceName).Msg("found network service name for interface") + iface.Name = networkServiceName + } + } + return nil +} diff --git a/cmd/ctrld/net_others.go b/cmd/ctrld/net_others.go new file mode 100644 index 0000000..9093395 --- /dev/null +++ b/cmd/ctrld/net_others.go @@ -0,0 +1,7 @@ +//go:build !darwin + +package main + +import "net" + +func patchNetIfaceName(iface *net.Interface) error { return nil } From a9fabd1b79109a027a0e928d3f8ca1d8efa7d7bd Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 19 Jan 2023 22:44:32 +0700 Subject: [PATCH 31/69] cmd/ctrld: separate iface variable for start/stop aliases While at it, also fix a bug in getDNSByResolvectl, which won't return correct DNS values if there's no "%" symbol in output. --- cmd/ctrld/cli.go | 6 ++++-- cmd/ctrld/main.go | 5 +++-- cmd/ctrld/os_linux.go | 7 ++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 97faa91..bb9fb13 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -388,20 +388,22 @@ func initCLI() { Use: "start", Short: "Quick start service and configure DNS on interface", Run: func(cmd *cobra.Command, args []string) { + os.Args = append(os.Args, "--iface="+ifaceStartStop) startCmd.Run(cmd, args) }, } - startCmdAlias.Flags().StringVarP(&iface, "iface", "", "auto", `Update DNS setting for iface, "auto" means the default interface gateway`) + startCmdAlias.Flags().StringVarP(&ifaceStartStop, "iface", "", "auto", `Update DNS setting for iface, "auto" means the default interface gateway`) startCmdAlias.Flags().AddFlagSet(startCmd.Flags()) rootCmd.AddCommand(startCmdAlias) stopCmdAlias := &cobra.Command{ Use: "stop", Short: "Quick stop service and remove DNS from interface", Run: func(cmd *cobra.Command, args []string) { + os.Args = append(os.Args, "--iface="+ifaceStartStop) stopCmd.Run(cmd, args) }, } - stopCmdAlias.Flags().StringVarP(&iface, "iface", "", "auto", `Reset DNS setting for iface, "auto" means the default interface gateway`) + stopCmdAlias.Flags().StringVarP(&ifaceStartStop, "iface", "", "auto", `Reset DNS setting for iface, "auto" means the default interface gateway`) stopCmdAlias.Flags().AddFlagSet(stopCmd.Flags()) rootCmd.AddCommand(stopCmdAlias) diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index db6b852..c336a2b 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -33,8 +33,9 @@ var ( mainLog = rootLogger proxyLog = rootLogger - cdUID string - iface string + cdUID string + iface string + ifaceStartStop string ) func main() { diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index ac94b04..39818a3 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -78,11 +78,8 @@ func getDNSByResolvectl(iface string) []string { if err != nil { return nil } - parts := strings.SplitN(string(b), "%", 2) - if len(parts) != 2 { - return nil - } - parts = strings.Fields(parts[0]) + + parts := strings.Fields(strings.SplitN(string(b), "%", 2)[0]) if len(parts) > 2 { return parts[3:] } From dc7d77b22e0ee461144cb1c7e79862fbffee2c6e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 19 Jan 2023 22:51:32 +0700 Subject: [PATCH 32/69] cmd/ctrld: only add "--iface" if not changed for start/stop aliases --- cmd/ctrld/cli.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index bb9fb13..5128459 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -388,7 +388,9 @@ func initCLI() { Use: "start", Short: "Quick start service and configure DNS on interface", Run: func(cmd *cobra.Command, args []string) { - os.Args = append(os.Args, "--iface="+ifaceStartStop) + if !cmd.Flags().Changed("iface") { + os.Args = append(os.Args, "--iface="+ifaceStartStop) + } startCmd.Run(cmd, args) }, } @@ -399,7 +401,9 @@ func initCLI() { Use: "stop", Short: "Quick stop service and remove DNS from interface", Run: func(cmd *cobra.Command, args []string) { - os.Args = append(os.Args, "--iface="+ifaceStartStop) + if !cmd.Flags().Changed("iface") { + os.Args = append(os.Args, "--iface="+ifaceStartStop) + } stopCmd.Run(cmd, args) }, } From 49e9b8b51c5308b5dbd1ed2a657dc82d4db05c67 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 19 Jan 2023 23:22:12 +0700 Subject: [PATCH 33/69] cmd/ctrld: do not change DNS for tailscale0 Let user decide which option is prefer. --- cmd/ctrld/cli.go | 9 --------- cmd/ctrld/prog.go | 6 ------ 2 files changed, 15 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 5128459..b449727 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -26,16 +26,10 @@ import ( "github.com/Control-D-Inc/ctrld/internal/controld" ) -const ( - tailscaleDevName = "tailscale0" - tailscaleDNS = "100.100.100.100" -) - var ( v = viper.NewWithOptions(viper.KeyDelimiter("::")) defaultConfigWritten = false defaultConfigFile = "ctrld.toml" - tailscaleIface *net.Interface ) var basicModeFlags = []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log", "cache_size"} @@ -612,9 +606,6 @@ func netIfaceFromName(ifaceName string) (*net.Interface, error) { if i.Name == ifaceName { iface = i.Interface } - if i.Name == tailscaleDevName { - tailscaleIface = i.Interface - } }) if iface == nil { return nil, errors.New("interface not found") diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index d6bcbca..8d26abe 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -46,12 +46,6 @@ func (p *prog) run() { } } } - // Sorry, tailscale! - if tailscaleIface != nil { - if err := setDNS(tailscaleIface, []string{cfg.Listener["0"].IP}); err != nil { - mainLog.Warn().Err(err).Msg("could not set DNS for tailscale interface") - } - } if p.cfg.Service.CacheEnable { cacher, err := dnscache.NewLRUCache(p.cfg.Service.CacheSize) From d418e57defd1f4a72842318f15375cb3c07aa1b8 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 20 Jan 2023 00:55:56 +0700 Subject: [PATCH 34/69] cmd/ctrld: workaround ipv6 dns resolver on Windows On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can listen on ::1, then spawn a listener for receiving DNS requests --- cmd/ctrld/dns_proxy.go | 15 +++++++++++++++ cmd/ctrld/net.go | 14 ++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 679d292..6d190cd 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "net" + "runtime" "strconv" "strings" "time" @@ -51,6 +52,20 @@ func (p *prog) serveUDP(listenerNum string) error { ctrld.Log(ctx, mainLog.Error().Err(err), "serveUDP: failed to send DNS response to client") } }) + + // On Windows, there's no easy way for disabling/removing IPv6 DNS resolver, so we check whether we can + // listen on ::1, then spawn a listener for receiving DNS requests. + if runtime.GOOS == "windows" && supportsIPv6ListenLocal() { + go func() { + s := &dns.Server{ + Addr: net.JoinHostPort("::1", strconv.Itoa(listenerConfig.Port)), + Net: "udp", + Handler: handler, + } + _ = s.ListenAndServe() + }() + } + s := &dns.Server{ Addr: net.JoinHostPort(listenerConfig.IP, strconv.Itoa(listenerConfig.Port)), Net: "udp", diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go index 257c8b4..665759b 100644 --- a/cmd/ctrld/net.go +++ b/cmd/ctrld/net.go @@ -8,14 +8,19 @@ import ( const controldIPv6Test = "ipv6.controld.io" var ( - stackOnce sync.Once - ipv6Enabled bool + stackOnce sync.Once + ipv6Enabled bool + canListenIPv6Local bool ) func probeStack() { if _, err := net.Dial("tcp6", controldIPv6Test); err == nil { ipv6Enabled = true } + if ln, err := net.Listen("tcp6", "[::1]:53"); err == nil { + ln.Close() + canListenIPv6Local = true + } } func supportsIPv6() bool { @@ -23,6 +28,11 @@ func supportsIPv6() bool { return ipv6Enabled } +func supportsIPv6ListenLocal() bool { + stackOnce.Do(probeStack) + return canListenIPv6Local +} + // isIPv6 checks if the provided IP is v6. func isIPv6(ip string) bool { parsedIP := net.ParseIP(ip) From 87091f20b05f7ea885ff001ed0872badc681f529 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 20 Jan 2023 12:50:09 +0700 Subject: [PATCH 35/69] cmd/ctrld: print writing config file message --- cmd/ctrld/cli.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index b449727..05bf9fb 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -434,7 +434,9 @@ func writeConfigFile() { } if err := f.Close(); err != nil { log.Printf("failed to write config file: %v\n", err) + os.Exit(1) } + fmt.Println("writing config file to:", defaultConfigFile) } func readConfigFile(writeDefaultConfig bool) bool { From 326d7a43d47812a1bf4c4b0698902040896309fa Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 20 Jan 2023 13:24:04 +0700 Subject: [PATCH 36/69] cmd/ctrld: rework reset DNS statically vs DHCP If the interface was originally configured DNS via DHCP, ctrld should reset the interface using DHCP, not statically. --- cmd/ctrld/cli.go | 2 +- cmd/ctrld/os_windows.go | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 05bf9fb..0235b1c 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -195,7 +195,7 @@ func initCLI() { writeDefaultConfig := !noConfigStart && configBase64 == "" if configPath == "" && writeDefaultConfig { defaultConfigFile = filepath.Join(dir, defaultConfigFile) - readConfigFile(true) + readConfigFile(writeDefaultConfig && cdUID == "") } sc.Arguments = append(sc.Arguments, "--homedir="+dir) } diff --git a/cmd/ctrld/os_windows.go b/cmd/ctrld/os_windows.go index 373479e..075b30d 100644 --- a/cmd/ctrld/os_windows.go +++ b/cmd/ctrld/os_windows.go @@ -4,6 +4,7 @@ package main import ( + "bytes" "errors" "net" "os/exec" @@ -42,6 +43,9 @@ func resetDNS(iface *net.Interface, nameservers []string) error { if err := resetDNSUseDHCP(iface); err != nil { mainLog.Debug().Err(err).Msg("could not reset DNS using DHCP") } + if len(nameservers) == 0 { + return nil + } return setDNS(iface, nameservers) } @@ -77,7 +81,7 @@ func addSecondaryDNS(iface *net.Interface, dns string) error { } func resetDNSUseDHCP(iface *net.Interface) error { - if supportsIPv6() { + if supportsIPv6ListenLocal() { if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil { mainLog.Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output)) } @@ -95,6 +99,9 @@ func netsh(args ...string) ([]byte, error) { } func currentDNS(iface *net.Interface) []string { + if hasDNSFromDHCP(iface, "ipv4") || hasDNSFromDHCP(iface, "ipv6") { + return nil + } luid, err := winipcfg.LUIDFromIndex(uint32(iface.Index)) if err != nil { mainLog.Error().Err(err).Msg("failed to get interface LUID") @@ -111,3 +118,9 @@ func currentDNS(iface *net.Interface) []string { } return ns } + +func hasDNSFromDHCP(iface *net.Interface, ipVer string) bool { + idx := strconv.Itoa(iface.Index) + output, _ := netsh("interface", ipVer, "show", "dnsservers", idx) + return bytes.Contains(output, []byte(" through DHCP:")) +} From 14ddb1faa09a37c4018a59a28ff5f81c1d0cb5c8 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 21 Jan 2023 00:08:02 +0700 Subject: [PATCH 37/69] cmd/ctrld: ensure writing config message is printed on non-Windows --- cmd/ctrld/cli.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 0235b1c..17baa8c 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -187,6 +187,7 @@ func initCLI() { osArgs = os.Args[3:] } sc.Arguments = append([]string{"run"}, osArgs...) + isWindows := runtime.GOOS == "windows" if dir, err := os.UserHomeDir(); err == nil { // WorkingDirectory is not supported on Windows. sc.WorkingDirectory = dir @@ -195,14 +196,14 @@ func initCLI() { writeDefaultConfig := !noConfigStart && configBase64 == "" if configPath == "" && writeDefaultConfig { defaultConfigFile = filepath.Join(dir, defaultConfigFile) - readConfigFile(writeDefaultConfig && cdUID == "") + readConfigFile(writeDefaultConfig && (cdUID == "" || !isWindows)) } sc.Arguments = append(sc.Arguments, "--homedir="+dir) } // On Windows, the service will be run as SYSTEM, so if ctrld start as Admin, // the user home dir is different, so pass specific arguments that relevant here. - if runtime.GOOS == "windows" { + if isWindows { processCDFlags() if configPath == "" { sc.Arguments = append(sc.Arguments, "--config="+defaultConfigFile) From d830706692e663380cd7288f9a458e1c83aa02c5 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 21 Jan 2023 00:28:28 +0700 Subject: [PATCH 38/69] cmd/ctrld: always process "--cd" in start mode So if there's any error in fetching configuration, it will be reported to user and service won't start. --- cmd/ctrld/cli.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 17baa8c..ab0fc2f 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -187,7 +187,6 @@ func initCLI() { osArgs = os.Args[3:] } sc.Arguments = append([]string{"run"}, osArgs...) - isWindows := runtime.GOOS == "windows" if dir, err := os.UserHomeDir(); err == nil { // WorkingDirectory is not supported on Windows. sc.WorkingDirectory = dir @@ -196,15 +195,15 @@ func initCLI() { writeDefaultConfig := !noConfigStart && configBase64 == "" if configPath == "" && writeDefaultConfig { defaultConfigFile = filepath.Join(dir, defaultConfigFile) - readConfigFile(writeDefaultConfig && (cdUID == "" || !isWindows)) + readConfigFile(writeDefaultConfig && cdUID == "") } sc.Arguments = append(sc.Arguments, "--homedir="+dir) } + processCDFlags() // On Windows, the service will be run as SYSTEM, so if ctrld start as Admin, // the user home dir is different, so pass specific arguments that relevant here. - if isWindows { - processCDFlags() + if runtime.GOOS == "windows" { if configPath == "" { sc.Arguments = append(sc.Arguments, "--config="+defaultConfigFile) } From 065a391ff4d79a00a206f84cd92413f77fc93264 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 21 Jan 2023 01:00:39 +0700 Subject: [PATCH 39/69] cmd/ctrld: check elevated privilege for service mode --- cmd/ctrld/cli.go | 38 +++++++++++++++++++++--------------- cmd/ctrld/service.go | 14 +++++++++++++ cmd/ctrld/service_others.go | 11 +++++++++++ cmd/ctrld/service_windows.go | 24 +++++++++++++++++++++++ 4 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 cmd/ctrld/service_others.go create mode 100644 cmd/ctrld/service_windows.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index ab0fc2f..f9c69cc 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -176,9 +176,10 @@ func initCLI() { rootCmd.AddCommand(runCmd) startCmd := &cobra.Command{ - Use: "start", - Short: "Start the ctrld service", - Args: cobra.NoArgs, + PreRun: checkHasElevatedPrivilege, + Use: "start", + Short: "Start the ctrld service", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { sc := &service.Config{} *sc = *svcConfig @@ -239,9 +240,10 @@ func initCLI() { startCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) stopCmd := &cobra.Command{ - Use: "stop", - Short: "Stop the ctrld service", - Args: cobra.NoArgs, + PreRun: checkHasElevatedPrivilege, + Use: "stop", + Short: "Stop the ctrld service", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { s, err := service.New(&prog{}, svcConfig) if err != nil { @@ -256,9 +258,10 @@ func initCLI() { stopCmd.Flags().StringVarP(&iface, "iface", "", "", `Reset DNS setting for iface, "auto" means the default interface gateway`) restartCmd := &cobra.Command{ - Use: "restart", - Short: "Restart the ctrld service", - Args: cobra.NoArgs, + PreRun: checkHasElevatedPrivilege, + Use: "restart", + Short: "Restart the ctrld service", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { s, err := service.New(&prog{}, svcConfig) if err != nil { @@ -298,9 +301,10 @@ func initCLI() { } uninstallCmd := &cobra.Command{ - Use: "uninstall", - Short: "Uninstall the ctrld service", - Args: cobra.NoArgs, + PreRun: checkHasElevatedPrivilege, + Use: "uninstall", + Short: "Uninstall the ctrld service", + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { s, err := service.New(&prog{}, svcConfig) if err != nil { @@ -379,8 +383,9 @@ func initCLI() { serviceCmd.AddCommand(interfacesCmd) rootCmd.AddCommand(serviceCmd) startCmdAlias := &cobra.Command{ - Use: "start", - Short: "Quick start service and configure DNS on interface", + PreRun: checkHasElevatedPrivilege, + Use: "start", + Short: "Quick start service and configure DNS on interface", Run: func(cmd *cobra.Command, args []string) { if !cmd.Flags().Changed("iface") { os.Args = append(os.Args, "--iface="+ifaceStartStop) @@ -392,8 +397,9 @@ func initCLI() { startCmdAlias.Flags().AddFlagSet(startCmd.Flags()) rootCmd.AddCommand(startCmdAlias) stopCmdAlias := &cobra.Command{ - Use: "stop", - Short: "Quick stop service and remove DNS from interface", + PreRun: checkHasElevatedPrivilege, + Use: "stop", + Short: "Quick stop service and remove DNS from interface", Run: func(cmd *cobra.Command, args []string) { if !cmd.Flags().Changed("iface") { os.Args = append(os.Args, "--iface="+ifaceStartStop) diff --git a/cmd/ctrld/service.go b/cmd/ctrld/service.go index 7307a89..14834c6 100644 --- a/cmd/ctrld/service.go +++ b/cmd/ctrld/service.go @@ -3,6 +3,8 @@ package main import ( "fmt" "os" + + "github.com/spf13/cobra" ) func stderrMsg(msg string) { @@ -29,3 +31,15 @@ func doTasks(tasks []task) bool { } return true } + +func checkHasElevatedPrivilege(cmd *cobra.Command, args []string) { + ok, err := hasElevatedPrivilege() + if err != nil { + fmt.Printf("could not detect user privilege: %v", err) + return + } + if !ok { + fmt.Println("Please relaunch process with admin/root privilege.") + os.Exit(1) + } +} diff --git a/cmd/ctrld/service_others.go b/cmd/ctrld/service_others.go new file mode 100644 index 0000000..82a6ea3 --- /dev/null +++ b/cmd/ctrld/service_others.go @@ -0,0 +1,11 @@ +//go:build !windows + +package main + +import ( + "os" +) + +func hasElevatedPrivilege() (bool, error) { + return os.Geteuid() == 0, nil +} diff --git a/cmd/ctrld/service_windows.go b/cmd/ctrld/service_windows.go new file mode 100644 index 0000000..0ce8d3a --- /dev/null +++ b/cmd/ctrld/service_windows.go @@ -0,0 +1,24 @@ +package main + +import "golang.org/x/sys/windows" + +func hasElevatedPrivilege() (bool, error) { + var sid *windows.SID + if err := windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, + 2, + windows.SECURITY_BUILTIN_DOMAIN_RID, + windows.DOMAIN_ALIAS_RID_ADMINS, + 0, + 0, + 0, + 0, + 0, + 0, + &sid, + ); err != nil { + return false, err + } + token := windows.Token(0) + return token.IsMember(sid) +} From 46965b04b41ded1d19f1e25f87936610080b0adb Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 20 Jan 2023 21:53:24 +0700 Subject: [PATCH 40/69] internal/resolvconffile: add build tag for test file --- internal/resolvconffile/dns_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/resolvconffile/dns_test.go b/internal/resolvconffile/dns_test.go index 1c21b58..ba571af 100644 --- a/internal/resolvconffile/dns_test.go +++ b/internal/resolvconffile/dns_test.go @@ -1,3 +1,5 @@ +//go:build !js && !windows + package resolvconffile import ( From f6371360bc04bdf3c48eb791796bf99362d51eae Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 20 Jan 2023 21:59:52 +0700 Subject: [PATCH 41/69] all: satisfy staticcheck --- cmd/ctrld/dns.go | 1 + cmd/ctrld/dns_proxy.go | 5 +++-- cmd/ctrld/net.go | 2 ++ resolver.go | 11 ----------- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/cmd/ctrld/dns.go b/cmd/ctrld/dns.go index 6d42867..770a630 100644 --- a/cmd/ctrld/dns.go +++ b/cmd/ctrld/dns.go @@ -1,3 +1,4 @@ package main +//lint:ignore U1000 use in os_linux.go type getDNS func(iface string) []string diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index 6d190cd..bd5176b 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -282,11 +282,12 @@ func containRcode(rcodes []int, rcode int) bool { } func setCachedAnswerTTL(answer *dns.Msg, now, expiredTime time.Time) { - ttl := uint32(expiredTime.Sub(now).Seconds()) - if ttl < 0 { + ttlSecs := expiredTime.Sub(now).Seconds() + if ttlSecs < 0 { return } + ttl := uint32(ttlSecs) for _, rr := range answer.Answer { rr.Header().Ttl = ttl } diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go index 665759b..f98feb9 100644 --- a/cmd/ctrld/net.go +++ b/cmd/ctrld/net.go @@ -34,6 +34,8 @@ func supportsIPv6ListenLocal() bool { } // isIPv6 checks if the provided IP is v6. +// +//lint:ignore U1000 use in os_windows.go func isIPv6(ip string) bool { parsedIP := net.ParseIP(ip) return parsedIP != nil && parsedIP.To4() == nil && parsedIP.To16() != nil diff --git a/resolver.go b/resolver.go index af153d1..5c04f37 100644 --- a/resolver.go +++ b/resolver.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net" - "strings" "sync/atomic" "github.com/miekg/dns" @@ -96,13 +95,3 @@ func (r *legacyResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, e answer, _, err := dnsClient.ExchangeContext(ctx, msg, r.endpoint) return answer, err } - -// canonicalName returns canonical name from FQDN with "." trimmed. -func canonicalName(fqdn string) string { - q := strings.TrimSpace(fqdn) - q = strings.TrimSuffix(q, ".") - // https://datatracker.ietf.org/doc/html/rfc4343 - q = strings.ToLower(q) - - return q -} From f9d6223af524e91a88ec71c14b7db69c605c9aad Mon Sep 17 00:00:00 2001 From: Yegor Sak Date: Sat, 21 Jan 2023 01:40:33 +0000 Subject: [PATCH 42/69] Update README.md Deleted docs/controld_config.md --- README.md | 69 ++++++++++++++++++++++++++++++++++++++--- docs/controld_config.md | 15 --------- 2 files changed, 64 insertions(+), 20 deletions(-) delete mode 100644 docs/controld_config.md diff --git a/README.md b/README.md index 96734cb..65fd2f9 100644 --- a/README.md +++ b/README.md @@ -42,23 +42,32 @@ $ go install github.com/Control-D-Inc/ctrld/cmd/ctrld@latest ## Arguments ``` + __ .__ .___ + _____/ |________| | __| _/ +_/ ___\ __\_ __ \ | / __ | +\ \___| | | | \/ |__/ /_/ | + \___ >__| |__| |____/\____ | + \/ dns forwarding proxy \/ + Usage: ctrld [command] Available Commands: - help Help about any command run Run the DNS proxy server + service Manage ctrld service + start Quick start service and configure DNS on default interface + stop Quick stop service and remove DNS from default interface Flags: - -h, --help help for ctrld - -v, --verbose verbose log output - --version version for ctrld + -h, --help help for ctrld + -v, --verbose count verbose log output, "-v" basic logging, "-vv" debug level logging + --version version for ctrld Use "ctrld [command] --help" for more information about a command. ``` ## Usage -To start the server with default configuration, simply run: `ctrld run`. This will create a generic `config.toml` file in the working directory and start the service. +To start the server with default configuration, simply run: `./ctrld run`. This will create a generic `ctrld.toml` file in the **working directory** and start the application in foreground. 1. Start the server ``` $ sudo ./ctrld run @@ -73,6 +82,56 @@ To start the server with default configuration, simply run: `ctrld run`. This wi If `verify.controld.com` resolves, you're successfully using the default Control D upstream. +### Service Mode +To run the application in service mode, simply run: `./ctrld start` as system/root user. This will create a generic `ctrld.toml` file in the **user home** directory, start the system service, and configure the listener on the default interface. Service will start on OS boot. + +In order to stop the service, and restore your DNS to original state, simply run `./ctrld stop`. + +For granular control of the service, run the `service` command. Each sub-command has its own help section so you can see what arguments you can supply. + +``` + Manage ctrld service + + Usage: + ctrld service [command] + + Available Commands: + interfaces Manage network interfaces + restart Restart the ctrld service + start Start the ctrld service + status Show status of the ctrld service + stop Stop the ctrld service + uninstall Uninstall the ctrld service + + Flags: + -h, --help help for service + + Global Flags: + -v, --verbose count verbose log output, "-v" basic logging, "-vv" debug level logging + + Use "ctrld service [command] --help" for more information about a command. +``` + +### Control D Auto Configuration +Application can be started with a specific resolver config, instead of the default one. Simply supply your resolver ID with a `--cd` flag, when using the `run` (foreground) or `start` (service) modes. + +The following command will start the application in foreground mode, using the free "p2" resolver, which blocks Ads & Trackers. + +```shell +./ctrld run --cd p2 +``` + +Alternatively, you can use your own personal Control D Device resolver, and start the application in service mode. Your resolver ID is the part after the slash of your DNS-over-HTTPS resolver. ie. https://dns.controld.com/abcd1234 + +```shell +./ctrld start --cd abcd1234 +``` + +Once you run the above command, the following things will happen: +- You resolver configuration will be fetched from the API, and config file templated with the resolver data +- Application will start as a service, and keep running (even after reboot) until you run the `stop` or `service uninstall` sub-commands +- Your default network interface will be updated to use the listener started by the service +- All OS DNS queries will be sent to the listener ## Configuration ### Example diff --git a/docs/controld_config.md b/docs/controld_config.md deleted file mode 100644 index 20c9c5b..0000000 --- a/docs/controld_config.md +++ /dev/null @@ -1,15 +0,0 @@ -# Control D config - -`ctrld` can build a Control D config and run with the specific resolver data. - -For example: - -```shell -ctrld run --cd p2 -``` - -Above command will fetch the `p2` resolver data from Control D API and use that data for running `ctrld`: - - - The resolver `doh` endpoint will be used as the primary upstream. - - The resolver `exclude` list will be used to create a rule policy which will steer them to the default OS resolver. -``` From 056b76d5a8d6006c1d8bf65af5ea4721ee0198fe Mon Sep 17 00:00:00 2001 From: Yegor Sak Date: Sat, 21 Jan 2023 02:04:43 +0000 Subject: [PATCH 43/69] Update docs/basic_mode.md --- docs/basic_mode.md | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/docs/basic_mode.md b/docs/basic_mode.md index 5e2bcda..b0af572 100644 --- a/docs/basic_mode.md +++ b/docs/basic_mode.md @@ -1,21 +1,7 @@ -# basic mode - -`ctrld` can operate in `basic` mode, which requires no configuration file. All necessary information is provided -via command line flags, and be translated to corresponding config. `ctrld` will start with that config but do not -write anything to disk. - -## Base64 encoded config - -`ctrld` can read a base64 encoded config via command line flag: - -```shell -ctrld run --base64_config="CltsaXN0ZW5lcl0KCiAgW2xpc3RlbmVyLjBdCiAgICBpcCA9ICIxMjcuMC4wLjEiCiAgICBwb3J0ID0gNTMKICAgIHJlc3RyaWN0ZWQgPSBmYWxzZQoKW25ldHdvcmtdCgogIFtuZXR3b3JrLjBdCiAgICBjaWRycyA9IFsiMC4wLjAuMC8wIl0KICAgIG5hbWUgPSAiTmV0d29yayAwIgoKW3Vwc3RyZWFtXQoKICBbdXBzdHJlYW0uMF0KICAgIGJvb3RzdHJhcF9pcCA9ICI3Ni43Ni4yLjExIgogICAgZW5kcG9pbnQgPSAiaHR0cHM6Ly9mcmVlZG5zLmNvbnRyb2xkLmNvbS9wMSIKICAgIG5hbWUgPSAiQ29udHJvbCBEIC0gQW50aS1NYWx3YXJlIgogICAgdGltZW91dCA9IDUwMDAKICAgIHR5cGUgPSAiZG9oIgoKICBbdXBzdHJlYW0uMV0KICAgIGJvb3RzdHJhcF9pcCA9ICI3Ni43Ni4yLjExIgogICAgZW5kcG9pbnQgPSAicDIuZnJlZWRucy5jb250cm9sZC5jb20iCiAgICBuYW1lID0gIkNvbnRyb2wgRCAtIE5vIEFkcyIKICAgIHRpbWVvdXQgPSAzMDAwCiAgICB0eXBlID0gImRvcSIK" -``` +# Ephemeral Mode +`ctrld` can operate in ephemeral mode which won't attempt to read or write any config file to disk. All necessary information is provided via command line flags. ## Launch arguments - -A set of arguments can be provided via command line flags. - ```shell $ ctrld run --help Run the DNS proxy server @@ -25,7 +11,7 @@ Usage: Flags: --base64_config string base64 encoded config - --cache_size int Enable cache with size items + --cache_size int Enable cache defined amount of slots --cd string Control D resolver uid -c, --config string Path to config file -d, --daemon Run as daemon @@ -78,4 +64,11 @@ Above command will be translated roughly to this config: ] ``` -`secondary_upstream`, `domains`, and `log` flags are optional. +Only `listen` and `primary_upstream` flags are required. + +## Base64 encoded config +`ctrld` can read a complete base64 encoded config via command line flag. This allows you to supply complex configurations. + +```shell +ctrld run --base64_config="CltsaXN0ZW5lcl0KCiAgW2xpc3RlbmVyLjBdCiAgICBpcCA9ICIxMjcuMC4wLjEiCiAgICBwb3J0ID0gNTMKICAgIHJlc3RyaWN0ZWQgPSBmYWxzZQoKW25ldHdvcmtdCgogIFtuZXR3b3JrLjBdCiAgICBjaWRycyA9IFsiMC4wLjAuMC8wIl0KICAgIG5hbWUgPSAiTmV0d29yayAwIgoKW3Vwc3RyZWFtXQoKICBbdXBzdHJlYW0uMF0KICAgIGJvb3RzdHJhcF9pcCA9ICI3Ni43Ni4yLjExIgogICAgZW5kcG9pbnQgPSAiaHR0cHM6Ly9mcmVlZG5zLmNvbnRyb2xkLmNvbS9wMSIKICAgIG5hbWUgPSAiQ29udHJvbCBEIC0gQW50aS1NYWx3YXJlIgogICAgdGltZW91dCA9IDUwMDAKICAgIHR5cGUgPSAiZG9oIgoKICBbdXBzdHJlYW0uMV0KICAgIGJvb3RzdHJhcF9pcCA9ICI3Ni43Ni4yLjExIgogICAgZW5kcG9pbnQgPSAicDIuZnJlZWRucy5jb250cm9sZC5jb20iCiAgICBuYW1lID0gIkNvbnRyb2wgRCAtIE5vIEFkcyIKICAgIHRpbWVvdXQgPSAzMDAwCiAgICB0eXBlID0gImRvcSIK" +``` From 4c8ea45922666c1beba09b4fb79359ace86398c4 Mon Sep 17 00:00:00 2001 From: Yegor Sak Date: Sat, 21 Jan 2023 02:10:59 +0000 Subject: [PATCH 44/69] Update docs/ephemeral_mode.md, README.md --- README.md | 10 ++++++---- docs/{basic_mode.md => ephemeral_mode.md} | 0 2 files changed, 6 insertions(+), 4 deletions(-) rename docs/{basic_mode.md => ephemeral_mode.md} (100%) diff --git a/README.md b/README.md index 65fd2f9..e7216b9 100644 --- a/README.md +++ b/README.md @@ -179,12 +179,14 @@ Once you run the above command, the following things will happen: ### Advanced The above is the most basic example, which will work out of the box. If you're looking to do advanced configurations using policies, see [Configuration Docs](docs/config.md) for complete documentation of the config file. +You can also supply configuration via launch argeuments, in [Ephemeral Mode](docs/ephemeral_mode.md). + ## Contributing See [Contribution Guideline](./docs/contributing.md) ## Roadmap -The following functionality is on the roadmap and will be available in future releases. -- Prometheus metrics exporter -- Local caching -- Service self-installation +The following functionality is on the roadmap and will be available in future releases. +- Router self-installation +- Client hostname/MAC passthrough +- Prometheus metrics exporter diff --git a/docs/basic_mode.md b/docs/ephemeral_mode.md similarity index 100% rename from docs/basic_mode.md rename to docs/ephemeral_mode.md From 340016ab70dbecad6877592a47ac5b3e6720b88a Mon Sep 17 00:00:00 2001 From: Yegor Sak Date: Sat, 21 Jan 2023 02:12:03 +0000 Subject: [PATCH 45/69] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e7216b9..09d6701 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,10 @@ Once you run the above command, the following things will happen: - All OS DNS queries will be sent to the listener ## Configuration -### Example +See [Configuration Docs](docs/config.md). + +### Example +>>>>>>> ed393c1 (Update README.md) - Start `listener.0` on 127.0.0.1:53 - Accept queries from any source address - Send all queries to `upstream.0` via DoH protocol From cd37d93b0651dc8128b6ad16b18eb722104e320b Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 21 Jan 2023 01:44:31 +0700 Subject: [PATCH 46/69] cmd/ctrld: ensure cleaning up done when self-uninstall While at it, also making DNS reset always use DHCP. --- cmd/ctrld/cli.go | 21 ++++++++++++++----- cmd/ctrld/os_linux.go | 45 +++++++++++++++++++++++++++++++++++++---- cmd/ctrld/os_mac.go | 6 ++++-- cmd/ctrld/os_windows.go | 40 +++++++++--------------------------- cmd/ctrld/prog.go | 8 +++----- 5 files changed, 74 insertions(+), 46 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index f9c69cc..b48813d 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -111,11 +111,7 @@ func initCLI() { initCache() if iface == "auto" { - dri, err := interfaces.DefaultRouteInterface() - if err != nil { - mainLog.Error().Err(err).Msg("failed to get default route interface") - } - iface = dri + iface = defaultIfaceName() } if daemon { @@ -450,6 +446,7 @@ func readConfigFile(writeDefaultConfig bool) bool { err := v.ReadInConfig() if err == nil { fmt.Println("loading config file from:", v.ConfigFileUsed()) + defaultConfigFile = v.ConfigFileUsed() return true } @@ -529,6 +526,12 @@ func processCDFlags() { stderrMsg(err.Error()) return } + if iface == "auto" { + iface = defaultIfaceName() + } + if netIface, _ := netIfaceFromName(iface); netIface != nil { + _ = resetDNS(netIface) + } tasks := []task{{s.Uninstall, true}} if doTasks(tasks) { log.Println("uninstalled service") @@ -623,3 +626,11 @@ func netIfaceFromName(ifaceName string) (*net.Interface, error) { } return iface, err } + +func defaultIfaceName() string { + dri, err := interfaces.DefaultRouteInterface() + if err != nil { + mainLog.Error().Err(err).Msg("failed to get default route interface") + } + return dri +} diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 39818a3..98ccf00 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -8,6 +8,10 @@ import ( "os/exec" "strings" + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/insomniacslk/dhcp/dhcpv4/client4" + "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/insomniacslk/dhcp/dhcpv6/client6" "tailscale.com/net/dns" "tailscale.com/util/dnsname" @@ -45,7 +49,7 @@ func setDNS(iface *net.Interface, nameservers []string) error { mainLog.Error().Err(err).Msg("failed to create DNS OS configurator") return err } - + defer r.Close() ns := make([]netip.Addr, 0, len(nameservers)) for _, nameserver := range nameservers { ns = append(ns, netip.MustParseAddr(nameserver)) @@ -56,11 +60,44 @@ func setDNS(iface *net.Interface, nameservers []string) error { }) } -func resetDNS(iface *net.Interface, nameservers []string) error { - if err := setDNS(iface, nameservers); err != nil { - mainLog.Error().Err(err).Msg("resetDNS failed.") +func resetDNS(iface *net.Interface) error { + c := client4.NewClient() + conversation, err := c.Exchange(iface.Name) + if err != nil { return err } + for _, packet := range conversation { + if packet.MessageType() == dhcpv4.MessageTypeAck { + nameservers := packet.DNS() + ns := make([]string, 0, len(nameservers)) + for _, nameserver := range nameservers { + ns = append(ns, nameserver.String()) + } + _ = setDNS(iface, ns) + } + } + + if supportsIPv6() { + c := client6.NewClient() + conversation, err := c.Exchange(iface.Name) + if err != nil { + return err + } + for _, packet := range conversation { + if packet.Type() == dhcpv6.MessageTypeReply { + msg, err := packet.GetInnerMessage() + if err != nil { + return err + } + nameservers := msg.Options.DNS() + ns := make([]string, 0, len(nameservers)) + for _, nameserver := range nameservers { + ns = append(ns, nameserver.String()) + } + _ = setDNS(iface, ns) + } + } + } return nil } diff --git a/cmd/ctrld/os_mac.go b/cmd/ctrld/os_mac.go index 99d7f91..95786f3 100644 --- a/cmd/ctrld/os_mac.go +++ b/cmd/ctrld/os_mac.go @@ -6,6 +6,8 @@ package main import ( "net" "os/exec" + + "github.com/Control-D-Inc/ctrld/internal/resolvconffile" ) // allocate loopback ip @@ -44,7 +46,7 @@ func setDNS(iface *net.Interface, nameservers []string) error { } // TODO(cuonglm): use system API -func resetDNS(iface *net.Interface, _ []string) error { +func resetDNS(iface *net.Interface) error { cmd := "networksetup" args := []string{"-setdnsservers", iface.Name, "empty"} @@ -56,5 +58,5 @@ func resetDNS(iface *net.Interface, _ []string) error { } func currentDNS(_ *net.Interface) []string { - return nil + return resolvconffile.NameServers("") } diff --git a/cmd/ctrld/os_windows.go b/cmd/ctrld/os_windows.go index 075b30d..213c104 100644 --- a/cmd/ctrld/os_windows.go +++ b/cmd/ctrld/os_windows.go @@ -4,7 +4,6 @@ package main import ( - "bytes" "errors" "net" "os/exec" @@ -39,14 +38,18 @@ func setDNS(iface *net.Interface, nameservers []string) error { } // TODO(cuonglm): should we use system API? -func resetDNS(iface *net.Interface, nameservers []string) error { - if err := resetDNSUseDHCP(iface); err != nil { - mainLog.Debug().Err(err).Msg("could not reset DNS using DHCP") +func resetDNS(iface *net.Interface) error { + if supportsIPv6ListenLocal() { + if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil { + mainLog.Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output)) + } } - if len(nameservers) == 0 { - return nil + output, err := netsh("interface", "ipv4", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp") + if err != nil { + mainLog.Error().Err(err).Msgf("failed to reset ipv4 DNS: %s", string(output)) + return err } - return setDNS(iface, nameservers) + return nil } func setPrimaryDNS(iface *net.Interface, dns string) error { @@ -80,28 +83,11 @@ func addSecondaryDNS(iface *net.Interface, dns string) error { return nil } -func resetDNSUseDHCP(iface *net.Interface) error { - if supportsIPv6ListenLocal() { - if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil { - mainLog.Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output)) - } - } - output, err := netsh("interface", "ipv4", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp") - if err != nil { - mainLog.Error().Err(err).Msgf("failed to reset ipv4 DNS: %s", string(output)) - return err - } - return nil -} - func netsh(args ...string) ([]byte, error) { return exec.Command("netsh", args...).Output() } func currentDNS(iface *net.Interface) []string { - if hasDNSFromDHCP(iface, "ipv4") || hasDNSFromDHCP(iface, "ipv6") { - return nil - } luid, err := winipcfg.LUIDFromIndex(uint32(iface.Index)) if err != nil { mainLog.Error().Err(err).Msg("failed to get interface LUID") @@ -118,9 +104,3 @@ func currentDNS(iface *net.Interface) []string { } return ns } - -func hasDNSFromDHCP(iface *net.Interface, ipVer string) bool { - idx := strconv.Itoa(iface.Index) - output, _ := netsh("interface", ipVer, "show", "dnsservers", idx) - return bytes.Contains(output, []byte(" through DHCP:")) -} diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 8d26abe..30a0688 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -23,9 +23,8 @@ var svcConfig = &service.Config{ } type prog struct { - cfg *ctrld.Config - cache dnscache.Cacher - origDNS []string + cfg *ctrld.Config + cache dnscache.Cacher } func (p *prog) Start(s service.Service) error { @@ -40,7 +39,6 @@ func (p *prog) run() { if err != nil { mainLog.Error().Err(err).Msg("could not get interface") } else { - p.origDNS = currentDNS(netIface) if err := setDNS(netIface, []string{cfg.Listener["0"].IP}); err != nil { mainLog.Error().Err(err).Str("iface", iface).Msgf("could not set DNS for interface") } @@ -179,7 +177,7 @@ func (p *prog) Stop(s service.Service) error { } if iface != "" { if netIface, err := netIfaceFromName(iface); err == nil { - if err := resetDNS(netIface, p.origDNS); err != nil { + if err := resetDNS(netIface); err != nil { mainLog.Error().Err(err).Str("iface", iface).Msgf("could not reset DNS") } } else { From 837563dcd50c262af14bc27f91e30b7ebc917fb7 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 21 Jan 2023 13:19:05 +0700 Subject: [PATCH 47/69] all: wait for network up before running If ctrld setup the interface correctly, the interface DNS is set to ctrld listener address. At boot time, the ctrld is not up yet, so it would break the processing Control D config fetching. Fixing this by waiting for network up before doing the query. --- cmd/ctrld/cli.go | 4 ++++ cmd/ctrld/net.go | 30 ++++++++++++++++++++++++++++-- internal/controld/config.go | 25 ++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index b48813d..173d1f7 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -103,6 +103,10 @@ func initCLI() { if err := v.Unmarshal(&cfg); err != nil { log.Fatalf("failed to unmarshal config: %v", err) } + // Wait for network up. + if !netUp() { + log.Fatal("network is not up yet") + } processCDFlags() if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil { log.Fatalf("invalid config: %v", err) diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go index f98feb9..96450c0 100644 --- a/cmd/ctrld/net.go +++ b/cmd/ctrld/net.go @@ -1,20 +1,41 @@ package main import ( + "context" + "fmt" "net" "sync" + "time" + + "tailscale.com/logtail/backoff" ) -const controldIPv6Test = "ipv6.controld.io" +const ( + controldIPv6Test = "ipv6.controld.io" + controldIPv4Test = "ipv4.controld.io" +) var ( stackOnce sync.Once ipv6Enabled bool canListenIPv6Local bool + hasNetworkUp bool ) func probeStack() { - if _, err := net.Dial("tcp6", controldIPv6Test); err == nil { + logf := func(format string, args ...any) { + fmt.Printf(format, args...) + } + b := backoff.NewBackoff("probeStack", logf, time.Minute) + for { + if _, err := net.Dial("tcp", net.JoinHostPort(controldIPv4Test, "80")); err == nil { + hasNetworkUp = true + break + } else { + b.BackOff(context.Background(), err) + } + } + if _, err := net.Dial("tcp6", net.JoinHostPort(controldIPv6Test, "80")); err == nil { ipv6Enabled = true } if ln, err := net.Listen("tcp6", "[::1]:53"); err == nil { @@ -23,6 +44,11 @@ func probeStack() { } } +func netUp() bool { + stackOnce.Do(probeStack) + return hasNetworkUp +} + func supportsIPv6() bool { stackOnce.Do(probeStack) return ipv6Enabled diff --git a/internal/controld/config.go b/internal/controld/config.go index 3ca1c5b..51a1353 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -2,8 +2,10 @@ package controld import ( "bytes" + "context" "encoding/json" "fmt" + "net" "net/http" "time" ) @@ -13,6 +15,20 @@ const ( InvalidConfigCode = 40401 ) +const bootstrapDNS = "76.76.2.0:53" + +var dialer = &net.Dialer{ + Resolver: &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{ + Timeout: 10 * time.Second, + } + return d.DialContext(ctx, "udp", bootstrapDNS) + }, + }, +} + // ResolverConfig represents Control D resolver data. type ResolverConfig struct { DOH string `json:"doh"` @@ -52,7 +68,14 @@ func FetchResolverConfig(uid string) (*ResolverConfig, error) { q.Set("platform", "ctrld") req.URL.RawQuery = q.Encode() req.Header.Add("Content-Type", "application/json") - client := http.Client{Timeout: 5 * time.Second} + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, network, addr) + } + client := http.Client{ + Timeout: 10 * time.Second, + Transport: transport, + } resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("client.Do: %w", err) From 11869635316249d19e52dc9707d9a138706c3acd Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 23 Jan 2023 00:41:04 +0700 Subject: [PATCH 48/69] all: use controld dialer for probing network --- cmd/ctrld/net.go | 7 ++++--- internal/controld/config.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go index 96450c0..e74c9a9 100644 --- a/cmd/ctrld/net.go +++ b/cmd/ctrld/net.go @@ -8,11 +8,12 @@ import ( "time" "tailscale.com/logtail/backoff" + + "github.com/Control-D-Inc/ctrld/internal/controld" ) const ( controldIPv6Test = "ipv6.controld.io" - controldIPv4Test = "ipv4.controld.io" ) var ( @@ -28,14 +29,14 @@ func probeStack() { } b := backoff.NewBackoff("probeStack", logf, time.Minute) for { - if _, err := net.Dial("tcp", net.JoinHostPort(controldIPv4Test, "80")); err == nil { + if _, err := controld.Dialer.Dial("udp", net.JoinHostPort(bootstrapDNS, "53")); err == nil { hasNetworkUp = true break } else { b.BackOff(context.Background(), err) } } - if _, err := net.Dial("tcp6", net.JoinHostPort(controldIPv6Test, "80")); err == nil { + if _, err := controld.Dialer.Dial("tcp6", net.JoinHostPort(controldIPv6Test, "80")); err == nil { ipv6Enabled = true } if ln, err := net.Listen("tcp6", "[::1]:53"); err == nil { diff --git a/internal/controld/config.go b/internal/controld/config.go index 51a1353..db01292 100644 --- a/internal/controld/config.go +++ b/internal/controld/config.go @@ -17,7 +17,7 @@ const ( const bootstrapDNS = "76.76.2.0:53" -var dialer = &net.Dialer{ +var Dialer = &net.Dialer{ Resolver: &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { @@ -70,7 +70,7 @@ func FetchResolverConfig(uid string) (*ResolverConfig, error) { req.Header.Add("Content-Type", "application/json") transport := http.DefaultTransport.(*http.Transport).Clone() transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialer.DialContext(ctx, network, addr) + return Dialer.DialContext(ctx, network, addr) } client := http.Client{ Timeout: 10 * time.Second, From 8a2c48e996f9b6331d952a2e7ee3d242d3bb9030 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 23 Jan 2023 13:41:43 +0700 Subject: [PATCH 49/69] cmd/ctrld: allow log/cache flags work wit --cd flag --- cmd/ctrld/cli.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 173d1f7..f0c8e60 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -32,7 +32,7 @@ var ( defaultConfigFile = "ctrld.toml" ) -var basicModeFlags = []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log", "cache_size"} +var basicModeFlags = []string{"listen", "primary_upstream", "secondary_upstream", "domains"} func isNoConfigStart(cmd *cobra.Command) bool { for _, flagName := range basicModeFlags { @@ -108,6 +108,7 @@ func initCLI() { log.Fatal("network is not up yet") } processCDFlags() + processLogAndCacheFlags() if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil { log.Fatalf("invalid config: %v", err) } @@ -512,8 +513,6 @@ func processNoConfigFlags(noConfigStart bool) { lc.Policy = &ctrld.ListenerPolicyConfig{Name: "My Policy", Rules: rules} } v.Set("upstream", upstream) - - processLogAndCacheFlags() } func processCDFlags() { @@ -577,6 +576,7 @@ func processCDFlags() { v.Set("network", cfg.Network) v.Set("upstream", cfg.Upstream) v.Set("listener", cfg.Listener) + processLogAndCacheFlags() writeConfigFile() } @@ -602,17 +602,15 @@ func processListenFlag() { } func processLogAndCacheFlags() { - sc := ctrld.ServiceConfig{} if logPath != "" { - sc.LogLevel = "debug" - sc.LogPath = logPath + cfg.Service.LogPath = logPath } if cacheSize != 0 { - sc.CacheEnable = true - sc.CacheSize = cacheSize + cfg.Service.CacheEnable = true + cfg.Service.CacheSize = cacheSize } - v.Set("service", sc) + v.Set("service", cfg.Service) } func netIfaceFromName(ifaceName string) (*net.Interface, error) { From 9fc546443ba1f76542310dbd7a9248889def44b3 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 24 Jan 2023 01:18:03 +0700 Subject: [PATCH 50/69] cmd/ctrld: ignore syscall.EINTR on Linux Observing while tested on Ubuntu 22.04.1, the request to reset using systemd resolved via dbus may be interruped. --- cmd/ctrld/os_linux.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 98ccf00..a6bc39e 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -7,6 +7,7 @@ import ( "net/netip" "os/exec" "strings" + "syscall" "github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4/client4" @@ -61,6 +62,7 @@ func setDNS(iface *net.Interface, nameservers []string) error { } func resetDNS(iface *net.Interface) error { + var ns []string c := client4.NewClient() conversation, err := c.Exchange(iface.Name) if err != nil { @@ -69,11 +71,9 @@ func resetDNS(iface *net.Interface) error { for _, packet := range conversation { if packet.MessageType() == dhcpv4.MessageTypeAck { nameservers := packet.DNS() - ns := make([]string, 0, len(nameservers)) for _, nameserver := range nameservers { ns = append(ns, nameserver.String()) } - _ = setDNS(iface, ns) } } @@ -81,7 +81,7 @@ func resetDNS(iface *net.Interface) error { c := client6.NewClient() conversation, err := c.Exchange(iface.Name) if err != nil { - return err + mainLog.Warn().Err(err).Msg("failed to exchange DHCPv6") } for _, packet := range conversation { if packet.Type() == dhcpv6.MessageTypeReply { @@ -90,15 +90,16 @@ func resetDNS(iface *net.Interface) error { return err } nameservers := msg.Options.DNS() - ns := make([]string, 0, len(nameservers)) for _, nameserver := range nameservers { ns = append(ns, nameserver.String()) } - _ = setDNS(iface, ns) } } } - return nil + + return ignoringEINTR(func() error { + return setDNS(iface, ns) + }) } func currentDNS(iface *net.Interface) []string { @@ -147,3 +148,12 @@ func getDNSByNmcli(iface string) []string { } return dns } + +func ignoringEINTR(fn func() error) error { + for { + err := fn() + if err != syscall.EINTR { + return err + } + } +} From 31e4bcb4c3f239a51e64de542c47a0b43aa890c7 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 24 Jan 2023 01:27:27 +0700 Subject: [PATCH 51/69] cmd/ctrld: init logging before processing --cd So it's easier to debug in case of weird thing happens. --- cmd/ctrld/cli.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index f0c8e60..65a9c7a 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -107,12 +107,14 @@ func initCLI() { if !netUp() { log.Fatal("network is not up yet") } - processCDFlags() processLogAndCacheFlags() + // Log config do not have thing to validate, so it's safe to init log here, + // so it's able to log information in processCDFlags. + initLogging() + processCDFlags() if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil { log.Fatalf("invalid config: %v", err) } - initLogging() initCache() if iface == "auto" { @@ -202,6 +204,7 @@ func initCLI() { sc.Arguments = append(sc.Arguments, "--homedir="+dir) } + initLogging() processCDFlags() // On Windows, the service will be run as SYSTEM, so if ctrld start as Admin, // the user home dir is different, so pass specific arguments that relevant here. @@ -522,27 +525,30 @@ func processCDFlags() { if iface == "" { iface = "auto" } + logger := mainLog.With().Str("mode", "cd").Logger() resolverConfig, err := controld.FetchResolverConfig(cdUID) if uer, ok := err.(*controld.UtilityErrorResponse); ok && uer.ErrorField.Code == controld.InvalidConfigCode { s, err := service.New(&prog{}, svcConfig) if err != nil { - stderrMsg(err.Error()) + logger.Warn().Err(err).Msg("failed to create new service") return } if iface == "auto" { iface = defaultIfaceName() } if netIface, _ := netIfaceFromName(iface); netIface != nil { - _ = resetDNS(netIface) + if err := resetDNS(netIface); err != nil { + logger.Warn().Err(err).Msg("something went wrong while restoring DNS") + } } tasks := []task{{s.Uninstall, true}} if doTasks(tasks) { - log.Println("uninstalled service") + logger.Info().Msg("uninstalled service") } - log.Fatalf("failed to fetch resolver config: %v", uer) + logger.Fatal().Err(uer).Msg("failed to fetch resolver config") } if err != nil { - log.Printf("could not fetch resolver config: %v", err) + logger.Warn().Err(err).Msg("could not fetch resolver config") return } From b0dc96aa0111b7ddced4c6b06b87e2f9ff714fa2 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 24 Jan 2023 12:32:54 +0700 Subject: [PATCH 52/69] cmd/ctrld: use debug level when --log set --- cmd/ctrld/cli.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 65a9c7a..4e0f829 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -609,6 +609,7 @@ func processListenFlag() { func processLogAndCacheFlags() { if logPath != "" { + cfg.Service.LogLevel = "debug" cfg.Service.LogPath = logPath } From c82a0e256271ea519f0d828a3d8e0b9b28b10f7d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 24 Jan 2023 13:55:43 +0700 Subject: [PATCH 53/69] cmd/ctrld: optimizing set/reset DNS Currently, when reset DNS, ctrld always find the net.Interface by interface name. This may produce unexpected error because the interface table may be cleared at the time ctrld is being stopped. Instead, we can get the net interface only once, and use that interface for restoring the DNS before shutting down. While at it, also making logging message clearer. --- cmd/ctrld/cli.go | 41 +++++++++++++++++------------ cmd/ctrld/main.go | 2 ++ cmd/ctrld/os_linux.go | 2 +- cmd/ctrld/prog.go | 60 ++++++++++++++++++++++++++++--------------- 4 files changed, 66 insertions(+), 39 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 4e0f829..6fac8b4 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -421,32 +421,28 @@ func initCLI() { } } -func writeConfigFile() { +func writeConfigFile() error { if cfu := v.ConfigFileUsed(); cfu != "" { defaultConfigFile = cfu } f, err := os.OpenFile(defaultConfigFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(0o644)) if err != nil { - log.Printf("failed to open config file: %v\n", err) - os.Exit(1) + return err } defer f.Close() if cdUID != "" { if _, err := f.WriteString("# AUTO-GENERATED VIA CD FLAG - DO NOT MODIFY\n\n"); err != nil { - log.Printf("failed to write header to config file: %v\n", err) - os.Exit(1) + return err } } enc := toml.NewEncoder(f).SetIndentTables(true) if err := enc.Encode(v.AllSettings()); err != nil { - log.Printf("failed to encode config file: %v\n", err) - os.Exit(1) + return err } if err := f.Close(); err != nil { - log.Printf("failed to write config file: %v\n", err) - os.Exit(1) + return err } - fmt.Println("writing config file to:", defaultConfigFile) + return nil } func readConfigFile(writeDefaultConfig bool) bool { @@ -464,7 +460,11 @@ func readConfigFile(writeDefaultConfig bool) bool { // If error is viper.ConfigFileNotFoundError, write default config. if _, ok := err.(viper.ConfigFileNotFoundError); ok { - writeConfigFile() + if err := writeConfigFile(); err != nil { + log.Fatalf("failed to write default config file: %v", err) + } else { + fmt.Println("writing default config file to: " + defaultConfigFile) + } defaultConfigWritten = true return false } @@ -526,6 +526,7 @@ func processCDFlags() { iface = "auto" } logger := mainLog.With().Str("mode", "cd").Logger() + logger.Info().Msg("fetching Controld-D configuration") resolverConfig, err := controld.FetchResolverConfig(cdUID) if uer, ok := err.(*controld.UtilityErrorResponse); ok && uer.ErrorField.Code == controld.InvalidConfigCode { s, err := service.New(&prog{}, svcConfig) @@ -533,10 +534,8 @@ func processCDFlags() { logger.Warn().Err(err).Msg("failed to create new service") return } - if iface == "auto" { - iface = defaultIfaceName() - } - if netIface, _ := netIfaceFromName(iface); netIface != nil { + + if netIface, _ := netInterface(iface); netIface != nil { if err := resetDNS(netIface); err != nil { logger.Warn().Err(err).Msg("something went wrong while restoring DNS") } @@ -552,6 +551,7 @@ func processCDFlags() { return } + logger.Info().Msg("generating ctrld config from Controld-D configuration") cfg = ctrld.Config{} cfg.Network = make(map[string]*ctrld.NetworkConfig) cfg.Network["0"] = &ctrld.NetworkConfig{ @@ -583,7 +583,11 @@ func processCDFlags() { v.Set("upstream", cfg.Upstream) v.Set("listener", cfg.Listener) processLogAndCacheFlags() - writeConfigFile() + if err := writeConfigFile(); err != nil { + logger.Fatal().Err(err).Msg("failed to write config file") + } else { + logger.Info().Msg("writing config file to: " + defaultConfigFile) + } } func processListenFlag() { @@ -620,7 +624,10 @@ func processLogAndCacheFlags() { v.Set("service", cfg.Service) } -func netIfaceFromName(ifaceName string) (*net.Interface, error) { +func netInterface(ifaceName string) (*net.Interface, error) { + if ifaceName == "auto" { + ifaceName = defaultIfaceName() + } var iface *net.Interface err := interfaces.ForeachInterface(func(i interfaces.Interface, prefixes []netip.Prefix) { if i.Name == ifaceName { diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index c336a2b..ff6a4ad 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "io" + "net" "os" "path/filepath" "time" @@ -35,6 +36,7 @@ var ( cdUID string iface string + netIface *net.Interface ifaceStartStop string ) diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index a6bc39e..a8ff7f7 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -81,7 +81,7 @@ func resetDNS(iface *net.Interface) error { c := client6.NewClient() conversation, err := c.Exchange(iface.Name) if err != nil { - mainLog.Warn().Err(err).Msg("failed to exchange DHCPv6") + mainLog.Warn().Err(err).Msg("could not exchange DHCPv6") } for _, packet := range conversation { if packet.Type() == dhcpv6.MessageTypeReply { diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 30a0688..bde892f 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -34,17 +34,7 @@ func (p *prog) Start(s service.Service) error { } func (p *prog) run() { - if iface != "" { - netIface, err := netIfaceFromName(iface) - if err != nil { - mainLog.Error().Err(err).Msg("could not get interface") - } else { - if err := setDNS(netIface, []string{cfg.Listener["0"].IP}); err != nil { - mainLog.Error().Err(err).Str("iface", iface).Msgf("could not set DNS for interface") - } - } - } - + p.setDNS() if p.cfg.Service.CacheEnable { cacher, err := dnscache.NewLRUCache(p.cfg.Service.CacheSize) if err != nil { @@ -150,7 +140,11 @@ func (p *prog) run() { Port: port, }, }) - writeConfigFile() + if err := writeConfigFile(); err != nil { + proxyLog.Fatal().Err(err).Msg("failed to write config file") + } else { + mainLog.Info().Msg("writing config file to: " + defaultConfigFile) + } mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, pc.LocalAddr()) // There can be a race between closing the listener and start our own UDP server, but it's // rare, and we only do this once, so let conservative here. @@ -175,15 +169,7 @@ func (p *prog) Stop(s service.Service) error { mainLog.Error().Err(err).Msg("de-allocate ip failed") return err } - if iface != "" { - if netIface, err := netIfaceFromName(iface); err == nil { - if err := resetDNS(netIface); err != nil { - mainLog.Error().Err(err).Str("iface", iface).Msgf("could not reset DNS") - } - } else { - mainLog.Error().Err(err).Msg("could not get interface") - } - } + p.resetDNS() return nil } @@ -205,3 +191,35 @@ func (p *prog) deAllocateIP() error { } return nil } + +func (p *prog) setDNS() { + if iface == "" { + return + } + logger := mainLog.With().Str("iface", iface).Logger() + var err error + netIface, err = netInterface(iface) + if err != nil { + logger.Error().Err(err).Msg("could not get interface") + return + } + logger.Debug().Msg("setting DNS for interface") + if err := setDNS(netIface, []string{p.cfg.Listener["0"].IP}); err != nil { + logger.Error().Err(err).Msgf("could not set DNS for interface") + return + } + logger.Debug().Msg("setting DNS successfully") +} + +func (p *prog) resetDNS() { + if netIface == nil { + return + } + logger := mainLog.With().Str("iface", iface).Logger() + logger.Debug().Msg("Restoring DNS for interface") + if err := resetDNS(netIface); err != nil { + logger.Error().Err(err).Msgf("could not reset DNS") + return + } + logger.Debug().Msg("Restoring DNS successfully") +} From 06372031b5cdf83299ab2e1c05daae50db1c60b2 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 30 Jan 2023 21:16:37 +0700 Subject: [PATCH 54/69] cmd/ctrld: add more logging details --- cmd/ctrld/cli.go | 3 +++ cmd/ctrld/prog.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 6fac8b4..f687642 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -536,8 +536,11 @@ func processCDFlags() { } if netIface, _ := netInterface(iface); netIface != nil { + logger.Debug().Str("iface", netIface.Name).Msg("Restoring DNS for interface") if err := resetDNS(netIface); err != nil { logger.Warn().Err(err).Msg("something went wrong while restoring DNS") + } else { + logger.Debug().Str("iface", netIface.Name).Msg("Restoring DNS successfully") } } tasks := []task{{s.Uninstall, true}} diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index bde892f..b0bde48 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -30,6 +30,7 @@ type prog struct { func (p *prog) Start(s service.Service) error { p.cfg = &cfg go p.run() + mainLog.Info().Msg("Service started") return nil } @@ -170,6 +171,7 @@ func (p *prog) Stop(s service.Service) error { return err } p.resetDNS() + mainLog.Info().Msg("Service stopped") return nil } From 4ea1e6479526fa3d1e07bcb6a616dee9d3522f1b Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 31 Jan 2023 03:16:41 +0700 Subject: [PATCH 55/69] all: make cache scope to upstream --- cmd/ctrld/dns_proxy.go | 20 ++++++++++++-------- cmd/ctrld/dns_proxy_test.go | 37 +++++++++++++++++++++++++++++++++++++ internal/dnscache/cache.go | 11 ++++++----- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/cmd/ctrld/dns_proxy.go b/cmd/ctrld/dns_proxy.go index bd5176b..4cdfab0 100644 --- a/cmd/ctrld/dns_proxy.go +++ b/cmd/ctrld/dns_proxy.go @@ -143,11 +143,20 @@ func (p *prog) upstreamFor(ctx context.Context, defaultUpstreamNum string, lc *c func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []int, msg *dns.Msg) *dns.Msg { var staleAnswer *dns.Msg serveStaleCache := p.cache != nil && p.cfg.Service.CacheServeStale + upstreamConfigs := p.upstreamConfigsFromUpstreamNumbers(upstreams) + if len(upstreamConfigs) == 0 { + upstreamConfigs = []*ctrld.UpstreamConfig{osUpstreamConfig} + upstreams = []string{"upstream.os"} + } // Inverse query should not be cached: https://www.rfc-editor.org/rfc/rfc1035#section-7.4 if p.cache != nil && msg.Question[0].Qtype != dns.TypePTR { - if cachedValue := p.cache.Get(dnscache.NewKey(msg)); cachedValue != nil { + for _, upstream := range upstreams { + cachedValue := p.cache.Get(dnscache.NewKey(msg, upstream)) + if cachedValue == nil { + continue + } answer := cachedValue.Msg.Copy() - answer.SetReply(msg) + answer.SetRcode(msg, answer.Rcode) now := time.Now() if cachedValue.Expire.After(now) { ctrld.Log(ctx, proxyLog.Debug(), "hit cached response") @@ -157,11 +166,6 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i staleAnswer = answer } } - upstreamConfigs := p.upstreamConfigsFromUpstreamNumbers(upstreams) - if len(upstreamConfigs) == 0 { - upstreamConfigs = []*ctrld.UpstreamConfig{osUpstreamConfig} - upstreams = []string{"upstream.os"} - } resolve := func(n int, upstreamConfig *ctrld.UpstreamConfig, msg *dns.Msg) *dns.Msg { ctrld.Log(ctx, proxyLog.Debug(), "sending query to %s: %s", upstreams[n], upstreamConfig.Name) dnsResolver, err := ctrld.NewResolver(upstreamConfig) @@ -206,7 +210,7 @@ func (p *prog) proxy(ctx context.Context, upstreams []string, failoverRcodes []i expired = now.Add(time.Duration(cachedTTL) * time.Second) } setCachedAnswerTTL(answer, now, expired) - p.cache.Add(dnscache.NewKey(msg), dnscache.NewValue(answer, expired)) + p.cache.Add(dnscache.NewKey(msg, upstreams[n]), dnscache.NewValue(answer, expired)) ctrld.Log(ctx, proxyLog.Debug(), "add cached response") } return answer diff --git a/cmd/ctrld/dns_proxy_test.go b/cmd/ctrld/dns_proxy_test.go index 8435f7d..82c0c95 100644 --- a/cmd/ctrld/dns_proxy_test.go +++ b/cmd/ctrld/dns_proxy_test.go @@ -4,11 +4,14 @@ import ( "context" "net" "testing" + "time" + "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Control-D-Inc/ctrld" + "github.com/Control-D-Inc/ctrld/internal/dnscache" "github.com/Control-D-Inc/ctrld/testhelper" ) @@ -115,3 +118,37 @@ func Test_prog_upstreamFor(t *testing.T) { }) } } + +func TestCache(t *testing.T) { + cfg := testhelper.SampleConfig(t) + prog := &prog{cfg: cfg} + for _, nc := range prog.cfg.Network { + for _, cidr := range nc.Cidrs { + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + t.Fatal(err) + } + nc.IPNets = append(nc.IPNets, ipNet) + } + } + cacher, err := dnscache.NewLRUCache(4096) + require.NoError(t, err) + prog.cache = cacher + + msg := new(dns.Msg) + msg.SetQuestion("example.com", dns.TypeA) + msg.MsgHdr.RecursionDesired = true + answer1 := new(dns.Msg) + answer1.SetRcode(msg, dns.RcodeSuccess) + + prog.cache.Add(dnscache.NewKey(msg, "upstream.1"), dnscache.NewValue(answer1, time.Now().Add(time.Minute))) + answer2 := new(dns.Msg) + answer2.SetRcode(msg, dns.RcodeRefused) + prog.cache.Add(dnscache.NewKey(msg, "upstream.0"), dnscache.NewValue(answer2, time.Now().Add(time.Minute))) + + got1 := prog.proxy(context.Background(), []string{"upstream.1"}, nil, msg) + got2 := prog.proxy(context.Background(), []string{"upstream.0"}, nil, msg) + assert.NotSame(t, got1, got2) + assert.Equal(t, answer1.Rcode, got1.Rcode) + assert.Equal(t, answer2.Rcode, got2.Rcode) +} diff --git a/internal/dnscache/cache.go b/internal/dnscache/cache.go index efbd8e3..4aa7f69 100644 --- a/internal/dnscache/cache.go +++ b/internal/dnscache/cache.go @@ -16,9 +16,10 @@ type Cacher interface { // Key is the caching key for DNS message. type Key struct { - Qtype uint16 - Qclass uint16 - Name string + Qtype uint16 + Qclass uint16 + Name string + Upstream string } type Value struct { @@ -49,9 +50,9 @@ func NewLRUCache(size int) (*LRUCache, error) { } // NewKey creates a new cache key for given DNS message. -func NewKey(msg *dns.Msg) Key { +func NewKey(msg *dns.Msg, upstream string) Key { q := msg.Question[0] - return Key{Qtype: q.Qtype, Qclass: q.Qclass, Name: normalizeQname(q.Name)} + return Key{Qtype: q.Qtype, Qclass: q.Qclass, Name: normalizeQname(q.Name), Upstream: upstream} } // NewValue creates a new cache value for given DNS message. From 149941f17f1d9dcc30aa72fdad2b391d3808b98d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 31 Jan 2023 11:03:50 +0700 Subject: [PATCH 56/69] cmd/ctrld: do set/reset DNS only when start/stop/uninstall --- cmd/ctrld/cli.go | 38 +++++++++++----------- cmd/ctrld/main.go | 2 -- cmd/ctrld/os_linux.go | 74 +++++++++++++++++++++++++++---------------- cmd/ctrld/prog.go | 23 ++++++++++---- go.mod | 6 ++-- go.sum | 4 +++ 6 files changed, 89 insertions(+), 58 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index f687642..5c897a4 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -117,10 +117,6 @@ func initCLI() { } initCache() - if iface == "auto" { - iface = defaultIfaceName() - } - if daemon { exe, err := os.Executable() if err != nil { @@ -173,8 +169,6 @@ func initCLI() { runCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") runCmd.Flags().StringVarP(&homedir, "homedir", "", "", "") _ = runCmd.Flags().MarkHidden("homedir") - runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) - _ = runCmd.Flags().MarkHidden("iface") rootCmd.AddCommand(runCmd) @@ -214,7 +208,8 @@ func initCLI() { } } - s, err := service.New(&prog{}, sc) + prog := &prog{} + s, err := service.New(prog, sc) if err != nil { stderrMsg(err.Error()) return @@ -226,12 +221,12 @@ func initCLI() { {s.Start, true}, } if doTasks(tasks) { - stdoutMsg("Service started") - return + mainLog.Info().Msg("Service started") + prog.setDNS() } }, } - // Keep these flags in sync with runCmd above, except for "-d". + // Keep these flags in sync with runCmd above, except for "-d", "--iface". startCmd.Flags().StringVarP(&configPath, "config", "c", "", "Path to config file") startCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "Base64 encoded config") startCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "Listener address and port, in format: address:port") @@ -249,13 +244,16 @@ func initCLI() { Short: "Stop the ctrld service", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - s, err := service.New(&prog{}, svcConfig) + prog := &prog{} + s, err := service.New(prog, svcConfig) if err != nil { stderrMsg(err.Error()) return } + initLogging() if doTasks([]task{{s.Stop, true}}) { - stdoutMsg("Service stopped") + mainLog.Info().Msg("Service stopped") + prog.resetDNS() } }, } @@ -272,6 +270,7 @@ func initCLI() { stderrMsg(err.Error()) return } + initLogging() if doTasks([]task{{s.Restart, true}}) { stdoutMsg("Service restarted") } @@ -310,7 +309,8 @@ func initCLI() { Short: "Uninstall the ctrld service", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - s, err := service.New(&prog{}, svcConfig) + prog := &prog{} + s, err := service.New(prog, svcConfig) if err != nil { stderrMsg(err.Error()) return @@ -319,8 +319,10 @@ func initCLI() { {s.Stop, false}, {s.Uninstall, true}, } + initLogging() if doTasks(tasks) { - stdoutMsg("Service uninstalled") + mainLog.Info().Msg("Service uninstalled") + prog.resetDNS() return } }, @@ -391,9 +393,7 @@ func initCLI() { Use: "start", Short: "Quick start service and configure DNS on interface", Run: func(cmd *cobra.Command, args []string) { - if !cmd.Flags().Changed("iface") { - os.Args = append(os.Args, "--iface="+ifaceStartStop) - } + iface = ifaceStartStop startCmd.Run(cmd, args) }, } @@ -405,9 +405,7 @@ func initCLI() { Use: "stop", Short: "Quick stop service and remove DNS from interface", Run: func(cmd *cobra.Command, args []string) { - if !cmd.Flags().Changed("iface") { - os.Args = append(os.Args, "--iface="+ifaceStartStop) - } + iface = ifaceStartStop stopCmd.Run(cmd, args) }, } diff --git a/cmd/ctrld/main.go b/cmd/ctrld/main.go index ff6a4ad..c336a2b 100644 --- a/cmd/ctrld/main.go +++ b/cmd/ctrld/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "io" - "net" "os" "path/filepath" "time" @@ -36,7 +35,6 @@ var ( cdUID string iface string - netIface *net.Interface ifaceStartStop string ) diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index a8ff7f7..4439bd9 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -3,16 +3,18 @@ package main import ( "bufio" "bytes" + "context" + "fmt" "net" "net/netip" "os/exec" "strings" "syscall" + "time" - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/insomniacslk/dhcp/dhcpv4/client4" + "github.com/insomniacslk/dhcp/dhcpv4/nclient4" "github.com/insomniacslk/dhcp/dhcpv6" - "github.com/insomniacslk/dhcp/dhcpv6/client6" + "github.com/insomniacslk/dhcp/dhcpv6/nclient6" "tailscale.com/net/dns" "tailscale.com/util/dnsname" @@ -63,40 +65,56 @@ func setDNS(iface *net.Interface, nameservers []string) error { func resetDNS(iface *net.Interface) error { var ns []string - c := client4.NewClient() - conversation, err := c.Exchange(iface.Name) + c, err := nclient4.New(iface.Name) if err != nil { - return err + return fmt.Errorf("nclient4.New: %w", err) } - for _, packet := range conversation { - if packet.MessageType() == dhcpv4.MessageTypeAck { - nameservers := packet.DNS() - for _, nameserver := range nameservers { - ns = append(ns, nameserver.String()) - } + defer c.Close() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + lease, err := c.Request(ctx) + if err != nil { + return fmt.Errorf("nclient4.Request: %w", err) + } + for _, nameserver := range lease.ACK.DNS() { + if nameserver.Equal(net.IPv4zero) { + continue } + ns = append(ns, nameserver.String()) } if supportsIPv6() { - c := client6.NewClient() - conversation, err := c.Exchange(iface.Name) + c, err := nclient6.New(iface.Name) if err != nil { - mainLog.Warn().Err(err).Msg("could not exchange DHCPv6") + mainLog.Warn().Err(err).Msg("could not create DHCPv6 client") + return nil } - for _, packet := range conversation { - if packet.Type() == dhcpv6.MessageTypeReply { - msg, err := packet.GetInnerMessage() - if err != nil { - return err - } - nameservers := msg.Options.DNS() - for _, nameserver := range nameservers { - ns = append(ns, nameserver.String()) - } - } - } - } + defer c.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + solicit, err := dhcpv6.NewSolicit(iface.HardwareAddr) + if err != nil { + return fmt.Errorf("dhcpv6.NewSolicit: %w", err) + } + advertise, err := dhcpv6.NewAdvertiseFromSolicit(solicit) + if err != nil { + return fmt.Errorf("dhcpv6.NewAdvertiseFromSolicit: %w", err) + } + msg, err := c.Request(ctx, advertise) + if err != nil { + return fmt.Errorf("nclient6.Request: %w", err) + } + nameservers := msg.Options.DNS() + for _, nameserver := range nameservers { + if nameserver.Equal(net.IPv6zero) { + continue + } + ns = append(ns, nameserver.String()) + } + + } return ignoringEINTR(func() error { return setDNS(iface, ns) }) diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index b0bde48..b17b94c 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -35,7 +35,6 @@ func (p *prog) Start(s service.Service) error { } func (p *prog) run() { - p.setDNS() if p.cfg.Service.CacheEnable { cacher, err := dnscache.NewLRUCache(p.cfg.Service.CacheSize) if err != nil { @@ -170,7 +169,6 @@ func (p *prog) Stop(s service.Service) error { mainLog.Error().Err(err).Msg("de-allocate ip failed") return err } - p.resetDNS() mainLog.Info().Msg("Service stopped") return nil } @@ -195,18 +193,23 @@ func (p *prog) deAllocateIP() error { } func (p *prog) setDNS() { + if cfg.Listener == nil || cfg.Listener["0"] == nil { + return + } if iface == "" { return } + if iface == "auto" { + iface = defaultIfaceName() + } logger := mainLog.With().Str("iface", iface).Logger() - var err error - netIface, err = netInterface(iface) + netIface, err := netInterface(iface) if err != nil { logger.Error().Err(err).Msg("could not get interface") return } logger.Debug().Msg("setting DNS for interface") - if err := setDNS(netIface, []string{p.cfg.Listener["0"].IP}); err != nil { + if err := setDNS(netIface, []string{cfg.Listener["0"].IP}); err != nil { logger.Error().Err(err).Msgf("could not set DNS for interface") return } @@ -214,10 +217,18 @@ func (p *prog) setDNS() { } func (p *prog) resetDNS() { - if netIface == nil { + if iface == "" { return } + if iface == "auto" { + iface = defaultIfaceName() + } logger := mainLog.With().Str("iface", iface).Logger() + netIface, err := netInterface(iface) + if err != nil { + logger.Error().Err(err).Msg("could not get interface") + return + } logger.Debug().Msg("Restoring DNS for interface") if err := resetDNS(netIface); err != nil { logger.Error().Err(err).Msgf("could not reset DNS") diff --git a/go.mod b/go.mod index f2d5684..1e6f67c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/go-playground/validator/v10 v10.11.1 github.com/hashicorp/golang-lru/v2 v2.0.1 + github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e github.com/kardianos/service v1.2.1 github.com/lucas-clemente/quic-go v0.29.1 github.com/miekg/dns v1.1.50 @@ -14,6 +15,7 @@ require ( github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.1 golang.org/x/sys v0.4.0 + golang.zx2c4.com/wireguard/windows v0.5.3 tailscale.com v1.34.1 ) @@ -35,7 +37,6 @@ require ( github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/illarion/gonotify v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e // indirect github.com/josharian/native v1.0.0 // indirect github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b // indirect github.com/leodido/go-urn v1.2.1 // indirect @@ -45,8 +46,10 @@ require ( github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect 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/genetlink v1.2.0 // indirect github.com/mdlayher/netlink v1.6.0 // indirect + github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 // indirect github.com/mdlayher/socket v0.2.3 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nxadm/tail v1.4.8 // indirect @@ -72,7 +75,6 @@ require ( golang.org/x/tools v0.1.12 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect - golang.zx2c4.com/wireguard/windows v0.5.3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index c965e25..2a4fe13 100644 --- a/go.sum +++ b/go.sum @@ -164,6 +164,7 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -212,6 +213,7 @@ github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZb github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU= github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ= @@ -222,6 +224,7 @@ github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZ 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/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= @@ -275,6 +278,7 @@ github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= From 37de5441c188265f55cc0e412ef18671df9c5609 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 31 Jan 2023 21:36:24 +0700 Subject: [PATCH 57/69] cmd/ctrld: silent DHCPv6 error It's hard to imagine a system with IPv6 but not IPv4, so silent the DHCPv6 error if any. --- cmd/ctrld/cli.go | 6 +++--- cmd/ctrld/os_linux.go | 42 +++++++++++++++++------------------------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 5c897a4..9fff2f8 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -221,8 +221,8 @@ func initCLI() { {s.Start, true}, } if doTasks(tasks) { - mainLog.Info().Msg("Service started") prog.setDNS() + mainLog.Info().Msg("Service started") } }, } @@ -252,8 +252,8 @@ func initCLI() { } initLogging() if doTasks([]task{{s.Stop, true}}) { - mainLog.Info().Msg("Service stopped") prog.resetDNS() + mainLog.Info().Msg("Service stopped") } }, } @@ -321,8 +321,8 @@ func initCLI() { } initLogging() if doTasks(tasks) { - mainLog.Info().Msg("Service uninstalled") prog.resetDNS() + mainLog.Info().Msg("Service uninstalled") return } }, diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 4439bd9..53d52b9 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -14,7 +14,7 @@ import ( "github.com/insomniacslk/dhcp/dhcpv4/nclient4" "github.com/insomniacslk/dhcp/dhcpv6" - "github.com/insomniacslk/dhcp/dhcpv6/nclient6" + "github.com/insomniacslk/dhcp/dhcpv6/client6" "tailscale.com/net/dns" "tailscale.com/util/dnsname" @@ -84,37 +84,29 @@ func resetDNS(iface *net.Interface) error { ns = append(ns, nameserver.String()) } + // TODO(cuonglm): handle DHCPv6 properly. if supportsIPv6() { - c, err := nclient6.New(iface.Name) + c := client6.NewClient() + conversation, err := c.Exchange(iface.Name) if err != nil { - mainLog.Warn().Err(err).Msg("could not create DHCPv6 client") + mainLog.Debug().Err(err).Msg("could not exchange DHCPv6") return nil } - defer c.Close() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - - solicit, err := dhcpv6.NewSolicit(iface.HardwareAddr) - if err != nil { - return fmt.Errorf("dhcpv6.NewSolicit: %w", err) - } - advertise, err := dhcpv6.NewAdvertiseFromSolicit(solicit) - if err != nil { - return fmt.Errorf("dhcpv6.NewAdvertiseFromSolicit: %w", err) - } - msg, err := c.Request(ctx, advertise) - if err != nil { - return fmt.Errorf("nclient6.Request: %w", err) - } - nameservers := msg.Options.DNS() - for _, nameserver := range nameservers { - if nameserver.Equal(net.IPv6zero) { - continue + for _, packet := range conversation { + if packet.Type() == dhcpv6.MessageTypeReply { + msg, err := packet.GetInnerMessage() + if err != nil { + mainLog.Debug().Err(err).Msg("could not get inner DHCPv6 message") + return nil + } + nameservers := msg.Options.DNS() + for _, nameserver := range nameservers { + ns = append(ns, nameserver.String()) + } } - ns = append(ns, nameserver.String()) } - } + return ignoringEINTR(func() error { return setDNS(iface, ns) }) From 61156453b2d6c146e8ca0761dd1bc9e5ae7093c2 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 1 Feb 2023 10:01:56 +0700 Subject: [PATCH 58/69] cmd/ctrld: workaround setting DNS issue on Linux On some Ubuntu systems, we experiment with DNS is not set even though systemd-resolved log indicates that it set them. To ensure the DNS will be set, after setting them, double check the current DNS for interface is actually the value was set, if not, attempting to set again. While at it, also make sure the DNS is set when ctrld start on Linux. --- cmd/ctrld/cli.go | 11 +++++++++-- cmd/ctrld/os_linux.go | 19 +++++++++++++++++-- cmd/ctrld/prog.go | 6 ++++++ cmd/ctrld/prog_linux.go | 9 +++++++++ cmd/ctrld/prog_others.go | 6 ++++++ 5 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 cmd/ctrld/prog_linux.go create mode 100644 cmd/ctrld/prog_others.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 9fff2f8..f83d281 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -169,6 +169,8 @@ func initCLI() { runCmd.Flags().StringVarP(&cdUID, "cd", "", "", "Control D resolver uid") runCmd.Flags().StringVarP(&homedir, "homedir", "", "", "") _ = runCmd.Flags().MarkHidden("homedir") + runCmd.Flags().StringVarP(&iface, "iface", "", "", `Update DNS setting for iface, "auto" means the default interface gateway`) + _ = runCmd.Flags().MarkHidden("iface") rootCmd.AddCommand(runCmd) @@ -226,7 +228,7 @@ func initCLI() { } }, } - // Keep these flags in sync with runCmd above, except for "-d", "--iface". + // Keep these flags in sync with runCmd above, except for "-d". startCmd.Flags().StringVarP(&configPath, "config", "c", "", "Path to config file") startCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "Base64 encoded config") startCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "Listener address and port, in format: address:port") @@ -321,7 +323,6 @@ func initCLI() { } initLogging() if doTasks(tasks) { - prog.resetDNS() mainLog.Info().Msg("Service uninstalled") return } @@ -393,6 +394,9 @@ func initCLI() { Use: "start", Short: "Quick start service and configure DNS on interface", Run: func(cmd *cobra.Command, args []string) { + if !cmd.Flags().Changed("iface") { + os.Args = append(os.Args, "--iface="+ifaceStartStop) + } iface = ifaceStartStop startCmd.Run(cmd, args) }, @@ -405,6 +409,9 @@ func initCLI() { Use: "stop", Short: "Quick stop service and remove DNS from interface", Run: func(cmd *cobra.Command, args []string) { + if !cmd.Flags().Changed("iface") { + os.Args = append(os.Args, "--iface="+ifaceStartStop) + } iface = ifaceStartStop stopCmd.Run(cmd, args) }, diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 53d52b9..110a23b 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -8,6 +8,7 @@ import ( "net" "net/netip" "os/exec" + "reflect" "strings" "syscall" "time" @@ -41,6 +42,8 @@ func deAllocateIP(ip string) error { return nil } +const maxSetDNSAttempts = 5 + // set the dns server for the provided network interface func setDNS(iface *net.Interface, nameservers []string) error { logf := func(format string, args ...any) { @@ -57,10 +60,22 @@ func setDNS(iface *net.Interface, nameservers []string) error { for _, nameserver := range nameservers { ns = append(ns, netip.MustParseAddr(nameserver)) } - return r.SetDNS(dns.OSConfig{ + + osConfig := dns.OSConfig{ Nameservers: ns, SearchDomains: []dnsname.FQDN{}, - }) + } + + for i := 0; i < maxSetDNSAttempts; i++ { + if err := r.SetDNS(osConfig); err != nil { + return err + } + currentNS := currentDNS(iface) + if reflect.DeepEqual(currentNS, nameservers) { + break + } + } + return nil } func resetDNS(iface *net.Interface) error { diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index b17b94c..fa11dfc 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -35,6 +35,7 @@ func (p *prog) Start(s service.Service) error { } func (p *prog) run() { + p.preRun() if p.cfg.Service.CacheEnable { cacher, err := dnscache.NewLRUCache(p.cfg.Service.CacheSize) if err != nil { @@ -173,6 +174,11 @@ func (p *prog) Stop(s service.Service) error { return nil } +func (p *prog) Uninstall(s service.Service) error { + p.resetDNS() + return nil +} + func (p *prog) allocateIP(ip string) error { if !p.cfg.Service.AllocateIP { return nil diff --git a/cmd/ctrld/prog_linux.go b/cmd/ctrld/prog_linux.go new file mode 100644 index 0000000..155c2fa --- /dev/null +++ b/cmd/ctrld/prog_linux.go @@ -0,0 +1,9 @@ +package main + +import "github.com/kardianos/service" + +func (p *prog) preRun() { + if !service.Interactive() { + p.setDNS() + } +} diff --git a/cmd/ctrld/prog_others.go b/cmd/ctrld/prog_others.go new file mode 100644 index 0000000..10310d5 --- /dev/null +++ b/cmd/ctrld/prog_others.go @@ -0,0 +1,6 @@ +//go:build !linux +// +build !linux + +package main + +func (p *prog) preRun() {} From 44bd580e48e747774bdae395076dac568d2da660 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 1 Feb 2023 18:40:10 +0700 Subject: [PATCH 59/69] cmd/ctrld: fix reset DNS when uninstalling The "--iface" needs to be explicitly passed, otherwise, ctrld does not know which interface to restore. --- cmd/ctrld/cli.go | 2 ++ cmd/ctrld/os_linux.go | 4 ++-- cmd/ctrld/prog.go | 5 ----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index f83d281..dc8470d 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -323,11 +323,13 @@ func initCLI() { } initLogging() if doTasks(tasks) { + prog.resetDNS() mainLog.Info().Msg("Service uninstalled") return } }, } + uninstallCmd.Flags().StringVarP(&iface, "iface", "", "auto", `Reset DNS setting for iface, "auto" means the default interface gateway`) listIfacesCmd := &cobra.Command{ Use: "list", diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 110a23b..582ae3c 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -72,9 +72,10 @@ func setDNS(iface *net.Interface, nameservers []string) error { } currentNS := currentDNS(iface) if reflect.DeepEqual(currentNS, nameservers) { - break + return nil } } + mainLog.Debug().Msg("DNS was not set for some reason") return nil } @@ -105,7 +106,6 @@ func resetDNS(iface *net.Interface) error { conversation, err := c.Exchange(iface.Name) if err != nil { mainLog.Debug().Err(err).Msg("could not exchange DHCPv6") - return nil } for _, packet := range conversation { if packet.Type() == dhcpv6.MessageTypeReply { diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index fa11dfc..532f57e 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -174,11 +174,6 @@ func (p *prog) Stop(s service.Service) error { return nil } -func (p *prog) Uninstall(s service.Service) error { - p.resetDNS() - return nil -} - func (p *prog) allocateIP(ip string) error { if !p.cfg.Service.AllocateIP { return nil From 8c47ffb5ec03af23335cb3cadafe251f3d5a03f6 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 1 Feb 2023 21:26:21 +0700 Subject: [PATCH 60/69] cmd/ctrld: make NetworkManger ignore auto dns So the DNS that set by ctrld won't be override on startup. --- cmd/ctrld/cli.go | 3 +++ cmd/ctrld/os_linux.go | 39 +++++++++++++++++++++++++++++++++++++++ cmd/ctrld/os_mac.go | 6 ++++++ cmd/ctrld/os_windows.go | 6 ++++++ 4 files changed, 54 insertions(+) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index dc8470d..0e9527a 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -223,6 +223,7 @@ func initCLI() { {s.Start, true}, } if doTasks(tasks) { + disableAutoDNS(iface) prog.setDNS() mainLog.Info().Msg("Service started") } @@ -254,6 +255,7 @@ func initCLI() { } initLogging() if doTasks([]task{{s.Stop, true}}) { + enableAutoDNS(iface) prog.resetDNS() mainLog.Info().Msg("Service stopped") } @@ -323,6 +325,7 @@ func initCLI() { } initLogging() if doTasks(tasks) { + enableAutoDNS(iface) prog.resetDNS() mainLog.Info().Msg("Service uninstalled") return diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 582ae3c..01c52c0 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -9,6 +9,7 @@ import ( "net/netip" "os/exec" "reflect" + "runtime" "strings" "syscall" "time" @@ -174,6 +175,25 @@ func getDNSByNmcli(iface string) []string { return dns } +func getConnByNmcli(iface string) string { + if iface == "auto" { + iface = defaultIfaceName() + } + b, err := exec.Command("nmcli", "dev", "show", iface).Output() + if err != nil { + return "" + } + s := bufio.NewScanner(bytes.NewReader(b)) + for s.Scan() { + line := s.Text() + if _, connName, found := strings.Cut(line, "GENERAL.CONNECTION:"); found { + return strings.TrimSpace(connName) + } + + } + return "" +} + func ignoringEINTR(fn func() error) error { for { err := fn() @@ -182,3 +202,22 @@ func ignoringEINTR(fn func() error) error { } } } + +func disableAutoDNS(iface string) { + networkManagerIgnoreAutoDNS(iface, "yes") +} + +func enableAutoDNS(iface string) { + networkManagerIgnoreAutoDNS(iface, "no") +} + +func networkManagerIgnoreAutoDNS(iface, answer string) { + if runtime.GOOS != "linux" { + return + } + if connName := getConnByNmcli(iface); connName != "" { + mainLog.Debug().Msg("enable auto DNS from network manager") + _ = exec.Command("nmcli", "con", "mod", connName, "ipv4.ignore-auto-dns", answer).Run() + _ = exec.Command("nmcli", "con", "mod", connName, "ipv6.ignore-auto-dns", answer).Run() + } +} diff --git a/cmd/ctrld/os_mac.go b/cmd/ctrld/os_mac.go index 95786f3..ba9dc0d 100644 --- a/cmd/ctrld/os_mac.go +++ b/cmd/ctrld/os_mac.go @@ -60,3 +60,9 @@ func resetDNS(iface *net.Interface) error { func currentDNS(_ *net.Interface) []string { return resolvconffile.NameServers("") } + +func disableAutoDNS(iface string) { +} + +func enableAutoDNS(iface string) { +} diff --git a/cmd/ctrld/os_windows.go b/cmd/ctrld/os_windows.go index 213c104..1652225 100644 --- a/cmd/ctrld/os_windows.go +++ b/cmd/ctrld/os_windows.go @@ -104,3 +104,9 @@ func currentDNS(iface *net.Interface) []string { } return ns } + +func disableAutoDNS(iface string) { +} + +func enableAutoDNS(iface string) { +} From 1c2cd555bdf8ccfccd7036e2c826c17201a2fd10 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 1 Feb 2023 22:50:14 +0700 Subject: [PATCH 61/69] cmd/ctrld: ensure ctrld start after NetworkManager --- cmd/ctrld/cli.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 0e9527a..3de242e 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -186,6 +186,11 @@ func initCLI() { if os.Args[1] == "service" { osArgs = os.Args[3:] } + if runtime.GOOS == "linux" { + sc.Dependencies = []string{ + "After=NetworkManager-wait-online.service", + } + } sc.Arguments = append([]string{"run"}, osArgs...) if dir, err := os.UserHomeDir(); err == nil { // WorkingDirectory is not supported on Windows. From eb0dd6235e72e8093f720c765334ea9df78b0d6a Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Thu, 2 Feb 2023 13:56:30 +0700 Subject: [PATCH 62/69] cmd/ctrld: use NetworkManager to disable DNS manager Currently, ctrld force NetworkManager ignore auto DNS setup from DHCP per connection. This does not work well, because an interface can be attached to many connections. So if `ctrld` started with a connection, then user connect to new one, the DNS configured by ctrld will be override. Instead, we can force NetworkManager not to manage DNS by: - Using dns=none - Set systemd-resolved=false So NetworkManager won't attempt to send DNS setup to systemd-resolved, leaving what ctrld set as-is. --- cmd/ctrld/cli.go | 8 ++-- cmd/ctrld/network_manager.go | 85 ++++++++++++++++++++++++++++++++++++ cmd/ctrld/os_linux.go | 39 ----------------- cmd/ctrld/os_mac.go | 6 --- cmd/ctrld/os_windows.go | 6 --- cmd/ctrld/prog.go | 8 ++++ go.mod | 1 + go.sum | 1 + 8 files changed, 100 insertions(+), 54 deletions(-) create mode 100644 cmd/ctrld/network_manager.go diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 3de242e..805db1a 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -228,7 +228,6 @@ func initCLI() { {s.Start, true}, } if doTasks(tasks) { - disableAutoDNS(iface) prog.setDNS() mainLog.Info().Msg("Service started") } @@ -260,7 +259,6 @@ func initCLI() { } initLogging() if doTasks([]task{{s.Stop, true}}) { - enableAutoDNS(iface) prog.resetDNS() mainLog.Info().Msg("Service stopped") } @@ -330,7 +328,6 @@ func initCLI() { } initLogging() if doTasks(tasks) { - enableAutoDNS(iface) prog.resetDNS() mainLog.Info().Msg("Service uninstalled") return @@ -551,6 +548,10 @@ func processCDFlags() { } if netIface, _ := netInterface(iface); netIface != nil { + if err := restoreNetworkManager(); err != nil { + logger.Error().Err(err).Msg("could not restore NetworkManager") + return + } logger.Debug().Str("iface", netIface.Name).Msg("Restoring DNS for interface") if err := resetDNS(netIface); err != nil { logger.Warn().Err(err).Msg("something went wrong while restoring DNS") @@ -558,6 +559,7 @@ func processCDFlags() { logger.Debug().Str("iface", netIface.Name).Msg("Restoring DNS successfully") } } + tasks := []task{{s.Uninstall, true}} if doTasks(tasks) { logger.Info().Msg("uninstalled service") diff --git a/cmd/ctrld/network_manager.go b/cmd/ctrld/network_manager.go new file mode 100644 index 0000000..670fe9c --- /dev/null +++ b/cmd/ctrld/network_manager.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "os" + "path/filepath" + "runtime" + "time" + + "github.com/coreos/go-systemd/v22/dbus" +) + +const ( + nmConfDir = "/etc/NetworkManager/conf.d" + nmCtrldConfFilename = "99-ctrld.conf" + nmCtrldConfContent = `[main] +dns=none +systemd-resolved=false +` + nmSystemdUnitName = "NetworkManager.service" + systemdEnabledState = "enabled" +) + +var networkManagerCtrldConfFile = filepath.Join(nmConfDir, nmCtrldConfFilename) + +func setupNetworkManager() error { + if runtime.GOOS != "linux" { + mainLog.Debug().Msg("skipping NetworkManager setup, not on Linux") + return nil + } + if content, _ := os.ReadFile(nmCtrldConfContent); string(content) == nmCtrldConfContent { + mainLog.Debug().Msg("NetworkManager already setup, nothing to do") + return nil + } + err := os.WriteFile(networkManagerCtrldConfFile, []byte(nmCtrldConfContent), os.FileMode(0644)) + if os.IsNotExist(err) { + mainLog.Debug().Msg("NetworkManager is not available") + return nil + } + if err != nil { + mainLog.Debug().Err(err).Msg("could not write NetworkManager ctrld config file") + return err + } + + reloadNetworkManager() + mainLog.Debug().Msg("setup NetworkManager done") + return nil +} + +func restoreNetworkManager() error { + if runtime.GOOS != "linux" { + mainLog.Debug().Msg("skipping NetworkManager restoring, not on Linux") + return nil + } + err := os.Remove(networkManagerCtrldConfFile) + if os.IsNotExist(err) { + mainLog.Debug().Msg("NetworkManager is not available") + return nil + } + if err != nil { + mainLog.Debug().Err(err).Msg("could not remove NetworkManager ctrld config file") + return err + } + + reloadNetworkManager() + mainLog.Debug().Msg("restore NetworkManager done") + return nil +} + +func reloadNetworkManager() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + conn, err := dbus.NewSystemConnectionContext(ctx) + if err != nil { + mainLog.Error().Err(err).Msg("could not create new system connection") + return + } + defer conn.Close() + + waitCh := make(chan string) + if _, err := conn.ReloadUnitContext(ctx, nmSystemdUnitName, "ignore-dependencies", waitCh); err != nil { + mainLog.Debug().Err(err).Msg("could not reload NetworkManager") + } + <-waitCh +} diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 01c52c0..582ae3c 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -9,7 +9,6 @@ import ( "net/netip" "os/exec" "reflect" - "runtime" "strings" "syscall" "time" @@ -175,25 +174,6 @@ func getDNSByNmcli(iface string) []string { return dns } -func getConnByNmcli(iface string) string { - if iface == "auto" { - iface = defaultIfaceName() - } - b, err := exec.Command("nmcli", "dev", "show", iface).Output() - if err != nil { - return "" - } - s := bufio.NewScanner(bytes.NewReader(b)) - for s.Scan() { - line := s.Text() - if _, connName, found := strings.Cut(line, "GENERAL.CONNECTION:"); found { - return strings.TrimSpace(connName) - } - - } - return "" -} - func ignoringEINTR(fn func() error) error { for { err := fn() @@ -202,22 +182,3 @@ func ignoringEINTR(fn func() error) error { } } } - -func disableAutoDNS(iface string) { - networkManagerIgnoreAutoDNS(iface, "yes") -} - -func enableAutoDNS(iface string) { - networkManagerIgnoreAutoDNS(iface, "no") -} - -func networkManagerIgnoreAutoDNS(iface, answer string) { - if runtime.GOOS != "linux" { - return - } - if connName := getConnByNmcli(iface); connName != "" { - mainLog.Debug().Msg("enable auto DNS from network manager") - _ = exec.Command("nmcli", "con", "mod", connName, "ipv4.ignore-auto-dns", answer).Run() - _ = exec.Command("nmcli", "con", "mod", connName, "ipv6.ignore-auto-dns", answer).Run() - } -} diff --git a/cmd/ctrld/os_mac.go b/cmd/ctrld/os_mac.go index ba9dc0d..95786f3 100644 --- a/cmd/ctrld/os_mac.go +++ b/cmd/ctrld/os_mac.go @@ -60,9 +60,3 @@ func resetDNS(iface *net.Interface) error { func currentDNS(_ *net.Interface) []string { return resolvconffile.NameServers("") } - -func disableAutoDNS(iface string) { -} - -func enableAutoDNS(iface string) { -} diff --git a/cmd/ctrld/os_windows.go b/cmd/ctrld/os_windows.go index 1652225..213c104 100644 --- a/cmd/ctrld/os_windows.go +++ b/cmd/ctrld/os_windows.go @@ -104,9 +104,3 @@ func currentDNS(iface *net.Interface) []string { } return ns } - -func disableAutoDNS(iface string) { -} - -func enableAutoDNS(iface string) { -} diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 532f57e..8a9cd0e 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -209,6 +209,10 @@ func (p *prog) setDNS() { logger.Error().Err(err).Msg("could not get interface") return } + if err := setupNetworkManager(); err != nil { + logger.Error().Err(err).Msg("could not patch NetworkManager") + return + } logger.Debug().Msg("setting DNS for interface") if err := setDNS(netIface, []string{cfg.Listener["0"].IP}); err != nil { logger.Error().Err(err).Msgf("could not set DNS for interface") @@ -230,6 +234,10 @@ func (p *prog) resetDNS() { logger.Error().Err(err).Msg("could not get interface") return } + if err := restoreNetworkManager(); err != nil { + logger.Error().Err(err).Msg("could not restore NetworkManager") + return + } logger.Debug().Msg("Restoring DNS for interface") if err := resetDNS(netIface); err != nil { logger.Error().Err(err).Msgf("could not reset DNS") diff --git a/go.mod b/go.mod index 1e6f67c..433326b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Control-D-Inc/ctrld go 1.19 require ( + github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 github.com/go-playground/validator/v10 v10.11.1 github.com/hashicorp/golang-lru/v2 v2.0.1 github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e diff --git a/go.sum b/go.sum index 2a4fe13..a450fe2 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= 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= From b8772d7b4a3defaf5cd92f84363622b283755e09 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 3 Feb 2023 00:09:03 +0700 Subject: [PATCH 63/69] cmd/ctrld: log fatal if could not start the listener --- cmd/ctrld/prog.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 8a9cd0e..d0de5f8 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -116,7 +116,7 @@ func (p *prog) run() { mainLog.Info().Msgf("Starting DNS server on listener.%s: %s", listenerNum, addr) err := p.serveUDP(listenerNum) if err != nil && !defaultConfigWritten { - proxyLog.Error().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum) + proxyLog.Fatal().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum) return } @@ -125,13 +125,13 @@ func (p *prog) run() { proxyLog.Warn().Msgf("Address %s already in used, pick a random one", addr) pc, err := net.ListenPacket("udp", net.JoinHostPort(listenerConfig.IP, "0")) if err != nil { - proxyLog.Error().Err(err).Msg("failed to listen packet") + proxyLog.Fatal().Err(err).Msg("failed to listen packet") return } _, portStr, _ := net.SplitHostPort(pc.LocalAddr().String()) port, err := strconv.Atoi(portStr) if err != nil { - proxyLog.Error().Err(err).Msg("malformed port") + proxyLog.Fatal().Err(err).Msg("malformed port") return } listenerConfig.Port = port @@ -150,11 +150,11 @@ func (p *prog) run() { // There can be a race between closing the listener and start our own UDP server, but it's // rare, and we only do this once, so let conservative here. if err := pc.Close(); err != nil { - proxyLog.Error().Err(err).Msg("failed to close packet conn") + proxyLog.Fatal().Err(err).Msg("failed to close packet conn") return } if err := p.serveUDP(listenerNum); err != nil { - proxyLog.Error().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum) + proxyLog.Fatal().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum) return } } From 851f9b9742ce83188b5ffe3d5df96efb9ac0cea6 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 3 Feb 2023 02:04:30 +0700 Subject: [PATCH 64/69] all: fork tailscale Linux dns manager package With modification to fit our use case. --- cmd/ctrld/os_linux.go | 22 +- cmd/ctrld/prog_linux.go | 4 +- go.mod | 8 +- go.sum | 2 + internal/dns/README.md | 2 + internal/dns/direct.go | 533 ++++++++++++++++++ internal/dns/direct_linux.go | 62 ++ internal/dns/direct_notlinux.go | 11 + internal/dns/direct_test.go | 199 +++++++ internal/dns/manager_linux.go | 387 +++++++++++++ internal/dns/manager_linux_test.go | 439 +++++++++++++++ internal/dns/nm.go | 269 +++++++++ internal/dns/osconfig.go | 124 ++++ internal/dns/osconfig_test.go | 44 ++ internal/dns/resolvconf.go | 26 + internal/dns/resolvconffile/resolvconffile.go | 119 ++++ .../dns/resolvconffile/resolvconffile_test.go | 104 ++++ internal/dns/resolved.go | 389 +++++++++++++ 18 files changed, 2735 insertions(+), 9 deletions(-) create mode 100644 internal/dns/README.md create mode 100644 internal/dns/direct.go create mode 100644 internal/dns/direct_linux.go create mode 100644 internal/dns/direct_notlinux.go create mode 100644 internal/dns/direct_test.go create mode 100644 internal/dns/manager_linux.go create mode 100644 internal/dns/manager_linux_test.go create mode 100644 internal/dns/nm.go create mode 100644 internal/dns/osconfig.go create mode 100644 internal/dns/osconfig_test.go create mode 100644 internal/dns/resolvconf.go create mode 100644 internal/dns/resolvconffile/resolvconffile.go create mode 100644 internal/dns/resolvconffile/resolvconffile_test.go create mode 100644 internal/dns/resolved.go diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 582ae3c..50ff469 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -16,12 +16,16 @@ import ( "github.com/insomniacslk/dhcp/dhcpv4/nclient4" "github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/dhcpv6/client6" - "tailscale.com/net/dns" "tailscale.com/util/dnsname" + "github.com/Control-D-Inc/ctrld/internal/dns" "github.com/Control-D-Inc/ctrld/internal/resolvconffile" ) +var logf = func(format string, args ...any) { + mainLog.Debug().Msgf(format, args...) +} + // allocate loopback ip // sudo ip a add 127.0.0.2/24 dev lo func allocateIP(ip string) error { @@ -46,16 +50,12 @@ const maxSetDNSAttempts = 5 // set the dns server for the provided network interface func setDNS(iface *net.Interface, nameservers []string) error { - logf := func(format string, args ...any) { - mainLog.Debug().Msgf(format, args...) - } - r, err := dns.NewOSConfigurator(logf, iface.Name) if err != nil { mainLog.Error().Err(err).Msg("failed to create DNS OS configurator") return err } - defer r.Close() + ns := make([]netip.Addr, 0, len(nameservers)) for _, nameserver := range nameservers { ns = append(ns, netip.MustParseAddr(nameserver)) @@ -80,6 +80,16 @@ func setDNS(iface *net.Interface, nameservers []string) error { } func resetDNS(iface *net.Interface) error { + if r, err := dns.NewOSConfigurator(logf, iface.Name); err == nil { + if err := r.Close(); err != nil { + mainLog.Error().Err(err).Msg("failed to rollback DNS setting") + return err + } + if r.Mode() == "direct" { + return nil + } + } + var ns []string c, err := nclient4.New(iface.Name) if err != nil { diff --git a/cmd/ctrld/prog_linux.go b/cmd/ctrld/prog_linux.go index 155c2fa..d1d5ee2 100644 --- a/cmd/ctrld/prog_linux.go +++ b/cmd/ctrld/prog_linux.go @@ -1,6 +1,8 @@ package main -import "github.com/kardianos/service" +import ( + "github.com/kardianos/service" +) func (p *prog) preRun() { if !service.Interactive() { diff --git a/go.mod b/go.mod index 433326b..a5cac4e 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,11 @@ go 1.19 require ( github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 + github.com/frankban/quicktest v1.14.3 github.com/go-playground/validator/v10 v10.11.1 + 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/kardianos/service v1.2.1 github.com/lucas-clemente/quic-go v0.29.1 @@ -29,17 +32,17 @@ require ( 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-20210107165309-348f09dbbbc0 // indirect - github.com/godbus/dbus/v5 v5.0.6 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect - github.com/illarion/gonotify v1.0.1 // 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 github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect @@ -57,6 +60,7 @@ require ( github.com/onsi/ginkgo v1.16.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index a450fe2..aee861f 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -259,6 +260,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w= +github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= diff --git a/internal/dns/README.md b/internal/dns/README.md new file mode 100644 index 0000000..aadc3a5 --- /dev/null +++ b/internal/dns/README.md @@ -0,0 +1,2 @@ +This is a fork of https://pkg.go.dev/tailscale.com@v1.34.2/net/dns with modification +to fit ctrld use case. \ No newline at end of file diff --git a/internal/dns/direct.go b/internal/dns/direct.go new file mode 100644 index 0000000..7258649 --- /dev/null +++ b/internal/dns/direct.go @@ -0,0 +1,533 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//lint:file-ignore U1000 satisfy CI. + +package dns + +import ( + "bytes" + "context" + "crypto/rand" + "errors" + "fmt" + "io" + "io/fs" + "net/netip" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "sync" + "time" + + "tailscale.com/health" + "tailscale.com/types/logger" + "tailscale.com/util/dnsname" + "tailscale.com/version/distro" + + "github.com/Control-D-Inc/ctrld/internal/dns/resolvconffile" +) + +const ( + backupConf = "/etc/resolv.pre-ctrld-backup.conf" + resolvConf = "/etc/resolv.conf" +) + +// writeResolvConf writes DNS configuration in resolv.conf format to the given writer. +func writeResolvConf(w io.Writer, servers []netip.Addr, domains []dnsname.FQDN) error { + c := &resolvconffile.Config{ + Nameservers: servers, + SearchDomains: domains, + } + return c.Write(w) +} + +func readResolv(r io.Reader) (OSConfig, error) { + c, err := resolvconffile.Parse(r) + if err != nil { + return OSConfig{}, err + } + return OSConfig{ + Nameservers: c.Nameservers, + SearchDomains: c.SearchDomains, + }, nil +} + +// resolvOwner returns the apparent owner of the resolv.conf +// configuration in bs - one of "resolvconf", "systemd-resolved" or +// "NetworkManager", or "" if no known owner was found. +func resolvOwner(bs []byte) string { + likely := "" + b := bytes.NewBuffer(bs) + for { + line, err := b.ReadString('\n') + if err != nil { + return likely + } + line = strings.TrimSpace(line) + if line == "" { + continue + } + if line[0] != '#' { + // First non-empty, non-comment line. Assume the owner + // isn't hiding further down. + return likely + } + + if strings.Contains(line, "systemd-resolved") { + likely = "systemd-resolved" + } else if strings.Contains(line, "NetworkManager") { + likely = "NetworkManager" + } else if strings.Contains(line, "resolvconf") { + likely = "resolvconf" + } + } +} + +// isResolvedRunning reports whether systemd-resolved is running on the system, +// even if it is not managing the system DNS settings. +func isResolvedRunning() bool { + if runtime.GOOS != "linux" { + return false + } + + // systemd-resolved is never installed without systemd. + _, err := exec.LookPath("systemctl") + if err != nil { + return false + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + err = exec.CommandContext(ctx, "systemctl", "is-active", "systemd-resolved.service").Run() + + // is-active exits with code 3 if the service is not active. + return err == nil +} + +func restartResolved() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + return exec.CommandContext(ctx, "systemctl", "restart", "systemd-resolved.service").Run() +} + +// directManager is an OSConfigurator which replaces /etc/resolv.conf with a file +// generated from the given configuration, creating a backup of its old state. +// +// This way of configuring DNS is precarious, since it does not react +// to the disappearance of the Tailscale interface. +// The caller must call Down before program shutdown +// or as cleanup if the program terminates unexpectedly. +type directManager struct { + logf logger.Logf + fs wholeFileFS + // renameBroken is set if fs.Rename to or from /etc/resolv.conf + // fails. This can happen in some container runtimes, where + // /etc/resolv.conf is bind-mounted from outside the container, + // and therefore /etc and /etc/resolv.conf are different + // filesystems as far as rename(2) is concerned. + // + // In those situations, we fall back to emulating rename with file + // copies and truncations, which is not as good (opens up a race + // where a reader can see an empty or partial /etc/resolv.conf), + // but is better than having non-functioning DNS. + renameBroken bool + + ctx context.Context // valid until Close + ctxClose context.CancelFunc // closes ctx + + mu sync.Mutex + wantResolvConf []byte // if non-nil, what we expect /etc/resolv.conf to contain + lastWarnContents []byte // last resolv.conf contents that we warned about +} + +func newDirectManagerOnFS(logf logger.Logf, fs wholeFileFS) *directManager { + ctx, cancel := context.WithCancel(context.Background()) + m := &directManager{ + logf: logf, + fs: fs, + ctx: ctx, + ctxClose: cancel, + } + go m.runFileWatcher() + return m +} + +func (m *directManager) readResolvFile(path string) (OSConfig, error) { + b, err := m.fs.ReadFile(path) + if err != nil { + return OSConfig{}, err + } + return readResolv(bytes.NewReader(b)) +} + +// ownedByCtrld reports whether /etc/resolv.conf seems to be a +// ctrld-managed file. +func (m *directManager) ownedByCtrld() (bool, error) { + isRegular, err := m.fs.Stat(resolvConf) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + if !isRegular { + return false, nil + } + bs, err := m.fs.ReadFile(resolvConf) + if err != nil { + return false, err + } + if bytes.Contains(bs, []byte("generated by ctrld")) { + return true, nil + } + return false, nil +} + +// backupConfig creates or updates a backup of /etc/resolv.conf, if +// resolv.conf does not currently contain a Tailscale-managed config. +func (m *directManager) backupConfig() error { + if _, err := m.fs.Stat(resolvConf); err != nil { + if os.IsNotExist(err) { + // No resolv.conf, nothing to back up. Also get rid of any + // existing backup file, to avoid restoring something old. + _ = m.fs.Remove(backupConf) + return nil + } + return err + } + + owned, err := m.ownedByCtrld() + if err != nil { + return err + } + if owned { + return nil + } + + return m.rename(resolvConf, backupConf) +} + +func (m *directManager) restoreBackup() (restored bool, err error) { + if _, err := m.fs.Stat(backupConf); err != nil { + if os.IsNotExist(err) { + // No backup, nothing we can do. + return false, nil + } + return false, err + } + owned, err := m.ownedByCtrld() + if err != nil { + return false, err + } + _, err = m.fs.Stat(resolvConf) + if err != nil && !os.IsNotExist(err) { + return false, err + } + resolvConfExists := !os.IsNotExist(err) + + if resolvConfExists && !owned { + // There's already a non-ctrld config in place, get rid of + // our backup. + _ = m.fs.Remove(backupConf) + return false, nil + } + + // We own resolv.conf, and a backup exists. + if err := m.rename(backupConf, resolvConf); err != nil { + return false, err + } + + return true, nil +} + +// rename tries to rename old to new using m.fs.Rename, and falls back +// to hand-copying bytes and truncating old if that fails. +// +// This is a workaround to /etc/resolv.conf being a bind-mounted file +// some container environments, which cannot be moved elsewhere in +// /etc (because that would be a cross-filesystem move) or deleted +// (because that would break the bind in surprising ways). +func (m *directManager) rename(old, new string) error { + if !m.renameBroken { + err := m.fs.Rename(old, new) + if err == nil { + return nil + } + if runtime.GOOS == "linux" && distro.Get() == distro.Synology { + // Fail fast. The fallback case below won't work anyway. + return err + } + m.logf("rename of %q to %q failed (%v), falling back to copy+delete", old, new, err) + m.renameBroken = true + } + + bs, err := m.fs.ReadFile(old) + if err != nil { + return fmt.Errorf("reading %q to rename: %w", old, err) + } + if err := m.fs.WriteFile(new, bs, 0644); err != nil { + return fmt.Errorf("writing to %q in rename of %q: %w", new, old, err) + } + + if err := m.fs.Remove(old); err != nil { + err2 := m.fs.Truncate(old) + if err2 != nil { + return fmt.Errorf("remove of %q failed (%w) and so did truncate: %v", old, err, err2) + } + } + return nil +} + +// setWant sets the expected contents of /etc/resolv.conf, if any. +// +// A value of nil means no particular value is expected. +// +// m takes ownership of want. +func (m *directManager) setWant(want []byte) { + m.mu.Lock() + defer m.mu.Unlock() + m.wantResolvConf = want +} + +var warnTrample = health.NewWarnable() + +// checkForFileTrample checks whether /etc/resolv.conf has been trampled +// by another program on the system. (e.g. a DHCP client) +func (m *directManager) checkForFileTrample() { + m.mu.Lock() + want := m.wantResolvConf + lastWarn := m.lastWarnContents + m.mu.Unlock() + + if want == nil { + return + } + + cur, err := m.fs.ReadFile(resolvConf) + if err != nil { + m.logf("trample: read error: %v", err) + return + } + if bytes.Equal(cur, want) { + warnTrample.Set(nil) + if lastWarn != nil { + m.mu.Lock() + m.lastWarnContents = nil + m.mu.Unlock() + m.logf("trample: resolv.conf again matches expected content") + } + return + } + if bytes.Equal(cur, lastWarn) { + // We already logged about this, so not worth doing it again. + return + } + + m.mu.Lock() + m.lastWarnContents = cur + m.mu.Unlock() + + show := cur + if len(show) > 1024 { + show = show[:1024] + } + m.logf("trample: resolv.conf changed from what we expected. did some other program interfere? current contents: %q", show) + //lint:ignore ST1005 This error is for human. + warnTrample.Set(errors.New("Linux DNS config not ideal. /etc/resolv.conf overwritten. See https://tailscale.com/s/dns-fight")) +} + +func (m *directManager) SetDNS(config OSConfig) (err error) { + defer func() { + if err != nil && errors.Is(err, fs.ErrPermission) && runtime.GOOS == "linux" && + distro.Get() == distro.Synology && os.Geteuid() != 0 { + // On Synology (notably DSM7 where we don't run as root), ignore all + // DNS configuration errors for now. We don't have permission. + // See https://github.com/tailscale/tailscale/issues/4017 + m.logf("ignoring SetDNS permission error on Synology (Issue 4017); was: %v", err) + err = nil + } + }() + m.setWant(nil) // reset our expectations before any work + var changed bool + if config.IsZero() { + changed, err = m.restoreBackup() + if err != nil { + return err + } + } else { + changed = true + if err := m.backupConfig(); err != nil { + return err + } + + buf := new(bytes.Buffer) + _ = writeResolvConf(buf, config.Nameservers, config.SearchDomains) + if err := m.atomicWriteFile(m.fs, resolvConf, buf.Bytes(), 0644); err != nil { + return err + } + + // Now that we've successfully written to the file, lock it in. + // If we see /etc/resolv.conf with different contents, we know somebody + // else trampled on it. + m.setWant(buf.Bytes()) + } + + // We might have taken over a configuration managed by resolved, + // in which case it will notice this on restart and gracefully + // start using our configuration. This shouldn't happen because we + // try to manage DNS through resolved when it's around, but as a + // best-effort fallback if we messed up the detection, try to + // restart resolved to make the system configuration consistent. + // + // We take care to only kick systemd-resolved if we've made some + // change to the system's DNS configuration, because this codepath + // can end up running in cases where the user has manually + // configured /etc/resolv.conf to point to systemd-resolved (but + // it's not managed explicitly by systemd-resolved), *and* has + // --accept-dns=false, meaning we pass an empty configuration to + // the running DNS manager. In that very edge-case scenario, we + // cause a disruptive DNS outage each time we reset an empty + // OS configuration. + if changed && isResolvedRunning() && !runningAsGUIDesktopUser() { + t0 := time.Now() + err := restartResolved() + d := time.Since(t0).Round(time.Millisecond) + if err != nil { + m.logf("error restarting resolved after %v: %v", d, err) + } else { + m.logf("restarted resolved after %v", d) + } + } + + return nil +} + +func (m *directManager) Close() error { + // We used to keep a file for the ctrld config and symlinked + // to it, but then we stopped because /etc/resolv.conf being a + // symlink to surprising places breaks snaps and other sandboxing + // things. Clean it up if it's still there. + _ = m.fs.Remove("/etc/resolv.ctrld.conf") + + if _, err := m.fs.Stat(backupConf); err != nil { + if os.IsNotExist(err) { + // No backup, nothing we can do. + return nil + } + return err + } + owned, err := m.ownedByCtrld() + if err != nil { + return err + } + _, err = m.fs.Stat(resolvConf) + if err != nil && !os.IsNotExist(err) { + return err + } + resolvConfExists := !os.IsNotExist(err) + + if resolvConfExists && !owned { + // There's already a non-ctrld config in place, get rid of + // our backup. + _ = m.fs.Remove(backupConf) + return nil + } + + // We own resolv.conf, and a backup exists. + if err := m.rename(backupConf, resolvConf); err != nil { + return err + } + + if isResolvedRunning() && !runningAsGUIDesktopUser() { + m.logf("restarting systemd-resolved...") + if err := restartResolved(); err != nil { + m.logf("restart of systemd-resolved failed: %v", err) + } else { + m.logf("restarted systemd-resolved") + } + } + + return nil +} + +func (m *directManager) Mode() string { + return "direct" +} + +func (m *directManager) atomicWriteFile(fs wholeFileFS, filename string, data []byte, perm os.FileMode) error { + var randBytes [12]byte + if _, err := rand.Read(randBytes[:]); err != nil { + return fmt.Errorf("atomicWriteFile: %w", err) + } + + tmpName := fmt.Sprintf("%s.%x.tmp", filename, randBytes[:]) + defer fs.Remove(tmpName) + + if err := fs.WriteFile(tmpName, data, perm); err != nil { + return fmt.Errorf("atomicWriteFile: %w", err) + } + return m.rename(tmpName, filename) +} + +// wholeFileFS is a high-level file system abstraction designed just for use +// by directManager, with the goal that it is easy to implement over wsl.exe. +// +// All name parameters are absolute paths. +type wholeFileFS interface { + Stat(name string) (isRegular bool, err error) + Rename(oldName, newName string) error + Remove(name string) error + ReadFile(name string) ([]byte, error) + Truncate(name string) error + WriteFile(name string, contents []byte, perm os.FileMode) error +} + +// directFS is a wholeFileFS implemented directly on the OS. +type directFS struct { + // prefix is file path prefix. + // + // All name parameters are absolute paths so this is typically a + // testing temporary directory like "/tmp". + prefix string +} + +func (fs directFS) path(name string) string { return filepath.Join(fs.prefix, name) } + +func (fs directFS) Stat(name string) (isRegular bool, err error) { + fi, err := os.Stat(fs.path(name)) + if err != nil { + return false, err + } + return fi.Mode().IsRegular(), nil +} + +func (fs directFS) Rename(oldName, newName string) error { + return os.Rename(fs.path(oldName), fs.path(newName)) +} + +func (fs directFS) Remove(name string) error { return os.Remove(fs.path(name)) } + +func (fs directFS) ReadFile(name string) ([]byte, error) { + return os.ReadFile(fs.path(name)) +} + +func (fs directFS) Truncate(name string) error { + return os.Truncate(fs.path(name), 0) +} + +func (fs directFS) WriteFile(name string, contents []byte, perm os.FileMode) error { + return os.WriteFile(fs.path(name), contents, perm) +} + +// runningAsGUIDesktopUser reports whether it seems that this code is +// being run as a regular user on a Linux desktop. This is a quick +// hack to fix Issue 2672 where PolicyKit pops up a GUI dialog asking +// to proceed we do a best effort attempt to restart +// systemd-resolved.service. There's surely a better way. +func runningAsGUIDesktopUser() bool { + return os.Getuid() != 0 && os.Getenv("DISPLAY") != "" +} diff --git a/internal/dns/direct_linux.go b/internal/dns/direct_linux.go new file mode 100644 index 0000000..565c227 --- /dev/null +++ b/internal/dns/direct_linux.go @@ -0,0 +1,62 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dns + +import ( + "context" + + "github.com/illarion/gonotify" +) + +func (m *directManager) runFileWatcher() { + in, err := gonotify.NewInotify() + if err != nil { + // Oh well, we tried. This is all best effort for now, to + // surface warnings to users. + m.logf("dns: inotify new: %v", err) + return + } + ctx, cancel := context.WithCancel(m.ctx) + defer cancel() + go m.closeInotifyOnDone(ctx, in) + + const events = gonotify.IN_ATTRIB | + gonotify.IN_CLOSE_WRITE | + gonotify.IN_CREATE | + gonotify.IN_DELETE | + gonotify.IN_MODIFY | + gonotify.IN_MOVE + + if err := in.AddWatch("/etc/", events); err != nil { + m.logf("dns: inotify addwatch: %v", err) + return + } + for { + events, err := in.Read() + if ctx.Err() != nil { + return + } + if err != nil { + m.logf("dns: inotify read: %v", err) + return + } + var match bool + for _, ev := range events { + if ev.Name == resolvConf { + match = true + break + } + } + if !match { + continue + } + m.checkForFileTrample() + } +} + +func (m *directManager) closeInotifyOnDone(ctx context.Context, in *gonotify.Inotify) { + <-ctx.Done() + _ = in.Close() +} diff --git a/internal/dns/direct_notlinux.go b/internal/dns/direct_notlinux.go new file mode 100644 index 0000000..5563586 --- /dev/null +++ b/internal/dns/direct_notlinux.go @@ -0,0 +1,11 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux + +package dns + +func (m *directManager) runFileWatcher() { + // Not implemented on other platforms. Maybe it could resort to polling. +} diff --git a/internal/dns/direct_test.go b/internal/dns/direct_test.go new file mode 100644 index 0000000..57962dd --- /dev/null +++ b/internal/dns/direct_test.go @@ -0,0 +1,199 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dns + +import ( + "errors" + "fmt" + "io/fs" + "net/netip" + "os" + "path/filepath" + "strings" + "syscall" + "testing" + + qt "github.com/frankban/quicktest" + "tailscale.com/util/dnsname" +) + +func TestDirectManager(t *testing.T) { + tmp := t.TempDir() + if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil { + t.Fatal(err) + } + testDirect(t, directFS{prefix: tmp}) +} + +type boundResolvConfFS struct { + directFS +} + +func (fs boundResolvConfFS) Rename(old, new string) error { + if old == "/etc/resolv.conf" || new == "/etc/resolv.conf" { + return errors.New("cannot move to/from /etc/resolv.conf") + } + return fs.directFS.Rename(old, new) +} + +func (fs boundResolvConfFS) Remove(name string) error { + if name == "/etc/resolv.conf" { + return errors.New("cannot remove /etc/resolv.conf") + } + return fs.directFS.Remove(name) +} + +func TestDirectBrokenRename(t *testing.T) { + tmp := t.TempDir() + if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil { + t.Fatal(err) + } + testDirect(t, boundResolvConfFS{directFS{prefix: tmp}}) +} + +func testDirect(t *testing.T, fs wholeFileFS) { + const orig = "nameserver 9.9.9.9 # orig" + resolvPath := "/etc/resolv.conf" + backupPath := "/etc/resolv.pre-ctrld-backup.conf" + + if err := fs.WriteFile(resolvPath, []byte(orig), 0644); err != nil { + t.Fatal(err) + } + + readFile := func(t *testing.T, path string) string { + t.Helper() + b, err := fs.ReadFile(path) + if err != nil { + t.Fatal(err) + } + return string(b) + } + assertBaseState := func(t *testing.T) { + if got := readFile(t, resolvPath); got != orig { + t.Fatalf("resolv.conf:\n%s, want:\n%s", got, orig) + } + if _, err := fs.Stat(backupPath); !os.IsNotExist(err) { + t.Fatalf("resolv.conf backup: want it to be gone but: %v", err) + } + } + + m := directManager{logf: t.Logf, fs: fs} + if err := m.SetDNS(OSConfig{ + Nameservers: []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("8.8.4.4")}, + SearchDomains: []dnsname.FQDN{"controld.com."}, + MatchDomains: []dnsname.FQDN{"ignored."}, + }); err != nil { + t.Fatal(err) + } + want := `# resolv.conf(5) file generated by ctrld +# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN + +nameserver 8.8.8.8 +nameserver 8.8.4.4 +search controld.com +` + if got := readFile(t, resolvPath); got != want { + t.Fatalf("resolv.conf:\n%s, want:\n%s", got, want) + } + if got := readFile(t, backupPath); got != orig { + t.Fatalf("resolv.conf backup:\n%s, want:\n%s", got, orig) + } + + // Test that a nil OSConfig cleans up resolv.conf. + if err := m.SetDNS(OSConfig{}); err != nil { + t.Fatal(err) + } + assertBaseState(t) + + // Test that Close cleans up resolv.conf. + if err := m.SetDNS(OSConfig{Nameservers: []netip.Addr{netip.MustParseAddr("8.8.8.8")}}); err != nil { + t.Fatal(err) + } + if err := m.Close(); err != nil { + t.Fatal(err) + } + assertBaseState(t) +} + +type brokenRemoveFS struct { + directFS +} + +func (b brokenRemoveFS) Rename(_, _ string) error { + return errors.New("nyaaah I'm a silly container!") +} + +func (b brokenRemoveFS) Remove(name string) error { + if strings.Contains(name, "/etc/resolv.conf") { + return fmt.Errorf("Faking remove failure: %q", &fs.PathError{Err: syscall.EBUSY}) + } + return b.directFS.Remove(name) +} + +func TestDirectBrokenRemove(t *testing.T) { + tmp := t.TempDir() + if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil { + t.Fatal(err) + } + testDirect(t, brokenRemoveFS{directFS{prefix: tmp}}) +} + +func TestReadResolve(t *testing.T) { + c := qt.New(t) + tests := []struct { + in string + want OSConfig + wantErr bool + }{ + {in: `nameserver 192.168.0.100`, + want: OSConfig{ + Nameservers: []netip.Addr{ + netip.MustParseAddr("192.168.0.100"), + }, + }, + }, + {in: `nameserver 192.168.0.100 # comment`, + want: OSConfig{ + Nameservers: []netip.Addr{ + netip.MustParseAddr("192.168.0.100"), + }, + }, + }, + {in: `nameserver 192.168.0.100#`, + want: OSConfig{ + Nameservers: []netip.Addr{ + netip.MustParseAddr("192.168.0.100"), + }, + }, + }, + {in: `nameserver #192.168.0.100`, wantErr: true}, + {in: `nameserver`, wantErr: true}, + {in: `# nameserver 192.168.0.100`, want: OSConfig{}}, + {in: `nameserver192.168.0.100`, wantErr: true}, + + {in: `search controld.com`, + want: OSConfig{ + SearchDomains: []dnsname.FQDN{"controld.com."}, + }, + }, + {in: `search controld.com # typo`, + want: OSConfig{ + SearchDomains: []dnsname.FQDN{"controld.com."}, + }, + }, + {in: `searchcontrold.com`, wantErr: true}, + {in: `search`, wantErr: true}, + } + + for _, test := range tests { + cfg, err := readResolv(strings.NewReader(test.in)) + if test.wantErr { + c.Assert(err, qt.IsNotNil) + } else { + c.Assert(err, qt.IsNil) + } + c.Assert(cfg, qt.DeepEquals, test.want) + } +} diff --git a/internal/dns/manager_linux.go b/internal/dns/manager_linux.go new file mode 100644 index 0000000..20ccf7e --- /dev/null +++ b/internal/dns/manager_linux.go @@ -0,0 +1,387 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dns + +import ( + "bytes" + "context" + "errors" + "fmt" + "os" + "strings" + "sync" + "time" + + "github.com/godbus/dbus/v5" + "tailscale.com/health" + "tailscale.com/net/netaddr" + "tailscale.com/types/logger" + "tailscale.com/util/clientmetric" + "tailscale.com/util/cmpver" +) + +var _ OSConfigurator = (*directManager)(nil) +var _ OSConfigurator = (*resolvedManager)(nil) +var _ OSConfigurator = (*nmManager)(nil) + +type kv struct { + k, v string +} + +func (kv kv) String() string { + return fmt.Sprintf("%s=%s", kv.k, kv.v) +} + +var publishOnce sync.Once + +func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurator, err error) { + env := newOSConfigEnv{ + fs: directFS{}, + dbusPing: dbusPing, + dbusReadString: dbusReadString, + nmIsUsingResolved: nmIsUsingResolved, + nmVersionBetween: nmVersionBetween, + resolvconfStyle: resolvconfStyle, + } + mode, err := dnsMode(logf, env) + if err != nil { + return nil, err + } + publishOnce.Do(func() { + sanitizedMode := strings.ReplaceAll(mode, "-", "_") + m := clientmetric.NewGauge(fmt.Sprintf("dns_manager_linux_mode_%s", sanitizedMode)) + m.Set(1) + }) + logf("dns: using %q mode", mode) + switch mode { + case "direct": + return newDirectManagerOnFS(logf, env.fs), nil + case "systemd-resolved": + return newResolvedManager(logf, interfaceName) + case "network-manager": + return newNMManager(interfaceName) + default: + logf("[unexpected] detected unknown DNS mode %q, using direct manager as last resort", mode) + return newDirectManagerOnFS(logf, env.fs), nil + } +} + +// newOSConfigEnv are the funcs newOSConfigurator needs, pulled out for testing. +type newOSConfigEnv struct { + fs wholeFileFS + dbusPing func(string, string) error + dbusReadString func(string, string, string, string) (string, error) + nmIsUsingResolved func() error + nmVersionBetween func(v1, v2 string) (safe bool, err error) + resolvconfStyle func() string +} + +func dnsMode(logf logger.Logf, env newOSConfigEnv) (ret string, err error) { + var debug []kv + dbg := func(k, v string) { + debug = append(debug, kv{k, v}) + } + defer func() { + if ret != "" { + dbg("ret", ret) + } + logf("dns: %v", debug) + }() + + // In all cases that we detect systemd-resolved, try asking it what it + // thinks the current resolv.conf mode is so we can add it to our logs. + defer func() { + if ret != "systemd-resolved" { + return + } + + // Try to ask systemd-resolved what it thinks the current + // status of resolv.conf is. This is documented at: + // https://www.freedesktop.org/software/systemd/man/org.freedesktop.resolve1.html + mode, err := env.dbusReadString("org.freedesktop.resolve1", "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", "ResolvConfMode") + if err != nil { + logf("dns: ResolvConfMode error: %v", err) + dbg("resolv-conf-mode", "error") + } else { + dbg("resolv-conf-mode", mode) + } + }() + + // Before we read /etc/resolv.conf (which might be in a broken + // or symlink-dangling state), try to ping the D-Bus service + // for systemd-resolved. If it's active on the machine, this + // will make it start up and write the /etc/resolv.conf file + // before it replies to the ping. (see how systemd's + // src/resolve/resolved.c calls manager_write_resolv_conf + // before the sd_event_loop starts) + resolvedUp := env.dbusPing("org.freedesktop.resolve1", "/org/freedesktop/resolve1") == nil + if resolvedUp { + dbg("resolved-ping", "yes") + } + + bs, err := env.fs.ReadFile(resolvConf) + if os.IsNotExist(err) { + dbg("rc", "missing") + return "direct", nil + } + if err != nil { + return "", fmt.Errorf("reading /etc/resolv.conf: %w", err) + } + + switch resolvOwner(bs) { + case "systemd-resolved": + dbg("rc", "resolved") + + // Some systems, for reasons known only to them, have a + // resolv.conf that has the word "systemd-resolved" in its + // header, but doesn't actually point to resolved. We mustn't + // try to program resolved in that case. + // https://github.com/tailscale/tailscale/issues/2136 + if err := resolvedIsActuallyResolver(bs); err != nil { + logf("dns: resolvedIsActuallyResolver error: %v", err) + dbg("resolved", "not-in-use") + return "direct", nil + } + if err := env.dbusPing("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/DnsManager"); err != nil { + dbg("nm", "no") + return "systemd-resolved", nil + } + dbg("nm", "yes") + if err := env.nmIsUsingResolved(); err != nil { + dbg("nm-resolved", "no") + return "systemd-resolved", nil + } + dbg("nm-resolved", "yes") + + // Version of NetworkManager before 1.26.6 programmed resolved + // incorrectly, such that NM's settings would always take + // precedence over other settings set by other resolved + // clients. + // + // If we're dealing with such a version, we have to set our + // DNS settings through NM to have them take. + // + // However, versions 1.26.6 later both fixed the resolved + // programming issue _and_ started ignoring DNS settings for + // "unmanaged" interfaces - meaning NM 1.26.6 and later + // actively ignore DNS configuration we give it. So, for those + // NM versions, we can and must use resolved directly. + // + // Even more fun, even-older versions of NM won't let us set + // DNS settings if the interface isn't managed by NM, with a + // hard failure on DBus requests. Empirically, NM 1.22 does + // this. Based on the versions popular distros shipped, we + // conservatively decree that only 1.26.0 through 1.26.5 are + // "safe" to use for our purposes. This roughly matches + // distros released in the latter half of 2020. + // + // In a perfect world, we'd avoid this by replacing + // configuration out from under NM entirely (e.g. using + // directManager to overwrite resolv.conf), but in a world + // where resolved runs, we need to get correct configuration + // into resolved regardless of what's in resolv.conf (because + // resolved can also be queried over dbus, or via an NSS + // module that bypasses /etc/resolv.conf). Given that we must + // get correct configuration into resolved, we have no choice + // but to use NM, and accept the loss of IPv6 configuration + // that comes with it (see + // https://github.com/tailscale/tailscale/issues/1699, + // https://github.com/tailscale/tailscale/pull/1945) + safe, err := env.nmVersionBetween("1.26.0", "1.26.5") + if err != nil { + // Failed to figure out NM's version, can't make a correct + // decision. + return "", fmt.Errorf("checking NetworkManager version: %v", err) + } + if safe { + dbg("nm-safe", "yes") + return "network-manager", nil + } + dbg("nm-safe", "no") + return "systemd-resolved", nil + case "resolvconf": + dbg("rc", "resolvconf") + style := env.resolvconfStyle() + switch style { + case "": + dbg("resolvconf", "no") + return "direct", nil + case "debian": + dbg("resolvconf", "debian") + return "debian-resolvconf", nil + case "openresolv": + dbg("resolvconf", "openresolv") + return "openresolv", nil + default: + // Shouldn't happen, that means we updated flavors of + // resolvconf without updating here. + dbg("resolvconf", style) + logf("[unexpected] got unknown flavor of resolvconf %q, falling back to direct manager", env.resolvconfStyle()) + return "direct", nil + } + case "NetworkManager": + dbg("rc", "nm") + // Sometimes, NetworkManager owns the configuration but points + // it at systemd-resolved. + if err := resolvedIsActuallyResolver(bs); err != nil { + logf("dns: resolvedIsActuallyResolver error: %v", err) + dbg("resolved", "not-in-use") + // You'd think we would use newNMManager here. However, as + // explained in + // https://github.com/tailscale/tailscale/issues/1699 , + // using NetworkManager for DNS configuration carries with + // it the cost of losing IPv6 configuration on the + // Tailscale network interface. So, when we can avoid it, + // we bypass NetworkManager by replacing resolv.conf + // directly. + // + // If you ever try to put NMManager back here, keep in mind + // that versions >=1.26.6 will ignore DNS configuration + // anyway, so you still need a fallback path that uses + // directManager. + return "direct", nil + } + dbg("nm-resolved", "yes") + + // See large comment above for reasons we'd use NM rather than + // resolved. systemd-resolved is actually in charge of DNS + // configuration, but in some cases we might need to configure + // it via NetworkManager. All the logic below is probing for + // that case: is NetworkManager running? If so, is it one of + // the versions that requires direct interaction with it? + if err := env.dbusPing("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/DnsManager"); err != nil { + dbg("nm", "no") + return "systemd-resolved", nil + } + safe, err := env.nmVersionBetween("1.26.0", "1.26.5") + if err != nil { + // Failed to figure out NM's version, can't make a correct + // decision. + return "", fmt.Errorf("checking NetworkManager version: %v", err) + } + if safe { + dbg("nm-safe", "yes") + return "network-manager", nil + } + health.SetDNSManagerHealth(errors.New("systemd-resolved and NetworkManager are wired together incorrectly; MagicDNS will probably not work. For more info, see https://tailscale.com/s/resolved-nm")) + dbg("nm-safe", "no") + return "systemd-resolved", nil + default: + dbg("rc", "unknown") + return "direct", nil + } +} + +func nmVersionBetween(first, last string) (bool, error) { + conn, err := dbus.SystemBus() + if err != nil { + // DBus probably not running. + return false, err + } + + nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager")) + v, err := nm.GetProperty("org.freedesktop.NetworkManager.Version") + if err != nil { + return false, err + } + + version, ok := v.Value().(string) + if !ok { + return false, fmt.Errorf("unexpected type %T for NM version", v.Value()) + } + + outside := cmpver.Compare(version, first) < 0 || cmpver.Compare(version, last) > 0 + return !outside, nil +} + +func nmIsUsingResolved() error { + conn, err := dbus.SystemBus() + if err != nil { + // DBus probably not running. + return err + } + + nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager/DnsManager")) + v, err := nm.GetProperty("org.freedesktop.NetworkManager.DnsManager.Mode") + if err != nil { + return fmt.Errorf("getting NM mode: %w", err) + } + mode, ok := v.Value().(string) + if !ok { + return fmt.Errorf("unexpected type %T for NM DNS mode", v.Value()) + } + if mode != "systemd-resolved" { + return errors.New("NetworkManager is not using systemd-resolved for DNS") + } + return nil +} + +// resolvedIsActuallyResolver reports whether the given resolv.conf +// bytes describe a configuration where systemd-resolved (127.0.0.53) +// is the only configured nameserver. +// +// Returns an error if the configuration is something other than +// exclusively systemd-resolved, or nil if the config is only +// systemd-resolved. +func resolvedIsActuallyResolver(bs []byte) error { + cfg, err := readResolv(bytes.NewBuffer(bs)) + if err != nil { + return err + } + // We've encountered at least one system where the line + // "nameserver 127.0.0.53" appears twice, so we look exhaustively + // through all of them and allow any number of repeated mentions + // of the systemd-resolved stub IP. + if len(cfg.Nameservers) == 0 { + return errors.New("resolv.conf has no nameservers") + } + for _, ns := range cfg.Nameservers { + if ns != netaddr.IPv4(127, 0, 0, 53) { + return fmt.Errorf("resolv.conf doesn't point to systemd-resolved; points to %v", cfg.Nameservers) + } + } + return nil +} + +func dbusPing(name, objectPath string) error { + conn, err := dbus.SystemBus() + if err != nil { + // DBus probably not running. + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + obj := conn.Object(name, dbus.ObjectPath(objectPath)) + call := obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0) + return call.Err +} + +// dbusReadString reads a string property from the provided name and object +// path. property must be in "interface.member" notation. +func dbusReadString(name, objectPath, iface, member string) (string, error) { + conn, err := dbus.SystemBus() + if err != nil { + // DBus probably not running. + return "", err + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + obj := conn.Object(name, dbus.ObjectPath(objectPath)) + + var result dbus.Variant + err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, iface, member).Store(&result) + if err != nil { + return "", err + } + + if s, ok := result.Value().(string); ok { + return s, nil + } + return result.String(), nil +} diff --git a/internal/dns/manager_linux_test.go b/internal/dns/manager_linux_test.go new file mode 100644 index 0000000..70a2be4 --- /dev/null +++ b/internal/dns/manager_linux_test.go @@ -0,0 +1,439 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dns + +import ( + "errors" + "io/fs" + "os" + "strings" + "testing" + + "tailscale.com/tstest" + "tailscale.com/util/cmpver" +) + +func TestLinuxDNSMode(t *testing.T) { + tests := []struct { + name string + env newOSConfigEnv + wantLog string + want string + }{ + { + name: "no_obvious_resolv.conf_owner", + env: env(resolvDotConf("nameserver 10.0.0.1")), + wantLog: "dns: [rc=unknown ret=direct]", + want: "direct", + }, + { + name: "network_manager", + env: env( + resolvDotConf( + "# Managed by NetworkManager", + "nameserver 10.0.0.1")), + wantLog: "dns: resolvedIsActuallyResolver error: resolv.conf doesn't point to systemd-resolved; points to [10.0.0.1]\n" + + "dns: [rc=nm resolved=not-in-use ret=direct]", + want: "direct", + }, + { + name: "resolvconf_but_no_resolvconf_binary", + env: env(resolvDotConf("# Managed by resolvconf", "nameserver 10.0.0.1")), + wantLog: "dns: [rc=resolvconf resolvconf=no ret=direct]", + want: "direct", + }, + { + name: "debian_resolvconf", + env: env( + resolvDotConf("# Managed by resolvconf", "nameserver 10.0.0.1"), + resolvconf("debian")), + wantLog: "dns: [rc=resolvconf resolvconf=debian ret=debian-resolvconf]", + want: "debian-resolvconf", + }, + { + name: "openresolv", + env: env( + resolvDotConf("# Managed by resolvconf", "nameserver 10.0.0.1"), + resolvconf("openresolv")), + wantLog: "dns: [rc=resolvconf resolvconf=openresolv ret=openresolv]", + want: "openresolv", + }, + { + name: "unknown_resolvconf_flavor", + env: env( + resolvDotConf("# Managed by resolvconf", "nameserver 10.0.0.1"), + resolvconf("daves-discount-resolvconf")), + wantLog: "[unexpected] got unknown flavor of resolvconf \"daves-discount-resolvconf\", falling back to direct manager\ndns: [rc=resolvconf resolvconf=daves-discount-resolvconf ret=direct]", + want: "direct", + }, + { + name: "resolved_alone_without_ping", + env: env(resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53")), + wantLog: "dns: ResolvConfMode error: dbus property not found\ndns: [rc=resolved nm=no resolv-conf-mode=error ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + name: "resolved_alone_with_ping", + env: env( + resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), + resolvedRunning()), + wantLog: "dns: [resolved-ping=yes rc=resolved nm=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + name: "resolved_and_networkmanager_not_using_resolved", + env: env( + resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), + resolvedRunning(), + nmRunning("1.2.3", false)), + wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + name: "resolved_and_mid_2020_networkmanager", + env: env( + resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), + resolvedRunning(), + nmRunning("1.26.2", true)), + wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=yes nm-safe=yes ret=network-manager]", + want: "network-manager", + }, + { + name: "resolved_and_2021_networkmanager", + env: env( + resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), + resolvedRunning(), + nmRunning("1.27.0", true)), + wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=yes nm-safe=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + name: "resolved_and_ancient_networkmanager", + env: env( + resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), + resolvedRunning(), + nmRunning("1.22.0", true)), + wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=yes nm-safe=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + // Regression tests for extreme corner cases below. + { + // One user reported a configuration whose comment string + // alleged that it was managed by systemd-resolved, but it + // was actually a completely static config file pointing + // elsewhere. + name: "allegedly_resolved_but_not_in_resolv.conf", + env: env(resolvDotConf("# Managed by systemd-resolved", "nameserver 10.0.0.1")), + wantLog: "dns: resolvedIsActuallyResolver error: resolv.conf doesn't point to systemd-resolved; points to [10.0.0.1]\n" + + "dns: [rc=resolved resolved=not-in-use ret=direct]", + want: "direct", + }, + { + // We used to incorrectly decide that resolved wasn't in + // charge when handed this (admittedly weird and bugged) + // resolv.conf. + name: "resolved_with_duplicates_in_resolv.conf", + env: env( + resolvDotConf( + "# Managed by systemd-resolved", + "nameserver 127.0.0.53", + "nameserver 127.0.0.53"), + resolvedRunning()), + wantLog: "dns: [resolved-ping=yes rc=resolved nm=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + // More than one user has had resolvconf write a config that points to + // systemd-resolved. We're better off using systemd-resolved. + // regression test for https://github.com/tailscale/tailscale/issues/3026 + name: "allegedly_resolvconf_but_actually_systemd-resolved", + env: env(resolvDotConf( + "# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)", + "# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN", + "# 127.0.0.53 is the systemd-resolved stub resolver.", + "# run \"systemd-resolve --status\" to see details about the actual nameservers.", + "nameserver 127.0.0.53"), + resolvedRunning()), + wantLog: "dns: [resolved-ping=yes rc=resolved nm=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + // More than one user has had resolvconf write a config that points to + // systemd-resolved. We're better off using systemd-resolved. + // and assuming that even if the ping doesn't show that env is correct + // regression test for https://github.com/tailscale/tailscale/issues/3026 + name: "allegedly_resolvconf_but_actually_systemd-resolved_but_no_ping", + env: env(resolvDotConf( + "# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)", + "# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN", + "# 127.0.0.53 is the systemd-resolved stub resolver.", + "# run \"systemd-resolve --status\" to see details about the actual nameservers.", + "nameserver 127.0.0.53")), + wantLog: "dns: ResolvConfMode error: dbus property not found\ndns: [rc=resolved nm=no resolv-conf-mode=error ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + // regression test for https://github.com/tailscale/tailscale/issues/3304 + name: "networkmanager_but_pointing_at_systemd-resolved", + env: env(resolvDotConf( + "# Generated by NetworkManager", + "nameserver 127.0.0.53", + "options edns0 trust-ad"), + resolvedRunning(), + nmRunning("1.32.12", true)), + wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm-safe=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + // regression test for https://github.com/tailscale/tailscale/issues/3304 + name: "networkmanager_but_pointing_at_systemd-resolved_but_no_resolved_ping", + env: env(resolvDotConf( + "# Generated by NetworkManager", + "nameserver 127.0.0.53", + "options edns0 trust-ad"), + nmRunning("1.32.12", true)), + wantLog: "dns: ResolvConfMode error: dbus property not found\ndns: [rc=nm nm-resolved=yes nm-safe=no resolv-conf-mode=error ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + // regression test for https://github.com/tailscale/tailscale/issues/3304 + name: "networkmanager_but_pointing_at_systemd-resolved_and_safe_nm", + env: env(resolvDotConf( + "# Generated by NetworkManager", + "nameserver 127.0.0.53", + "options edns0 trust-ad"), + resolvedRunning(), + nmRunning("1.26.3", true)), + wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm-safe=yes ret=network-manager]", + want: "network-manager", + }, + { + // regression test for https://github.com/tailscale/tailscale/issues/3304 + name: "networkmanager_but_pointing_at_systemd-resolved_and_no_networkmanager", + env: env(resolvDotConf( + "# Generated by NetworkManager", + "nameserver 127.0.0.53", + "options edns0 trust-ad"), + resolvedRunning()), + wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + // regression test for https://github.com/tailscale/tailscale/issues/3531 + name: "networkmanager_but_systemd-resolved_with_search_domain", + env: env(resolvDotConf( + "# Generated by NetworkManager", + "search lan", + "nameserver 127.0.0.53"), + resolvedRunning()), + wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + // Make sure that we ping systemd-resolved to let it start up and write its resolv.conf + // before we read its file. + env: env(resolvedStartOnPingAndThen( + resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), + resolvedDbusProperty(), + )), + wantLog: "dns: [resolved-ping=yes rc=resolved nm=no resolv-conf-mode=fortests ret=systemd-resolved]", + want: "systemd-resolved", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var logBuf tstest.MemLogger + got, err := dnsMode(logBuf.Logf, tt.env) + if err != nil { + t.Fatal(err) + } + if got != tt.want { + t.Errorf("got %s; want %s", got, tt.want) + } + if got := strings.TrimSpace(logBuf.String()); got != tt.wantLog { + t.Errorf("log output mismatch:\n got: %q\nwant: %q\n", got, tt.wantLog) + } + }) + } +} + +type memFS map[string]any // full path => string for regular files + +func (m memFS) Stat(name string) (isRegular bool, err error) { + v, ok := m[name] + if !ok { + return false, fs.ErrNotExist + } + if _, ok := v.(string); ok { + return true, nil + } + return false, nil +} + +func (m memFS) Rename(_, _ string) error { panic("TODO") } +func (m memFS) Remove(_ string) error { panic("TODO") } +func (m memFS) ReadFile(name string) ([]byte, error) { + v, ok := m[name] + if !ok { + return nil, fs.ErrNotExist + } + if s, ok := v.(string); ok { + return []byte(s), nil + } + panic("TODO") +} + +func (m memFS) Truncate(name string) error { + v, ok := m[name] + if !ok { + return fs.ErrNotExist + } + if s, ok := v.(string); ok { + m[name] = s[:0] + } + + return nil +} + +func (m memFS) WriteFile(name string, contents []byte, _ os.FileMode) error { + m[name] = string(contents) + return nil +} + +type dbusService struct { + name, path string + hook func() // if non-nil, run on ping +} + +type dbusProperty struct { + name, path string + iface, member string + hook func() (string, error) // what to return +} + +type envBuilder struct { + fs memFS + dbus []dbusService + dbusProperties []dbusProperty + nmUsingResolved bool + nmVersion string + resolvconfStyle string +} + +type envOption interface { + apply(*envBuilder) +} + +type envOpt func(*envBuilder) + +func (e envOpt) apply(b *envBuilder) { + e(b) +} + +func env(opts ...envOption) newOSConfigEnv { + b := &envBuilder{ + fs: memFS{}, + } + for _, opt := range opts { + opt.apply(b) + } + + return newOSConfigEnv{ + fs: b.fs, + dbusPing: func(name, path string) error { + for _, svc := range b.dbus { + if svc.name == name && svc.path == path { + if svc.hook != nil { + svc.hook() + } + return nil + } + } + return errors.New("dbus service not found") + }, + dbusReadString: func(name, path, iface, member string) (string, error) { + for _, svc := range b.dbusProperties { + if svc.name == name && svc.path == path && svc.iface == iface && svc.member == member { + return svc.hook() + } + } + return "", errors.New("dbus property not found") + }, + nmIsUsingResolved: func() error { + if !b.nmUsingResolved { + return errors.New("networkmanager not using resolved") + } + return nil + }, + nmVersionBetween: func(first, last string) (bool, error) { + outside := cmpver.Compare(b.nmVersion, first) < 0 || cmpver.Compare(b.nmVersion, last) > 0 + return !outside, nil + }, + resolvconfStyle: func() string { return b.resolvconfStyle }, + } +} + +func resolvDotConf(ss ...string) envOption { + return envOpt(func(b *envBuilder) { + b.fs["/etc/resolv.conf"] = strings.Join(ss, "\n") + }) +} + +// resolvedRunning returns an option that makes resolved reply to a dbusPing +// and the ResolvConfMode property. +func resolvedRunning() envOption { + return resolvedStartOnPingAndThen(resolvedDbusProperty()) +} + +// resolvedDbusProperty returns an option that responds to the ResolvConfMode +// property that resolved exposes. +func resolvedDbusProperty() envOption { + return setDbusProperty("org.freedesktop.resolve1", "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", "ResolvConfMode", "fortests") +} + +// resolvedStartOnPingAndThen returns an option that makes resolved be +// active but not yet running. On a dbus ping, it then applies the +// provided options. +func resolvedStartOnPingAndThen(opts ...envOption) envOption { + return envOpt(func(b *envBuilder) { + b.dbus = append(b.dbus, dbusService{ + name: "org.freedesktop.resolve1", + path: "/org/freedesktop/resolve1", + hook: func() { + for _, opt := range opts { + opt.apply(b) + } + }, + }) + }) +} + +func nmRunning(version string, usingResolved bool) envOption { + return envOpt(func(b *envBuilder) { + b.nmUsingResolved = usingResolved + b.nmVersion = version + b.dbus = append(b.dbus, dbusService{name: "org.freedesktop.NetworkManager", path: "/org/freedesktop/NetworkManager/DnsManager"}) + }) +} + +func resolvconf(s string) envOption { + return envOpt(func(b *envBuilder) { + b.resolvconfStyle = s + }) +} + +func setDbusProperty(name, path, iface, member, value string) envOption { + return envOpt(func(b *envBuilder) { + b.dbusProperties = append(b.dbusProperties, dbusProperty{ + name: name, + path: path, + iface: iface, + member: member, + hook: func() (string, error) { + return value, nil + }, + }) + }) +} diff --git a/internal/dns/nm.go b/internal/dns/nm.go new file mode 100644 index 0000000..68ce71b --- /dev/null +++ b/internal/dns/nm.go @@ -0,0 +1,269 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux + +package dns + +import ( + "context" + "fmt" + "net" + "net/netip" + "time" + + "github.com/godbus/dbus/v5" + "tailscale.com/util/dnsname" + "tailscale.com/util/endian" +) + +const ( + highestPriority = int32(-1 << 31) + mediumPriority = int32(1) // Highest priority that doesn't hard-override + lowerPriority = int32(200) // lower than all builtin auto priorities +) + +// nmManager uses the NetworkManager DBus API. +type nmManager struct { + interfaceName string + manager dbus.BusObject + dnsManager dbus.BusObject +} + +func newNMManager(interfaceName string) (*nmManager, error) { + conn, err := dbus.SystemBus() + if err != nil { + return nil, err + } + + return &nmManager{ + interfaceName: interfaceName, + manager: conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager")), + dnsManager: conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager/DnsManager")), + }, nil +} + +type nmConnectionSettings map[string]map[string]dbus.Variant + +func (m *nmManager) SetDNS(config OSConfig) error { + ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout) + defer cancel() + + // NetworkManager only lets you set DNS settings on "active" + // connections, which requires an assigned IP address. This got + // configured before the DNS manager was invoked, but it might + // take a little time for the netlink notifications to propagate + // up. So, keep retrying for the duration of the reconfigTimeout. + var err error + for ctx.Err() == nil { + err = m.trySet(ctx, config) + if err == nil { + break + } + time.Sleep(10 * time.Millisecond) + } + + return err +} + +func (m *nmManager) trySet(ctx context.Context, config OSConfig) error { + conn, err := dbus.SystemBus() + if err != nil { + return fmt.Errorf("connecting to system bus: %w", err) + } + + // This is how we get at the DNS settings: + // + // org.freedesktop.NetworkManager + // | + // [GetDeviceByIpIface] + // | + // v + // org.freedesktop.NetworkManager.Device <--------\ + // (describes a network interface) | + // | | + // [GetAppliedConnection] [Reapply] + // | | + // v | + // org.freedesktop.NetworkManager.Connection | + // (connection settings) ------/ + // contains {dns, dns-priority, dns-search} + // + // Ref: https://developer.gnome.org/NetworkManager/stable/settings-ipv4.html. + + nm := conn.Object( + "org.freedesktop.NetworkManager", + dbus.ObjectPath("/org/freedesktop/NetworkManager"), + ) + + var devicePath dbus.ObjectPath + err = nm.CallWithContext( + ctx, "org.freedesktop.NetworkManager.GetDeviceByIpIface", 0, + m.interfaceName, + ).Store(&devicePath) + if err != nil { + return fmt.Errorf("getDeviceByIpIface: %w", err) + } + device := conn.Object("org.freedesktop.NetworkManager", devicePath) + + var ( + settings nmConnectionSettings + version uint64 + ) + err = device.CallWithContext( + ctx, "org.freedesktop.NetworkManager.Device.GetAppliedConnection", 0, + uint32(0), + ).Store(&settings, &version) + if err != nil { + return fmt.Errorf("getAppliedConnection: %w", err) + } + + // Frustratingly, NetworkManager represents IPv4 addresses as uint32s, + // although IPv6 addresses are represented as byte arrays. + // Perform the conversion here. + var ( + dnsv4 []uint32 + dnsv6 [][]byte + ) + for _, ip := range config.Nameservers { + b := ip.As16() + if ip.Is4() { + dnsv4 = append(dnsv4, endian.Native.Uint32(b[12:])) + } else { + dnsv6 = append(dnsv6, b[:]) + } + } + + // NetworkManager wipes out IPv6 address configuration unless we + // tell it explicitly to keep it. Read out the current interface + // settings and mirror them out to NetworkManager. + var addrs6 []map[string]any + if netIface, err := net.InterfaceByName(m.interfaceName); err == nil { + if addrs, err := netIface.Addrs(); err == nil { + for _, a := range addrs { + if ipnet, ok := a.(*net.IPNet); ok { + nip, ok := netip.AddrFromSlice(ipnet.IP) + nip = nip.Unmap() + if ok && nip.Is6() { + addrs6 = append(addrs6, map[string]any{ + "address": nip.String(), + "prefix": uint32(128), + }) + } + } + } + } + } + + seen := map[dnsname.FQDN]bool{} + var search []string + for _, dom := range config.SearchDomains { + if seen[dom] { + continue + } + seen[dom] = true + search = append(search, dom.WithTrailingDot()) + } + for _, dom := range config.MatchDomains { + if seen[dom] { + continue + } + seen[dom] = true + search = append(search, "~"+dom.WithTrailingDot()) + } + if len(config.MatchDomains) == 0 { + // Non-split routing requested, add an all-domains match. + search = append(search, "~.") + } + + // Ideally we would like to disable LLMNR and mdns on the + // interface here, but older NetworkManagers don't understand + // those settings and choke on them, so we don't. Both LLMNR and + // mdns will fail since tailscale0 doesn't do multicast, so it's + // effectively fine. We used to try and enforce LLMNR and mdns + // settings here, but that led to #1870. + + ipv4Map := settings["ipv4"] + ipv4Map["dns"] = dbus.MakeVariant(dnsv4) + ipv4Map["dns-search"] = dbus.MakeVariant(search) + // We should only request priority if we have nameservers to set. + if len(dnsv4) == 0 { + ipv4Map["dns-priority"] = dbus.MakeVariant(lowerPriority) + } else if len(config.MatchDomains) > 0 { + // Set a fairly high priority, but don't override all other + // configs when in split-DNS mode. + ipv4Map["dns-priority"] = dbus.MakeVariant(mediumPriority) + } else { + // Negative priority means only the settings from the most + // negative connection get used. The way this mixes with + // per-domain routing is unclear, but it _seems_ that the + // priority applies after routing has found possible + // candidates for a resolution. + ipv4Map["dns-priority"] = dbus.MakeVariant(highestPriority) + } + + ipv6Map := settings["ipv6"] + // In IPv6 settings, you're only allowed to provide additional + // static DNS settings in "auto" (SLAAC) or "manual" mode. In + // "manual" mode you also have to specify IP addresses, so we use + // "auto". + // + // NM actually documents that to set just DNS servers, you should + // use "auto" mode and then set ignore auto routes and DNS, which + // basically means "autoconfigure but ignore any autoconfiguration + // results you might get". As a safety, we also say that + // NetworkManager should never try to make us the default route + // (none of its business anyway, we handle our own default + // routing). + ipv6Map["method"] = dbus.MakeVariant("auto") + if len(addrs6) > 0 { + ipv6Map["address-data"] = dbus.MakeVariant(addrs6) + } + ipv6Map["ignore-auto-routes"] = dbus.MakeVariant(true) + ipv6Map["ignore-auto-dns"] = dbus.MakeVariant(true) + ipv6Map["never-default"] = dbus.MakeVariant(true) + + ipv6Map["dns"] = dbus.MakeVariant(dnsv6) + ipv6Map["dns-search"] = dbus.MakeVariant(search) + if len(dnsv6) == 0 { + ipv6Map["dns-priority"] = dbus.MakeVariant(lowerPriority) + } else if len(config.MatchDomains) > 0 { + // Set a fairly high priority, but don't override all other + // configs when in split-DNS mode. + ipv6Map["dns-priority"] = dbus.MakeVariant(mediumPriority) + } else { + ipv6Map["dns-priority"] = dbus.MakeVariant(highestPriority) + } + + // deprecatedProperties are the properties in interface settings + // that are deprecated by NetworkManager. + // + // In practice, this means that they are returned for reading, + // but submitting a settings object with them present fails + // with hard-to-diagnose errors. They must be removed. + deprecatedProperties := []string{ + "addresses", "routes", + } + + for _, property := range deprecatedProperties { + delete(ipv4Map, property) + delete(ipv6Map, property) + } + + if call := device.CallWithContext(ctx, "org.freedesktop.NetworkManager.Device.Reapply", 0, settings, version, uint32(0)); call.Err != nil { + return fmt.Errorf("reapply: %w", call.Err) + } + + return nil +} + +func (m *nmManager) Close() error { + // No need to do anything on close, NetworkManager will delete our + // settings when the tailscale interface goes away. + return nil +} + +func (m *nmManager) Mode() string { + return "network-maanger" +} diff --git a/internal/dns/osconfig.go b/internal/dns/osconfig.go new file mode 100644 index 0000000..0f5e91d --- /dev/null +++ b/internal/dns/osconfig.go @@ -0,0 +1,124 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dns + +import ( + "bufio" + "fmt" + "net/netip" + + "tailscale.com/types/logger" + "tailscale.com/util/dnsname" +) + +// An OSConfigurator applies DNS settings to the operating system. +type OSConfigurator interface { + // SetDNS updates the OS's DNS configuration to match cfg. + // If cfg is the zero value, all ctrld-related DNS + // configuration is removed. + // SetDNS must not be called after Close. + // SetDNS takes ownership of cfg. + SetDNS(cfg OSConfig) error + + // Close removes ctrld-related DNS configuration from the OS. + Close() error + + Mode() string +} + +// HostEntry represents a single line in the OS's hosts file. +type HostEntry struct { + Addr netip.Addr + Hosts []string +} + +// OSConfig is an OS DNS configuration. +type OSConfig struct { + // Hosts is a map of DNS FQDNs to their IPs, which should be added to the + // OS's hosts file. Currently, (2022-08-12) it is only populated for Windows + // in SplitDNS mode and with Smart Name Resolution turned on. + Hosts []*HostEntry + // Nameservers are the IP addresses of the nameservers to use. + Nameservers []netip.Addr + // SearchDomains are the domain suffixes to use when expanding + // single-label name queries. SearchDomains is additive to + // whatever non-Tailscale search domains the OS has. + SearchDomains []dnsname.FQDN + // MatchDomains are the DNS suffixes for which Nameservers should + // be used. If empty, Nameservers is installed as the "primary" resolver. + MatchDomains []dnsname.FQDN +} + +func (o OSConfig) IsZero() bool { + return len(o.Nameservers) == 0 && len(o.SearchDomains) == 0 && len(o.MatchDomains) == 0 +} + +func (a OSConfig) Equal(b OSConfig) bool { + if len(a.Nameservers) != len(b.Nameservers) { + return false + } + if len(a.SearchDomains) != len(b.SearchDomains) { + return false + } + if len(a.MatchDomains) != len(b.MatchDomains) { + return false + } + + for i := range a.Nameservers { + if a.Nameservers[i] != b.Nameservers[i] { + return false + } + } + for i := range a.SearchDomains { + if a.SearchDomains[i] != b.SearchDomains[i] { + return false + } + } + for i := range a.MatchDomains { + if a.MatchDomains[i] != b.MatchDomains[i] { + return false + } + } + + return true +} + +// Format implements the fmt.Formatter interface to ensure that Hosts is +// printed correctly (i.e. not as a bunch of pointers). +// +// Fixes https://github.com/tailscale/tailscale/issues/5669 +func (a OSConfig) Format(f fmt.State, verb rune) { + logger.ArgWriter(func(w *bufio.Writer) { + _, _ = w.WriteString(`{Nameservers:[`) + for i, ns := range a.Nameservers { + if i != 0 { + _, _ = w.WriteString(" ") + } + _, _ = fmt.Fprintf(w, "%+v", ns) + } + _, _ = w.WriteString(`] SearchDomains:[`) + for i, domain := range a.SearchDomains { + if i != 0 { + _, _ = w.WriteString(" ") + } + _, _ = fmt.Fprintf(w, "%+v", domain) + } + _, _ = w.WriteString(`] MatchDomains:[`) + for i, domain := range a.MatchDomains { + if i != 0 { + _, _ = w.WriteString(" ") + } + _, _ = fmt.Fprintf(w, "%+v", domain) + } + _, _ = w.WriteString(`] Hosts:[`) + for i, host := range a.Hosts { + if i != 0 { + _, _ = w.WriteString(" ") + } + _, _ = fmt.Fprintf(w, "%+v", host) + } + _, _ = w.WriteString(`]}`) + }).Format(f, verb) +} diff --git a/internal/dns/osconfig_test.go b/internal/dns/osconfig_test.go new file mode 100644 index 0000000..24ec35b --- /dev/null +++ b/internal/dns/osconfig_test.go @@ -0,0 +1,44 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dns + +import ( + "fmt" + "net/netip" + "testing" + + "tailscale.com/util/dnsname" +) + +func TestOSConfigPrintable(t *testing.T) { + ocfg := OSConfig{ + Hosts: []*HostEntry{ + { + Addr: netip.AddrFrom4([4]byte{100, 1, 2, 3}), + Hosts: []string{"server", "client"}, + }, + { + Addr: netip.AddrFrom4([4]byte{100, 1, 2, 4}), + Hosts: []string{"otherhost"}, + }, + }, + Nameservers: []netip.Addr{ + netip.AddrFrom4([4]byte{8, 8, 8, 8}), + }, + SearchDomains: []dnsname.FQDN{ + dnsname.FQDN("foo.beta.controld.com."), + dnsname.FQDN("bar.beta.controld.com."), + }, + MatchDomains: []dnsname.FQDN{ + dnsname.FQDN("controld.com."), + }, + } + s := fmt.Sprintf("%+v", ocfg) + + const expected = `{Nameservers:[8.8.8.8] SearchDomains:[foo.beta.controld.com. bar.beta.controld.com.] MatchDomains:[controld.com.] Hosts:[&{Addr:100.1.2.3 Hosts:[server client]} &{Addr:100.1.2.4 Hosts:[otherhost]}]}` + if s != expected { + t.Errorf("format mismatch:\n got: %s\n want: %s", s, expected) + } +} diff --git a/internal/dns/resolvconf.go b/internal/dns/resolvconf.go new file mode 100644 index 0000000..b317b3b --- /dev/null +++ b/internal/dns/resolvconf.go @@ -0,0 +1,26 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux || freebsd || openbsd + +package dns + +import ( + "os/exec" +) + +func resolvconfStyle() string { + if _, err := exec.LookPath("resolvconf"); err != nil { + return "" + } + if _, err := exec.Command("resolvconf", "--version").CombinedOutput(); err != nil { + // Debian resolvconf doesn't understand --version, and + // exits with a specific error code. + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 99 { + return "debian" + } + } + // Treat everything else as openresolv, by far the more popular implementation. + return "openresolv" +} diff --git a/internal/dns/resolvconffile/resolvconffile.go b/internal/dns/resolvconffile/resolvconffile.go new file mode 100644 index 0000000..5572891 --- /dev/null +++ b/internal/dns/resolvconffile/resolvconffile.go @@ -0,0 +1,119 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package resolvconffile parses & serializes /etc/resolv.conf-style files. +package resolvconffile + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net/netip" + "os" + "strings" + + "tailscale.com/util/dnsname" + "tailscale.com/util/strs" +) + +// Path is the canonical location of resolv.conf. +const Path = "/etc/resolv.conf" + +// Config represents a resolv.conf(5) file. +type Config struct { + // Nameservers are the IP addresses of the nameservers to use. + Nameservers []netip.Addr + + // SearchDomains are the domain suffixes to use when expanding + // single-label name queries. SearchDomains is additive to + // whatever non-Tailscale search domains the OS has. + SearchDomains []dnsname.FQDN +} + +// Write writes c to w. It does so in one Write call. +func (c *Config) Write(w io.Writer) error { + buf := new(bytes.Buffer) + io.WriteString(buf, "# resolv.conf(5) file generated by ctrld\n") + io.WriteString(buf, "# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN\n\n") + for _, ns := range c.Nameservers { + io.WriteString(buf, "nameserver ") + io.WriteString(buf, ns.String()) + io.WriteString(buf, "\n") + } + if len(c.SearchDomains) > 0 { + io.WriteString(buf, "search") + for _, domain := range c.SearchDomains { + io.WriteString(buf, " ") + io.WriteString(buf, domain.WithoutTrailingDot()) + } + io.WriteString(buf, "\n") + } + _, err := w.Write(buf.Bytes()) + return err +} + +// Parse parses a resolv.conf file from r. +func Parse(r io.Reader) (*Config, error) { + config := new(Config) + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + line, _, _ = strings.Cut(line, "#") // remove any comments + line = strings.TrimSpace(line) + + if s, ok := strs.CutPrefix(line, "nameserver"); ok { + nameserver := strings.TrimSpace(s) + if len(nameserver) == len(s) { + return nil, fmt.Errorf("missing space after \"nameserver\" in %q", line) + } + ip, err := netip.ParseAddr(nameserver) + if err != nil { + return nil, err + } + config.Nameservers = append(config.Nameservers, ip) + continue + } + + if s, ok := strs.CutPrefix(line, "search"); ok { + domains := strings.TrimSpace(s) + if len(domains) == len(s) { + // No leading space?! + return nil, fmt.Errorf("missing space after \"search\" in %q", line) + } + for len(domains) > 0 { + domain := domains + i := strings.IndexAny(domain, " \t") + if i != -1 { + domain = domain[:i] + domains = strings.TrimSpace(domains[i+1:]) + } else { + domains = "" + } + fqdn, err := dnsname.ToFQDN(domain) + if err != nil { + return nil, fmt.Errorf("parsing search domain %q in %q: %w", domain, line, err) + } + config.SearchDomains = append(config.SearchDomains, fqdn) + } + } + } + return config, nil +} + +// ParseFile parses the named resolv.conf file. +func ParseFile(name string) (*Config, error) { + fi, err := os.Stat(name) + if err != nil { + return nil, err + } + if n := fi.Size(); n > 10<<10 { + return nil, fmt.Errorf("unexpectedly large %q file: %d bytes", name, n) + } + all, err := os.ReadFile(name) + if err != nil { + return nil, err + } + return Parse(bytes.NewReader(all)) +} diff --git a/internal/dns/resolvconffile/resolvconffile_test.go b/internal/dns/resolvconffile/resolvconffile_test.go new file mode 100644 index 0000000..e5b5cc6 --- /dev/null +++ b/internal/dns/resolvconffile/resolvconffile_test.go @@ -0,0 +1,104 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package resolvconffile + +import ( + "net/netip" + "reflect" + "strings" + "testing" + + "tailscale.com/util/dnsname" +) + +func TestParse(t *testing.T) { + tests := []struct { + in string + want *Config + wantErr bool + }{ + {in: `nameserver 192.168.0.100`, + want: &Config{ + Nameservers: []netip.Addr{ + netip.MustParseAddr("192.168.0.100"), + }, + }, + }, + {in: `nameserver 192.168.0.100 # comment`, + want: &Config{ + Nameservers: []netip.Addr{ + netip.MustParseAddr("192.168.0.100"), + }, + }, + }, + {in: `nameserver 192.168.0.100#`, + want: &Config{ + Nameservers: []netip.Addr{ + netip.MustParseAddr("192.168.0.100"), + }, + }, + }, + {in: `nameserver #192.168.0.100`, wantErr: true}, + {in: `nameserver`, wantErr: true}, + {in: `# nameserver 192.168.0.100`, want: &Config{}}, + {in: `nameserver192.168.0.100`, wantErr: true}, + + {in: `search tailsacle.com`, + want: &Config{ + SearchDomains: []dnsname.FQDN{"tailsacle.com."}, + }, + }, + {in: `search tailsacle.com # typo`, + want: &Config{ + SearchDomains: []dnsname.FQDN{"tailsacle.com."}, + }, + }, + {in: `searchtailsacle.com`, wantErr: true}, + {in: `search`, wantErr: true}, + + // Issue 6875: there can be multiple search domains, and even if they're + // over 253 bytes long total. + { + in: "search search-01.example search-02.example search-03.example search-04.example search-05.example search-06.example search-07.example search-08.example search-09.example search-10.example search-11.example search-12.example search-13.example search-14.example search-15.example\n", + want: &Config{ + SearchDomains: []dnsname.FQDN{ + "search-01.example.", + "search-02.example.", + "search-03.example.", + "search-04.example.", + "search-05.example.", + "search-06.example.", + "search-07.example.", + "search-08.example.", + "search-09.example.", + "search-10.example.", + "search-11.example.", + "search-12.example.", + "search-13.example.", + "search-14.example.", + "search-15.example.", + }, + }, + }, + } + + for _, tt := range tests { + cfg, err := Parse(strings.NewReader(tt.in)) + if tt.wantErr { + if err != nil { + continue + } + t.Errorf("missing error for %q", tt.in) + continue + } + if err != nil { + t.Errorf("unexpected error for %q: %v", tt.in, err) + continue + } + if !reflect.DeepEqual(cfg, tt.want) { + t.Errorf("got: %v\nwant: %v\n", cfg, tt.want) + } + } +} diff --git a/internal/dns/resolved.go b/internal/dns/resolved.go new file mode 100644 index 0000000..6c0b1de --- /dev/null +++ b/internal/dns/resolved.go @@ -0,0 +1,389 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux + +package dns + +import ( + "context" + "fmt" + "net" + "strings" + "time" + + "github.com/godbus/dbus/v5" + "golang.org/x/sys/unix" + "tailscale.com/health" + "tailscale.com/logtail/backoff" + "tailscale.com/types/logger" + "tailscale.com/util/dnsname" +) + +const reconfigTimeout = time.Second + +// DBus entities we talk to. +// +// DBus is an RPC bus. In particular, the bus we're talking to is the +// system-wide bus (there is also a per-user session bus for +// user-specific applications). +// +// Daemons connect to the bus, and advertise themselves under a +// well-known object name. That object exposes paths, and each path +// implements one or more interfaces that contain methods, properties, +// and signals. +// +// Clients connect to the bus and walk that same hierarchy to invoke +// RPCs, get/set properties, or listen for signals. +const ( + dbusResolvedObject = "org.freedesktop.resolve1" + dbusResolvedPath dbus.ObjectPath = "/org/freedesktop/resolve1" + dbusResolvedInterface = "org.freedesktop.resolve1.Manager" + dbusPath dbus.ObjectPath = "/org/freedesktop/DBus" + dbusInterface = "org.freedesktop.DBus" + dbusOwnerSignal = "NameOwnerChanged" // broadcast when a well-known name's owning process changes. +) + +type resolvedLinkNameserver struct { + Family int32 + Address []byte +} + +type resolvedLinkDomain struct { + Domain string + RoutingOnly bool +} + +// changeRequest tracks latest OSConfig and related error responses to update. +type changeRequest struct { + config OSConfig // configs OSConfigs, one per each SetDNS call + res chan<- error // response channel +} + +// resolvedManager is an OSConfigurator which uses the systemd-resolved DBus API. +type resolvedManager struct { + ctx context.Context + cancel func() // terminate the context, for close + + logf logger.Logf + ifidx int + + configCR chan changeRequest // tracks OSConfigs changes and error responses +} + +func newResolvedManager(logf logger.Logf, interfaceName string) (*resolvedManager, error) { + iface, err := net.InterfaceByName(interfaceName) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithCancel(context.Background()) + logf = logger.WithPrefix(logf, "dns: ") + + mgr := &resolvedManager{ + ctx: ctx, + cancel: cancel, + + logf: logf, + ifidx: iface.Index, + + configCR: make(chan changeRequest), + } + + go mgr.run(ctx) + + return mgr, nil +} + +func (m *resolvedManager) SetDNS(config OSConfig) error { + errc := make(chan error, 1) + defer close(errc) + + select { + case <-m.ctx.Done(): + return m.ctx.Err() + case m.configCR <- changeRequest{config, errc}: + } + + select { + case <-m.ctx.Done(): + return m.ctx.Err() + case err := <-errc: + if err != nil { + m.logf("failed to configure resolved: %v", err) + } + return err + } +} + +func (m *resolvedManager) run(ctx context.Context) { + var ( + conn *dbus.Conn + signals chan *dbus.Signal + rManager dbus.BusObject // rManager is the Resolved DBus connection + ) + bo := backoff.NewBackoff("resolved-dbus", m.logf, 30*time.Second) + needsReconnect := make(chan bool, 1) + defer func() { + if conn != nil { + _ = conn.Close() + } + }() + + // Reconnect the systemBus if disconnected. + reconnect := func() error { + var err error + signals = make(chan *dbus.Signal, 16) + conn, err = dbus.SystemBus() + if err != nil { + m.logf("dbus connection error: %v", err) + } else { + m.logf("[v1] dbus connected") + } + + if err != nil { + // Backoff increases time between reconnect attempts. + go func() { + bo.BackOff(ctx, err) + needsReconnect <- true + }() + return err + } + + rManager = conn.Object(dbusResolvedObject, dbus.ObjectPath(dbusResolvedPath)) + + // Only receive the DBus signals we need to resync our config on + // resolved restart. Failure to set filters isn't a fatal error, + // we'll just receive all broadcast signals and have to ignore + // them on our end. + if err = conn.AddMatchSignal(dbus.WithMatchObjectPath(dbusPath), dbus.WithMatchInterface(dbusInterface), dbus.WithMatchMember(dbusOwnerSignal), dbus.WithMatchArg(0, dbusResolvedObject)); err != nil { + m.logf("[v1] Setting DBus signal filter failed: %v", err) + } + conn.Signal(signals) + + // Reset backoff and SetNSOSHealth after successful on reconnect. + bo.BackOff(ctx, nil) + health.SetDNSOSHealth(nil) + return nil + } + + // Create initial systemBus connection. + _ = reconnect() + + lastConfig := OSConfig{} + + for { + select { + case <-ctx.Done(): + if rManager == nil { + return + } + // RevertLink resets all per-interface settings on systemd-resolved to defaults. + // When ctx goes away systemd-resolved auto reverts. + // Keeping for potential use in future refactor. + if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".RevertLink", 0, m.ifidx); call.Err != nil { + m.logf("[v1] RevertLink: %v", call.Err) + return + } + return + case configCR := <-m.configCR: + // Track and update sync with latest config change. + lastConfig = configCR.config + + if rManager == nil { + configCR.res <- fmt.Errorf("resolved DBus does not have a connection") + continue + } + err := m.setConfigOverDBus(ctx, rManager, configCR.config) + configCR.res <- err + case <-needsReconnect: + if err := reconnect(); err != nil { + m.logf("[v1] SystemBus reconnect error %T", err) + } + continue + case signal, ok := <-signals: + // If signal ends and is nil then program tries to reconnect. + if !ok { + if err := reconnect(); err != nil { + m.logf("[v1] SystemBus reconnect error %T", err) + } + continue + } + // In theory the signal was filtered by DBus, but if + // AddMatchSignal in the constructor failed, we may be + // getting other spam. + if signal.Path != dbusPath || signal.Name != dbusInterface+"."+dbusOwnerSignal { + continue + } + if lastConfig.IsZero() { + continue + } + // signal.Body is a []any of 3 strings: bus name, previous owner, new owner. + if len(signal.Body) != 3 { + m.logf("[unexpected] DBus NameOwnerChanged len(Body) = %d, want 3") + } + if name, ok := signal.Body[0].(string); !ok || name != dbusResolvedObject { + continue + } + newOwner, ok := signal.Body[2].(string) + if !ok { + m.logf("[unexpected] DBus NameOwnerChanged.new_owner is a %T, not a string", signal.Body[2]) + } + if newOwner == "" { + // systemd-resolved left the bus, no current owner, + // nothing to do. + continue + } + // The resolved bus name has a new owner, meaning resolved + // restarted. Reprogram current config. + m.logf("systemd-resolved restarted, syncing DNS config") + err := m.setConfigOverDBus(ctx, rManager, lastConfig) + // Set health while holding the lock, because this will + // graciously serialize the resync's health outcome with a + // concurrent SetDNS call. + health.SetDNSOSHealth(err) + if err != nil { + m.logf("failed to configure systemd-resolved: %v", err) + } + } + } +} + +// setConfigOverDBus updates resolved DBus config and is only called from the run goroutine. +func (m *resolvedManager) setConfigOverDBus(ctx context.Context, rManager dbus.BusObject, config OSConfig) error { + ctx, cancel := context.WithTimeout(ctx, reconfigTimeout) + defer cancel() + + var linkNameservers = make([]resolvedLinkNameserver, len(config.Nameservers)) + for i, server := range config.Nameservers { + ip := server.As16() + if server.Is4() { + linkNameservers[i] = resolvedLinkNameserver{ + Family: unix.AF_INET, + Address: ip[12:], + } + } else { + linkNameservers[i] = resolvedLinkNameserver{ + Family: unix.AF_INET6, + Address: ip[:], + } + } + } + err := rManager.CallWithContext( + ctx, dbusResolvedInterface+".SetLinkDNS", 0, + m.ifidx, linkNameservers, + ).Store() + if err != nil { + return fmt.Errorf("setLinkDNS: %w", err) + } + linkDomains := make([]resolvedLinkDomain, 0, len(config.SearchDomains)+len(config.MatchDomains)) + seenDomains := map[dnsname.FQDN]bool{} + for _, domain := range config.SearchDomains { + if seenDomains[domain] { + continue + } + seenDomains[domain] = true + linkDomains = append(linkDomains, resolvedLinkDomain{ + Domain: domain.WithTrailingDot(), + RoutingOnly: false, + }) + } + for _, domain := range config.MatchDomains { + if seenDomains[domain] { + // Search domains act as both search and match in + // resolved, so it's correct to skip. + continue + } + seenDomains[domain] = true + linkDomains = append(linkDomains, resolvedLinkDomain{ + Domain: domain.WithTrailingDot(), + RoutingOnly: true, + }) + } + if len(config.MatchDomains) == 0 && len(config.Nameservers) > 0 { + // Caller requested full DNS interception, install a + // routing-only root domain. + linkDomains = append(linkDomains, resolvedLinkDomain{ + Domain: ".", + RoutingOnly: true, + }) + } + + err = rManager.CallWithContext( + ctx, dbusResolvedInterface+".SetLinkDomains", 0, + m.ifidx, linkDomains, + ).Store() + if err != nil && err.Error() == "Argument list too long" { // TODO: better error match + // Issue 3188: older systemd-resolved had argument length limits. + // Trim out the *.arpa. entries and try again. + err = rManager.CallWithContext( + ctx, dbusResolvedInterface+".SetLinkDomains", 0, + m.ifidx, linkDomainsWithoutReverseDNS(linkDomains), + ).Store() + } + if err != nil { + return fmt.Errorf("setLinkDomains: %w", err) + } + + if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDefaultRoute", 0, m.ifidx, len(config.MatchDomains) == 0); call.Err != nil { + if dbusErr, ok := call.Err.(dbus.Error); ok && dbusErr.Name == dbus.ErrMsgUnknownMethod.Name { + // on some older systems like Kubuntu 18.04.6 with systemd 237 method SetLinkDefaultRoute is absent, + // but otherwise it's working good + m.logf("[v1] failed to set SetLinkDefaultRoute: %v", call.Err) + } else { + return fmt.Errorf("setLinkDefaultRoute: %w", call.Err) + } + } + + // Some best-effort setting of things, but resolved should do the + // right thing if these fail (e.g. a really old resolved version + // or something). + + // Disable LLMNR, we don't do multicast. + if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkLLMNR", 0, m.ifidx, "no"); call.Err != nil { + m.logf("[v1] failed to disable LLMNR: %v", call.Err) + } + + // Disable mdns. + if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkMulticastDNS", 0, m.ifidx, "no"); call.Err != nil { + m.logf("[v1] failed to disable mdns: %v", call.Err) + } + + // We don't support dnssec consistently right now, force it off to + // avoid partial failures when we split DNS internally. + if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSSEC", 0, m.ifidx, "no"); call.Err != nil { + m.logf("[v1] failed to disable DNSSEC: %v", call.Err) + } + + if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSOverTLS", 0, m.ifidx, "no"); call.Err != nil { + m.logf("[v1] failed to disable DoT: %v", call.Err) + } + + if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".FlushCaches", 0); call.Err != nil { + m.logf("failed to flush resolved DNS cache: %v", call.Err) + } + return nil +} + +func (m *resolvedManager) Close() error { + m.cancel() // stops the 'run' method goroutine + return nil +} + +func (m *resolvedManager) Mode() string { + return "systemd-resolved" +} + +// linkDomainsWithoutReverseDNS returns a copy of v without +// *.arpa. entries. +func linkDomainsWithoutReverseDNS(v []resolvedLinkDomain) (ret []resolvedLinkDomain) { + for _, d := range v { + if strings.HasSuffix(d.Domain, ".arpa.") { + // Oh well. At least the rest will work. + continue + } + ret = append(ret, d) + } + return ret +} From beca95d5b90a8184b05c1fbc1552b12551c7896e Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 3 Feb 2023 20:32:12 +0700 Subject: [PATCH 65/69] cmd/ctrld: fix systemd dependencies config See https://github.com/systemd/systemd/issues/22360 --- cmd/ctrld/cli.go | 6 +----- cmd/ctrld/prog_linux.go | 9 +++++++++ cmd/ctrld/prog_others.go | 4 ++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 805db1a..9f97f3b 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -186,11 +186,7 @@ func initCLI() { if os.Args[1] == "service" { osArgs = os.Args[3:] } - if runtime.GOOS == "linux" { - sc.Dependencies = []string{ - "After=NetworkManager-wait-online.service", - } - } + setDependencies(sc) sc.Arguments = append([]string{"run"}, osArgs...) if dir, err := os.UserHomeDir(); err == nil { // WorkingDirectory is not supported on Windows. diff --git a/cmd/ctrld/prog_linux.go b/cmd/ctrld/prog_linux.go index d1d5ee2..7d4f87a 100644 --- a/cmd/ctrld/prog_linux.go +++ b/cmd/ctrld/prog_linux.go @@ -9,3 +9,12 @@ func (p *prog) preRun() { p.setDNS() } } + +func setDependencies(svc *service.Config) { + svc.Dependencies = []string{ + "Wants=network-online.target", + "After=network-online.target", + "Wants=NetworkManager-wait-online.service", + "After=NetworkManager-wait-online.service", + } +} diff --git a/cmd/ctrld/prog_others.go b/cmd/ctrld/prog_others.go index 10310d5..9d72f91 100644 --- a/cmd/ctrld/prog_others.go +++ b/cmd/ctrld/prog_others.go @@ -3,4 +3,8 @@ package main +import "github.com/kardianos/service" + func (p *prog) preRun() {} + +func setDependencies(svc *service.Config) {} From 318fec27de17ee6a8df7f848ee1c1b01d375a1f7 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 3 Feb 2023 22:44:27 +0700 Subject: [PATCH 66/69] cmd/ctrld: fatal loudly if listen failed For address already in use error when listening, we have a workaround to spawn a new listener on different port. However, if that case does not match, we must fatal to notice the error to user. --- cmd/ctrld/prog.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index d0de5f8..b8e22bd 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -119,6 +119,9 @@ func (p *prog) run() { proxyLog.Fatal().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum) return } + if err == nil { + return + } if opErr, ok := err.(*net.OpError); ok { if sErr, ok := opErr.Err.(*os.SyscallError); ok && errors.Is(opErr.Err, syscall.EADDRINUSE) || errors.Is(sErr.Err, errWindowsAddrInUse) { @@ -159,6 +162,7 @@ func (p *prog) run() { } } } + proxyLog.Fatal().Err(err).Msgf("Unable to start dns proxy on listener.%s", listenerNum) }(listenerNum) } From d3fe2c730ccde31f8acb006a884cb864ce2603db Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 3 Feb 2023 23:50:49 +0700 Subject: [PATCH 67/69] cmd/ctrld: surpress backoff logging message --- cmd/ctrld/net.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go index e74c9a9..595f03f 100644 --- a/cmd/ctrld/net.go +++ b/cmd/ctrld/net.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" "net" "sync" "time" @@ -24,10 +23,7 @@ var ( ) func probeStack() { - logf := func(format string, args ...any) { - fmt.Printf(format, args...) - } - b := backoff.NewBackoff("probeStack", logf, time.Minute) + b := backoff.NewBackoff("probeStack", func(format string, args ...any) {}, time.Minute) for { if _, err := controld.Dialer.Dial("udp", net.JoinHostPort(bootstrapDNS, "53")); err == nil { hasNetworkUp = true From 8571580aae8fb1e5653d26371f8f79b593634c2c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Sat, 4 Feb 2023 12:37:08 +0700 Subject: [PATCH 68/69] cmd/ctrld: fatal if failed to get default iface name So it left a chance for system service manager to bring up ctrld for us. Without default iface name, ctrld could not work properly anyway. --- cmd/ctrld/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index 9f97f3b..f0ff3eb 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -662,7 +662,7 @@ func netInterface(ifaceName string) (*net.Interface, error) { func defaultIfaceName() string { dri, err := interfaces.DefaultRouteInterface() if err != nil { - mainLog.Error().Err(err).Msg("failed to get default route interface") + mainLog.Fatal().Err(err).Msg("failed to get default route interface") } return dri } From 0e09b45bca2005eeb23eb536ec87afb7dc7e1453 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 20 Jan 2023 21:44:03 +0700 Subject: [PATCH 69/69] cmd/ctrld: bump version to v1.1.0 --- cmd/ctrld/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index f0ff3eb..df96bde 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -60,7 +60,7 @@ func initCLI() { rootCmd := &cobra.Command{ Use: "ctrld", Short: strings.TrimLeft(rootShortDesc, "\n"), - Version: "1.0.1", + Version: "1.1.0", } rootCmd.PersistentFlags().CountVarP( &verbose,