diff --git a/doh-server/config.go b/doh-server/config.go index 796f300..8fb6c76 100644 --- a/doh-server/config.go +++ b/doh-server/config.go @@ -31,19 +31,21 @@ import ( ) type config struct { - Listen []string `toml:"listen"` - LocalAddr string `toml:"local_addr"` - Cert string `toml:"cert"` - Key string `toml:"key"` - Path string `toml:"path"` - Upstream []string `toml:"upstream"` - Timeout uint `toml:"timeout"` - Tries uint `toml:"tries"` - Verbose bool `toml:"verbose"` - DebugHTTPHeaders []string `toml:"debug_http_headers"` - LogGuessedIP bool `toml:"log_guessed_client_ip"` - LocalIPFilter bool `toml:"ecs_allow_non_global_ip"` - ECSFullSubnet bool `toml:"ecs_use_precise_ip"` + Listen []string `toml:"listen"` + LocalAddr string `toml:"local_addr"` + Cert string `toml:"cert"` + Key string `toml:"key"` + Path string `toml:"path"` + Upstream []string `toml:"upstream"` + Timeout uint `toml:"timeout"` + Tries uint `toml:"tries"` + Verbose bool `toml:"verbose"` + DebugHTTPHeaders []string `toml:"debug_http_headers"` + LogGuessedIP bool `toml:"log_guessed_client_ip"` + ECSAllowNonGlobalIP bool `toml:"ecs_allow_non_global_ip"` + ECSUsePreciseIP bool `toml:"ecs_use_precise_ip"` + TLSClientAuth bool `toml:"tls_client_auth"` + TLSClientAuthCA string `toml:"tls_client_auth_ca"` } func loadConfig(path string) (*config, error) { diff --git a/doh-server/doh-server.conf b/doh-server/doh-server.conf index c6c7955..dcdaa84 100644 --- a/doh-server/doh-server.conf +++ b/doh-server/doh-server.conf @@ -57,9 +57,10 @@ log_guessed_client_ip = false # 1. the upstream server knowing your private LAN addresses; # 2. the upstream server unable to provide geographically near results, # or even fail to provide any result. -# However, if you are deploying a split tunnel corporation network environment, -# or for any other reason you want to inhibit this behavior, change the following -# option to "true". +# However, if you are deploying a split tunnel corporation network +# environment, or for any other reason you want to inhibit this +# behavior and allow local (eg RFC1918) address to be forwarded, +# change the following option to "true". ecs_allow_non_global_ip = false # If ECS is added to the request, let the full IP address or @@ -69,3 +70,9 @@ ecs_allow_non_global_ip = false # internet where IP address may be used to identify the user and # not only the approximate location. ecs_use_precise_ip = false + +# If DOH is used for a controlled network, it is possible to enable +# the client TLS certificate validation with a specific certificate +# authority used to sign any client one. Disabled by default. +# tls_client_auth = true +# tls_client_auth_ca = "root-ca-public.crt" diff --git a/doh-server/ietf.go b/doh-server/ietf.go index 497ff27..7d9f2d1 100644 --- a/doh-server/ietf.go +++ b/doh-server/ietf.go @@ -134,7 +134,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r if ipv4 := ednsClientAddress.To4(); ipv4 != nil { ednsClientFamily = 1 ednsClientAddress = ipv4 - if s.conf.ECSFullSubnet { + if s.conf.ECSUsePreciseIP { ednsClientNetmask = 32 } else { ednsClientNetmask = 24 @@ -142,7 +142,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r } } else { ednsClientFamily = 2 - if s.conf.ECSFullSubnet { + if s.conf.ECSUsePreciseIP { ednsClientNetmask = 128 } else { ednsClientNetmask = 56 diff --git a/doh-server/server.go b/doh-server/server.go index ecf01b9..4c11e2d 100644 --- a/doh-server/server.go +++ b/doh-server/server.go @@ -25,7 +25,10 @@ package main import ( "context" + "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" "log" "math/rand" "net" @@ -107,12 +110,48 @@ func (s *Server) Start() error { if s.conf.Verbose { servemux = handlers.CombinedLoggingHandler(os.Stdout, servemux) } + + var clientCAPool *x509.CertPool + if s.conf.TLSClientAuth { + if s.conf.TLSClientAuthCA != "" { + clientCA, err := ioutil.ReadFile(s.conf.TLSClientAuthCA) + if err != nil { + log.Fatalf("Reading certificate for client authentication has failed: %v", err) + } + clientCAPool = x509.NewCertPool() + clientCAPool.AppendCertsFromPEM(clientCA) + log.Println("Certificate loaded for client TLS authentication") + } else { + log.Fatalln("TLS client authentication requires both tls_client_auth and tls_client_auth_ca, exiting.") + } + } + results := make(chan error, len(s.conf.Listen)) for _, addr := range s.conf.Listen { go func(addr string) { var err error if s.conf.Cert != "" || s.conf.Key != "" { - err = http.ListenAndServeTLS(addr, s.conf.Cert, s.conf.Key, servemux) + if clientCAPool != nil { + srvtls := &http.Server{ + Handler: servemux, + Addr: addr, + TLSConfig: &tls.Config{ + ClientCAs: clientCAPool, + ClientAuth: tls.RequireAndVerifyClientCert, + GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) { + c, err := tls.LoadX509KeyPair(s.conf.Cert, s.conf.Key) + if err != nil { + fmt.Printf("Error loading server certificate key pair: %v\n", err) + return nil, err + } + return &c, nil + }, + }, + } + err = srvtls.ListenAndServeTLS("", "") + } else { + err = http.ListenAndServeTLS(addr, s.conf.Cert, s.conf.Key, servemux) + } } else { err = http.ListenAndServe(addr, servemux) } @@ -265,7 +304,7 @@ func (s *Server) findClientIP(r *http.Request) net.IP { if XRealIP != "" { addr := strings.TrimSpace(XRealIP) ip := net.ParseIP(addr) - if !s.conf.LocalIPFilter || jsondns.IsGlobalIP(ip) { + if s.conf.ECSAllowNonGlobalIP || jsondns.IsGlobalIP(ip) { return ip } } @@ -274,13 +313,10 @@ func (s *Server) findClientIP(r *http.Request) net.IP { if err != nil { return nil } - if !s.conf.LocalIPFilter { - return remoteAddr.IP - } - if ip := remoteAddr.IP; jsondns.IsGlobalIP(ip) { + ip := remoteAddr.IP + if s.conf.ECSAllowNonGlobalIP || jsondns.IsGlobalIP(ip) { return ip } - return nil }