2018-07-29 23:44:23 +08:00
|
|
|
package dns
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
2018-08-01 00:09:55 +08:00
|
|
|
"errors"
|
2018-07-29 23:44:23 +08:00
|
|
|
"io"
|
2018-08-01 00:09:55 +08:00
|
|
|
"net"
|
2018-07-29 23:44:23 +08:00
|
|
|
"strings"
|
2018-08-05 23:41:34 +08:00
|
|
|
"time"
|
2018-07-29 23:44:23 +08:00
|
|
|
|
|
|
|
"github.com/nadoo/glider/common/log"
|
2020-08-23 23:23:30 +08:00
|
|
|
"github.com/nadoo/glider/common/pool"
|
2018-07-29 23:44:23 +08:00
|
|
|
"github.com/nadoo/glider/proxy"
|
|
|
|
)
|
|
|
|
|
2020-09-25 11:04:13 +08:00
|
|
|
// AnswerHandler function handles the dns TypeA or TypeAAAA answer.
|
|
|
|
type AnswerHandler func(domain, ip string) error
|
2018-07-29 23:44:23 +08:00
|
|
|
|
2019-03-12 23:32:23 +08:00
|
|
|
// Config for dns.
|
2018-08-07 19:43:52 +08:00
|
|
|
type Config struct {
|
2018-08-26 22:36:14 +08:00
|
|
|
Servers []string
|
|
|
|
Timeout int
|
|
|
|
MaxTTL int
|
|
|
|
MinTTL int
|
|
|
|
Records []string
|
|
|
|
AlwaysTCP bool
|
2018-08-07 19:43:52 +08:00
|
|
|
}
|
|
|
|
|
2019-03-12 23:32:23 +08:00
|
|
|
// Client is a dns client struct.
|
2018-07-29 23:44:23 +08:00
|
|
|
type Client struct {
|
2019-09-18 19:40:14 +08:00
|
|
|
proxy proxy.Proxy
|
2018-08-01 00:09:55 +08:00
|
|
|
cache *Cache
|
2018-08-07 19:43:52 +08:00
|
|
|
config *Config
|
2020-05-06 20:10:18 +08:00
|
|
|
upStream *UPStream
|
|
|
|
upStreamMap map[string]*UPStream
|
2020-09-25 11:04:13 +08:00
|
|
|
handlers []AnswerHandler
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2019-03-12 23:32:23 +08:00
|
|
|
// NewClient returns a new dns client.
|
2019-09-18 19:40:14 +08:00
|
|
|
func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) {
|
2018-07-29 23:44:23 +08:00
|
|
|
c := &Client{
|
2019-09-18 19:40:14 +08:00
|
|
|
proxy: proxy,
|
2020-08-23 23:23:30 +08:00
|
|
|
cache: NewCache(true),
|
2018-08-07 19:43:52 +08:00
|
|
|
config: config,
|
2020-05-06 20:10:18 +08:00
|
|
|
upStream: NewUPStream(config.Servers),
|
|
|
|
upStreamMap: make(map[string]*UPStream),
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2018-08-12 12:37:25 +08:00
|
|
|
// custom records
|
|
|
|
for _, record := range config.Records {
|
|
|
|
c.AddRecord(record)
|
|
|
|
}
|
|
|
|
|
2018-07-29 23:44:23 +08:00
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2019-03-12 23:32:23 +08:00
|
|
|
// Exchange handles request message and returns response message.
|
2020-08-21 23:54:18 +08:00
|
|
|
// NOTE: reqBytes = reqLen + reqMsg.
|
2018-08-02 00:11:22 +08:00
|
|
|
func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([]byte, error) {
|
2018-07-30 01:05:08 +08:00
|
|
|
req, err := UnmarshalMessage(reqBytes[2:])
|
2018-07-29 23:44:23 +08:00
|
|
|
if err != nil {
|
2018-08-01 00:09:55 +08:00
|
|
|
return nil, err
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2018-07-30 01:05:08 +08:00
|
|
|
if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA {
|
2020-08-23 23:23:30 +08:00
|
|
|
v := c.cache.GetCopy(qKey(req.Question))
|
|
|
|
if len(v) > 4 {
|
2018-08-01 00:09:55 +08:00
|
|
|
binary.BigEndian.PutUint16(v[2:4], req.ID)
|
|
|
|
log.F("[dns] %s <-> cache, type: %d, %s",
|
|
|
|
clientAddr, req.Question.QTYPE, req.Question.QNAME)
|
|
|
|
|
|
|
|
return v, nil
|
|
|
|
}
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2019-09-18 22:08:48 +08:00
|
|
|
dnsServer, network, dialerAddr, respBytes, err := c.exchange(req.Question.QNAME, reqBytes, preferTCP)
|
2018-07-29 23:44:23 +08:00
|
|
|
if err != nil {
|
2018-08-01 00:09:55 +08:00
|
|
|
return nil, err
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2018-07-30 01:05:08 +08:00
|
|
|
if req.Question.QTYPE != QTypeA && req.Question.QTYPE != QTypeAAAA {
|
2019-09-18 22:08:48 +08:00
|
|
|
log.F("[dns] %s <-> %s(%s) via %s, type: %d, %s",
|
|
|
|
clientAddr, dnsServer, network, dialerAddr, req.Question.QTYPE, req.Question.QNAME)
|
2018-08-01 00:09:55 +08:00
|
|
|
return respBytes, nil
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2018-08-01 00:09:55 +08:00
|
|
|
resp, err := UnmarshalMessage(respBytes[2:])
|
2018-07-29 23:44:23 +08:00
|
|
|
if err != nil {
|
2018-08-01 00:09:55 +08:00
|
|
|
return respBytes, err
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2020-04-13 00:55:11 +08:00
|
|
|
ips, ttl := c.extractAnswer(resp)
|
|
|
|
|
|
|
|
// add to cache only when there's a valid ip address
|
|
|
|
if len(ips) != 0 && ttl > 0 {
|
2020-08-23 23:23:30 +08:00
|
|
|
c.cache.Put(qKey(resp.Question), respBytes, ttl)
|
2020-04-13 00:55:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
log.F("[dns] %s <-> %s(%s) via %s, type: %d, %s: %s",
|
|
|
|
clientAddr, dnsServer, network, dialerAddr, resp.Question.QTYPE, resp.Question.QNAME, strings.Join(ips, ","))
|
|
|
|
|
|
|
|
return respBytes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) extractAnswer(resp *Message) ([]string, int) {
|
|
|
|
var ips []string
|
2018-08-07 19:43:52 +08:00
|
|
|
ttl := c.config.MinTTL
|
2018-07-30 01:05:08 +08:00
|
|
|
for _, answer := range resp.Answers {
|
|
|
|
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
|
2018-08-01 00:09:55 +08:00
|
|
|
for _, h := range c.handlers {
|
2018-07-30 01:05:08 +08:00
|
|
|
h(resp.Question.QNAME, answer.IP)
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
if answer.IP != "" {
|
2018-07-30 00:18:10 +08:00
|
|
|
ips = append(ips, answer.IP)
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
2018-08-08 00:03:32 +08:00
|
|
|
if answer.TTL != 0 {
|
|
|
|
ttl = int(answer.TTL)
|
|
|
|
}
|
2018-08-01 00:36:11 +08:00
|
|
|
}
|
2018-08-01 00:09:55 +08:00
|
|
|
}
|
|
|
|
|
2018-08-07 19:43:52 +08:00
|
|
|
if ttl > c.config.MaxTTL {
|
|
|
|
ttl = c.config.MaxTTL
|
|
|
|
} else if ttl < c.config.MinTTL {
|
|
|
|
ttl = c.config.MinTTL
|
|
|
|
}
|
|
|
|
|
2020-04-13 00:55:11 +08:00
|
|
|
return ips, ttl
|
2018-08-02 00:11:22 +08:00
|
|
|
}
|
|
|
|
|
2019-03-12 23:32:23 +08:00
|
|
|
// exchange choose a upstream dns server based on qname, communicate with it on the network.
|
2019-09-19 18:03:48 +08:00
|
|
|
func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
|
|
|
server, network, dialerAddr string, respBytes []byte, err error) {
|
|
|
|
|
2018-08-02 00:11:22 +08:00
|
|
|
// use tcp to connect upstream server default
|
|
|
|
network = "tcp"
|
2019-09-18 19:40:14 +08:00
|
|
|
dialer := c.proxy.NextDialer(qname + ":53")
|
2018-08-02 00:11:22 +08:00
|
|
|
|
2018-08-14 22:32:55 +08:00
|
|
|
// if we are resolving the dialer's domain, then use Direct to avoid denpency loop
|
2019-09-19 18:03:48 +08:00
|
|
|
// TODO: dialer.Addr() == "REJECT", tricky
|
2019-03-08 00:14:37 +08:00
|
|
|
if strings.Contains(dialer.Addr(), qname) || dialer.Addr() == "REJECT" {
|
2018-08-15 00:54:17 +08:00
|
|
|
dialer = proxy.Default
|
2018-08-14 22:32:55 +08:00
|
|
|
}
|
|
|
|
|
2018-08-02 00:11:22 +08:00
|
|
|
// If client uses udp and no forwarders specified, use udp
|
2019-03-08 00:14:37 +08:00
|
|
|
// TODO: dialer.Addr() == "DIRECT", tricky
|
2018-08-26 22:36:14 +08:00
|
|
|
if !preferTCP && !c.config.AlwaysTCP && dialer.Addr() == "DIRECT" {
|
2018-08-02 00:11:22 +08:00
|
|
|
network = "udp"
|
|
|
|
}
|
|
|
|
|
2020-05-02 20:02:19 +08:00
|
|
|
ups := c.UpStream(qname)
|
|
|
|
server = ups.Server()
|
|
|
|
for i := 0; i < ups.Len(); i++ {
|
2018-08-07 19:43:52 +08:00
|
|
|
var rc net.Conn
|
2019-09-18 19:40:14 +08:00
|
|
|
rc, err = dialer.Dial(network, server)
|
2018-08-05 23:41:34 +08:00
|
|
|
if err != nil {
|
2020-05-03 20:02:11 +08:00
|
|
|
newServer := ups.SwitchIf(server)
|
|
|
|
log.F("[dns] error in resolving %s, failed to connect to server %v via %s: %v, switch to %s",
|
|
|
|
qname, server, dialer.Addr(), err, newServer)
|
|
|
|
server = newServer
|
2018-08-05 23:41:34 +08:00
|
|
|
continue
|
|
|
|
}
|
2018-08-06 00:46:07 +08:00
|
|
|
defer rc.Close()
|
2018-07-29 23:44:23 +08:00
|
|
|
|
2018-08-06 08:48:13 +08:00
|
|
|
// TODO: support timeout setting for different upstream server
|
2020-05-05 01:30:57 +08:00
|
|
|
if c.config.Timeout > 0 {
|
|
|
|
rc.SetDeadline(time.Now().Add(time.Duration(c.config.Timeout) * time.Second))
|
|
|
|
}
|
2018-08-06 08:48:13 +08:00
|
|
|
|
2018-08-05 23:41:34 +08:00
|
|
|
switch network {
|
|
|
|
case "tcp":
|
|
|
|
respBytes, err = c.exchangeTCP(rc, reqBytes)
|
|
|
|
case "udp":
|
|
|
|
respBytes, err = c.exchangeUDP(rc, reqBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
2018-08-06 08:03:07 +08:00
|
|
|
|
2020-05-03 20:02:11 +08:00
|
|
|
newServer := ups.SwitchIf(server)
|
2020-05-02 20:02:19 +08:00
|
|
|
log.F("[dns] error in resolving %s, failed to exchange with server %v via %s: %v, switch to %s",
|
|
|
|
qname, server, dialer.Addr(), err, newServer)
|
|
|
|
|
|
|
|
server = newServer
|
2018-08-02 00:11:22 +08:00
|
|
|
}
|
|
|
|
|
2020-05-02 20:02:19 +08:00
|
|
|
// if all dns upstreams failed, then maybe the forwarder is not available.
|
2020-04-28 15:18:19 +08:00
|
|
|
if err != nil {
|
|
|
|
c.proxy.Record(dialer, false)
|
|
|
|
}
|
2020-05-02 20:02:19 +08:00
|
|
|
|
2019-09-18 22:08:48 +08:00
|
|
|
return server, network, dialer.Addr(), respBytes, err
|
2018-08-02 00:11:22 +08:00
|
|
|
}
|
|
|
|
|
2019-03-12 23:32:23 +08:00
|
|
|
// exchangeTCP exchange with server over tcp.
|
2018-08-02 00:11:22 +08:00
|
|
|
func (c *Client) exchangeTCP(rc net.Conn, reqBytes []byte) ([]byte, error) {
|
|
|
|
if _, err := rc.Write(reqBytes); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var respLen uint16
|
|
|
|
if err := binary.Read(rc, binary.BigEndian, &respLen); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-08-23 23:23:30 +08:00
|
|
|
respBytes := pool.GetBuffer(int(respLen) + 2)
|
2018-08-02 00:11:22 +08:00
|
|
|
binary.BigEndian.PutUint16(respBytes[:2], respLen)
|
|
|
|
|
|
|
|
_, err := io.ReadFull(rc, respBytes[2:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-29 23:44:23 +08:00
|
|
|
|
2018-08-01 00:09:55 +08:00
|
|
|
return respBytes, nil
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2019-03-12 23:32:23 +08:00
|
|
|
// exchangeUDP exchange with server over udp.
|
2018-08-02 00:11:22 +08:00
|
|
|
func (c *Client) exchangeUDP(rc net.Conn, reqBytes []byte) ([]byte, error) {
|
|
|
|
if _, err := rc.Write(reqBytes[2:]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-08-23 23:23:30 +08:00
|
|
|
respBytes := pool.GetBuffer(UDPMaxLen)
|
2020-05-05 01:30:57 +08:00
|
|
|
n, err := rc.Read(respBytes[2:])
|
2018-08-02 00:11:22 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-05 01:30:57 +08:00
|
|
|
binary.BigEndian.PutUint16(respBytes[:2], uint16(n))
|
2018-08-02 00:11:22 +08:00
|
|
|
|
2020-05-05 01:30:57 +08:00
|
|
|
return respBytes[:2+n], nil
|
2018-08-02 00:11:22 +08:00
|
|
|
}
|
|
|
|
|
2019-03-12 23:32:23 +08:00
|
|
|
// SetServers sets upstream dns servers for the given domain.
|
2020-05-02 20:02:19 +08:00
|
|
|
func (c *Client) SetServers(domain string, servers []string) {
|
2020-08-16 12:00:46 +08:00
|
|
|
c.upStreamMap[strings.ToLower(domain)] = NewUPStream(servers)
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2020-05-02 20:02:19 +08:00
|
|
|
// UpStream returns upstream dns server for the given domain.
|
2020-05-06 20:10:18 +08:00
|
|
|
func (c *Client) UpStream(domain string) *UPStream {
|
2020-08-16 12:00:46 +08:00
|
|
|
domain = strings.ToLower(domain)
|
|
|
|
for i := len(domain); i != -1; {
|
|
|
|
i = strings.LastIndexByte(domain[:i], '.')
|
|
|
|
if upstream, ok := c.upStreamMap[domain[i+1:]]; ok {
|
2020-05-02 20:02:19 +08:00
|
|
|
return upstream
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
}
|
2020-05-02 20:02:19 +08:00
|
|
|
return c.upStream
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|
|
|
|
|
2019-03-12 23:32:23 +08:00
|
|
|
// AddHandler adds a custom handler to handle the resolved result (A and AAAA).
|
2020-09-25 11:04:13 +08:00
|
|
|
func (c *Client) AddHandler(h AnswerHandler) {
|
2018-08-01 00:09:55 +08:00
|
|
|
c.handlers = append(c.handlers, h)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddRecord adds custom record to dns cache, format:
|
|
|
|
// www.example.com/1.2.3.4 or www.example.com/2606:2800:220:1:248:1893:25c8:1946
|
|
|
|
func (c *Client) AddRecord(record string) error {
|
|
|
|
r := strings.Split(record, "/")
|
|
|
|
domain, ip := r[0], r[1]
|
2020-08-23 23:23:30 +08:00
|
|
|
m, err := c.MakeResponse(domain, ip)
|
2018-08-01 00:09:55 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-23 23:23:30 +08:00
|
|
|
wb := pool.GetWriteBuffer()
|
|
|
|
defer pool.PutWriteBuffer(wb)
|
2018-08-01 00:09:55 +08:00
|
|
|
|
2020-08-23 23:23:30 +08:00
|
|
|
wb.Write([]byte{0, 0})
|
2018-08-01 00:09:55 +08:00
|
|
|
|
2020-08-23 23:23:30 +08:00
|
|
|
n, err := m.MarshalTo(wb)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
binary.BigEndian.PutUint16(wb.Bytes()[:2], uint16(n))
|
|
|
|
c.cache.Put(qKey(m.Question), wb.Bytes(), LongTTL)
|
2018-08-01 00:09:55 +08:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-23 23:23:30 +08:00
|
|
|
// MakeResponse makes a dns response message for the given domain and ip address.
|
|
|
|
func (c *Client) MakeResponse(domain string, ip string) (*Message, error) {
|
2018-08-01 00:09:55 +08:00
|
|
|
ipb := net.ParseIP(ip)
|
|
|
|
if ipb == nil {
|
|
|
|
return nil, errors.New("GenResponse: invalid ip format")
|
|
|
|
}
|
|
|
|
|
|
|
|
var rdata []byte
|
|
|
|
var qtype, rdlen uint16
|
|
|
|
if rdata = ipb.To4(); rdata != nil {
|
|
|
|
qtype = QTypeA
|
|
|
|
rdlen = net.IPv4len
|
|
|
|
} else {
|
|
|
|
qtype = QTypeAAAA
|
|
|
|
rdlen = net.IPv6len
|
|
|
|
rdata = ipb
|
|
|
|
}
|
|
|
|
|
|
|
|
m := NewMessage(0, Response)
|
|
|
|
m.SetQuestion(NewQuestion(qtype, domain))
|
2018-08-02 00:11:22 +08:00
|
|
|
rr := &RR{NAME: domain, TYPE: qtype, CLASS: ClassINET,
|
2019-01-06 20:38:15 +08:00
|
|
|
TTL: uint32(c.config.MinTTL), RDLENGTH: rdlen, RDATA: rdata}
|
2018-08-01 00:09:55 +08:00
|
|
|
m.AddAnswer(rr)
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
2020-08-23 23:23:30 +08:00
|
|
|
func qKey(q *Question) string {
|
2020-08-26 19:21:35 +08:00
|
|
|
switch q.QTYPE {
|
|
|
|
case QTypeA:
|
|
|
|
return q.QNAME + "/4"
|
|
|
|
case QTypeAAAA:
|
|
|
|
return q.QNAME + "/6"
|
|
|
|
}
|
|
|
|
return q.QNAME
|
2018-07-29 23:44:23 +08:00
|
|
|
}
|