Skip to content

Commit be92d68

Browse files
committed
Pull request 395: AG-38975 Rebirth attack mitigation
Merge in GO/dnsproxy from AG-38975-rebirth-attack to master Squashed commit of the following: commit 715cfb8 Merge: 6ca2379 8f7a66d Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Apr 17 20:03:33 2025 +0300 Merge branch 'master' into AG-38975-rebirth-attack commit 6ca2379 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Apr 17 19:45:54 2025 +0300 proxy: fix typo commit c1b3f4a Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Apr 14 15:50:33 2025 +0300 all: imp code commit 96f975a Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Apr 11 18:05:41 2025 +0300 all: imp code commit 6deeaa8 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Apr 10 17:24:31 2025 +0300 all: imp code, add arg commit aee70c9 Merge: 8e220ae 970056b Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Apr 10 17:24:04 2025 +0300 Merge branch 'master' into AG-38975-rebirth-attack commit 8e220ae Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Apr 4 19:24:04 2025 +0300 all: add pending requests
1 parent 8f7a66d commit be92d68

17 files changed

+584
-182
lines changed

README.md

Lines changed: 99 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -46,67 +46,105 @@ make build
4646
## Usage
4747

4848
```none
49-
Usage:
50-
dnsproxy [OPTIONS]
51-
52-
Application Options:
53-
--config-path= yaml configuration file. Minimal working configuration in config.yaml.dist. Options passed through command
54-
line will override the ones from this file.
55-
-o, --output= Path to the log file. If not set, write to stdout.
56-
-c, --tls-crt= Path to a file with the certificate chain
57-
-k, --tls-key= Path to a file with the private key
58-
--https-server-name= Set the Server header for the responses from the HTTPS server. (default: dnsproxy)
59-
--https-userinfo= If set, all DoH queries are required to have this basic authentication information.
60-
-g, --dnscrypt-config= Path to a file with DNSCrypt configuration. You can generate one using https://github.com/ameshkov/dnscrypt
61-
--edns-addr= Send EDNS Client Address
62-
--upstream-mode= Defines the upstreams logic mode, possible values: load_balance, parallel, fastest_addr (default:
63-
load_balance)
64-
-l, --listen= Listening addresses
65-
-p, --port= Listening ports. Zero value disables TCP and UDP listeners
66-
-s, --https-port= Listening ports for DNS-over-HTTPS
67-
-t, --tls-port= Listening ports for DNS-over-TLS
68-
-q, --quic-port= Listening ports for DNS-over-QUIC
69-
-y, --dnscrypt-port= Listening ports for DNSCrypt
70-
-u, --upstream= An upstream to be used (can be specified multiple times). You can also specify path to a file with the
71-
list of servers
72-
-b, --bootstrap= Bootstrap DNS for DoH and DoT, can be specified multiple times (default: use system-provided)
73-
-f, --fallback= Fallback resolvers to use when regular ones are unavailable, can be specified multiple times. You can also
74-
specify path to a file with the list of servers
75-
--private-rdns-upstream= Private DNS upstreams to use for reverse DNS lookups of private addresses, can be specified multiple times
76-
--dns64-prefix= Prefix used to handle DNS64. If not specified, dnsproxy uses the 'Well-Known Prefix' 64:ff9b::. Can be
77-
specified multiple times
78-
--private-subnets= Private subnets to use for reverse DNS lookups of private addresses
79-
--bogus-nxdomain= Transform the responses containing at least a single IP that matches specified addresses and CIDRs into
80-
NXDOMAIN. Can be specified multiple times.
81-
--hosts-files= List of paths to the hosts files, can be specified multiple times
82-
--timeout= Timeout for outbound DNS queries to remote upstream servers in a human-readable form (default: 10s)
83-
--cache-min-ttl= Minimum TTL value for DNS entries, in seconds. Capped at 3600. Artificially extending TTLs should only be
84-
done with careful consideration.
85-
--cache-max-ttl= Maximum TTL value for DNS entries, in seconds.
86-
--cache-size= Cache size (in bytes). Default: 64k
87-
-r, --ratelimit= Ratelimit (requests per second)
88-
--ratelimit-subnet-len-ipv4= Ratelimit subnet length for IPv4. (default: 24)
89-
--ratelimit-subnet-len-ipv6= Ratelimit subnet length for IPv6. (default: 56)
90-
--udp-buf-size= Set the size of the UDP buffer in bytes. A value <= 0 will use the system default.
91-
--max-go-routines= Set the maximum number of go routines. A zero value will not not set a maximum.
92-
--tls-min-version= Minimum TLS version, for example 1.0
93-
--tls-max-version= Maximum TLS version, for example 1.3
94-
--pprof If present, exposes pprof information on localhost:6060.
95-
--version Prints the program version
96-
-v, --verbose Verbose output (optional)
97-
--insecure Disable secure TLS certificate validation
98-
--ipv6-disabled If specified, all AAAA requests will be replied with NoError RCode and empty answer
99-
--http3 Enable HTTP/3 support
100-
--cache-optimistic If specified, optimistic DNS cache is enabled
101-
--cache If specified, DNS cache is enabled
102-
--refuse-any If specified, refuse ANY requests
103-
--edns Use EDNS Client Subnet extension
104-
--dns64 If specified, dnsproxy will act as a DNS64 server
105-
--use-private-rdns If specified, use private upstreams for reverse DNS lookups of private addresses
106-
--hosts-file-enabled= If specified, use hosts files for resolving (default: true)
107-
108-
Help Options:
109-
-h, --help Show this help message
49+
Usage of ./dnsproxy:
50+
--bogus-nxdomain=subnet
51+
Transform the responses containing at least a single IP that matches specified addresses and CIDRs into NXDOMAIN. Can be specified multiple times.
52+
--bootstrap/-b
53+
Bootstrap DNS for DoH and DoT, can be specified multiple times (default: use system-provided).
54+
--cache
55+
If specified, DNS cache is enabled.
56+
--cache-max-ttl=uint32
57+
Maximum TTL value for DNS entries, in seconds.
58+
--cache-min-ttl=uint32
59+
Minimum TTL value for DNS entries, in seconds. Capped at 3600. Artificially extending TTLs should only be done with careful consideration.
60+
--cache-optimistic
61+
If specified, optimistic DNS cache is enabled.
62+
--cache-size=int
63+
Cache size (in bytes). Default: 64k.
64+
--config-path=path
65+
YAML configuration file. Minimal working configuration in config.yaml.dist. Options passed through command line will override the ones from this file.
66+
--dns64
67+
If specified, dnsproxy will act as a DNS64 server.
68+
--dns64-prefix=subnet
69+
Prefix used to handle DNS64. If not specified, dnsproxy uses the 'Well-Known Prefix' 64:ff9b::. Can be specified multiple times.
70+
--dnscrypt-config=path/-g path
71+
Path to a file with DNSCrypt configuration. You can generate one using https://github.com/ameshkov/dnscrypt.
72+
--dnscrypt-port=port/-y port
73+
Listening ports for DNSCrypt.
74+
--edns
75+
Use EDNS Client Subnet extension.
76+
--edns-addr=address
77+
Send EDNS Client Address.
78+
--fallback/-f
79+
Fallback resolvers to use when regular ones are unavailable, can be specified multiple times. You can also specify path to a file with the list of servers.
80+
--help/-h
81+
Print this help message and quit.
82+
--hosts-file-enabled
83+
If specified, use hosts files for resolving.
84+
--hosts-files=path
85+
List of paths to the hosts files, can be specified multiple times.
86+
--http3
87+
Enable HTTP/3 support.
88+
--https-port=port/-s port
89+
Listening ports for DNS-over-HTTPS.
90+
--https-server-name=name
91+
Set the Server header for the responses from the HTTPS server.
92+
--https-userinfo=name
93+
If set, all DoH queries are required to have this basic authentication information.
94+
--insecure
95+
Disable secure TLS certificate validation.
96+
--ipv6-disabled
97+
If specified, all AAAA requests will be replied with NoError RCode and empty answer.
98+
--listen=address/-l address
99+
Listening addresses.
100+
--max-go-routines=uint
101+
Set the maximum number of go routines. A zero value will not not set a maximum.
102+
--output=path/-o path
103+
Path to the log file.
104+
--pending-requests-enabled
105+
If specified, the server will track duplicate queries and only send the first of them to the upstream server, propagating its result to others. Disabling it introduces a vulnerability to cache poisoning attacks.
106+
--port=port/-p port
107+
Listening ports. Zero value disables TCP and UDP listeners.
108+
--pprof
109+
If present, exposes pprof information on localhost:6060.
110+
--private-rdns-upstream
111+
Private DNS upstreams to use for reverse DNS lookups of private addresses, can be specified multiple times.
112+
--private-subnets=subnet
113+
Private subnets to use for reverse DNS lookups of private addresses.
114+
--quic-port=port/-q port
115+
Listening ports for DNS-over-QUIC.
116+
--ratelimit=int/-r int
117+
Ratelimit (requests per second).
118+
--ratelimit-subnet-len-ipv4=int
119+
Ratelimit subnet length for IPv4.
120+
--ratelimit-subnet-len-ipv6=int
121+
Ratelimit subnet length for IPv6.
122+
--refuse-any
123+
If specified, refuses ANY requests.
124+
--timeout=duration
125+
Timeout for outbound DNS queries to remote upstream servers in a human-readable form
126+
--tls-crt=path/-c path
127+
Path to a file with the certificate chain.
128+
--tls-key=path/-k path
129+
Path to a file with the private key.
130+
--tls-max-version=version
131+
Maximum TLS version, for example 1.3.
132+
--tls-min-version=version
133+
Minimum TLS version, for example 1.0.
134+
--tls-port=port/-t port
135+
Listening ports for DNS-over-TLS.
136+
--udp-buf-size=int
137+
Set the size of the UDP buffer in bytes. A value <= 0 will use the system default.
138+
--upstream/-u
139+
An upstream to be used (can be specified multiple times). You can also specify path to a file with the list of servers.
140+
--upstream-mode=mode
141+
Defines the upstreams logic mode, possible values: load_balance, parallel, fastest_addr (default: load_balance).
142+
--use-private-rdns
143+
If specified, use private upstreams for reverse DNS lookups of private addresses.
144+
--verbose/-v
145+
Verbose output.
146+
--version
147+
Prints the program version.
110148
```
111149

