CoreDNS粗解( 五 )


解析dns各种协议coredns支持dns, tls(dot), https(doh), grpc等多种传输协议
package mainimport ( "fmt" "github.com/coredns/coredns/plugin/pkg/parse")func main() { hosts := []string{"tls://127.0.0.1:853","127.0.0.1:53","grpc://127.0.0.1:999","https://127.0.0.1:1443", } for _, host := range hosts {trans, addr := parse.Transport(host)fmt.Println(host, "->", trans, addr) }}输出如下:
tls://127.0.0.1:853 -> tls 127.0.0.1:853127.0.0.1:53 -> dns 127.0.0.1:53grpc://127.0.0.1:999 -> grpc 127.0.0.1:999https://127.0.0.1:1443 -> https 127.0.0.1:1443各种协议的请求根据需要发起各种类型的请求
我们可以不用到处找支持这些协议的dns服务器 , 用coredns自身监听即可 , 下面是配置文件
.:53 tls://.:853 https://.:1043 {tls plugin/tls/test_cert.pem plugin/tls/test_key.pem plugin/tls/test_ca.pemlogerrorshosts example.hosts}example.hosts就只有一行
127.0.0.1 example.comcoredns有一个小小的坑, 如果你没有显式的设置证书 , 那么即使是指定了tls, https等协议 , 最终还是以tcp和http的方式来监听 。

