diff --git a/config.go b/config.go index 000cbffd..83bd1764 100644 --- a/config.go +++ b/config.go @@ -61,6 +61,7 @@ type dnsConfig struct { } var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"} +var defaultBootstrap = []string{"1.1.1.1"} type tlsConfigSettings struct { Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DOT/DOH/HTTPS) status @@ -114,8 +115,8 @@ var config = configuration{ QueryLogEnabled: true, Ratelimit: 20, RefuseAny: true, + BootstrapDNS: defaultBootstrap, AllServers: false, - BootstrapDNS: "8.8.8.8:53", }, UpstreamDNS: defaultDNS, }, diff --git a/control.go b/control.go index d57114ce..81befc50 100644 --- a/control.go +++ b/control.go @@ -312,6 +312,16 @@ func handleSetUpstreamConfig(w http.ResponseWriter, r *http.Request) { func handleSetUpstreamDNS(w http.ResponseWriter, r *http.Request) { log.Tracef("%s %v", r.Method, r.URL) + setDNSServers(w, r, true) +} + +func handleSetBootstrapDNS(w http.ResponseWriter, r *http.Request) { + log.Tracef("%s %v", r.Method, r.URL) + setDNSServers(w, r, false) +} + +// setDNSServers sets upstream and bootstrap DNS servers +func setDNSServers(w http.ResponseWriter, r *http.Request, upstreams bool) { body, err := ioutil.ReadAll(r.Body) if err != nil { httpError(w, http.StatusBadRequest, "Failed to read request body: %s", err) @@ -320,10 +330,35 @@ func handleSetUpstreamDNS(w http.ResponseWriter, r *http.Request) { // if empty body -- user is asking for default servers hosts := strings.Fields(string(body)) - if len(hosts) == 0 { + // bootstrap servers are plain DNS only. We should remove tls:// https:// and sdns:// hosts from slice + bootstraps := []string{} + if !upstreams && len(hosts) > 0 { + for _, host := range hosts { + err = checkBootstrapDNS(host) + if err != nil { + log.Tracef("%s can not be used as bootstrap DNS cause: %s", host, err) + continue + } + hosts = append(bootstraps, host) + } + } + + // count of upstream or bootstrap servers + count := len(hosts) + if !upstreams { + count = len(bootstraps) + } + + if upstreams { config.DNS.UpstreamDNS = defaultDNS + if count != 0 { + config.DNS.UpstreamDNS = hosts + } } else { - config.DNS.UpstreamDNS = hosts + config.DNS.BootstrapDNS = defaultBootstrap + if count != 0 { + config.DNS.BootstrapDNS = bootstraps + } } err = writeAllConfigs() @@ -336,7 +371,7 @@ func handleSetUpstreamDNS(w http.ResponseWriter, r *http.Request) { httpError(w, http.StatusInternalServerError, "Couldn't reconfigure the DNS server: %s", err) return } - _, err = fmt.Fprintf(w, "OK %d servers\n", len(hosts)) + _, err = fmt.Fprintf(w, "OK %d servers\n", count) if err != nil { httpError(w, http.StatusInternalServerError, "Couldn't write body: %s", err) } @@ -373,6 +408,18 @@ func handleAllServersStatus(w http.ResponseWriter, r *http.Request) { } } +// checkBootstrapDNS checks if host is plain DNS +func checkBootstrapDNS(host string) error { + // Check if host is ip without port + if net.ParseIP(host) != nil { + return nil + } + + // Check if host is ip with port + _, _, err := net.SplitHostPort(host) + return err +} + func handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) { log.Tracef("%s %v", r.Method, r.URL) body, err := ioutil.ReadAll(r.Body) @@ -1299,6 +1346,7 @@ func registerControlHandlers() { http.HandleFunc("/control/set_upstream_dns", postInstall(optionalAuth(ensurePOST(handleSetUpstreamDNS)))) http.HandleFunc("/control/set_upstreams_config", postInstall(optionalAuth(ensurePOST(handleSetUpstreamConfig)))) http.HandleFunc("/control/test_upstream_dns", postInstall(optionalAuth(ensurePOST(handleTestUpstreamDNS)))) + http.HandleFunc("/control/set_bootstrap_dns", postInstall(optionalAuth(ensurePOST(handleSetBootstrapDNS)))) http.HandleFunc("/control/all_servers/enable", postInstall(optionalAuth(ensurePOST(handleAllServersEnable)))) http.HandleFunc("/control/all_servers/disable", postInstall(optionalAuth(ensurePOST(handleAllServersDisable)))) http.HandleFunc("/control/all_servers/status", postInstall(optionalAuth(ensureGET(handleAllServersStatus)))) diff --git a/dns.go b/dns.go index 5503d871..56fae956 100644 --- a/dns.go +++ b/dns.go @@ -61,7 +61,7 @@ func generateServerConfig() dnsforward.ServerConfig { for _, u := range config.DNS.UpstreamDNS { opts := upstream.Options{ Timeout: dnsforward.DefaultTimeout, - Bootstrap: []string{config.DNS.BootstrapDNS}, + Bootstrap: config.DNS.BootstrapDNS, } dnsUpstream, err := upstream.AddressToUpstream(u, opts) if err != nil { diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index 57ca8130..4f910bc8 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -66,7 +66,7 @@ type FilteringConfig struct { Ratelimit int `yaml:"ratelimit"` RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` RefuseAny bool `yaml:"refuse_any"` - BootstrapDNS string `yaml:"bootstrap_dns"` + BootstrapDNS []string `yaml:"bootstrap_dns"` AllServers bool `yaml:"all_servers"` dnsfilter.Config `yaml:",inline"` diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index f0fb68ee..ed788794 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -194,6 +194,30 @@ paths: 8.8.4.4: OK "192.168.1.104:53535": "Couldn't communicate with DNS server" + /set_bootstrap_dns: + post: + tags: + - global + operationId: setBootstrapDNS + summary: 'Set bootstrap DNS for DNS-over-HTTPS and DNS-over-TLS upstreams, empty value will reset it to default values' + consumes: + - text/plain + parameters: + - in: body + name: upstream + description: 'Bootstrap servers, separated by newline or space, port is optional after colon' + schema: + # TODO: use JSON + type: string + example: | + 1.1.1.1 + 1.0.0.1 + 8.8.8.8 8.8.4.4 + 192.168.1.104:53535 + responses: + 200: + description: OK + /version.json: get: tags: diff --git a/upgrade.go b/upgrade.go index c988700a..0c9aeca7 100644 --- a/upgrade.go +++ b/upgrade.go @@ -10,7 +10,7 @@ import ( yaml "gopkg.in/yaml.v2" ) -const currentSchemaVersion = 2 // used for upgrading from old configs to new config +const currentSchemaVersion = 3 // used for upgrading from old configs to new config // Performs necessary upgrade operations if needed func upgradeConfig() error { @@ -59,12 +59,17 @@ func upgradeConfig() error { func upgradeConfigSchema(oldVersion int, diskConfig *map[string]interface{}) error { switch oldVersion { case 0: - err := upgradeSchema0to2(diskConfig) + err := upgradeSchema0to3(diskConfig) if err != nil { return err } case 1: - err := upgradeSchema1to2(diskConfig) + err := upgradeSchema1to3(diskConfig) + if err != nil { + return err + } + case 2: + err := upgradeSchema2to3(diskConfig) if err != nil { return err } @@ -135,12 +140,60 @@ func upgradeSchema1to2(diskConfig *map[string]interface{}) error { return nil } -// jump two schemas at once -- this time we just do it sequentially -func upgradeSchema0to2(diskConfig *map[string]interface{}) error { +// Third schema upgrade: +// Bootstrap DNS becomes an array +func upgradeSchema2to3(diskConfig *map[string]interface{}) error { + log.Printf("%s(): called", _Func()) + + // Let's read dns configuration from diskConfig + dnsConfig, ok := (*diskConfig)["dns"] + if !ok { + return fmt.Errorf("no DNS configuration in config file") + } + + // Convert interface{} to map[string]interface{} + newDNSConfig := make(map[string]interface{}) + + switch v := dnsConfig.(type) { + case map[interface{}]interface{}: + for k, v := range v { + newDNSConfig[fmt.Sprint(k)] = v + } + default: + return fmt.Errorf("DNS configuration is not a map") + } + + // Replace bootstrap_dns value filed with new array contains old bootstrap_dns inside + if bootstrapDNS, ok := (newDNSConfig)["bootstrap_dns"]; ok { + newBootstrapConfig := []string{fmt.Sprint(bootstrapDNS)} + (newDNSConfig)["bootstrap_dns"] = newBootstrapConfig + (*diskConfig)["dns"] = newDNSConfig + } else { + return fmt.Errorf("no bootstrap DNS in DNS config") + } + + // Bump schema version + (*diskConfig)["schema_version"] = 3 + + return nil +} + +// jump three schemas at once -- this time we just do it sequentially +func upgradeSchema0to3(diskConfig *map[string]interface{}) error { err := upgradeSchema0to1(diskConfig) if err != nil { return err } - return upgradeSchema1to2(diskConfig) + return upgradeSchema1to3(diskConfig) +} + +// jump two schemas at once -- this time we just do it sequentially +func upgradeSchema1to3(diskConfig *map[string]interface{}) error { + err := upgradeSchema1to2(diskConfig) + if err != nil { + return err + } + + return upgradeSchema2to3(diskConfig) }