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:
Dimitry Kolyshev
2026-03-11 00:56:47 +00:00
parent b4607cf194
commit b7d4b054e2
12 changed files with 263 additions and 130 deletions
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+37
View File
@@ -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
View File
@@ -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")
+23 -16
View File
@@ -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)
+7 -8
View File
@@ -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)
+8 -9
View File
@@ -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)