个人觉得更好的体验是自动在本地创建一个证书或者在日志中发出警告, 但是coredns暗搓搓的把tls的那层给去掉了 。。。。。
原生的DNS请求package mainimport ( "fmt" "net" "os" "time" "github.com/miekg/dns")func main() { if len(os.Args) == 1 {fmt.Println("必须指定一个域名")os.Exit(1) } // 将域名标准化, 比如example.com 变成 example.com. 最后面加了一个点 query := dns.Fqdn(os.Args[1]) m1 := new(dns.Msg) m1.Id = dns.Id() m1.RecursionDesired = true m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: query, Qtype: dns.TypeA, Qclass: dns.ClassINET} c := new(dns.Client) in, rtt, err := c.Exchange(m1, "127.0.0.1:53") if err != nil {panic(err) } if len(in.Answer) == 0 {fmt.Println("没有查询到任何结果")os.Exit(1) } if a, ok := in.Answer[0].(*dns.A); ok {fmt.Println("获取到ip地址:", a.A.String()) } else {fmt.Println("返回结果不是A记录:", in) } fmt.Println("耗时: ", rtt) c2 := new(dns.Client) laddr := net.UDPAddr{IP:net.ParseIP("[::1]"),Port: 12345,Zone: "", } fmt.Println("设置超时的客户端的响应结果:") c2.Dialer = &net.Dialer{Timeout:200 * time.Millisecond,LocalAddr: &laddr, } in2, rtt2, err2 := c2.Exchange(m1, "127.0.0.1:53") if err2 != nil {panic(err) } if a, ok := in2.Answer[0].(*dns.A); ok {fmt.Println("获取到ip地址:", a.A.String()) } else {fmt.Println("返回结果不是A记录:", in) } fmt.Println("耗时: ", rtt2)}输出如下:
获取到ip地址: 127.0.0.1耗时:512µs设置超时的客户端的响应结果:获取到ip地址: 127.0.0.1耗时:0sDNS Over TLS请求(DOT)package mainimport ( "crypto/tls" "fmt" "log" "os" "github.com/miekg/dns")func main() { tlsConfig := new(tls.Config) tlsConfig.InsecureSkipVerify = true conn, err := dns.DialWithTLS("tcp", "127.0.0.1:853", tlsConfig) if err != nil {log.Fatalln("连接出错:", err) } defer conn.Close() query := dns.Fqdn(os.Args[1]) m1 := new(dns.Msg) m1.Id = dns.Id() m1.RecursionDesired = true m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: query, Qtype: dns.TypeA, Qclass: dns.ClassINET} err = conn.WriteMsg(m1) if err != nil {log.Fatal("发送请求失败:", err) } ret, err := conn.ReadMsg() if err != nil {log.Fatal("读取响应失败:", err) } fmt.Println(ret)}输出如下:
获取到ip地址: 127.0.0.1DNS Over HTTPS请求(DOH)package mainimport ( "crypto/tls" "fmt" "os" "bytes" "encoding/base64" "io" "net/http" "github.com/miekg/dns")func main() { query := dns.Fqdn(os.Args[1]) m1 := new(dns.Msg) m1.Id = dns.Id() m1.RecursionDesired = true m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: query, Qtype: dns.TypeA, Qclass: dns.ClassINET} req, err := NewRequest("GET", "127.0.0.1:1043", m1) if err != nil {panic("创建请求失败: " + err.Error()) } httpClient := http.Client{} httpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true,}, } resp, err := httpClient.Do(req) if err != nil {panic("读取响应失败: " + err.Error()) } in, err := ResponseToMsg(resp) if err != nil {panic("解析响应结果失败: " + err.Error()) } if len(in.Answer) == 0 {fmt.Println("没有查询到任何结果")os.Exit(1) } if a, ok := in.Answer[0].(*dns.A); ok {fmt.Println("获取到ip地址:", a.A.String()) } else {fmt.Println("返回结果不是A记录:", in) }}// 以下代码复制自pluginpkgdoh// MimeType is the DoH mimetype that should be used.const MimeType = "application/dns-message"// Path is the URL path that should be used.const Path = "/dns-query"// NewRequest returns a new DoH request given a method, URL (without any paths, so exclude /dns-query) and dns.Msg.func NewRequest(method, url string, m *dns.Msg) (*http.Request, error) { buf, err := m.Pack() if err != nil {return nil, err } switch method { case http.MethodGet:b64 := base64.RawURLEncoding.EncodeToString(buf)req, err := http.NewRequest(http.MethodGet, "https://"+url+Path+"?dns="+b64, nil)if err != nil {return req, err}req.Header.Set("content-type", MimeType)req.Header.Set("accept", MimeType)return req, nil case http.MethodPost:req, err := http.NewRequest(http.MethodPost, "https://"+url+Path+"?bla=foo:443", bytes.NewReader(buf))if err != nil {return req, err}req.Header.Set("content-type", MimeType)req.Header.Set("accept", MimeType)return req, nil default:return nil, fmt.Errorf("method not allowed: %s", method) }}// ResponseToMsg converts a http.Response to a dns message.func ResponseToMsg(resp *http.Response) (*dns.Msg, error) { defer resp.Body.Close() return toMsg(resp.Body)}// RequestToMsg converts a http.Request to a dns message.func RequestToMsg(req *http.Request) (*dns.Msg, error) { switch req.Method { case http.MethodGet:return requestToMsgGet(req) case http.MethodPost:return requestToMsgPost(req) default:return nil, fmt.Errorf("method not allowed: %s", req.Method) }}// requestToMsgPost extracts the dns message from the request body.func requestToMsgPost(req *http.Request) (*dns.Msg, error) { defer req.Body.Close() return toMsg(req.Body)}// requestToMsgGet extract the dns message from the GET request.func requestToMsgGet(req *http.Request) (*dns.Msg, error) { values := req.URL.Query() b64, ok := values["dns"] if !ok {return nil, fmt.Errorf("no 'dns' query parameter found") } if len(b64) != 1 {return nil, fmt.Errorf("multiple 'dns' query values found") } return base64ToMsg(b64[0])}func toMsg(r io.ReadCloser) (*dns.Msg, error) { buf, err := io.ReadAll(http.MaxBytesReader(nil, r, 65536)) if err != nil {return nil, err } m := new(dns.Msg) err = m.Unpack(buf) return m, err}func base64ToMsg(b64 string) (*dns.Msg, error) { buf, err := b64Enc.DecodeString(b64) if err != nil {return nil, err } m := new(dns.Msg) err = m.Unpack(buf) return m, err}var b64Enc = base64.RawURLEncoding


推荐阅读