miekg / dns

DNS library in Go
https://miek.nl/2014/august/16/go-dns-package
BSD 3-Clause "New" or "Revised" License
7.86k stars 1.12k forks source link

Forwarding DNS Queries to other Handler in TCP Connections #1538

Closed gouravkrosx closed 3 months ago

gouravkrosx commented 4 months ago

I recently encountered an issue with my Go application while handling DNS queries. Initially, everything was running smoothly as DNS queries were coming in over UDP connections. However, I recently discovered that DNS queries can also arrive via TCP connections, which has caused this problem.

I have a TCP connection handler implemented like this:

func handleConnection(ctx context.Context, conn net.Conn){

    if destPort == 53 {
        // Now, I need to handle the DNS queries using the ServeDNS handler.
    }
}

Additionally, I've already developed a DNS UDP server to manage DNS queries, which is functioning properly:

func startDnsServer() error {
    addr := ":6789"
    handler := ps
    server := &dns.Server{
        Addr:      addr,
        Net:       "udp",
        Handler:   dns.HandlerFunc(ServeDNS),
        UDPSize:   65535,
        ReusePort: true,
    }

    err := server.ListenAndServe()
    if err != nil {
        return err
    }
}

The DNS handler, which is responsible for processing UDP-based DNS queries, looks like this:

func ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
    msg := new(dns.Msg)
    msg.SetReply(r)
    msg.Authoritative = true
    for _, question := range r.Question {
        var answers []dns.RR

        // If the resolution failed, return a default A record with Proxy IP
                   answers = resolveDNSQuery(question.Name, logger, timeout)

        if question.Qtype == dns.TypeA {
            answers = []dns.RR{&dns.A{
                Hdr: dns.RR_Header{Name: question.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600},
                A:   net.IPv4(127, 0, 0, 5),
            }}
        }

        msg.Answer = append(msg.Answer, answers...)
    }

    err := w.WriteMsg(msg)
    if err != nil {
        log.Printf("Failed to write message: %v", err)
    }

}

Now, with the realization that DNS queries can also be transmitted over TCP connections, I'm unsure of how to adapt my existing setup to handle this scenario. I'm aware of that server.ActivateAndServe exists, but it requires an existing listener, which doesn't seem to address my specific challenge.

I want that somehow I can forward the DNS query coming to the handleConnection to the ServeDNS without parsing the req buffer, just using the conn object of the handleConnection.

Thanks in advance!

miekg commented 4 months ago

Just run a second dns.Server for udp, see https://github.com/miekg/exdns/blob/master/as112/as112.go#L75 for example

[ Quoting @.***> in "[miekg/dns] Forwarding DNS Queries ..." ]

I recently encountered an issue with my Go application while handling DNS queries. Initially, everything was running smoothly as DNS queries were coming in over UDP connections. However, I recently discovered that DNS queries can also arrive via TCP connections, which has caused this problem.

I have a TCP connection handler implemented like this:

func handleConnection(ctx context.Context, conn net.Conn){

   if destPort == 53 {
           // Now, I need to handle the DNS queries using the ServeDNS handler.
   }

}

Additionally, I've already developed a DNS UDP server to manage DNS queries, which is functioning properly:

func startDnsServer() error { addr := ":6789" handler := ps server := &dns.Server{ Addr: addr, Net: "udp", Handler: dns.HandlerFunc(ServeDNS), UDPSize: 65535, ReusePort: true, }

   err := server.ListenAndServe()
   if err != nil {
           return err
   }

}

The DNS handler, which is responsible for processing UDP-based DNS queries, looks like this:

func ServeDNS(w dns.ResponseWriter, r *dns.Msg) { msg := new(dns.Msg) msg.SetReply(r) msg.Authoritative = true for _, question := range r.Question { var answers []dns.RR

           // If the resolution failed, return a default A record with Proxy IP
              answers = resolveDNSQuery(question.Name, logger, timeout)

           if question.Qtype == dns.TypeA {
                   answers = []dns.RR{&dns.A{
                           Hdr: dns.RR_Header{Name: question.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600},
                           A:   net.IPv4(127, 0, 0, 5),
                   }}
           }

           msg.Answer = append(msg.Answer, answers...)
   }

   err := w.WriteMsg(msg)
   if err != nil {
           log.Printf("Failed to write message: %v", err)
   }

}

Now, with the realization that DNS queries can also be transmitted over TCP connections, I'm unsure of how to adapt my existing setup to handle this scenario. I'm aware of that server.ActivateAndServe exists, but it requires an existing listener, which doesn't seem to address my specific challenge.

I want that somehow I can forward the DNS query coming to the handleConnection to the ServeDNS without parsing the req buffer, just using the conn object of the handleConnection.

Thanks in advance!

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you are subscribed to this thread.*Message ID: @.***>

/Miek

-- Miek Gieben

gouravkrosx commented 4 months ago

You mean second dns.Server for tcp? as I have already one for udp. And using the same ServeDNS handler for this server as well.

semihalev commented 4 months ago

Short explain;

"When a DNS response exceeds the maximum packet size that can be transmitted over UDP (typically 512 bytes without EDNS), the server sends back a truncated response to the client. This truncated response indicates to the client that the full response was not received due to size constraints.

In such cases, the client needs to establish a TCP connection to the DNS server to retrieve the complete DNS response. TCP is used as an alternative protocol when UDP responses are truncated, as it allows for the transmission of larger data payloads without the limitations of UDP packet size.

Therefore, TCP is essential for handling large DNS responses that exceed the UDP packet size limit and ensuring that clients receive complete and accurate DNS information. Without TCP, clients may encounter incomplete or inaccurate DNS data, leading to potential issues with DNS resolution and service reliability."

You set UDPSize as 65535 but this cannot be work in real world. OS or other limitations maybe stop that. If your server received truncated response from UDP response then they must be try connect TCP connection.

Did you check the response size for the queries?

gouravkrosx commented 4 months ago

Thanks for the detailed answer, I haven't checked the request size and according to your explanation it should be changed but my problem is that how can i use my existing handler (ServeDNS) from the handleConnection as i am getting dns queries over tcp server. Right now i have configured my client to use tcp server to solve the problem if any query comes via tcp in future.One quick way is to make another dns server for tcp and use different port and i guess that server can use the same ServeDNS handler which udp dns server is using. Then why can't i use my existing connection getting in handleConnection to be handled in ServeDNS for dns queries (coming at port 53).

I hope you understood my thought process.

semihalev commented 4 months ago

You can use same ServeDNS handler. You must be check response was truncated or not and try TCP query in resolveDNSQuery function. You can check the this short example functions;

func Exchange(req *dns.Msg, addr string, net string) (*dns.Msg, error) {
    client := dns.Client{Net: net}
    resp, _, err := client.Exchange(req, addr)

    if err == nil && resp.Truncated && net == "udp" {
        return Exchange(req, addr, "tcp")
    }

    return resp, err
}

func Query() {
        req := new(dns.Msg)
        req.SetQuestion("cisco.com", dns.TypeTXT)
        resp, err := Exchange(req, "8.8.8.8:53", "udp")
}

Note: You must be consider your ServeDNS response to client. If your client cannot be get large UDP response size, you need return your client truncated tag with empty answer reponse and then your client can try TCP connection to your server.

gouravkrosx commented 3 months ago

I solved this issue by creating another TCP DNS server and using the same handler for both the servers.