support DOH and DOT

I noticed that glider doesn't support DNT and DOH, so I added it. It also solves a problem in issue. Now you can specify whether to select UDP or TCP or DOH and dot. I have done some examples to show it
This commit is contained in:
xiaolunzhou 2020-12-07 19:33:17 +08:00
parent 42c15b9262
commit 1242f4b500

View File

@ -1,10 +1,16 @@
package dns package dns
import ( import (
"crypto/tls"
"encoding/base64"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"io" "io"
"io/ioutil"
"net" "net"
"net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -26,6 +32,7 @@ type Config struct {
Records []string Records []string
AlwaysTCP bool AlwaysTCP bool
CacheSize int CacheSize int
} }
// Client is a dns client struct. // Client is a dns client struct.
@ -36,6 +43,7 @@ type Client struct {
upStream *UPStream upStream *UPStream
upStreamMap map[string]*UPStream upStreamMap map[string]*UPStream
handlers []AnswerHandler handlers []AnswerHandler
httpClient *http.Client
} }
// NewClient returns a new dns client. // NewClient returns a new dns client.
@ -46,6 +54,8 @@ func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) {
config: config, config: config,
upStream: NewUPStream(config.Servers), upStream: NewUPStream(config.Servers),
upStreamMap: make(map[string]*UPStream), upStreamMap: make(map[string]*UPStream),
httpClient: &http.Client{
},
} }
// custom records // custom records
@ -145,11 +155,10 @@ func (c *Client) extractAnswer(resp *Message) ([]string, int) {
// exchange choose a upstream dns server based on qname, communicate with it on the network. // exchange choose a upstream dns server based on qname, communicate with it on the network.
func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) ( func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
server, network, dialerAddr string, respBytes []byte, err error) { server, network, dialerAddr string, respBytes []byte, err error) {
ups := c.UpStream(qname)
// use tcp to connect upstream server default fmt.Println(ups)
network = "tcp" network = "tcp"
dialer := c.proxy.NextDialer(qname + ":53") dialer := c.proxy.NextDialer(qname + ":53")
// if we are resolving the dialer's domain, then use Direct to avoid denpency loop // if we are resolving the dialer's domain, then use Direct to avoid denpency loop
// TODO: dialer.Addr() == "REJECT", tricky // TODO: dialer.Addr() == "REJECT", tricky
if strings.Contains(dialer.Addr(), qname) || dialer.Addr() == "REJECT" { if strings.Contains(dialer.Addr(), qname) || dialer.Addr() == "REJECT" {
@ -161,12 +170,35 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
if !preferTCP && !c.config.AlwaysTCP && dialer.Addr() == "DIRECT" { if !preferTCP && !c.config.AlwaysTCP && dialer.Addr() == "DIRECT" {
network = "udp" network = "udp"
} }
//init conn and option
ups := c.UpStream(qname) var rc net.Conn
server = ups.Server() var op string
for i := 0; i < ups.Len(); i++ { for i := 0; i < ups.Len(); i++ {
var rc net.Conn u, err := url.Parse(ups.Server())
rc, err = dialer.Dial(network, server) if err!=nil{
server=ups.Server()
op=network
}else{
server=u.Host
op=u.Scheme
}
//if not set option use network else use special option
switch op{
case "tcp":
network = "tcp"
rc, err = dialer.Dial(network, server)
case "udp":
network = "udp"
rc, err = dialer.Dial(network, server)
case "dot":
network="tcp"
rc,err=tls.Dial(network,server,&tls.Config{InsecureSkipVerify: false,})
case "doh":
net.DefaultResolver=&net.Resolver{}
network="doh"
default:
break
}
if err != nil { if err != nil {
newServer := ups.SwitchIf(server) newServer := ups.SwitchIf(server)
log.F("[dns] error in resolving %s, failed to connect to server %v via %s: %v, next server: %s", log.F("[dns] error in resolving %s, failed to connect to server %v via %s: %v, next server: %s",
@ -174,18 +206,21 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
server = newServer server = newServer
continue continue
} }
defer rc.Close() //TODO: if we use DOH (network=="doh") we don't need close connection
if network!="doh" {defer rc.Close()}
// TODO: support timeout setting for different upstream server // TODO: support timeout setting for different upstream server
if c.config.Timeout > 0 { if c.config.Timeout > 0 && network!="doh" {
rc.SetDeadline(time.Now().Add(time.Duration(c.config.Timeout) * time.Second)) rc.SetDeadline(time.Now().Add(time.Duration(c.config.Timeout) * time.Second))
} }
switch network { switch network {
case "tcp": case "tcp","dot":
respBytes, err = c.exchangeTCP(rc, reqBytes) respBytes, err = c.exchangeTCP(rc, reqBytes)
case "udp": case "udp":
respBytes, err = c.exchangeUDP(rc, reqBytes) respBytes, err = c.exchangeUDP(rc, reqBytes)
case "doh":
respBytes, err = c.exchangeHTTPS(server, reqBytes)
} }
if err == nil { if err == nil {
@ -206,7 +241,21 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
return server, network, dialer.Addr(), respBytes, err return server, network, dialer.Addr(), respBytes, err
} }
//exchangeHTTP exchange with server over https
func (c*Client) exchangeHTTPS(server string,reqBytes[]byte)(body[]byte,err error){
query := strings.Replace(base64.URLEncoding.EncodeToString(reqBytes), "=", "", -1)
urls := "https://" + server + "/dns-query?dns=" + query
res, err := c.httpClient.Get(urls)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return
}
// exchangeTCP exchange with server over tcp. // exchangeTCP exchange with server over tcp.
func (c *Client) exchangeTCP(rc net.Conn, reqBytes []byte) ([]byte, error) { func (c *Client) exchangeTCP(rc net.Conn, reqBytes []byte) ([]byte, error) {
lenBuf := pool.GetBuffer(2) lenBuf := pool.GetBuffer(2)