mirror of
https://github.com/AdguardTeam/dnsproxy.git
synced 2026-10-12 02:01:26 +00:00
Pull request: AGDNS-2848-imp-http-conf
Merge in GO/dnsproxy from AGDNS-2848-imp-http-conf to master Squashed commit of the following: commit daa12cc5676e5f130646595c437961fedbdc684e Author: Dimitry Kolyshev <[email protected]> Date: Tue Mar 10 16:31:45 2026 +0700 all: imp code commit 30141c38e5f286927eae9bf261af87bf5060a3fd Author: Dimitry Kolyshev <[email protected]> Date: Tue Mar 10 16:28:36 2026 +0700 all: imp code commit 5718a97c5eb9de11991b56c80e183edaf56606a3 Author: Dimitry Kolyshev <[email protected]> Date: Tue Mar 10 16:23:52 2026 +0700 proxy: mv UserInfo commit aa0bd41c4249610a087bd686f680e59e97e122c5 Author: Dimitry Kolyshev <[email protected]> Date: Mon Mar 9 15:41:00 2026 +0700 proxy: imp docs commit 82048dc9dfe4fdce27cb831e4524753846761718 Author: Dimitry Kolyshev <[email protected]> Date: Fri Mar 6 13:32:08 2026 +0700 cmd: imp commit 6af19f9443a5012b7cfd598760cc11809b4f55a3 Author: Dimitry Kolyshev <[email protected]> Date: Fri Mar 6 10:34:54 2026 +0700 proxy: imp docs commit a0d03fd1966ccf2e1f5a684b418248b43ab827b7 Author: Dimitry Kolyshev <[email protected]> Date: Fri Mar 6 09:16:23 2026 +0700 cmd: imp code commit cc82e1f9b2687a45453c6a5857e45e1a7b22ac36 Author: Dimitry Kolyshev <[email protected]> Date: Thu Mar 5 12:33:46 2026 +0700 proxy: imp http conf commit cde8d76d8c7057c33778456bcbb8d44d85129ed3 Author: Dimitry Kolyshev <[email protected]> Date: Thu Mar 5 12:20:53 2026 +0700 all: imp code commit 57547f3d8ff13be9e17a2a9eada7b56dce00c70e Author: Dimitry Kolyshev <[email protected]> Date: Thu Mar 5 12:05:37 2026 +0700 proxy: doh routes commit 1b23b3326bafee937272e83f10cd86edd6fcbb95 Author: Dimitry Kolyshev <[email protected]> Date: Wed Mar 4 13:29:15 2026 +0700 proxy: use http conf commit 60963a93c4118f9ca1601d9ea6042a1dc0c230a8 Author: Dimitry Kolyshev <[email protected]> Date: Tue Mar 3 12:51:38 2026 +0700 cmd: doh conf commit 4620723ea912254958df417517cb78bc7f1de40a Author: Dimitry Kolyshev <[email protected]> Date: Tue Mar 3 12:29:09 2026 +0700 proxy: http conf
This commit is contained in:
@@ -63,6 +63,14 @@ Usage of ./dnsproxy:
|
||||
Cache size (in bytes). Default: 64k.
|
||||
--config-path=path
|
||||
YAML configuration file. Minimal working configuration in config.yaml.dist. Options passed through command line will override the ones from this file.
|
||||
--doh-insecure-enabled
|
||||
If specified, the DoH server will skip TLS certificate verification.
|
||||
--doh-routes
|
||||
Routes for DNS-over-HTTPS. If not specified, the default routes are registered:
|
||||
- "GET /", deprecated and will soon be removed,
|
||||
- "POST /", deprecated and will soon be removed.
|
||||
- "GET /dns-query",
|
||||
- "POST /dns-query".
|
||||
--dns64
|
||||
If specified, dnsproxy will act as a DNS64 server.
|
||||
--dns64-prefix=subnet
|
||||
|
||||
+18
-2
@@ -67,6 +67,8 @@ const (
|
||||
pendingRequestsEnabledIdx
|
||||
dns64Idx
|
||||
usePrivateRDNSIdx
|
||||
dohRoutesIdx
|
||||
dohInsecureEnabledIdx
|
||||
)
|
||||
|
||||
// commandLineOption contains information about a command-line option: its long
|
||||
@@ -405,6 +407,18 @@ var commandLineOptions = []*commandLineOption{
|
||||
short: "",
|
||||
valueType: "",
|
||||
},
|
||||
dohRoutesIdx: {
|
||||
description: "List of routes for DNS-over-HTTPS, can be specified multiple times.",
|
||||
long: "doh-routes",
|
||||
short: "",
|
||||
valueType: "route",
|
||||
},
|
||||
dohInsecureEnabledIdx: {
|
||||
description: "If specified, the DoH server will skip TLS certificate verification.",
|
||||
long: "doh-insecure-enabled",
|
||||
short: "",
|
||||
valueType: "",
|
||||
},
|
||||
}
|
||||
|
||||
// parseCmdLineOptions parses the command-line options. conf must not be nil.
|
||||
@@ -464,6 +478,8 @@ func parseCmdLineOptions(conf *configuration) (err error) {
|
||||
pendingRequestsEnabledIdx: &conf.PendingRequestsEnabled,
|
||||
dns64Idx: &conf.DNS64,
|
||||
usePrivateRDNSIdx: &conf.UsePrivateRDNS,
|
||||
dohRoutesIdx: &conf.DoHRoutes,
|
||||
dohInsecureEnabledIdx: &conf.DoHInsecureEnabled,
|
||||
} {
|
||||
addOption(flags, fieldPtr, commandLineOptions[i])
|
||||
}
|
||||
@@ -536,8 +552,8 @@ func addOption(flags *flag.FlagSet, fieldPtr any, o *commandLineOption) {
|
||||
defineFlagVar(flags, (*uint32Value)(fieldPtr), o)
|
||||
case *float32:
|
||||
defineFlagVar(flags, (*float32Value)(fieldPtr), o)
|
||||
case *[]int:
|
||||
defineFlagVar(flags, newIntSliceValue(fieldPtr), o)
|
||||
case *[]uint16:
|
||||
defineFlagVar(flags, newUInt16SliceValue(fieldPtr), o)
|
||||
case *[]string:
|
||||
defineFlagVar(flags, newStringSliceValue(fieldPtr), o)
|
||||
case *timeutil.Duration:
|
||||
|
||||
+15
-6
@@ -46,20 +46,25 @@ type configuration struct {
|
||||
// ListenAddrs is the list of server's listen addresses.
|
||||
ListenAddrs []string `yaml:"listen-addrs"`
|
||||
|
||||
// ListenPorts are the ports server listens on.
|
||||
ListenPorts []int `yaml:"listen-ports"`
|
||||
// DoHRoutes is the list of routes for DNS-over-HTTPS. It must be a slice
|
||||
// of valid route patterns, if it is empty, the default routes are
|
||||
// registered.
|
||||
DoHRoutes []string `yaml:"doh-routes"`
|
||||
|
||||
// ListenPorts are the ports server listens on for plain DNS.
|
||||
ListenPorts []uint16 `yaml:"listen-ports"`
|
||||
|
||||
// HTTPSListenPorts are the ports server listens on for DNS-over-HTTPS.
|
||||
HTTPSListenPorts []int `yaml:"https-port"`
|
||||
HTTPSListenPorts []uint16 `yaml:"https-port"`
|
||||
|
||||
// TLSListenPorts are the ports server listens on for DNS-over-TLS.
|
||||
TLSListenPorts []int `yaml:"tls-port"`
|
||||
TLSListenPorts []uint16 `yaml:"tls-port"`
|
||||
|
||||
// QUICListenPorts are the ports server listens on for DNS-over-QUIC.
|
||||
QUICListenPorts []int `yaml:"quic-port"`
|
||||
QUICListenPorts []uint16 `yaml:"quic-port"`
|
||||
|
||||
// DNSCryptListenPorts are the ports server listens on for DNSCrypt.
|
||||
DNSCryptListenPorts []int `yaml:"dnscrypt-port"`
|
||||
DNSCryptListenPorts []uint16 `yaml:"dnscrypt-port"`
|
||||
|
||||
// Upstreams is the list of DNS upstream servers.
|
||||
Upstreams []string `yaml:"upstream"`
|
||||
@@ -146,6 +151,10 @@ type configuration struct {
|
||||
// TODO(d.kolyshev): Use more suitable type.
|
||||
TLSMaxVersion float32 `yaml:"tls-max-version"`
|
||||
|
||||
// DoHInsecureEnabled controls whether the DoH server should skip TLS
|
||||
// certificate verification.
|
||||
DoHInsecureEnabled bool `yaml:"doh-insecure-enabled"`
|
||||
|
||||
// help, if true, prints the command-line option help message and quit with
|
||||
// a successful exit-code.
|
||||
help bool
|
||||
|
||||
+19
-18
@@ -47,47 +47,48 @@ func (i *float32Value) String() (out string) {
|
||||
return strconv.FormatFloat(float64(*i), 'f', 3, 32)
|
||||
}
|
||||
|
||||
// intSliceValue represent a struct with a slice of integers that can be defined
|
||||
// as a flag for [flag.FlagSet].
|
||||
type intSliceValue struct {
|
||||
// values is the pointer to a slice of integers to store parsed values.
|
||||
values *[]int
|
||||
// uint16SliceValue represent a struct with a slice of uint16 values that can be
|
||||
// defined as a flag for [flag.FlagSet].
|
||||
type uint16SliceValue struct {
|
||||
// values is the pointer to a slice of uint16 to store parsed values.
|
||||
values *[]uint16
|
||||
|
||||
// isSet is false until the corresponding flag is met for the first time.
|
||||
// When the flag is found, the default value is overwritten with zero value.
|
||||
isSet bool
|
||||
}
|
||||
|
||||
// newIntSliceValue returns a pointer to intSliceValue with the given value.
|
||||
func newIntSliceValue(p *[]int) (out *intSliceValue) {
|
||||
return &intSliceValue{
|
||||
// newUInt16SliceValue returns a pointer to uint16SliceValue with the given
|
||||
// value.
|
||||
func newUInt16SliceValue(p *[]uint16) (out *uint16SliceValue) {
|
||||
return &uint16SliceValue{
|
||||
values: p,
|
||||
isSet: false,
|
||||
}
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ flag.Value = (*intSliceValue)(nil)
|
||||
var _ flag.Value = (*uint16SliceValue)(nil)
|
||||
|
||||
// Set implements the [flag.Value] interface for *intSliceValue.
|
||||
func (i *intSliceValue) Set(s string) (err error) {
|
||||
v, err := strconv.Atoi(s)
|
||||
// Set implements the [flag.Value] interface for *uint16SliceValue.
|
||||
func (i *uint16SliceValue) Set(s string) (err error) {
|
||||
v, err := strconv.ParseUint(s, 10, 16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing integer slice arg %q: %w", s, err)
|
||||
return fmt.Errorf("parsing uint16 slice arg %q: %w", s, err)
|
||||
}
|
||||
|
||||
if !i.isSet {
|
||||
i.isSet = true
|
||||
*i.values = []int{}
|
||||
*i.values = []uint16{}
|
||||
}
|
||||
|
||||
*i.values = append(*i.values, v)
|
||||
*i.values = append(*i.values, uint16(v))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the [flag.Value] interface for *intSliceValue.
|
||||
func (i *intSliceValue) String() (out string) {
|
||||
// String implements the [flag.Value] interface for *uint16SliceValue.
|
||||
func (i *uint16SliceValue) String() (out string) {
|
||||
if i == nil || i.values == nil {
|
||||
return ""
|
||||
}
|
||||
@@ -98,7 +99,7 @@ func (i *intSliceValue) String() (out string) {
|
||||
stringutil.WriteToBuilder(sb, ",")
|
||||
}
|
||||
|
||||
stringutil.WriteToBuilder(sb, strconv.Itoa(v))
|
||||
stringutil.WriteToBuilder(sb, strconv.FormatUint(uint64(v), 10))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
|
||||
+43
-32
@@ -26,6 +26,11 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// defaultHTTPTimeout is the default timeout for HTTP server operations.
|
||||
//
|
||||
// TODO(a.garipov): Consider making configurable.
|
||||
const defaultHTTPTimeout = 10 * time.Second
|
||||
|
||||
// TODO(e.burkov): Use a separate type for the YAML configuration file.
|
||||
|
||||
// createProxyConfig initializes [proxy.Config]. l must not be nil.
|
||||
@@ -58,6 +63,24 @@ func createProxyConfig(
|
||||
return nil, fmt.Errorf("ratelimit mw: %w", err)
|
||||
}
|
||||
|
||||
httpConf := &proxy.HTTPConfig{
|
||||
ServerHeader: conf.HTTPSServerName,
|
||||
Routes: conf.DoHRoutes,
|
||||
ReadTimeout: defaultHTTPTimeout,
|
||||
WriteTimeout: defaultHTTPTimeout,
|
||||
HTTP3Enabled: conf.HTTP3,
|
||||
InsecureEnabled: conf.DoHInsecureEnabled,
|
||||
}
|
||||
|
||||
if uiStr := conf.HTTPSUserinfo; uiStr != "" {
|
||||
user, pass, ok := strings.Cut(uiStr, ":")
|
||||
if ok {
|
||||
httpConf.Userinfo = url.UserPassword(user, pass)
|
||||
} else {
|
||||
httpConf.Userinfo = url.User(user)
|
||||
}
|
||||
}
|
||||
|
||||
proxyConf = &proxy.Config{
|
||||
Logger: l.With(slogutil.KeyPrefix, proxy.LogPrefix),
|
||||
CacheEnabled: conf.Cache,
|
||||
@@ -68,7 +91,6 @@ func createProxyConfig(
|
||||
CacheOptimisticMaxAge: time.Duration(conf.OptimisticMaxAge),
|
||||
CacheOptimistic: conf.CacheOptimistic,
|
||||
RefuseAny: conf.RefuseAny,
|
||||
HTTP3: conf.HTTP3,
|
||||
// TODO(e.burkov): The following CIDRs are aimed to match any address.
|
||||
// This is not quite proper approach to be used by default so think
|
||||
// about configuring it.
|
||||
@@ -78,7 +100,6 @@ func createProxyConfig(
|
||||
},
|
||||
EnableEDNSClientSubnet: conf.EnableEDNSSubnet,
|
||||
UDPBufferSize: conf.UDPBufferSize,
|
||||
HTTPSServerName: conf.HTTPSServerName,
|
||||
MaxGoroutines: conf.MaxGoRoutines,
|
||||
UsePrivateRDNS: conf.UsePrivateRDNS,
|
||||
PrivateSubnets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
@@ -86,15 +107,7 @@ func createProxyConfig(
|
||||
PendingRequests: &proxy.PendingRequestsConfig{
|
||||
Enabled: conf.PendingRequestsEnabled,
|
||||
},
|
||||
}
|
||||
|
||||
if uiStr := conf.HTTPSUserinfo; uiStr != "" {
|
||||
user, pass, ok := strings.Cut(uiStr, ":")
|
||||
if ok {
|
||||
proxyConf.Userinfo = url.UserPassword(user, pass)
|
||||
} else {
|
||||
proxyConf.Userinfo = url.User(user)
|
||||
}
|
||||
HTTPConfig: httpConf,
|
||||
}
|
||||
|
||||
conf.initBogusNXDomain(ctx, l, proxyConf)
|
||||
@@ -391,60 +404,58 @@ func (conf *configuration) initListenAddrs(config *proxy.Config) (err error) {
|
||||
if len(conf.ListenPorts) == 0 {
|
||||
// If ListenPorts has not been parsed through config file nor command
|
||||
// line we set it to 53.
|
||||
conf.ListenPorts = []int{53}
|
||||
conf.ListenPorts = []uint16{53}
|
||||
}
|
||||
|
||||
for _, port := range conf.ListenPorts {
|
||||
for _, ip := range addrs {
|
||||
addrPort := netip.AddrPortFrom(ip, uint16(port))
|
||||
addrPort := netip.AddrPortFrom(ip, port)
|
||||
|
||||
config.UDPListenAddr = append(config.UDPListenAddr, net.UDPAddrFromAddrPort(addrPort))
|
||||
config.TCPListenAddr = append(config.TCPListenAddr, net.TCPAddrFromAddrPort(addrPort))
|
||||
}
|
||||
}
|
||||
|
||||
initTLSListenAddrs(config, conf, addrs)
|
||||
initDNSCryptListenAddrs(config, conf, addrs)
|
||||
if config.TLSConfig != nil {
|
||||
initTLSListenAddrs(config, conf, addrs)
|
||||
}
|
||||
|
||||
if config.DNSCryptResolverCert != nil && config.DNSCryptProviderName != "" {
|
||||
initDNSCryptListenAddrs(config, conf, addrs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initTLSListenAddrs sets up proxy configuration TLS listen addresses.
|
||||
// initTLSListenAddrs sets up proxy configuration TLS listen addresses. conf,
|
||||
// proxyConf must not be nil. If conf.HTTPSListenPorts is not empty,
|
||||
// proxyConf.HTTPConfig must not be nil.
|
||||
func initTLSListenAddrs(proxyConf *proxy.Config, conf *configuration, addrs []netip.Addr) {
|
||||
if proxyConf.TLSConfig == nil {
|
||||
return
|
||||
}
|
||||
|
||||
httpConfig := proxyConf.HTTPConfig
|
||||
for _, ip := range addrs {
|
||||
for _, port := range conf.TLSListenPorts {
|
||||
a := net.TCPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16(port)))
|
||||
a := net.TCPAddrFromAddrPort(netip.AddrPortFrom(ip, port))
|
||||
proxyConf.TLSListenAddr = append(proxyConf.TLSListenAddr, a)
|
||||
}
|
||||
|
||||
for _, port := range conf.HTTPSListenPorts {
|
||||
a := net.TCPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16(port)))
|
||||
proxyConf.HTTPSListenAddr = append(proxyConf.HTTPSListenAddr, a)
|
||||
a := netip.AddrPortFrom(ip, port)
|
||||
httpConfig.ListenAddresses = append(httpConfig.ListenAddresses, a)
|
||||
}
|
||||
|
||||
for _, port := range conf.QUICListenPorts {
|
||||
a := net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip, uint16(port)))
|
||||
a := net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip, port))
|
||||
proxyConf.QUICListenAddr = append(proxyConf.QUICListenAddr, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initDNSCryptListenAddrs sets up proxy configuration DNSCrypt listen
|
||||
// addresses.
|
||||
// addresses. proxyConf and conf must not be nil.
|
||||
func initDNSCryptListenAddrs(proxyConf *proxy.Config, conf *configuration, addrs []netip.Addr) {
|
||||
if proxyConf.DNSCryptResolverCert == nil || proxyConf.DNSCryptProviderName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for _, port := range conf.DNSCryptListenPorts {
|
||||
p := uint16(port)
|
||||
|
||||
for _, ip := range addrs {
|
||||
addrPort := netip.AddrPortFrom(ip, p)
|
||||
addrPort := netip.AddrPortFrom(ip, port)
|
||||
|
||||
tcp := net.TCPAddrFromAddrPort(addrPort)
|
||||
proxyConf.DNSCryptTCPListenAddr = append(proxyConf.DNSCryptTCPListenAddr, tcp)
|
||||
|
||||
+46
-19
@@ -76,11 +76,6 @@ type Config struct {
|
||||
// TODO(e.burkov): Add explicit boolean for disabling fallbacks.
|
||||
Fallbacks *UpstreamConfig
|
||||
|
||||
// Userinfo is the sole permitted userinfo for the DoH basic authentication.
|
||||
// If Userinfo is set, all DoH queries are required to have this basic
|
||||
// authentication information.
|
||||
Userinfo *url.Userinfo
|
||||
|
||||
// TLSConfig is the TLS configuration. Required for DNS-over-TLS,
|
||||
// DNS-over-HTTP, and DNS-over-QUIC servers.
|
||||
TLSConfig *tls.Config
|
||||
@@ -93,14 +88,14 @@ type Config struct {
|
||||
// retries are disabled.
|
||||
BindRetryConfig *BindRetryConfig
|
||||
|
||||
// HTTPConfig is the configuration for HTTP requests proxying. Required for
|
||||
// DoH server. If nil, the DoH server is disabled.
|
||||
HTTPConfig *HTTPConfig
|
||||
|
||||
// DNSCryptProviderName is the DNSCrypt provider name. Required for
|
||||
// DNSCrypt server.
|
||||
DNSCryptProviderName string
|
||||
|
||||
// HTTPSServerName sets the Server header of the HTTPS server responses, if
|
||||
// not empty.
|
||||
HTTPSServerName string
|
||||
|
||||
// UpstreamMode determines the logic through which upstreams will be used.
|
||||
// If not specified the [proxy.UpstreamModeLoadBalance] is used.
|
||||
UpstreamMode UpstreamMode
|
||||
@@ -113,10 +108,6 @@ type Config struct {
|
||||
// DNS-over-TCP requests.
|
||||
TCPListenAddr []*net.TCPAddr
|
||||
|
||||
// HTTPSListenAddr is the set of TCP addresses to listen for DNS-over-HTTPS
|
||||
// requests.
|
||||
HTTPSListenAddr []*net.TCPAddr
|
||||
|
||||
// TLSListenAddr is the set of TCP addresses to listen for DNS-over-TLS
|
||||
// requests.
|
||||
TLSListenAddr []*net.TCPAddr
|
||||
@@ -182,9 +173,6 @@ type Config struct {
|
||||
// RefuseAny makes proxy refuse the requests of type ANY.
|
||||
RefuseAny bool
|
||||
|
||||
// HTTP3 enables HTTP/3 support for HTTPS server.
|
||||
HTTP3 bool
|
||||
|
||||
// Enable EDNS Client Subnet option DNS requests to the upstream server will
|
||||
// contain an OPT record with Client Subnet option. If the original request
|
||||
// already has this option set, we pass it through as is. Otherwise, we set
|
||||
@@ -237,6 +225,45 @@ type PendingRequestsConfig struct {
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// HTTPConfig is the configuration for HTTP requests proxying.
|
||||
type HTTPConfig struct {
|
||||
// Userinfo is the sole permitted userinfo for the DoH basic authentication.
|
||||
// If Userinfo is set, all DoH queries are required to have this basic
|
||||
// authentication information.
|
||||
Userinfo *url.Userinfo
|
||||
|
||||
// ServerHeader sets the Server header of the HTTPS server responses, if not
|
||||
// empty.
|
||||
ServerHeader string
|
||||
|
||||
// ListenAddresses is the set of addresses to listen for DNS-over-HTTPS
|
||||
// requests. If it is empty the proxy doesn't start the HTTPS server, but
|
||||
// still can be used as an http.Handler with [Proxy.ServeHTTP].
|
||||
ListenAddresses []netip.AddrPort
|
||||
|
||||
// Routes is the set of routes to handle. It must be a slice of valid route
|
||||
// patterns, if it is empty, the default routes are registered. It is
|
||||
// ignored if ListenAddresses is empty.
|
||||
Routes []string
|
||||
|
||||
// ReadTimeout is the maximum duration before timing out reads of the
|
||||
// request. A zero or negative value means there will be no timeout. It is
|
||||
// ignored if ListenAddresses is empty.
|
||||
ReadTimeout time.Duration
|
||||
|
||||
// WriteTimeout is the maximum duration before timing out writes of the
|
||||
// response. A zero or negative value means there will be no timeout. It
|
||||
// is ignored if ListenAddresses is empty.
|
||||
WriteTimeout time.Duration
|
||||
|
||||
// HTTP3Enabled specifies if HTTP/3 support for HTTPS server. It is ignored
|
||||
// if ListenAddresses is empty.
|
||||
HTTP3Enabled bool
|
||||
|
||||
// InsecureEnabled specifies if unencrypted DoH requests are allowed.
|
||||
InsecureEnabled bool
|
||||
}
|
||||
|
||||
// validateConfig verifies that the supplied configuration is valid and returns
|
||||
// an error if it's not.
|
||||
//
|
||||
@@ -339,7 +366,7 @@ func (p *Proxy) validateTLSConfig() (err error) {
|
||||
return errors.Error("tls listener configuration not found")
|
||||
}
|
||||
|
||||
if p.HTTPSListenAddr != nil {
|
||||
if p.HTTPConfig != nil && p.HTTPConfig.ListenAddresses != nil {
|
||||
return errors.Error("https listener configuration not found")
|
||||
}
|
||||
|
||||
@@ -351,11 +378,11 @@ func (p *Proxy) validateTLSConfig() (err error) {
|
||||
}
|
||||
|
||||
// hasListenAddrs - is there any addresses to listen to?
|
||||
func (p *Proxy) hasListenAddrs() bool {
|
||||
func (p *Proxy) hasListenAddrs() (ok bool) {
|
||||
return p.UDPListenAddr != nil ||
|
||||
p.TCPListenAddr != nil ||
|
||||
p.TLSListenAddr != nil ||
|
||||
p.HTTPSListenAddr != nil ||
|
||||
(p.HTTPConfig != nil && p.HTTPConfig.ListenAddresses != nil) ||
|
||||
p.QUICListenAddr != nil ||
|
||||
p.DNSCryptUDPListenAddr != nil ||
|
||||
p.DNSCryptTCPListenAddr != nil
|
||||
|
||||
+4
-5
@@ -293,15 +293,14 @@ func loggerOrDefault(l *slog.Logger) (logger *slog.Logger) {
|
||||
return slog.Default().With(slogutil.KeyPrefix, LogPrefix)
|
||||
}
|
||||
|
||||
// validateBasicAuth validates the basic-auth mode settings if p.Config.Userinfo
|
||||
// is set.
|
||||
// validateBasicAuth validates the HTTP settings if HTTPConfig.Userinfo is set.
|
||||
func (p *Proxy) validateBasicAuth() (err error) {
|
||||
conf := p.Config
|
||||
if conf.Userinfo == nil {
|
||||
conf := p.Config.HTTPConfig
|
||||
if conf == nil || conf.Userinfo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return validate.NotEmptySlice("HTTPSListenAddr", conf.HTTPSListenAddr)
|
||||
return validate.NotEmptySlice("HTTPConfig.ListenAddresses", conf.ListenAddresses)
|
||||
}
|
||||
|
||||
// Returns true if proxy is started. It is safe for concurrent use.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package proxy
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Default path pattern constants.
|
||||
const (
|
||||
// Deprecated: Use pathPatternDNSQuery instead.
|
||||
pathPatternRoot = "/"
|
||||
pathPatternDNSQuery = "/dns-query"
|
||||
)
|
||||
|
||||
// Default route pattern constants.
|
||||
const (
|
||||
routePatternRootGet = http.MethodGet + " " + pathPatternRoot
|
||||
routePatternRootPost = http.MethodPost + " " + pathPatternRoot
|
||||
routePatternDNSQueryGet = http.MethodGet + " " + pathPatternDNSQuery
|
||||
routePatternDNSQueryPost = http.MethodPost + " " + pathPatternDNSQuery
|
||||
)
|
||||
|
||||
// routeDoH registers DoH handlers in mux. p.HTTPConfig must not be nil.
|
||||
// p.HTTPConfig.Routes must be valid, if p.HTTPConfig.Routes is empty, the
|
||||
// default routes are registered.
|
||||
func (p *Proxy) routeDoH(mux *http.ServeMux) {
|
||||
routes := p.HTTPConfig.Routes
|
||||
if len(routes) == 0 {
|
||||
mux.Handle(routePatternRootGet, p)
|
||||
mux.Handle(routePatternRootPost, p)
|
||||
mux.Handle(routePatternDNSQueryGet, p)
|
||||
mux.Handle(routePatternDNSQueryPost, p)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
mux.Handle(route, p)
|
||||
}
|
||||
}
|
||||
+35
-15
@@ -75,29 +75,40 @@ func (p *Proxy) listenH3(
|
||||
|
||||
// initHTTPSListeners creates TCP/UDP listeners and HTTP/H3 servers.
|
||||
func (p *Proxy) initHTTPSListeners(ctx context.Context) (err error) {
|
||||
p.httpsServer = &http.Server{
|
||||
Handler: p,
|
||||
ReadHeaderTimeout: defaultTimeout,
|
||||
WriteTimeout: defaultTimeout,
|
||||
httpConf := p.HTTPConfig
|
||||
if httpConf == nil {
|
||||
p.logger.DebugContext(ctx, "no https configuration provided")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.HTTP3 {
|
||||
mux := http.NewServeMux()
|
||||
p.routeDoH(mux)
|
||||
|
||||
p.httpsServer = &http.Server{
|
||||
Handler: mux,
|
||||
ReadTimeout: httpConf.ReadTimeout,
|
||||
ReadHeaderTimeout: httpConf.ReadTimeout,
|
||||
WriteTimeout: httpConf.WriteTimeout,
|
||||
}
|
||||
|
||||
if httpConf.HTTP3Enabled {
|
||||
p.h3Server = &http3.Server{
|
||||
Handler: p,
|
||||
Handler: mux,
|
||||
}
|
||||
}
|
||||
|
||||
for _, addr := range p.HTTPSListenAddr {
|
||||
for _, addr := range httpConf.ListenAddresses {
|
||||
p.logger.InfoContext(ctx, "creating an https server")
|
||||
|
||||
ln, tcpAddr, lErr := p.listenHTTP(ctx, addr)
|
||||
ln, tcpAddr, lErr := p.listenHTTP(ctx, net.TCPAddrFromAddrPort(addr))
|
||||
if lErr != nil {
|
||||
return fmt.Errorf("failed to start HTTPS server on %s: %w", addr, lErr)
|
||||
return fmt.Errorf("failed to start https server on %s: %w", addr, lErr)
|
||||
}
|
||||
|
||||
p.httpsListen = append(p.httpsListen, ln)
|
||||
|
||||
if p.HTTP3 {
|
||||
if httpConf.HTTP3Enabled {
|
||||
// HTTP/3 server listens to the same pair IP:port as the one HTTP/2
|
||||
// server listens to.
|
||||
udpAddr := &net.UDPAddr{IP: tcpAddr.IP, Port: tcpAddr.Port}
|
||||
@@ -105,7 +116,7 @@ func (p *Proxy) initHTTPSListeners(ctx context.Context) (err error) {
|
||||
var quicListen *quic.EarlyListener
|
||||
quicListen, err = p.listenH3(ctx, udpAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start HTTP/3 server on %s: %w", udpAddr, err)
|
||||
return fmt.Errorf("failed to start h3 server on %s: %w", udpAddr, err)
|
||||
}
|
||||
|
||||
p.h3Listen = append(p.h3Listen, quicListen)
|
||||
@@ -172,6 +183,8 @@ func newDoHReq(r *http.Request, l *slog.Logger) (req *dns.Msg, statusCode int) {
|
||||
//
|
||||
// Here is what it returns:
|
||||
//
|
||||
// - http.StatusNotFound if the request is not encrypted and proxy is not
|
||||
// configured to accept unencrypted requests,
|
||||
// - http.StatusBadRequest if there is no DNS request data,
|
||||
// - http.StatusUnsupportedMediaType if request content type is not
|
||||
// "application/dns-message",
|
||||
@@ -179,6 +192,13 @@ func newDoHReq(r *http.Request, l *slog.Logger) (req *dns.Msg, statusCode int) {
|
||||
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
p.logger.Debug("incoming https request", "url", r.URL)
|
||||
|
||||
if !p.HTTPConfig.InsecureEnabled && r.TLS == nil {
|
||||
statusCode := http.StatusNotFound
|
||||
http.Error(w, http.StatusText(statusCode), statusCode)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
raddr, prx, err := remoteAddr(r, p.logger)
|
||||
if err != nil {
|
||||
p.logger.Debug("getting real ip", slogutil.KeyError, err)
|
||||
@@ -221,13 +241,13 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// checkBasicAuth checks the basic authorization data, if necessary, and if the
|
||||
// data isn't valid, it writes an error. shouldHandle is false if the request
|
||||
// has been denied.
|
||||
// has been denied. p.HTTPConfig must not be nil.
|
||||
func (p *Proxy) checkBasicAuth(
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
raddr netip.AddrPort,
|
||||
) (shouldHandle bool) {
|
||||
ui := p.Config.Userinfo
|
||||
ui := p.HTTPConfig.Userinfo
|
||||
if ui == nil {
|
||||
return true
|
||||
}
|
||||
@@ -273,8 +293,8 @@ func (p *Proxy) respondHTTPS(d *DNSContext) (err error) {
|
||||
return fmt.Errorf("packing message: %w", err)
|
||||
}
|
||||
|
||||
if srvName := p.Config.HTTPSServerName; srvName != "" {
|
||||
w.Header().Set(httphdr.Server, srvName)
|
||||
if srvHeader := p.HTTPConfig.ServerHeader; srvHeader != "" {
|
||||
w.Header().Set(httphdr.Server, srvHeader)
|
||||
}
|
||||
|
||||
w.Header().Set(httphdr.ContentType, "application/dns-message")
|
||||
|
||||
@@ -38,15 +38,19 @@ func TestHttpsProxy(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tlsConf, caPem := newTLSConfig(t)
|
||||
|
||||
httpConf := &HTTPConfig{
|
||||
ListenAddresses: []netip.AddrPort{localhostAnyPort},
|
||||
HTTP3Enabled: tc.http3,
|
||||
}
|
||||
dnsProxy := mustNew(t, &Config{
|
||||
Logger: testLogger,
|
||||
TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
HTTPSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(localhostAnyPort)},
|
||||
TLSConfig: tlsConf,
|
||||
UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr),
|
||||
TrustedProxies: defaultTrustedProxies,
|
||||
HTTP3: tc.http3,
|
||||
Logger: testLogger,
|
||||
TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(localhostAnyPort)},
|
||||
TLSConfig: tlsConf,
|
||||
UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr),
|
||||
TrustedProxies: defaultTrustedProxies,
|
||||
HTTPConfig: httpConf,
|
||||
})
|
||||
|
||||
servicetest.RequireRun(t, dnsProxy, testTimeout)
|
||||
@@ -83,15 +87,18 @@ func TestProxy_trustedProxies(t *testing.T) {
|
||||
|
||||
// Prepare the proxy server.
|
||||
tlsConf, caPem := newTLSConfig(t)
|
||||
httpConf := &HTTPConfig{
|
||||
ListenAddresses: []netip.AddrPort{localhostAnyPort},
|
||||
}
|
||||
dnsProxy := mustNew(t, &Config{
|
||||
Logger: testLogger,
|
||||
UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr),
|
||||
TrustedProxies: defaultTrustedProxies,
|
||||
RequestHandler: reqHandler,
|
||||
TLSConfig: tlsConf,
|
||||
TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
HTTPSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(localhostAnyPort)},
|
||||
Logger: testLogger,
|
||||
UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr),
|
||||
TrustedProxies: defaultTrustedProxies,
|
||||
RequestHandler: reqHandler,
|
||||
TLSConfig: tlsConf,
|
||||
TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(localhostAnyPort)},
|
||||
HTTPConfig: httpConf,
|
||||
})
|
||||
|
||||
client := createTestHTTPClient(dnsProxy, caPem, false)
|
||||
|
||||
@@ -93,14 +93,13 @@ func TestProxy_quicLargePackets(t *testing.T) {
|
||||
|
||||
serverConfig, caPem := newTLSConfig(t)
|
||||
dnsProxy := mustNew(t, &Config{
|
||||
Logger: testLogger,
|
||||
UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr),
|
||||
TrustedProxies: defaultTrustedProxies,
|
||||
RequestHandler: reqHandler,
|
||||
TLSConfig: serverConfig,
|
||||
TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
HTTPSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(localhostAnyPort)},
|
||||
Logger: testLogger,
|
||||
UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr),
|
||||
TrustedProxies: defaultTrustedProxies,
|
||||
RequestHandler: reqHandler,
|
||||
TLSConfig: serverConfig,
|
||||
TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(localhostAnyPort)},
|
||||
})
|
||||
|
||||
servicetest.RequireRun(t, dnsProxy, testTimeout)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTcpProxy(t *testing.T) {
|
||||
func TestProxy_tcp(t *testing.T) {
|
||||
dnsProxy := mustStartDefaultProxy(t)
|
||||
|
||||
// Create a DNS-over-TCP client connection
|
||||
@@ -22,16 +22,15 @@ func TestTcpProxy(t *testing.T) {
|
||||
sendTestMessages(t, conn)
|
||||
}
|
||||
|
||||
func TestTlsProxy(t *testing.T) {
|
||||
func TestProxy_tls(t *testing.T) {
|
||||
serverConfig, caPem := newTLSConfig(t)
|
||||
dnsProxy := mustNew(t, &Config{
|
||||
Logger: testLogger,
|
||||
TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
HTTPSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(localhostAnyPort)},
|
||||
TLSConfig: serverConfig,
|
||||
UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr),
|
||||
TrustedProxies: defaultTrustedProxies,
|
||||
Logger: testLogger,
|
||||
TLSListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
|
||||
QUICListenAddr: []*net.UDPAddr{net.UDPAddrFromAddrPort(localhostAnyPort)},
|
||||
TLSConfig: serverConfig,
|
||||
UpstreamConfig: newTestUpstreamConfig(t, defaultTimeout, testDefaultUpstreamAddr),
|
||||
TrustedProxies: defaultTrustedProxies,
|
||||
})
|
||||
|
||||
servicetest.RequireRun(t, dnsProxy, testTimeout)
|
||||
|
||||
Reference in New Issue
Block a user