diff --git a/cmd/ctrld/cli.go b/cmd/ctrld/cli.go index b9d3f3e..27e009c 100644 --- a/cmd/ctrld/cli.go +++ b/cmd/ctrld/cli.go @@ -36,8 +36,6 @@ import ( "github.com/Control-D-Inc/ctrld/internal/router" ) -const selfCheckFQDN = "verify.controld.com" - var ( version = "dev" commit = "none" @@ -289,6 +287,10 @@ func initCLI() { processCDFlags() + if err := ctrld.ValidateConfig(validator.New(), &cfg); err != nil { + mainLog.Fatal().Msgf("invalid config: %v", err) + } + // Explicitly passing config, so on system where home directory could not be obtained, // or sub-process env is different with the parent, we still behave correctly and use // the expected config file. @@ -320,7 +322,8 @@ func initCLI() { return } - status = selfCheckStatus(status) + domain := cfg.Upstream["0"].VerifyDomain() + status = selfCheckStatus(status, domain) switch status { case service.StatusRunning: mainLog.Notice().Msg("Service started") @@ -855,7 +858,11 @@ func defaultIfaceName() string { return dri } -func selfCheckStatus(status service.Status) service.Status { +func selfCheckStatus(status service.Status, domain string) service.Status { + if domain == "" { + // Nothing to do, return the status as-is. + return status + } c := new(dns.Client) bo := backoff.NewBackoff("self-check", logf, 10*time.Second) bo.LogLongerThan = 500 * time.Millisecond @@ -883,16 +890,16 @@ func selfCheckStatus(status service.Status) service.Status { } mu.Unlock() m := new(dns.Msg) - m.SetQuestion(selfCheckFQDN+".", dns.TypeA) + m.SetQuestion(domain+".", dns.TypeA) m.RecursionDesired = true r, _, err := c.ExchangeContext(ctx, m, net.JoinHostPort(lc.IP, strconv.Itoa(lc.Port))) if r != nil && r.Rcode == dns.RcodeSuccess && len(r.Answer) > 0 { - mainLog.Debug().Msgf("self-check against %q succeeded", selfCheckFQDN) + mainLog.Debug().Msgf("self-check against %q succeeded", domain) return status } bo.BackOff(ctx, fmt.Errorf("ExchangeContext: %w", err)) } - mainLog.Debug().Msgf("self-check against %q failed", selfCheckFQDN) + mainLog.Debug().Msgf("self-check against %q failed", domain) return service.StatusUnknown } diff --git a/cmd/ctrld/dns_proxy_test.go b/cmd/ctrld/dns_proxy_test.go index c9ff9d9..c0e8443 100644 --- a/cmd/ctrld/dns_proxy_test.go +++ b/cmd/ctrld/dns_proxy_test.go @@ -174,7 +174,7 @@ func Test_macFromMsg(t *testing.T) { t.Fatal(err) } m := new(dns.Msg) - m.SetQuestion(selfCheckFQDN+".", dns.TypeA) + m.SetQuestion("example.com.", dns.TypeA) o := &dns.OPT{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT}} if tc.wantMac { ec1 := &dns.EDNS0_LOCAL{Code: EDNS0_OPTION_MAC, Data: hw} diff --git a/config.go b/config.go index b06f09f..5f37998 100644 --- a/config.go +++ b/config.go @@ -29,9 +29,19 @@ const ( IpStackV4 = "v4" IpStackV6 = "v6" IpStackSplit = "split" + + controlDComDomain = "controld.com" + controlDNetDomain = "controld.net" + controlDDevDomain = "controld.dev" ) -var controldParentDomains = []string{"controld.com", "controld.net", "controld.dev"} +var ( + controldParentDomains = []string{controlDComDomain, controlDNetDomain, controlDDevDomain} + controldVerifiedDomain = map[string]string{ + controlDComDomain: "verify.controld.com", + controlDDevDomain: "verify.controld.dev", + } +) // SetConfigName set the config name that ctrld will look for. // DEPRECATED: use SetConfigNameWithPath instead. @@ -201,6 +211,23 @@ func (uc *UpstreamConfig) Init() { } } +// VerifyDomain returns the domain name that could be resolved by the upstream endpoint. +// It returns empty for non-ControlD upstream endpoint. +func (uc *UpstreamConfig) VerifyDomain() string { + domain := uc.Domain + if domain == "" { + if u, err := url.Parse(uc.Endpoint); err == nil { + domain = u.Hostname() + } + } + for _, parent := range controldParentDomains { + if dns.IsSubDomain(parent, domain) { + return controldVerifiedDomain[parent] + } + } + return "" +} + // UpstreamSendClientInfo reports whether the upstream is // configured to send client info to Control D DNS server. // diff --git a/config_internal_test.go b/config_internal_test.go index bf310f9..fb3692e 100644 --- a/config_internal_test.go +++ b/config_internal_test.go @@ -190,6 +190,39 @@ func TestUpstreamConfig_Init(t *testing.T) { } } +func TestUpstreamConfig_VerifyDomain(t *testing.T) { + tests := []struct { + name string + uc *UpstreamConfig + verifyDomain string + }{ + { + controlDComDomain, + &UpstreamConfig{Endpoint: "https://freedns.controld.com/p2"}, + controldVerifiedDomain[controlDComDomain], + }, + { + controlDDevDomain, + &UpstreamConfig{Endpoint: "https://freedns.controld.dev/p2"}, + controldVerifiedDomain[controlDDevDomain], + }, + { + "non-ControlD upstream", + &UpstreamConfig{Endpoint: "https://dns.google/dns-query"}, + "", + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if got := tc.uc.VerifyDomain(); got != tc.verifyDomain { + t.Errorf("unexpected verify domain, want: %q, got: %q", tc.verifyDomain, got) + } + }) + } +} func ptrBool(b bool) *bool { return &b }