+ dnsforward: refactor

+ dnsforward: own HTTP handlers
* dnsforward: no DNS reload on ProtectionEnabled setting change
* dnsforward: move QueryLog* settings out
* dnsforward: move dnsfilter settings out
* clients,i18n: no DNS reload on settings change
This commit is contained in:
Simon Zolin
2019-10-30 11:52:58 +03:00
parent 73d17ffa81
commit 7bb32eae3d
10 changed files with 399 additions and 325 deletions

View File

@@ -3,6 +3,7 @@ package dnsforward
import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"runtime"
@@ -50,6 +51,8 @@ type Server struct {
DisallowedClientsIPNet []net.IPNet // CIDRs of clients that should be blocked
BlockedHosts map[string]bool // hosts that should be blocked
webRegistered bool
sync.RWMutex
conf ServerConfig
}
@@ -64,6 +67,7 @@ func NewServer(dnsFilter *dnsfilter.Dnsfilter, stats stats.Stats, queryLog query
return s
}
// Close - close object
func (s *Server) Close() {
s.Lock()
s.dnsFilter = nil
@@ -72,20 +76,23 @@ func (s *Server) Close() {
s.Unlock()
}
// WriteDiskConfig - write configuration
func (s *Server) WriteDiskConfig(c *FilteringConfig) {
s.Lock()
*c = s.conf.FilteringConfig
s.Unlock()
}
// FilteringConfig represents the DNS filtering configuration of AdGuard Home
// The zero FilteringConfig is empty and ready for use.
type FilteringConfig struct {
// Filtering callback function
FilterHandler func(clientAddr string, settings *dnsfilter.RequestFilteringSettings) `yaml:"-"`
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours)
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features
BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests
BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600)
QueryLogEnabled bool `yaml:"querylog_enabled"` // if true, query log is enabled
QueryLogInterval uint32 `yaml:"querylog_interval"` // time interval for query log (in days)
Ratelimit int `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable)
RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` // a list of whitelisted client IP addresses
RefuseAny bool `yaml:"refuse_any"` // if true, refuse ANY requests
@@ -104,9 +111,8 @@ type FilteringConfig struct {
// Per-client settings can override this configuration.
BlockedServices []string `yaml:"blocked_services"`
CacheSize uint `yaml:"cache_size"` // DNS cache size (in bytes)
DnsfilterConf dnsfilter.Config `yaml:",inline"`
CacheSize uint `yaml:"cache_size"` // DNS cache size (in bytes)
UpstreamDNS []string `yaml:"upstream_dns"`
}
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
@@ -133,6 +139,12 @@ type ServerConfig struct {
FilteringConfig
TLSConfig
// Called when the configuration is changed by HTTP request
ConfigModified func()
// Register an HTTP handler
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request))
}
// if any of ServerConfig values are zero, then default values from below are used
@@ -198,6 +210,13 @@ func (s *Server) startInternal(config *ServerConfig) error {
if config != nil {
s.conf = *config
upstreamConfig, err := proxy.ParseUpstreamsConfig(s.conf.UpstreamDNS, s.conf.BootstrapDNS, DefaultTimeout)
if err != nil {
return fmt.Errorf("DNS: proxy.ParseUpstreamsConfig: %s", err)
}
s.conf.Upstreams = upstreamConfig.Upstreams
s.conf.DomainsReservedUpstreams = upstreamConfig.DomainReservedUpstreams
}
if len(s.conf.ParentalBlockHost) == 0 {
s.conf.ParentalBlockHost = parentalBlockHost
@@ -257,6 +276,11 @@ func (s *Server) startInternal(config *ServerConfig) error {
proxyConfig.Upstreams = defaultValues.Upstreams
}
if !s.webRegistered && s.conf.HTTPRegister != nil {
s.webRegistered = true
s.registerHandlers()
}
// Initialize and start the DNS proxy
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
return s.dnsProxy.Start()
@@ -293,7 +317,28 @@ func (s *Server) IsRunning() bool {
return isRunning
}
// Reconfigure applies the new configuration to the DNS server
// Reconfigure2 - safely apply and write new configuration and restart
func (s *Server) Reconfigure2(newconf FilteringConfig) error {
s.Lock()
s.conf.FilteringConfig = newconf
s.Unlock()
s.conf.ConfigModified()
s.Lock()
defer s.Unlock()
log.Print("Start reconfiguring the server")
err := s.stopInternal()
if err != nil {
return errorx.Decorate(err, "could not reconfigure the server")
}
err = s.startInternal(nil)
if err != nil {
return errorx.Decorate(err, "could not reconfigure the server")
}
return nil
}
func (s *Server) Reconfigure(config *ServerConfig) error {
s.Lock()
defer s.Unlock()

View File

@@ -0,0 +1,286 @@
package dnsforward
import (
"encoding/json"
"fmt"
"net"
"net/http"
"runtime"
"strconv"
"strings"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/utils"
"github.com/miekg/dns"
)
var defaultDNS = []string{
"https://1.1.1.1/dns-query",
"https://1.0.0.1/dns-query",
}
var defaultBootstrap = []string{"1.1.1.1", "1.0.0.1"}
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
text := fmt.Sprintf(format, args...)
log.Info("DNS: %s %s: %s", r.Method, r.URL, text)
http.Error(w, text, code)
}
func (s *Server) handleProtectionEnable(w http.ResponseWriter, r *http.Request) {
s.conf.ProtectionEnabled = true
s.conf.ConfigModified()
}
func (s *Server) handleProtectionDisable(w http.ResponseWriter, r *http.Request) {
s.conf.ProtectionEnabled = false
s.conf.ConfigModified()
}
type upstreamJSON struct {
Upstreams []string `json:"upstream_dns"` // Upstreams
BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS
AllServers bool `json:"all_servers"` // --all-servers param for dnsproxy
}
func (s *Server) handleSetUpstreamConfig(w http.ResponseWriter, r *http.Request) {
req := upstreamJSON{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
httpError(r, w, http.StatusBadRequest, "Failed to parse new upstreams config json: %s", err)
return
}
err = validateUpstreams(req.Upstreams)
if err != nil {
httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err)
return
}
newconf := FilteringConfig{}
newconf.UpstreamDNS = defaultDNS
if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
// Use plain DNS on MIPS, encryption is too slow
newconf.UpstreamDNS = []string{"1.1.1.1", "1.0.0.1"}
}
if len(req.Upstreams) != 0 {
newconf.UpstreamDNS = req.Upstreams
}
// bootstrap servers are plain DNS only
for _, host := range req.BootstrapDNS {
if err := checkPlainDNS(host); err != nil {
httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", host, err)
return
}
}
newconf.BootstrapDNS = defaultBootstrap
if len(req.BootstrapDNS) != 0 {
newconf.BootstrapDNS = req.BootstrapDNS
}
newconf.AllServers = req.AllServers
err = s.Reconfigure2(newconf)
if err != nil {
httpError(r, w, http.StatusInternalServerError, "%s", err)
return
}
}
// validateUpstreams validates each upstream and returns an error if any upstream is invalid or if there are no default upstreams specified
func validateUpstreams(upstreams []string) error {
var defaultUpstreamFound bool
for _, u := range upstreams {
d, err := validateUpstream(u)
if err != nil {
return err
}
// Check this flag until default upstream will not be found
if !defaultUpstreamFound {
defaultUpstreamFound = d
}
}
// Return error if there are no default upstreams
if !defaultUpstreamFound {
return fmt.Errorf("no default upstreams specified")
}
return nil
}
var protocols = []string{"tls://", "https://", "tcp://", "sdns://"}
func validateUpstream(u string) (bool, error) {
// Check if user tries to specify upstream for domain
u, defaultUpstream, err := separateUpstream(u)
if err != nil {
return defaultUpstream, err
}
// The special server address '#' means "use the default servers"
if u == "#" && !defaultUpstream {
return defaultUpstream, nil
}
// Check if the upstream has a valid protocol prefix
for _, proto := range protocols {
if strings.HasPrefix(u, proto) {
return defaultUpstream, nil
}
}
// Return error if the upstream contains '://' without any valid protocol
if strings.Contains(u, "://") {
return defaultUpstream, fmt.Errorf("wrong protocol")
}
// Check if upstream is valid plain DNS
return defaultUpstream, checkPlainDNS(u)
}
// separateUpstream returns upstream without specified domains and a bool flag that indicates if no domains were specified
// error will be returned if upstream per domain specification is invalid
func separateUpstream(upstream string) (string, bool, error) {
defaultUpstream := true
if strings.HasPrefix(upstream, "[/") {
defaultUpstream = false
// split domains and upstream string
domainsAndUpstream := strings.Split(strings.TrimPrefix(upstream, "[/"), "/]")
if len(domainsAndUpstream) != 2 {
return "", defaultUpstream, fmt.Errorf("wrong DNS upstream per domain specification: %s", upstream)
}
// split domains list and validate each one
for _, host := range strings.Split(domainsAndUpstream[0], "/") {
if host != "" {
if err := utils.IsValidHostname(host); err != nil {
return "", defaultUpstream, err
}
}
}
upstream = domainsAndUpstream[1]
}
return upstream, defaultUpstream, nil
}
// checkPlainDNS checks if host is plain DNS
func checkPlainDNS(upstream string) error {
// Check if host is ip without port
if net.ParseIP(upstream) != nil {
return nil
}
// Check if host is ip with port
ip, port, err := net.SplitHostPort(upstream)
if err != nil {
return err
}
if net.ParseIP(ip) == nil {
return fmt.Errorf("%s is not a valid IP", ip)
}
_, err = strconv.ParseInt(port, 0, 64)
if err != nil {
return fmt.Errorf("%s is not a valid port: %s", port, err)
}
return nil
}
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
req := upstreamJSON{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
httpError(r, w, http.StatusBadRequest, "Failed to read request body: %s", err)
return
}
if len(req.Upstreams) == 0 {
httpError(r, w, http.StatusBadRequest, "No servers specified")
return
}
result := map[string]string{}
for _, host := range req.Upstreams {
err = checkDNS(host, req.BootstrapDNS)
if err != nil {
log.Info("%v", err)
result[host] = err.Error()
} else {
result[host] = "OK"
}
}
jsonVal, err := json.Marshal(result)
if err != nil {
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonVal)
if err != nil {
httpError(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
return
}
}
func checkDNS(input string, bootstrap []string) error {
// separate upstream from domains list
input, defaultUpstream, err := separateUpstream(input)
if err != nil {
return fmt.Errorf("wrong upstream format: %s", err)
}
// No need to check this entrance
if input == "#" && !defaultUpstream {
return nil
}
if _, err := validateUpstream(input); err != nil {
return fmt.Errorf("wrong upstream format: %s", err)
}
if len(bootstrap) == 0 {
bootstrap = defaultBootstrap
}
log.Debug("Checking if DNS %s works...", input)
u, err := upstream.AddressToUpstream(input, upstream.Options{Bootstrap: bootstrap, Timeout: DefaultTimeout})
if err != nil {
return fmt.Errorf("failed to choose upstream for %s: %s", input, err)
}
req := dns.Msg{}
req.Id = dns.Id()
req.RecursionDesired = true
req.Question = []dns.Question{
{Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
}
reply, err := u.Exchange(&req)
if err != nil {
return fmt.Errorf("couldn't communicate with DNS server %s: %s", input, err)
}
if len(reply.Answer) != 1 {
return fmt.Errorf("DNS server %s returned wrong answer", input)
}
if t, ok := reply.Answer[0].(*dns.A); ok {
if !net.IPv4(8, 8, 8, 8).Equal(t.A) {
return fmt.Errorf("DNS server %s returned wrong answer: %v", input, t.A)
}
}
log.Debug("DNS %s works OK", input)
return nil
}
func (s *Server) registerHandlers() {
s.conf.HTTPRegister("POST", "/control/enable_protection", s.handleProtectionEnable)
s.conf.HTTPRegister("POST", "/control/disable_protection", s.handleProtectionDisable)
s.conf.HTTPRegister("POST", "/control/set_upstreams_config", s.handleSetUpstreamConfig)
s.conf.HTTPRegister("POST", "/control/test_upstream_dns", s.handleTestUpstreamDNS)
}