mirror of
https://github.com/AdguardTeam/dnsproxy.git
synced 2026-10-12 10:09:55 +00:00
Pull request: AG-32410-proxy
Squashed commit of the following: commit 0d4b13d220d8a2e1ddf807c36b4677e445821f55 Author: Dimitry Kolyshev <[email protected]> Date: Tue May 28 12:11:16 2024 +0400 proxy: imp code commit 10b564b201deb0bd6d8b9f8a591d1ed1b0934a22 Author: Dimitry Kolyshev <[email protected]> Date: Mon May 27 11:48:11 2024 +0400 proxy: imp docs commit ee010801be05f6ee35538d400d3e2687687a8cd9 Author: Dimitry Kolyshev <[email protected]> Date: Fri May 24 11:41:38 2024 +0300 proxy: imp code commit f3033c104ffb8c7610fba27ca23cfdc07779ea54 Author: Dimitry Kolyshev <[email protected]> Date: Fri May 24 11:39:17 2024 +0300 proxy: imp code commit 222c2e1cc673c5beab3a4f529f46677fa4ff62e4 Author: Dimitry Kolyshev <[email protected]> Date: Fri May 24 11:31:31 2024 +0300 proxy: imp code commit 195526347f581f9199f2d142596c49aaa6cf64a6 Author: Dimitry Kolyshev <[email protected]> Date: Thu May 23 12:46:12 2024 +0300 proxy: imp docs commit f9348cd20b8de8c56c0f9b15dbb2bcfc35e983b4 Author: Dimitry Kolyshev <[email protected]> Date: Thu May 23 12:44:35 2024 +0300 proxy: imp code commit d7b2073786b048b6881dd178052b64af50bbe8a4 Author: Dimitry Kolyshev <[email protected]> Date: Thu May 23 11:59:32 2024 +0300 proxy: imp code commit c976bf6506f19780c761b1f3d619a888676f8d5f Author: Dimitry Kolyshev <[email protected]> Date: Thu May 23 11:35:38 2024 +0300 proxy: imp code commit 15645eb6738800d07f3d110352edc149cc76f196 Author: Dimitry Kolyshev <[email protected]> Date: Thu May 23 11:14:16 2024 +0300 proxy: imp code commit d67444568fa6372cedca9bd87d565b09e3209e55 Author: Dimitry Kolyshev <[email protected]> Date: Thu May 23 10:52:00 2024 +0300 proxy: imp code commit 9eef793f38c6633311b17cdbe3538449c35feb38 Author: Dimitry Kolyshev <[email protected]> Date: Wed May 22 13:24:38 2024 +0300 proxy: imp code commit 9d07061d80c343cd7fc81ef568c684ad4a1827f1 Author: Dimitry Kolyshev <[email protected]> Date: Wed May 22 13:06:58 2024 +0300 proxy: imp code commit ccd43943a7fc14627ac558bd14cf3956d7570dd5 Author: Dimitry Kolyshev <[email protected]> Date: Wed May 22 12:55:46 2024 +0300 proxy: imp code commit bf9bcd3634ddf9377c94494537eb0aa94d44131e Author: Dimitry Kolyshev <[email protected]> Date: Wed May 22 12:42:32 2024 +0300 proxy: imp code commit 7ff481719e1a8947fc868fcf2224162d6db225ba Author: Dimitry Kolyshev <[email protected]> Date: Wed May 22 12:38:03 2024 +0300 proxy: imp code commit de4b8a7e0cdd3a5c2157c53956480d6dfabc7632 Author: Dimitry Kolyshev <[email protected]> Date: Tue May 21 13:52:52 2024 +0300 proxy: imp code commit b28f71b4a18f5cd5d2097719d58eee703a7d5384 Author: Dimitry Kolyshev <[email protected]> Date: Tue May 21 13:46:52 2024 +0300 proxy: imp code commit 3947f376687dfe11374b2969be5f5f6caa8c63ed Author: Dimitry Kolyshev <[email protected]> Date: Tue May 21 13:12:10 2024 +0300 proxy: imp code commit 580c135382652b61dff546bb282c6b042e585562 Author: Dimitry Kolyshev <[email protected]> Date: Tue May 21 12:52:53 2024 +0300 proxy: imp code ... and 6 more commits
This commit is contained in:
+31
-13
@@ -339,28 +339,46 @@ func (p *Proxy) logConfigInfo() {
|
||||
|
||||
// validateListenAddrs returns an error if the addresses are not configured
|
||||
// properly.
|
||||
func (p *Proxy) validateListenAddrs() error {
|
||||
func (p *Proxy) validateListenAddrs() (err error) {
|
||||
if !p.hasListenAddrs() {
|
||||
return errors.Error("no listen address specified")
|
||||
}
|
||||
|
||||
if p.TLSConfig == nil {
|
||||
if p.TLSListenAddr != nil {
|
||||
return errors.Error("cannot create tls listener without tls config")
|
||||
err = p.validateTLSConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid tls configuration: %w", err)
|
||||
}
|
||||
|
||||
if p.DNSCryptResolverCert == nil || p.DNSCryptProviderName == "" {
|
||||
if p.DNSCryptTCPListenAddr != nil {
|
||||
return errors.Error("cannot create dnscrypt tcp listener without dnscrypt config")
|
||||
}
|
||||
|
||||
if p.HTTPSListenAddr != nil {
|
||||
return errors.Error("cannot create https listener without tls config")
|
||||
}
|
||||
|
||||
if p.QUICListenAddr != nil {
|
||||
return errors.Error("cannot create quic listener without tls config")
|
||||
if p.DNSCryptUDPListenAddr != nil {
|
||||
return errors.Error("cannot create dnscrypt udp listener without dnscrypt config")
|
||||
}
|
||||
}
|
||||
|
||||
if (p.DNSCryptTCPListenAddr != nil || p.DNSCryptUDPListenAddr != nil) &&
|
||||
(p.DNSCryptResolverCert == nil || p.DNSCryptProviderName == "") {
|
||||
return errors.Error("cannot create dnscrypt listener without dnscrypt config")
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateTLSConfig returns an error if proxy TLS configuration parameters are
|
||||
// needed but aren't provided.
|
||||
func (p *Proxy) validateTLSConfig() (err error) {
|
||||
if p.TLSConfig != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.TLSListenAddr != nil {
|
||||
return errors.Error("tls listener configuration not found")
|
||||
}
|
||||
|
||||
if p.HTTPSListenAddr != nil {
|
||||
return errors.Error("https listener configuration not found")
|
||||
}
|
||||
|
||||
if p.QUICListenAddr != nil {
|
||||
return errors.Error("quic listener configuration not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+2
-3
@@ -105,12 +105,11 @@ type DNSContext struct {
|
||||
//
|
||||
// TODO(e.burkov): Consider creating DNSContext with this everywhere, to
|
||||
// actually respect the contract of DNSContext.RequestID field.
|
||||
//
|
||||
// TODO(e.burkov): Add remote address into arguments.
|
||||
func (p *Proxy) newDNSContext(proto Proto, req *dns.Msg) (d *DNSContext) {
|
||||
func (p *Proxy) newDNSContext(proto Proto, req *dns.Msg, addr netip.AddrPort) (d *DNSContext) {
|
||||
return &DNSContext{
|
||||
Proto: proto,
|
||||
Req: req,
|
||||
Addr: addr,
|
||||
|
||||
RequestID: p.counter.Add(1),
|
||||
}
|
||||
|
||||
+21
-9
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/AdguardTeam/dnsproxy/proxyutil"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
@@ -18,11 +19,15 @@ type lookupResult struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// lookupIPAddr resolves the specified host IP addresses.
|
||||
// lookupIPAddr resolves the specified host IP addresses. It is intended to be
|
||||
// used as a goroutine.
|
||||
func (p *Proxy) lookupIPAddr(host string, qtype uint16, ch chan *lookupResult) {
|
||||
defer log.OnPanic("Proxy.lookupIPAddr")
|
||||
|
||||
req := (&dns.Msg{}).SetQuestion(host, qtype)
|
||||
|
||||
d := p.newDNSContext(ProtoUDP, req)
|
||||
// TODO(d.kolyshev): Investigate why the client address is not defined.
|
||||
d := p.newDNSContext(ProtoUDP, req, netip.AddrPort{})
|
||||
err := p.Resolve(d)
|
||||
ch <- &lookupResult{d.Res, err}
|
||||
}
|
||||
@@ -36,7 +41,7 @@ var _ upstream.Resolver = (*Proxy)(nil)
|
||||
|
||||
// LookupNetIP implements the [upstream.Resolver] interface for *Proxy. It
|
||||
// resolves the specified host IP addresses by sending two DNS queries (A and
|
||||
// AAAA) in parallel. It returns both results for those two queries.
|
||||
// AAAA) in parallel. It returns both results for those two queries.
|
||||
func (p *Proxy) LookupNetIP(
|
||||
_ context.Context,
|
||||
_ string,
|
||||
@@ -61,12 +66,7 @@ func (p *Proxy) LookupNetIP(
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ans := range result.resp.Answer {
|
||||
a := proxyutil.IPFromRR(ans)
|
||||
if a != (netip.Addr{}) {
|
||||
addrs = append(addrs, a)
|
||||
}
|
||||
}
|
||||
addrs = appendAnswerAddrs(addrs, result.resp.Answer)
|
||||
}
|
||||
|
||||
if len(addrs) == 0 && len(errs) != 0 {
|
||||
@@ -81,3 +81,15 @@ func (p *Proxy) LookupNetIP(
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// appendAnswerAddrs returns addrs with addresses appended from the given ans.
|
||||
func appendAnswerAddrs(addrs []netip.Addr, ans []dns.RR) (res []netip.Addr) {
|
||||
for _, ansRR := range ans {
|
||||
a := proxyutil.IPFromRR(ansRR)
|
||||
if a != (netip.Addr{}) {
|
||||
addrs = append(addrs, a)
|
||||
}
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
+70
-78
@@ -340,6 +340,14 @@ func (p *Proxy) validateBasicAuth() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if proxy is started. It is safe for concurrent use.
|
||||
func (p *Proxy) isStarted() (ok bool) {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
return p.started
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ service.Interface = (*Proxy)(nil)
|
||||
|
||||
@@ -360,11 +368,12 @@ func (p *Proxy) Start(ctx context.Context) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.startListeners(ctx)
|
||||
err = p.configureListeners(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting listeners: %w", err)
|
||||
return fmt.Errorf("configuring listeners: %w", err)
|
||||
}
|
||||
|
||||
p.startListeners()
|
||||
p.started = true
|
||||
|
||||
return nil
|
||||
@@ -458,97 +467,80 @@ func (p *Proxy) Shutdown(_ context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Addrs returns all listen addresses for the specified proto or nil if the proxy does not listen to it.
|
||||
// proto must be "tcp", "tls", "https", "quic", or "udp"
|
||||
func (p *Proxy) Addrs(proto Proto) []net.Addr {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
// addrFunc provides the address from the given A.
|
||||
type addrFunc[A any] func(l A) (addr net.Addr)
|
||||
|
||||
var addrs []net.Addr
|
||||
|
||||
switch proto {
|
||||
case ProtoTCP:
|
||||
for _, l := range p.tcpListen {
|
||||
addrs = append(addrs, l.Addr())
|
||||
}
|
||||
|
||||
case ProtoTLS:
|
||||
for _, l := range p.tlsListen {
|
||||
addrs = append(addrs, l.Addr())
|
||||
}
|
||||
|
||||
case ProtoHTTPS:
|
||||
for _, l := range p.httpsListen {
|
||||
addrs = append(addrs, l.Addr())
|
||||
}
|
||||
|
||||
case ProtoUDP:
|
||||
for _, l := range p.udpListen {
|
||||
addrs = append(addrs, l.LocalAddr())
|
||||
}
|
||||
|
||||
case ProtoQUIC:
|
||||
for _, l := range p.quicListen {
|
||||
addrs = append(addrs, l.Addr())
|
||||
}
|
||||
|
||||
case ProtoDNSCrypt:
|
||||
// Using only UDP addrs here
|
||||
// TODO: to do it better we should either do ProtoDNSCryptTCP/ProtoDNSCryptUDP
|
||||
// or we should change the configuration so that it was not possible to
|
||||
// set different ports for TCP/UDP listeners.
|
||||
for _, l := range p.dnsCryptUDPListen {
|
||||
addrs = append(addrs, l.LocalAddr())
|
||||
}
|
||||
|
||||
default:
|
||||
panic("proto must be 'tcp', 'tls', 'https', 'quic', 'dnscrypt' or 'udp'")
|
||||
// collectAddrs returns the slice of network addresses of the given listeners
|
||||
// using the given addrFunc.
|
||||
func collectAddrs[A any](listeners []A, af addrFunc[A]) (addrs []net.Addr) {
|
||||
for _, l := range listeners {
|
||||
addrs = append(addrs, af(l))
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
// Addr returns the first listen address for the specified proto or null if the proxy does not listen to it
|
||||
// proto must be "tcp", "tls", "https", "quic", or "udp"
|
||||
func (p *Proxy) Addr(proto Proto) net.Addr {
|
||||
// Addrs returns all listen addresses for the specified proto or nil if the
|
||||
// proxy does not listen to it. proto must be one of [Proto]: [ProtoTCP],
|
||||
// [ProtoUDP], [ProtoTLS], [ProtoHTTPS], [ProtoQUIC], or [ProtoDNSCrypt].
|
||||
func (p *Proxy) Addrs(proto Proto) (addrs []net.Addr) {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
switch proto {
|
||||
case ProtoTCP:
|
||||
if len(p.tcpListen) == 0 {
|
||||
return nil
|
||||
}
|
||||
return p.tcpListen[0].Addr()
|
||||
|
||||
return collectAddrs(p.tcpListen, net.Listener.Addr)
|
||||
case ProtoTLS:
|
||||
if len(p.tlsListen) == 0 {
|
||||
return nil
|
||||
}
|
||||
return p.tlsListen[0].Addr()
|
||||
|
||||
return collectAddrs(p.tlsListen, net.Listener.Addr)
|
||||
case ProtoHTTPS:
|
||||
if len(p.httpsListen) == 0 {
|
||||
return nil
|
||||
}
|
||||
return p.httpsListen[0].Addr()
|
||||
|
||||
return collectAddrs(p.httpsListen, net.Listener.Addr)
|
||||
case ProtoUDP:
|
||||
if len(p.udpListen) == 0 {
|
||||
return nil
|
||||
}
|
||||
return p.udpListen[0].LocalAddr()
|
||||
|
||||
return collectAddrs(p.udpListen, (*net.UDPConn).LocalAddr)
|
||||
case ProtoQUIC:
|
||||
if len(p.quicListen) == 0 {
|
||||
return nil
|
||||
}
|
||||
return p.quicListen[0].Addr()
|
||||
|
||||
return collectAddrs(p.quicListen, (*quic.EarlyListener).Addr)
|
||||
case ProtoDNSCrypt:
|
||||
if len(p.dnsCryptUDPListen) == 0 {
|
||||
return nil
|
||||
}
|
||||
return p.dnsCryptUDPListen[0].LocalAddr()
|
||||
// Using only UDP addrs here
|
||||
//
|
||||
// TODO(ameshkov): To do it better we should either do
|
||||
// ProtoDNSCryptTCP/ProtoDNSCryptUDP or we should change the
|
||||
// configuration so that it was not possible to set different ports for
|
||||
// TCP/UDP listeners.
|
||||
return collectAddrs(p.dnsCryptUDPListen, (*net.UDPConn).LocalAddr)
|
||||
default:
|
||||
panic("proto must be 'tcp', 'tls', 'https', 'quic', 'dnscrypt' or 'udp'")
|
||||
}
|
||||
}
|
||||
|
||||
// firstAddr returns the network address of the first listener in the given
|
||||
// listeners or nil using the given addrFunc.
|
||||
func firstAddr[A any](listeners []A, af addrFunc[A]) (addr net.Addr) {
|
||||
if len(listeners) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return af(listeners[0])
|
||||
}
|
||||
|
||||
// Addr returns the first listen address for the specified proto or nil if the
|
||||
// proxy does not listen to it. proto must be one of [Proto]: [ProtoTCP],
|
||||
// [ProtoUDP], [ProtoTLS], [ProtoHTTPS], [ProtoQUIC], or [ProtoDNSCrypt].
|
||||
func (p *Proxy) Addr(proto Proto) (addr net.Addr) {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
|
||||
switch proto {
|
||||
case ProtoTCP:
|
||||
return firstAddr(p.tcpListen, net.Listener.Addr)
|
||||
case ProtoTLS:
|
||||
return firstAddr(p.tlsListen, net.Listener.Addr)
|
||||
case ProtoHTTPS:
|
||||
return firstAddr(p.httpsListen, net.Listener.Addr)
|
||||
case ProtoUDP:
|
||||
return firstAddr(p.udpListen, (*net.UDPConn).LocalAddr)
|
||||
case ProtoQUIC:
|
||||
return firstAddr(p.quicListen, (*quic.EarlyListener).Addr)
|
||||
case ProtoDNSCrypt:
|
||||
return firstAddr(p.dnsCryptUDPListen, (*net.UDPConn).LocalAddr)
|
||||
default:
|
||||
panic("proto must be 'tcp', 'tls', 'https', 'quic', 'dnscrypt' or 'udp'")
|
||||
}
|
||||
|
||||
+64
-35
@@ -335,30 +335,28 @@ func TestProxyRace(t *testing.T) {
|
||||
g.Wait()
|
||||
}
|
||||
|
||||
func TestProxy_Resolve_dnssecCache(t *testing.T) {
|
||||
const host = "example.com"
|
||||
// newTxts returns new test TXT RR strings.
|
||||
func newTxts(t *testing.T, txtDataLen int) (txts []string) {
|
||||
t.Helper()
|
||||
|
||||
const (
|
||||
// Larger than UDP buffer size to invoke truncation.
|
||||
txtDataLen = 1024
|
||||
txtDataChunkLen = 255
|
||||
)
|
||||
const txtDataChunkLen = 255
|
||||
|
||||
txtDataChunkNum := txtDataLen / txtDataChunkLen
|
||||
if txtDataLen%txtDataChunkLen > 0 {
|
||||
txtDataChunkNum++
|
||||
}
|
||||
|
||||
txts := make([]string, txtDataChunkNum)
|
||||
txts = make([]string, txtDataChunkNum)
|
||||
randData := make([]byte, txtDataLen)
|
||||
n, err := rand.Read(randData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, txtDataLen, n)
|
||||
|
||||
for i, c := range randData {
|
||||
randData[i] = c%26 + 'a'
|
||||
}
|
||||
// *dns.TXT requires splitting the actual data into
|
||||
// 256-byte chunks.
|
||||
|
||||
// *dns.TXT requires splitting the actual data into 256-byte chunks.
|
||||
for i := range txtDataChunkNum {
|
||||
r := txtDataChunkLen * (i + 1)
|
||||
if r > txtDataLen {
|
||||
@@ -366,13 +364,60 @@ func TestProxy_Resolve_dnssecCache(t *testing.T) {
|
||||
}
|
||||
txts[i] = string(randData[txtDataChunkLen*i : r])
|
||||
}
|
||||
|
||||
return txts
|
||||
}
|
||||
|
||||
// newDNSContext returns new DNS request message context with Proto set to
|
||||
// [ProtoUDP]. Constructs request message from the given parameters.
|
||||
func newDNSContext(
|
||||
domain string,
|
||||
qtype uint16,
|
||||
qclass uint16,
|
||||
edns bool,
|
||||
udpsize uint16,
|
||||
) (dctx *DNSContext) {
|
||||
req := newReq(domain, qtype, qclass)
|
||||
if edns {
|
||||
req.SetEdns0(udpsize, true)
|
||||
}
|
||||
|
||||
return &DNSContext{
|
||||
Req: req,
|
||||
Proto: ProtoUDP,
|
||||
}
|
||||
}
|
||||
|
||||
// newReq returns new request message for provided parameters.
|
||||
func newReq(domain string, qtype, qclass uint16) (req *dns.Msg) {
|
||||
return &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{{
|
||||
Name: dns.Fqdn(domain),
|
||||
Qtype: qtype,
|
||||
Qclass: qclass,
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxy_Resolve_dnssecCache(t *testing.T) {
|
||||
const (
|
||||
host = "example.com"
|
||||
|
||||
// Larger than UDP buffer size to invoke truncation.
|
||||
txtDataLen = 1024
|
||||
)
|
||||
|
||||
txt := &dns.TXT{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: dns.Fqdn(host),
|
||||
Rrtype: dns.TypeTXT,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
Txt: txts,
|
||||
Txt: newTxts(t, txtDataLen),
|
||||
}
|
||||
|
||||
a := &dns.A{
|
||||
@@ -406,10 +451,10 @@ func TestProxy_Resolve_dnssecCache(t *testing.T) {
|
||||
SignerName: dns.Fqdn(host),
|
||||
Signature: "c29tZSBycnNpZyByZWxhdGVkIHN0dWZm",
|
||||
}
|
||||
|
||||
u := &fakeUpstream{
|
||||
onExchange: func(m *dns.Msg) (resp *dns.Msg, err error) {
|
||||
resp = &dns.Msg{}
|
||||
resp.SetReply(m)
|
||||
resp = (&dns.Msg{}).SetReply(m)
|
||||
|
||||
q := m.Question[0]
|
||||
switch q.Qtype {
|
||||
@@ -490,29 +535,13 @@ func TestProxy_Resolve_dnssecCache(t *testing.T) {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
req := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
},
|
||||
Compress: true,
|
||||
Question: []dns.Question{{
|
||||
Name: dns.Fqdn(tc.wantAns.Header().Name),
|
||||
Qtype: tc.wantAns.Header().Rrtype,
|
||||
Qclass: tc.wantAns.Header().Class,
|
||||
}},
|
||||
}
|
||||
if tc.edns {
|
||||
req.SetEdns0(txtDataLen/2, true)
|
||||
}
|
||||
|
||||
dctx := &DNSContext{
|
||||
Req: req,
|
||||
Proto: ProtoUDP,
|
||||
}
|
||||
ansHdr := tc.wantAns.Header()
|
||||
dctx := newDNSContext(ansHdr.Name, ansHdr.Rrtype, ansHdr.Class, tc.edns, txtDataLen/2)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Cleanup(p.cache.items.Clear)
|
||||
err = p.Resolve(dctx)
|
||||
|
||||
err := p.Resolve(dctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
res := dctx.Res
|
||||
@@ -536,6 +565,7 @@ func TestProxy_Resolve_dnssecCache(t *testing.T) {
|
||||
cached, expired, key := p.cache.get(dctx.Req)
|
||||
require.NotNil(t, cached)
|
||||
require.Len(t, cached.m.Answer, 2)
|
||||
|
||||
assert.False(t, expired)
|
||||
assert.Equal(t, key, msgToKey(dctx.Req))
|
||||
|
||||
@@ -1575,8 +1605,7 @@ func TestProxy_HandleDNSRequest_private(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dctx := p.newDNSContext(ProtoUDP, tc.req)
|
||||
dctx.Addr = tc.cliAddr
|
||||
dctx := p.newDNSContext(ProtoUDP, tc.req, tc.cliAddr)
|
||||
|
||||
require.NoError(t, p.handleDNSRequest(dctx))
|
||||
assert.Equal(t, tc.want, dctx.Res)
|
||||
|
||||
+8
-5
@@ -13,9 +13,9 @@ import (
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
// startListeners configures and starts listener loops
|
||||
func (p *Proxy) startListeners(ctx context.Context) error {
|
||||
err := p.createUDPListeners(ctx)
|
||||
// configureListeners configures listeners.
|
||||
func (p *Proxy) configureListeners(ctx context.Context) (err error) {
|
||||
err = p.createUDPListeners(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -45,6 +45,11 @@ func (p *Proxy) startListeners(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// startListeners starts listener loops.
|
||||
func (p *Proxy) startListeners() {
|
||||
for _, l := range p.udpListen {
|
||||
go p.udpPacketLoop(l, p.requestsSema)
|
||||
}
|
||||
@@ -76,8 +81,6 @@ func (p *Proxy) startListeners(ctx context.Context) error {
|
||||
for _, l := range p.dnsCryptTCPListen {
|
||||
go func(l net.Listener) { _ = p.dnsCryptServer.ServeTCP(l) }(l)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleDNSRequest processes the context. The only error it returns is the one
|
||||
|
||||
@@ -71,8 +71,7 @@ var _ dnscrypt.Handler = &dnsCryptHandler{}
|
||||
|
||||
// ServeDNS - processes the DNS query
|
||||
func (h *dnsCryptHandler) ServeDNS(rw dnscrypt.ResponseWriter, req *dns.Msg) (err error) {
|
||||
d := h.proxy.newDNSContext(ProtoDNSCrypt, req)
|
||||
d.Addr = netutil.NetAddrToAddrPort(rw.RemoteAddr())
|
||||
d := h.proxy.newDNSContext(ProtoDNSCrypt, req, netutil.NetAddrToAddrPort(rw.RemoteAddr()))
|
||||
d.DNSCryptResponseWriter = rw
|
||||
|
||||
// TODO(d.kolyshev): Pass and use context from above.
|
||||
|
||||
+65
-49
@@ -90,12 +90,62 @@ func (p *Proxy) createHTTPSListeners() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newDoHReq returns new DNS request parsed from the given HTTP request. In
|
||||
// case of invalid request returns nil and the suitable status code for an HTTP
|
||||
// error response.
|
||||
func newDoHReq(r *http.Request) (req *dns.Msg, statusCode int) {
|
||||
var buf []byte
|
||||
var err error
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
dnsParam := r.URL.Query().Get("dns")
|
||||
buf, err = base64.RawURLEncoding.DecodeString(dnsParam)
|
||||
if len(buf) == 0 || err != nil {
|
||||
log.Debug("dnsproxy: parsing dns request from get param %q: %v", dnsParam, err)
|
||||
|
||||
return nil, http.StatusBadRequest
|
||||
}
|
||||
case http.MethodPost:
|
||||
contentType := r.Header.Get(httphdr.ContentType)
|
||||
if contentType != "application/dns-message" {
|
||||
log.Debug("dnsproxy: unsupported media type %q", contentType)
|
||||
|
||||
return nil, http.StatusUnsupportedMediaType
|
||||
}
|
||||
|
||||
// TODO(d.kolyshev): Limit reader.
|
||||
buf, err = io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Debug("dnsproxy: reading http request body: %s", err)
|
||||
|
||||
return nil, http.StatusBadRequest
|
||||
}
|
||||
|
||||
defer log.OnCloserError(r.Body, log.DEBUG)
|
||||
default:
|
||||
log.Debug("dnsproxy: bad http method %q", r.Method)
|
||||
|
||||
return nil, http.StatusMethodNotAllowed
|
||||
}
|
||||
|
||||
req = &dns.Msg{}
|
||||
if err = req.Unpack(buf); err != nil {
|
||||
log.Debug("dnsproxy: unpacking http msg: %s", err)
|
||||
|
||||
return nil, http.StatusBadRequest
|
||||
}
|
||||
|
||||
return req, http.StatusOK
|
||||
}
|
||||
|
||||
// ServeHTTP is the http.Handler implementation that handles DoH queries.
|
||||
//
|
||||
// Here is what it returns:
|
||||
//
|
||||
// - http.StatusBadRequest if there is no DNS request data;
|
||||
// - http.StatusBadRequest if there is no DNS request data,
|
||||
// - http.StatusUnsupportedMediaType if request content type is not
|
||||
// "application/dns-message";
|
||||
// "application/dns-message",
|
||||
// - http.StatusMethodNotAllowed if request method is not GET or POST.
|
||||
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log.Debug("dnsproxy: incoming https request on %s", r.URL)
|
||||
@@ -109,65 +159,31 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
dnsParam := r.URL.Query().Get("dns")
|
||||
buf, err = base64.RawURLEncoding.DecodeString(dnsParam)
|
||||
if len(buf) == 0 || err != nil {
|
||||
log.Debug("dnsproxy: parsing dns request from get param %q: %v", dnsParam, err)
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
case http.MethodPost:
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if contentType != "application/dns-message" {
|
||||
log.Debug("dnsproxy: unsupported media type %q", contentType)
|
||||
http.Error(w, http.StatusText(http.StatusUnsupportedMediaType), http.StatusUnsupportedMediaType)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
buf, err = io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Debug("dnsproxy: reading http request body: %s", err)
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer log.OnCloserError(r.Body, log.DEBUG)
|
||||
default:
|
||||
log.Debug("dnsproxy: bad http method %q", r.Method)
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
req, statusCode := newDoHReq(r)
|
||||
if req == nil {
|
||||
http.Error(w, http.StatusText(statusCode), statusCode)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
req := &dns.Msg{}
|
||||
if err = req.Unpack(buf); err != nil {
|
||||
log.Debug("dnsproxy: unpacking http msg: %s", err)
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
d := p.newDNSContext(ProtoHTTPS, req)
|
||||
d.Addr = raddr
|
||||
d.HTTPRequest = r
|
||||
d.HTTPResponseWriter = w
|
||||
|
||||
if prx.IsValid() {
|
||||
log.Debug("dnsproxy: request came from proxy server %s", prx)
|
||||
|
||||
if !p.TrustedProxies.Contains(prx.Addr()) {
|
||||
log.Debug("dnsproxy: proxy %s is not trusted, using original remote addr", prx)
|
||||
d.Addr = prx
|
||||
|
||||
// So the address of the proxy itself is used, as the remote address
|
||||
// parsed from headers cannot be trusted.
|
||||
//
|
||||
// TODO(e.burkov): Do not parse headers in this case.
|
||||
raddr = prx
|
||||
}
|
||||
}
|
||||
|
||||
d := p.newDNSContext(ProtoHTTPS, req, raddr)
|
||||
d.HTTPRequest = r
|
||||
d.HTTPResponseWriter = w
|
||||
|
||||
err = p.handleDNSRequest(d)
|
||||
if err != nil {
|
||||
log.Debug("dnsproxy: handling dns (%s) request: %s", d.Proto, err)
|
||||
|
||||
@@ -237,8 +237,7 @@ func (p *Proxy) handleQUICStream(stream quic.Stream, conn quic.Connection) {
|
||||
return
|
||||
}
|
||||
|
||||
d := p.newDNSContext(ProtoQUIC, req)
|
||||
d.Addr = netutil.NetAddrToAddrPort(conn.RemoteAddr())
|
||||
d := p.newDNSContext(ProtoQUIC, req, netutil.NetAddrToAddrPort(conn.RemoteAddr()))
|
||||
d.QUICStream = stream
|
||||
d.QUICConnection = conn
|
||||
d.DoQVersion = doqVersion
|
||||
|
||||
+42
-36
@@ -58,8 +58,8 @@ func (p *Proxy) createTLSListeners() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// tcpPacketLoop listens for incoming TCP packets. proto must be either "tcp"
|
||||
// or "tls".
|
||||
// tcpPacketLoop listens for incoming TCP packets. proto must be either
|
||||
// [ProtoTCP] or [ProtoTLS].
|
||||
//
|
||||
// See also the comment on Proxy.requestsSema.
|
||||
func (p *Proxy) tcpPacketLoop(l net.Listener, proto Proto, reqSema syncutil.Semaphore) {
|
||||
@@ -68,11 +68,7 @@ func (p *Proxy) tcpPacketLoop(l net.Listener, proto Proto, reqSema syncutil.Sema
|
||||
for {
|
||||
clientConn, err := l.Accept()
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
log.Debug("dnsproxy: tcp connection %s closed", l.Addr())
|
||||
} else {
|
||||
log.Error("dnsproxy: reading from tcp: %s", err)
|
||||
}
|
||||
logTCPConnError(err, l)
|
||||
|
||||
break
|
||||
}
|
||||
@@ -84,21 +80,25 @@ func (p *Proxy) tcpPacketLoop(l net.Listener, proto Proto, reqSema syncutil.Sema
|
||||
|
||||
break
|
||||
}
|
||||
go func() {
|
||||
defer reqSema.Release()
|
||||
|
||||
p.handleTCPConnection(clientConn, proto)
|
||||
}()
|
||||
go p.handleTCPConnection(clientConn, proto, reqSema)
|
||||
}
|
||||
}
|
||||
|
||||
// logTCPConnError writes suitable log message for given err.
|
||||
func logTCPConnError(err error, l net.Listener) {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
log.Debug("dnsproxy: tcp connection %s closed", l.Addr())
|
||||
} else {
|
||||
log.Error("dnsproxy: reading from tcp: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleTCPConnection starts a loop that handles an incoming TCP connection.
|
||||
// proto must be either ProtoTCP or ProtoTLS.
|
||||
func (p *Proxy) handleTCPConnection(conn net.Conn, proto Proto) {
|
||||
// proto must be either [ProtoTCP] or [ProtoTLS].
|
||||
func (p *Proxy) handleTCPConnection(conn net.Conn, proto Proto, reqSema syncutil.Semaphore) {
|
||||
defer log.OnPanic("proxy.handleTCPConnection")
|
||||
|
||||
log.Debug("dnsproxy: handling new %s request from %s", proto, conn.RemoteAddr())
|
||||
|
||||
defer reqSema.Release()
|
||||
defer func() {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
@@ -106,36 +106,21 @@ func (p *Proxy) handleTCPConnection(conn net.Conn, proto Proto) {
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
p.RLock()
|
||||
if !p.started {
|
||||
return
|
||||
}
|
||||
p.RUnlock()
|
||||
log.Debug("dnsproxy: handling new %s request from %s", proto, conn.RemoteAddr())
|
||||
|
||||
for p.isStarted() {
|
||||
err := conn.SetDeadline(time.Now().Add(defaultTimeout))
|
||||
if err != nil {
|
||||
// Consider deadline errors non-critical.
|
||||
logWithNonCrit(err, "handling tcp: setting deadline")
|
||||
}
|
||||
|
||||
packet, err := readPrefixed(conn)
|
||||
if err != nil {
|
||||
logWithNonCrit(err, "handling tcp: reading msg")
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
req := &dns.Msg{}
|
||||
err = req.Unpack(packet)
|
||||
if err != nil {
|
||||
log.Error("dnsproxy: handling tcp: unpacking msg: %s", err)
|
||||
|
||||
req := readDNSReq(conn)
|
||||
if req == nil {
|
||||
return
|
||||
}
|
||||
|
||||
d := p.newDNSContext(proto, req)
|
||||
d.Addr = netutil.NetAddrToAddrPort(conn.RemoteAddr())
|
||||
d := p.newDNSContext(proto, req, netutil.NetAddrToAddrPort(conn.RemoteAddr()))
|
||||
d.Conn = conn
|
||||
|
||||
err = p.handleDNSRequest(d)
|
||||
@@ -145,6 +130,27 @@ func (p *Proxy) handleTCPConnection(conn net.Conn, proto Proto) {
|
||||
}
|
||||
}
|
||||
|
||||
// readDNSReq returns DNS request message from the given connection or nil if
|
||||
// it failed to read it. Properly logs the error if it happened.
|
||||
func readDNSReq(conn net.Conn) (req *dns.Msg) {
|
||||
packet, err := readPrefixed(conn)
|
||||
if err != nil {
|
||||
logWithNonCrit(err, "handling tcp: reading msg")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
req = &dns.Msg{}
|
||||
err = req.Unpack(packet)
|
||||
if err != nil {
|
||||
log.Error("dnsproxy: handling tcp: unpacking msg: %s", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
// errTooLarge means that a DNS message is larger than 64KiB.
|
||||
const errTooLarge errors.Error = "dns message is too large"
|
||||
|
||||
|
||||
+17
-17
@@ -66,18 +66,13 @@ func (p *Proxy) udpPacketLoop(conn *net.UDPConn, reqSema syncutil.Semaphore) {
|
||||
log.Info("dnsproxy: entering udp listener loop on %s", conn.LocalAddr())
|
||||
|
||||
b := make([]byte, dns.MaxMsgSize)
|
||||
for {
|
||||
p.RLock()
|
||||
if !p.started {
|
||||
return
|
||||
}
|
||||
p.RUnlock()
|
||||
|
||||
for p.isStarted() {
|
||||
n, localIP, remoteAddr, err := proxynetutil.UDPRead(conn, b, p.udpOOBSize)
|
||||
// documentation says to handle the packet even if err occurs, so do that first
|
||||
// The documentation says to handle the packet even if err occurs.
|
||||
if n > 0 {
|
||||
// make a copy of all bytes because ReadFrom() will overwrite contents of b on next call
|
||||
// we need the contents to survive the call because we're handling them in goroutine
|
||||
// Make a copy of all bytes because ReadFrom() will overwrite the
|
||||
// contents of b on the next call. We need that contents to sustain
|
||||
// the call because we're handling them in goroutines.
|
||||
packet := make([]byte, n)
|
||||
copy(packet, b)
|
||||
|
||||
@@ -94,18 +89,24 @@ func (p *Proxy) udpPacketLoop(conn *net.UDPConn, reqSema syncutil.Semaphore) {
|
||||
p.udpHandlePacket(packet, localIP, remoteAddr, conn)
|
||||
}()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
log.Debug("dnsproxy: udp connection %s closed", conn.LocalAddr())
|
||||
} else {
|
||||
log.Error("dnsproxy: reading from udp: %s", err)
|
||||
}
|
||||
logUDPConnError(err, conn)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logUDPConnError writes suitable log message for given err.
|
||||
func logUDPConnError(err error, conn *net.UDPConn) {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
log.Debug("dnsproxy: udp connection %s closed", conn.LocalAddr())
|
||||
} else {
|
||||
log.Error("dnsproxy: reading from udp: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// udpHandlePacket processes the incoming UDP packet and sends a DNS response
|
||||
func (p *Proxy) udpHandlePacket(
|
||||
packet []byte,
|
||||
@@ -123,8 +124,7 @@ func (p *Proxy) udpHandlePacket(
|
||||
return
|
||||
}
|
||||
|
||||
d := p.newDNSContext(ProtoUDP, req)
|
||||
d.Addr = netutil.NetAddrToAddrPort(remoteAddr)
|
||||
d := p.newDNSContext(ProtoUDP, req, netutil.NetAddrToAddrPort(remoteAddr))
|
||||
d.Conn = conn
|
||||
d.localIP = localIP
|
||||
|
||||
|
||||
@@ -156,12 +156,12 @@ run_linter gocyclo --over 10\
|
||||
./internal/netutil/\
|
||||
./internal/version/\
|
||||
./fastip/\
|
||||
./proxy/\
|
||||
./proxyutil/\
|
||||
./upstream/\
|
||||
;
|
||||
|
||||
run_linter gocyclo --over 20 ./main.go
|
||||
run_linter gocyclo --over 15 ./proxy/
|
||||
|
||||
# TODO(a.garipov): Enable for all.
|
||||
run_linter gocognit --over 10\
|
||||
@@ -169,12 +169,12 @@ run_linter gocognit --over 10\
|
||||
./internal/netutil/\
|
||||
./internal/version/\
|
||||
./fastip/\
|
||||
./proxy/\
|
||||
./proxyutil/\
|
||||
./upstream/\
|
||||
;
|
||||
|
||||
run_linter gocognit --over 35 ./main.go
|
||||
run_linter gocognit --over 17 ./proxy/
|
||||
|
||||
run_linter ineffassign ./...
|
||||
|
||||
|
||||
Reference in New Issue
Block a user