112150
## Examples

internal/cmd/args.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const (
6262
cacheIdx
6363
refuseAnyIdx
6464
enableEDNSSubnetIdx
65+
pendingRequestsEnabledIdx
6566
dns64Idx
6667
usePrivateRDNSIdx
6768
)
@@ -369,6 +370,14 @@ var commandLineOptions = []*commandLineOption{
369370
short: "",
370371
valueType: "",
371372
},
373+
pendingRequestsEnabledIdx: {
374+
description: "If specified, the server will track duplicate queries and only send the " +
375+
"first of them to the upstream server, propagating its result to others. " +
376+
"Disabling it introduces a vulnerability to cache poisoning attacks.",
377+
long: "pending-requests-enabled",
378+
short: "",
379+
valueType: "",
380+
},
372381
dns64Idx: {
373382
description: "If specified, dnsproxy will act as a DNS64 server.",
374383
long: "dns64",
@@ -436,6 +445,7 @@ func parseCmdLineOptions(conf *configuration) (err error) {
436445
cacheIdx: &conf.Cache,
437446
refuseAnyIdx: &conf.RefuseAny,
438447
enableEDNSSubnetIdx: &conf.EnableEDNSSubnet,
448+
pendingRequestsEnabledIdx: &conf.PendingRequestsEnabled,
439449
dns64Idx: &conf.DNS64,
440450
usePrivateRDNSIdx: &conf.UsePrivateRDNS,
441451
} {

internal/cmd/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ type configuration struct {
180180
// EnableEDNSSubnet uses EDNS Client Subnet extension.
181181
EnableEDNSSubnet bool `yaml:"edns"`
182182

183+
// PendingRequestsEnabled controls whether the server should track duplicate
184+
// queries and only send the first of them to the upstream server. It is
185+
// used to mitigate the cache poisoning attacks.
186+
PendingRequestsEnabled bool `yaml:"pending-requests-enabled"`
187+
183188
// DNS64 defines whether DNS64 functionality is enabled or not.
184189
DNS64 bool `yaml:"dns64"`
185190

@@ -200,6 +205,7 @@ func parseConfig() (conf *configuration, exitCode int, err error) {
200205
RatelimitSubnetLenIPv4: 24,
201206
RatelimitSubnetLenIPv6: 56,
202207
HostsFileEnabled: true,
208+
PendingRequestsEnabled: true,
203209
}
204210

205211
err = parseCmdLineOptions(conf)

internal/cmd/proxy.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ func createProxyConfig(
8080
UsePrivateRDNS: conf.UsePrivateRDNS,
8181
PrivateSubnets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
8282
RequestHandler: reqHdlr.HandleRequest,
83+
PendingRequests: &proxy.PendingRequestsConfig{
84+
Enabled: conf.PendingRequestsEnabled,
85+
},
8386
}
8487

8588
if uiStr := conf.HTTPSUserinfo; uiStr != "" {

internal/dnsproxytest/interface.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"github.com/miekg/dns"
55
)
66

7-
// FakeUpstream is a fake [Upstream] implementation for tests.
7+
// FakeUpstream is a fake [proxy.Upstream] implementation for tests.
88
//
99
// TODO(e.burkov): Move this to the golibs some time later.
1010
type FakeUpstream struct {
@@ -13,22 +13,22 @@ type FakeUpstream struct {
1313
OnClose func() (err error)
1414
}
1515

16-
// Address implements the [Upstream] interface for *FakeUpstream.
16+
// Address implements the [proxy.Upstream] interface for *FakeUpstream.
1717
func (u *FakeUpstream) Address() (addr string) {
1818
return u.OnAddress()
1919
}
2020

21-
// Exchange implements the [Upstream] interface for *FakeUpstream.
21+
// Exchange implements the [proxy.Upstream] interface for *FakeUpstream.
2222
func (u *FakeUpstream) Exchange(req *dns.Msg) (resp *dns.Msg, err error) {
2323
return u.OnExchange(req)
2424
}
2525

26-
// Close implements the [Upstream] interface for *FakeUpstream.
26+
// Close implements the [proxy.Upstream] interface for *FakeUpstream.
2727
func (u *FakeUpstream) Close() (err error) {
2828
return u.OnClose()
2929
}
3030

31-
// TestMessageConstructor is a fake [dnsmsg.MessageConstructor] implementation
31+
// TestMessageConstructor is a fake [proxy.MessageConstructor] implementation
3232
// for tests.
3333
type TestMessageConstructor struct {
3434
OnNewMsgNXDOMAIN func(req *dns.Msg) (resp *dns.Msg)
@@ -56,19 +56,19 @@ func NewTestMessageConstructor() (c *TestMessageConstructor) {
5656
}
5757
}
5858

59-
// NewMsgNXDOMAIN implements the [MessageConstructor] interface for
59+
// NewMsgNXDOMAIN implements the [proxy.MessageConstructor] interface for
6060
// *TestMessageConstructor.
6161
func (c *TestMessageConstructor) NewMsgNXDOMAIN(req *dns.Msg) (resp *dns.Msg) {
6262
return c.OnNewMsgNXDOMAIN(req)
6363
}
6464

65-
// NewMsgSERVFAIL implements the [MessageConstructor] interface for
65+
// NewMsgSERVFAIL implements the [proxy.MessageConstructor] interface for
6666
// *TestMessageConstructor.
6767
func (c *TestMessageConstructor) NewMsgSERVFAIL(req *dns.Msg) (resp *dns.Msg) {
6868
return c.OnNewMsgSERVFAIL(req)
6969
}
7070

71-
// NewMsgNOTIMPLEMENTED implements the [MessageConstructor] interface for
71+
// NewMsgNOTIMPLEMENTED implements the [proxy.MessageConstructor] interface for
7272
// *TestMessageConstructor.
7373
func (c *TestMessageConstructor) NewMsgNOTIMPLEMENTED(req *dns.Msg) (resp *dns.Msg) {
7474
return c.OnNewMsgNOTIMPLEMENTED(req)

proxy/beforerequest_internal_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88
"time"
99

10+
"github.com/AdguardTeam/dnsproxy/internal/dnsproxytest"
1011
"github.com/AdguardTeam/dnsproxy/upstream"
1112
"github.com/AdguardTeam/golibs/errors"
1213
"github.com/AdguardTeam/golibs/logutil/slogutil"
@@ -56,12 +57,12 @@ func TestProxy_HandleDNSRequest_beforeRequestHandler(t *testing.T) {
5657
Logger: slogutil.NewDiscardLogger(),
5758
TCPListenAddr: []*net.TCPAddr{net.TCPAddrFromAddrPort(localhostAnyPort)},
5859
UpstreamConfig: &UpstreamConfig{
59-
Upstreams: []upstream.Upstream{&fakeUpstream{
60-
onExchange: func(m *dns.Msg) (resp *dns.Msg, err error) {
60+
Upstreams: []upstream.Upstream{&dnsproxytest.FakeUpstream{
61+
OnExchange: func(m *dns.Msg) (resp *dns.Msg, err error) {
6162
return allowedResponse.Copy(), nil
6263
},
63-
onAddress: func() (addr string) { return "general" },
64-
onClose: func() (err error) { return nil },
64+
OnAddress: func() (addr string) { return "general" },
65+
OnClose: func() (err error) { return nil },
6566
}},
6667
},
6768
TrustedProxies: defaultTrustedProxies,

proxy/cache_internal_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010
"time"
1111

12+
"github.com/AdguardTeam/dnsproxy/internal/dnsproxytest"
1213
"github.com/AdguardTeam/dnsproxy/upstream"
1314
"github.com/AdguardTeam/golibs/logutil/slogutil"
1415
"github.com/AdguardTeam/golibs/netutil"
@@ -24,10 +25,10 @@ const testCacheSize = 4096
2425

2526
const testUpsAddr = "https://upstream.address"
2627

27-
var upstreamWithAddr = &fakeUpstream{
28-
onExchange: func(m *dns.Msg) (resp *dns.Msg, err error) { panic("not implemented") },
29-
onClose: func() (err error) { panic("not implemented") },
30-
onAddress: func() (addr string) { return testUpsAddr },
28+
var upstreamWithAddr = &dnsproxytest.FakeUpstream{
29+
OnExchange: func(m *dns.Msg) (resp *dns.Msg, err error) { panic("not implemented") },
30+
OnClose: func() (err error) { panic("not implemented") },
31+
OnAddress: func() (addr string) { return testUpsAddr },
3132
}
3233

3334
func TestServeCached(t *testing.T) {

proxy/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ type Config struct {
5858
// constructor will be used.
5959
MessageConstructor MessageConstructor
6060

61+
// PendingRequests is used to mitigate the cache poisoning attacks by
62+
// tracking identical requests and returning the same response for them,
63+
// performing a single lookup. If nil, it will be enabled by default.
64+
PendingRequests *PendingRequestsConfig
65+
6166
// BeforeRequestHandler is an optional custom handler called before each DNS
6267
// request is started processing, see [BeforeRequestHandler]. The default
6368
// no-op implementation is used, if it's nil.
@@ -248,6 +253,12 @@ type Config struct {
248253
PreferIPv6 bool
249254
}
250255

256+
// PendingRequestsConfig is the configuration for tracking identical requests.
257+
type PendingRequestsConfig struct {
258+
// Enabled defines if the duplicate requests should be tracked.
259+
Enabled bool
260+
}
261+
251262
// validateConfig verifies that the supplied configuration is valid and returns
252263
// an error if it's not.
253264
//

0 commit comments

Comments
 (0)