From fc5839864bfbd5bfed512cbb6f05e32894e93938 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 20 May 2022 13:30:11 -0700 Subject: [PATCH] wgengine/netstack: handle multiple magicDNS queries per UDP socket (#4708) Fixes: #4686 Signed-off-by: Tom DNetto --- wgengine/netstack/netstack.go | 37 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 7e6067b99..0a427e8db 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -386,7 +386,6 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Re } } - var pn tcpip.NetworkProtocolNumber switch p.IPVersion { case 4: @@ -900,20 +899,36 @@ func (ns *Impl) handleMagicDNSUDP(srcAddr netaddr.IPPort, c *gonet.UDPConn) { // In practice, implementations are advised not to exceed 512 bytes // due to fragmenting. Just to be sure, we bump all the way to the MTU. const maxUDPReqSize = mtu + // Packets are being generated by the local host, so there should be + // very, very little latency. 150ms was chosen as something of an upper + // bound on resource usage, while hopefully still being long enough for + // a heavily loaded system. + const readDeadline = 150 * time.Millisecond defer c.Close() q := make([]byte, maxUDPReqSize) - n, err := c.Read(q) - if err != nil { - ns.logf("dns udp read: %v", err) - return - } - resp, err := ns.dns.Query(context.Background(), q[:n], srcAddr) - if err != nil { - ns.logf("dns udp query: %v", err) - return + + // libresolv from glibc is quite adamant that transmitting multiple DNS + // requests down the same UDP socket is valid. To support this, we read + // in a loop (with a tight deadline so we don't chew too many resources). + // + // See: https://github.com/bminor/glibc/blob/f7fbb99652eceb1b6b55e4be931649df5946497c/resolv/res_send.c#L995 + for { + c.SetReadDeadline(time.Now().Add(readDeadline)) + n, _, err := c.ReadFrom(q) + if err != nil { + if oe, ok := err.(*net.OpError); !(ok && oe.Timeout()) { + ns.logf("dns udp read: %v", err) // log non-timeout errors + } + return + } + resp, err := ns.dns.Query(context.Background(), q[:n], srcAddr) + if err != nil { + ns.logf("dns udp query: %v", err) + return + } + c.Write(resp) } - c.Write(resp) } // forwardUDP proxies between client (with addr clientAddr) and dstAddr.