1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package proxy
import (
"context"
"encoding/binary"
"errors"
"io"
"net"
"time"
"golang.org/x/net/dns/dnsmessage"
"github.com/armanlife/firefox-vpn-client/core/internal/logx"
)
// DNSPort is DNS's port, which Mozilla's proxy does not connect to. A
// client's DNS sent through the proxy over TCP (PassWall2's remote DNS,
// v2rayN's and Clash's DNS through a SOCKS outbound) is therefore answered
// here: each query goes, as it is, to DNS over HTTPS through Mozilla, and
// the answer comes back to the client (RoutedOpener).
const DNSPort = "53"
// dnsIdle ends a client's DNS connection that long after its last query;
// dnsQueryTimeout bounds the answer to one query.
const (
dnsIdle = 30 * time.Second
dnsQueryTimeout = 10 * time.Second
)
// ExchangeFunc sends a DNS query in wire format and returns the answer.
type ExchangeFunc func(ctx context.Context, query []byte) ([]byte, error)
// serveDNS answers DNS over TCP on conn (RFC 1035 4.2.2: each message after
// its length in two bytes), one query after the other, until the client
// closes it or stays idle. A query that cannot be answered gets SERVFAIL;
// what is not a DNS query ends the connection.
func serveDNS(conn net.Conn, exchange ExchangeFunc) {
defer conn.Close()
for {
_ = conn.SetReadDeadline(time.Now().Add(dnsIdle))
var size [2]byte
if _, err := io.ReadFull(conn, size[:]); err != nil {
return
}
query := make([]byte, binary.BigEndian.Uint16(size[:]))
if _, err := io.ReadFull(conn, query); err != nil {
return
}
answer, err := dnsAnswer(query, exchange)
if err != nil {
logx.Debug("DNS through the proxy: %s", logx.Err(err))
return
}
_ = conn.SetWriteDeadline(time.Now().Add(dnsIdle))
binary.BigEndian.PutUint16(size[:], uint16(len(answer)))
if _, err := conn.Write(append(size[:], answer...)); err != nil {
return
}
}
}
// dnsAnswer is the answer to one query: the DNS server's, else SERVFAIL.
func dnsAnswer(query []byte, exchange ExchangeFunc) ([]byte, error) {
var p dnsmessage.Parser
header, err := p.Start(query)
if err != nil || header.Response {
return nil, errors.New("not a DNS query")
}
ctx, cancel := context.WithTimeout(context.Background(), dnsQueryTimeout)
defer cancel()
answer, err := exchange(ctx, query)
if err == nil && len(answer) > 0 && len(answer) <= 0xffff {
return answer, nil
}
if err == nil {
err = errors.New("an empty or oversized answer")
}
logx.Debug("a DNS query through the proxy failed; answering SERVFAIL err=%s", logx.Err(err))
questions, _ := p.AllQuestions()
return (&dnsmessage.Message{
Header: dnsmessage.Header{
ID: header.ID, Response: true, OpCode: header.OpCode,
RecursionDesired: header.RecursionDesired, RecursionAvailable: true,
RCode: dnsmessage.RCodeServerFailure,
},
Questions: questions,
}).Pack()
}