From 1f88a61e05e18c10e07d1a073804fc81c0c397cc Mon Sep 17 00:00:00 2001 From: Dingjun Date: Sun, 9 Oct 2016 16:00:06 +0800 Subject: [PATCH] add google https dns support --- .gitignore | 3 +- README.md | 1 + example_config/config.json | 2 +- httpdns.go | 181 +++++++++++++++++++++++++++++++++++++ httpdns_test.go | 80 ++++++++++++++++ routers.go | 8 +- 6 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 httpdns.go create mode 100644 httpdns_test.go diff --git a/.gitignore b/.gitignore index da68ad3..a7571cb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ *.swp *.json *.txt -gdns -gdns_arm +gdns* diff --git a/README.md b/README.md index 9ed01dd..090b700 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,4 @@ a dns forward proxy write by go - support configure different domains to forward to different upstream servers - support tcp or udp to communicate to upstream servers - support ip blacklist to filter unwanted dns reply +- support google https dns diff --git a/example_config/config.json b/example_config/config.json index 9baacab..1a673a1 100644 --- a/example_config/config.json +++ b/example_config/config.json @@ -13,7 +13,7 @@ }, { "domain_list_file":"domain2.txt", - "servers":["tcp:8.8.4.4:53"] + "servers":["tcp:8.8.4.4:53","https:74.125.200.100"] } ] } diff --git a/httpdns.go b/httpdns.go new file mode 100644 index 0000000..c399c19 --- /dev/null +++ b/httpdns.go @@ -0,0 +1,181 @@ +package main + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "github.com/miekg/dns" + "io/ioutil" + "net" + "net/http" + "net/url" + "sync" + "time" +) + +// ServerAddr is Google dns server ip +var ServerAddr = "74.125.200.100" +var queryIPApi = "https://www.simicloud.com/media/httpbin/ip" + +// HTTPDns struct +type HTTPDns struct { + myip string + l sync.Mutex +} + +func (h *HTTPDns) getMyIP() string { + if h.myip != "" { + return h.myip + } + go h.queryMyIP() + return "" +} + +type ipAPI struct { + Ip string `json:"origin"` +} + +func (h *HTTPDns) queryMyIP() { + h.l.Lock() + defer h.l.Unlock() + if h.myip != "" { + //fmt.Printf("myip: %s\n", h.myip) + return + } + //fmt.Println("get ip...") + res, err := http.Get(queryIPApi) + if err != nil { + //fmt.Println(err) + return + } + defer res.Body.Close() + d, err := ioutil.ReadAll(res.Body) + if err != nil { + //fmt.Println(err) + return + } + //fmt.Printf("%s\n", string(d)) + ip := ipAPI{} + err = json.Unmarshal(d, &ip) + if err != nil { + //fmt.Println(err) + return + } + //fmt.Printf("got: %s\n", ip.Ip) + h.myip = ip.Ip +} + +func (h *HTTPDns) getMyNet() string { + ip := h.getMyIP() + if ip == "" { + return "" + } + mask := net.IPv4Mask(255, 255, 255, 0) + ipByte := net.ParseIP(ip) + ipnet := net.IPNet{ipByte.Mask(mask), mask} + return ipnet.String() +} + +// Exchange send query to server and return the response +func (h *HTTPDns) Exchange(m *dns.Msg, addr string) (*dns.Msg, time.Duration, error) { + name := m.Question[0].Name + t := dns.TypeToString[m.Question[0].Qtype] + mynet := h.getMyNet() + r, err := query(name, t, mynet, "", addr) + if err != nil { + return nil, 0, err + } + + m1 := new(dns.Msg) + + m1.SetRcode(m, r.Status) + for _, rr := range r.Answer { + _rr := fmt.Sprintf("%s %d IN %s %s", rr.Name, rr.TTL, + dns.TypeToString[uint16(rr.Type)], rr.Data) + + an, err := dns.NewRR(_rr) + if err != nil { + return nil, 0, err + } + m1.Answer = append(m1.Answer, an) + } + return m1, 0, nil + +} + +// DefaultHTTPDnsClient default http dns client +var DefaultHTTPDnsClient = &HTTPDns{} + +// Response represent the dns response from server +type Response struct { + Status int + TC bool + RD bool + RA bool + AD bool + CD bool + Question []RR + Answer []RR + Additional []RR + EDNSClientSubnet string `json:"edns_client_subnet"` + Comment string +} + +// RR represent the RR record +type RR struct { + Name string `json:"name"` + Type int `json:"type"` + TTL int + Data string `json:"data"` +} + +var httpclient = &http.Client{ + Timeout: 3 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ServerName: "dns.google.com"}, + TLSHandshakeTimeout: 3 * time.Second, + }, +} + +func query(name, t, ednsClientSubnet, padding, srvAddr string) (*Response, error) { + srvaddr := ServerAddr + if srvAddr != "" { + srvaddr = srvAddr + } + v := url.Values{} + v.Add("name", name) + v.Add("type", t) + + if ednsClientSubnet != "" { + v.Add("edns_client_subnet", ednsClientSubnet) + } + + if padding != "" { + v.Add("random_padding", padding) + } + + u := fmt.Sprintf("https://%s/resolve?%s", srvaddr, v.Encode()) + r, _ := http.NewRequest("GET", u, nil) + r.Host = "dns.google.com" + //r.URL.Host = "dns.google.com" + + res, err := httpclient.Do(r) + if err != nil { + return nil, err + } + defer res.Body.Close() + + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + d := Response{} + err = json.Unmarshal(data, &d) + + if err != nil { + return nil, err + } + + return &d, nil +} diff --git a/httpdns_test.go b/httpdns_test.go new file mode 100644 index 0000000..12dca27 --- /dev/null +++ b/httpdns_test.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/miekg/dns" + "io/ioutil" + "net" + "net/http" + "testing" + "time" +) + +// MyIP my ip +type MyIP struct { + IP string `json:"origin"` +} + +func myip() string { + res, err := http.Get("https://www.simicloud.com/media/httpbin/ip") + if err != nil { + return "" + } + defer res.Body.Close() + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return "" + } + ip := MyIP{} + err = json.Unmarshal(data, &ip) + if err != nil { + return "" + } + return ip.IP + +} +func TestQuery(t *testing.T) { + + m := net.IPv4Mask(255, 255, 255, 0) + ip1 := net.ParseIP(myip()) + ipnet := net.IPNet{ip1.Mask(m), m} + r, err := query("www.taobao.com", "a", ipnet.String(), "", "") + if err != nil { + t.Error(err) + } + fmt.Printf("%+v\n", r) + + r, err = query("www.taobao.com", "a", "", "", "") + if err != nil { + t.Error(err) + } + fmt.Printf("%+v\n", r) + +} + +func TestHttpsQuery(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion("www.taobao.com", dns.TypeA) + m2, _, err := DefaultHTTPDnsClient.Exchange(m, ServerAddr) + if err != nil { + t.Error(err) + } + fmt.Println(m2) + + time.Sleep(1 * time.Second) + m2, _, err = DefaultHTTPDnsClient.Exchange(m, ServerAddr) + if err != nil { + t.Error(err) + } + fmt.Println(m2) +} + +func TestGetMyIP(t *testing.T) { + a := DefaultHTTPDnsClient.getMyIP() + time.Sleep(4 * time.Second) + a = DefaultHTTPDnsClient.getMyIP() + if a == "" { + t.Errorf("get ip failed") + } +} diff --git a/routers.go b/routers.go index 896ca7c..aec0495 100644 --- a/routers.go +++ b/routers.go @@ -38,8 +38,12 @@ func (r *routers) checkBlacklist(m *dns.Msg) bool { return false } +type dnsClient interface { + Exchange(*dns.Msg, string) (*dns.Msg, time.Duration, error) +} + func (r *routers) query(m *dns.Msg, servers []addr) (*dns.Msg, error) { - var up *dns.Client + var up dnsClient var lastErr error // query cache @@ -56,6 +60,8 @@ func (r *routers) query(m *dns.Msg, servers []addr) (*dns.Msg, error) { up = r.tcp case "udp": up = r.udp + case "https": + up = DefaultHTTPDnsClient default: up = r.udp }