暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

golang源码分析:将域名解析代理到自定义域名服务器

        开发过程中,好多域名是内网域名,直接改/etc/host是一个选择,但是如果不及时改回去,在切换环境的时候会给我们排查问题带来很大干扰,如果能够实现一个代理,在运行的时候走指定代理服务器,代理服务器内部将域名解析发送到自定义的域名服务器上,如果自定义域名服务器解析不了,再走默认的域名服务器,是不是很爽?

        先贴地址然后分析下如何实现:https://github.com/xiazemin/dns_proxy

        首先我们需要定义一个dns服务器

    package dns


    /*
    dig命令主要用来从 DNS 域名服务器查询主机地址信息。


    查找www.baidu.com的ip (A记录):
    命令:dig @127.0.0.1 www.baidu.com
    根据ip查找对应域名 (PTR记录):
    命令:dig @127.0.0.1 -x 220.181.38.150


    */


    import (
    "fmt"
    "net"


    "golang.org/x/net/dns/dnsmessage"
    )


    func Serve() {
    conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 53})
    if err != nil {
    panic(err)
    }
    defer conn.Close()
    fmt.Println("Listing ...")
    for {
    buf := make([]byte, 512)
    _, addr, _ := conn.ReadFromUDP(buf)
    var msg dnsmessage.Message
    if err := msg.Unpack(buf); err != nil {
    fmt.Println(err)
    continue
    }
    go ServerDNS(addr, conn, msg)
    }
    }


    // ServerDNS serve
    func ServerDNS(addr *net.UDPAddr, conn *net.UDPConn, msg dnsmessage.Message) {
    // query info
    if len(msg.Questions) < 1 {
    return
    }
    question := msg.Questions[0]
    var (
    queryTypeStr = question.Type.String()
    queryNameStr = question.Name.String()
    queryType = question.Type
    queryName, _ = dnsmessage.NewName(queryNameStr)
    )
    fmt.Printf("[%s] queryName: [%s]\n", queryTypeStr, queryNameStr)
    // find record
    var resource dnsmessage.Resource
    switch queryType {
    case dnsmessage.TypeA, dnsmessage.TypeAAAA:
    if rst, ok := addressBookOfA[queryNameStr]; ok {
    resource = NewAResource(queryName, rst)
    } else {
    fmt.Printf("not fount A record queryName: [%s] \n", queryNameStr)
    Response(addr, conn, msg)
    return
    }
    case dnsmessage.TypePTR:
    if rst, ok := addressBookOfPTR[queryName.String()]; ok {
    resource = NewPTRResource(queryName, rst)
    } else {
    fmt.Printf("not fount PTR record queryName: [%s] \n", queryNameStr)
    Response(addr, conn, msg)
    return
    }
    default:
    fmt.Printf("not support dns queryType: [%s] \n", queryTypeStr)
    return
    }
    // send response
    msg.Response = true
    msg.Answers = append(msg.Answers, resource)
    Response(addr, conn, msg)
    }


    // Response return
    func Response(addr *net.UDPAddr, conn *net.UDPConn, msg dnsmessage.Message) {
    packed, err := msg.Pack()
    if err != nil {
    fmt.Println(err)
    return
    }
    if _, err := conn.WriteToUDP(packed, addr); err != nil {
    fmt.Println(err)
    }
    }


    // NewAResource A record
    func NewAResource(query dnsmessage.Name, a [4]byte) dnsmessage.Resource {
    return dnsmessage.Resource{
    Header: dnsmessage.ResourceHeader{
    Name: query,
    Class: dnsmessage.ClassINET,
    TTL: 600,
    },
    Body: &dnsmessage.AResource{
    A: a,
    },
    }
    }


    // NewPTRResource PTR record
    func NewPTRResource(query dnsmessage.Name, ptr string) dnsmessage.Resource {
    name, _ := dnsmessage.NewName(ptr)
    return dnsmessage.Resource{
    Header: dnsmessage.ResourceHeader{
    Name: query,
    Class: dnsmessage.ClassINET,
    },
    Body: &dnsmessage.PTRResource{
    PTR: name,
    },
    }
    }

                用dig命令测试下

      % dig @127.0.0.1 www.baidu.com


      ; <<>> DiG 9.10.6 <<>> @127.0.0.1 www.baidu.com
      ; (1 server found)
      ;; global options: +cmd
      ;; Got answer:
      ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30165
      ;; flags: qr rd ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
      ;; WARNING: recursion requested but not available


      ;; OPT PSEUDOSECTION:
      ; EDNS: version: 0, flags:; udp: 4096
      ;; QUESTION SECTION:
      ;www.baidu.com. IN A


      ;; ANSWER SECTION:
      www.baidu.com. 600 IN A 127.0.0.8


      ;; Query time: 1 msec
      ;; SERVER: 127.0.0.1#53(127.0.0.1)
      ;; WHEN: Sun Nov 13 18:38:20 CST 2022
      ;; MSG SIZE rcvd: 58


      % dig @127.0.0.1 -x 127.0.0.8
      ;; Warning: query response not set


      ; <<>> DiG 9.10.6 <<>> @127.0.0.1 -x 127.0.0.8
      ; (1 server found)
      ;; global options: +cmd
      ;; Got answer:
      ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34734
      ;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
      ;; WARNING: recursion requested but not available


      ;; OPT PSEUDOSECTION:
      ; EDNS: version: 0, flags:; udp: 4096
      ;; QUESTION SECTION:
      ;8.0.0.127.in-addr.arpa. IN PTR


      ;; Query time: 0 msec
      ;; SERVER: 127.0.0.1#53(127.0.0.1)
      ;; WHEN: Sun Nov 13 18:38:38 CST 2022
      ;; MSG SIZE rcvd: 51


      % dig @127.0.0.1 -x 127.0.0.9
      ;; Warning: query response not set


      ; <<>> DiG 9.10.6 <<>> @127.0.0.1 -x 127.0.0.9
      ; (1 server found)
      ;; global options: +cmd
      ;; Got answer:
      ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28830
      ;; flags: rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
      ;; WARNING: recursion requested but not available


      ;; OPT PSEUDOSECTION:
      ; EDNS: version: 0, flags:; udp: 4096
      ;; QUESTION SECTION:
      ;9.0.0.127.in-addr.arpa. IN PTR


      ;; Query time: 0 msec
      ;; SERVER: 127.0.0.1#53(127.0.0.1)
      ;; WHEN: Sun Nov 13 18:38:42 CST 2022
      ;; MSG SIZE rcvd: 51

                  有了dns服务器,首先我们要考虑如何在client请求里指定dns服务器,我们可以在创建链接的时候定义dialer,指定resolver的Dial方法

        package main


        import (
        "context"
        "fmt"
        "net"
        "net/http"
        "net/url"
        "os"
        "time"
        )


        func main() {
        fmt.Println(os.Getenv("HTTP_PROXY"))
        u, err := url.Parse(os.Getenv("HTTP_PROXY"))
        if err != nil {
        fmt.Println(err, u)
        }
        resolver := &net.Resolver{
        PreferGo: true, //否则不生效
        Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
        return net.DialUDP("udp", nil, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 53})
        },
        }


        fmt.Println(resolver.LookupAddr(context.TODO(), net.ParseIP("127.0.0.8").String()))
        //[xiazemin.com.] <nil>
        fmt.Println(resolver.LookupAddr(context.TODO(), net.ParseIP("127.0.0.1").String()))
        //[localhost kubernetes.docker.internal.] <nil>


        dialer := &net.Dialer{
        Timeout: 1 * time.Second,
        Resolver: resolver,
        }
        client := http.Client{
        Transport: &http.Transport{
        DialContext: dialer.DialContext,
        TLSHandshakeTimeout: 10 * time.Second,
        // Proxy: http.ProxyURL(u),
        Proxy: http.ProxyFromEnvironment,
        },
        Timeout: 1 * time.Second,
        }
        req, _ := http.NewRequest("GET", "http://xiazemin.com:8080", &net.Buffers{[]byte("xiazemin http get")})
        fmt.Println(client.Do(req))
        }


        可以定义一个简单的服务器测试下

          package main


          import (
          "fmt"
          "net/http"
          )


          func main() {
          http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
          fmt.Println("request from client and host :", r.Host)
          fmt.Fprintln(w, r.Body)
          })
          http.ListenAndServe(":8080", nil)
          }


             % go run learn/dns/http/server/main.go
            request from client and host : xiazemin.com:8080


            % go run learn/dns/server/main.go
            Listing ...
            [TypeAAAA] queryName: [xiazemin.com.]
            [TypeA] queryName: [xiazemin.com.]




            % go run learn/dns/http/client/main.go
            &{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[87] Content-Type:[text/plain; charset=utf-8] Date:[Sun, 13 Nov 2022 13:01:18 GMT]] 0xc000028320 87 [] false false map[] 0xc00013c100 <nil>} <nil>




            发现我们的dns服务器已经能解析我自己定义的域名xiazemin.com了,当然,我们的域名服务器也实现了反向查域名的能力,这里有个细节需要注意的是:in-addr.arpa里表达的ip就是反过来表达的,即

              d.c.b.a.in-addr.arpaIP地址是a.b.c.d

              这么骚的设计还是挺耐人寻味的。

                          然后我们可以定义一个tcp代理,在做转发之前嵌入我们的域名服务器解析地址,如果解析失败,尝试系统默认的解析方法

                package tcpproxy


                import (
                "bytes"
                "context"
                "fmt"
                "io"
                "log"
                "net"
                "net/url"
                "strings"
                "time"
                )


                //https://www.51sjk.com/b123b258404/
                func Serve() {
                log.SetFlags(log.LstdFlags | log.Lshortfile)
                l, err := net.Listen("tcp", ":8081")
                if err != nil {
                log.Panic(err)
                }


                for {
                client, err := l.Accept()
                if err != nil {
                log.Panic(err)
                }


                go handleclientrequest(client)
                }
                }


                func handleclientrequest(client net.Conn) {
                if client == nil {
                return
                }
                defer client.Close()


                var b [1024]byte
                n, err := client.Read(b[:])
                if err != nil {
                log.Println(err)
                return
                }
                var method, host, address string
                fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &host)
                fmt.Println(method, host, address)
                hostporturl, err := url.Parse(host)
                fmt.Println(hostporturl)
                if err != nil {
                log.Println(err)
                return
                }


                if hostporturl.Opaque == "443" { //https访问
                address = hostporturl.Scheme + ":443"
                } else { //http访问
                if strings.Index(hostporturl.Host, ":") == -1 { //host不带端口, 默认80
                address = hostporturl.Host + ":80"
                } else {
                address = hostporturl.Host
                }
                }


                //获得了请求的host和port,就开始拨号吧
                dialer := net.Dialer{
                Resolver: &net.Resolver{
                PreferGo: true, //否则不生效
                Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
                return net.DialUDP("udp", nil, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 53})
                },
                },
                Timeout: 1 * time.Second,
                }
                //net.Dial
                server, err := dialer.Dial("tcp", address)
                if err != nil {
                log.Println(err, "retry default")
                server, err = net.Dial("tcp", address)
                if err != nil {
                log.Println(err)
                return
                }
                }
                if method == "connect" {
                fmt.Fprint(client, "http/1.1 200 connection established\r\n")
                } else {
                server.Write(b[:n])
                }
                //进行转发
                go io.Copy(server, client)
                io.Copy(client, server)
                }


                做完以后尝试下

                   % HTTP_PROXY="http://localhost:8081" curl http://www.baidu.com


                  文章转载自golang算法架构leetcode技术php,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                  评论