-
Notifications
You must be signed in to change notification settings - Fork 1
/
classic.go
131 lines (115 loc) · 3.15 KB
/
classic.go
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package dnsclient
import (
"context"
"crypto/tls"
"fmt"
"io"
"math/rand"
"net"
"time"
"github.com/miekg/dns"
"golang.org/x/net/proxy"
)
// ClassicDNS provides functionality to create DNS over UDP, DNS over TCP and DNS over TLS
type ClassicDNS struct {
dialer proxy.Dialer
conn net.Conn
proxy string
isTCP bool
isTLS bool
isSkipVerify bool
}
// NewClassicDNS provides a client interface which you can query on
func NewClassicDNS(server net.Addr, UseTCP bool, UseTLS bool, SkipVerify bool, proxy string) (Client, error) {
classic := ClassicDNS{
isTCP: UseTCP,
isTLS: UseTLS,
isSkipVerify: SkipVerify,
proxy: proxy,
}
var err error
// due to the implementation limitation (https://github.com/golang/go/issues/32790)
// UDP will not work behind a proxy
var s *net.UDPAddr
if s, err = net.ResolveUDPAddr(server.Network(), server.String()); err == nil {
classic.conn, err = net.DialUDP(server.Network(), nil, s)
return &classic, err
}
classic.dialer, err = GetDialer(proxy)
if err != nil {
return &classic, err
}
proxiedConnection, err := classic.dialer.Dial(server.Network(), server.String())
if err != nil {
return &classic, err
}
if classic.isTLS && !classic.isTCP {
err = fmt.Errorf("can't use DNS over TLS without TCP")
return &classic, err
}
if classic.isTLS {
tlsCfg := &tls.Config{
InsecureSkipVerify: SkipVerify,
}
classic.conn = tls.Client(proxiedConnection, tlsCfg)
return &classic, nil
}
// TCP
classic.conn = proxiedConnection
return &classic, err
}
// Query takes a dns message and returns a list of resources
func (c *ClassicDNS) Query(ctx context.Context, q *dns.Msg) (responses []dns.RR, rtt time.Duration, err error) {
t1 := time.Now()
fnDone := make(chan bool)
go func() {
co := &dns.Conn{Conn: c.conn}
if err = co.WriteMsg(q); err != nil {
fnDone <- true
}
var r *dns.Msg
r, err = co.ReadMsg()
if err == nil {
if r.Truncated {
err = fmt.Errorf("response for query %d was truncated. consider using a different protocol (TCP) for large queries", r.Id)
} else if r.Id != q.Id {
err = fmt.Errorf("query id (%d) and response id (%d) mismatch", q.Id, r.Id)
} else {
responses = r.Answer
}
} else if err == io.EOF {
// auto-reconnect on connection failure
// NOTE: potentially a chance to make this a configurable item
// wait for a random number of milliseconds before reconnecting
time.Sleep(time.Duration(1000+rand.Intn(1000)) * time.Millisecond)
c.Reconnect()
}
fnDone <- true
}()
for {
select {
case <-fnDone:
rtt = time.Since(t1)
return
case <-ctx.Done():
rtt = time.Since(t1)
err = fmt.Errorf("request timed out")
return
}
}
}
// Close closes the DNS Client
func (c *ClassicDNS) Close() error {
return c.conn.Close()
}
// Reconnect reads the configuration from the running instance, and tries to replace the client
// with a fresh connection on-the-fly
func (c *ClassicDNS) Reconnect() error {
newClient, err := NewClassicDNS(c.conn.RemoteAddr(), c.isTCP, c.isTLS, c.isSkipVerify, c.proxy)
if err != nil {
return err
}
c2 := newClient.(*ClassicDNS)
c.conn = c2.conn
return nil
}