diff --git a/dnscrypt-proxy/dnsutils.go b/dnscrypt-proxy/dnsutils.go index aaf317bf..b1087c10 100644 --- a/dnscrypt-proxy/dnsutils.go +++ b/dnscrypt-proxy/dnsutils.go @@ -434,75 +434,19 @@ func _dnsExchange( if err != nil { return DNSExchangeResponse{err: err} } - udpAddr, err := net.ResolveUDPAddr("udp", serverAddress) + packet, rtt, err = udpExchange(proxy, serverAddress, relay, binQuery) if err != nil { return DNSExchangeResponse{err: err} } - upstreamAddr := udpAddr - if relay != nil { - proxy.prepareForRelay(udpAddr.IP, udpAddr.Port, &binQuery) - upstreamAddr = relay.RelayUDPAddr - } - now := time.Now() - pc, err := net.DialUDP("udp", nil, upstreamAddr) - if err != nil { - return DNSExchangeResponse{err: err} - } - defer pc.Close() - if err := pc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil { - return DNSExchangeResponse{err: err} - } - if _, err := pc.Write(binQuery); err != nil { - return DNSExchangeResponse{err: err} - } - packet = make([]byte, MaxDNSPacketSize) - length, err := pc.Read(packet) - if err != nil { - return DNSExchangeResponse{err: err} - } - rtt = time.Since(now) - packet = packet[:length] } else { binQuery, err := query.Pack() if err != nil { return DNSExchangeResponse{err: err} } - tcpAddr, err := net.ResolveTCPAddr("tcp", serverAddress) + packet, rtt, err = tcpExchange(proxy, serverAddress, relay, binQuery) if err != nil { return DNSExchangeResponse{err: err} } - upstreamAddr := tcpAddr - if relay != nil { - proxy.prepareForRelay(tcpAddr.IP, tcpAddr.Port, &binQuery) - upstreamAddr = relay.RelayTCPAddr - } - now := time.Now() - var pc net.Conn - proxyDialer := proxy.xTransport.proxyDialer - if proxyDialer == nil { - pc, err = net.DialTCP("tcp", nil, upstreamAddr) - } else { - pc, err = (*proxyDialer).Dial("tcp", tcpAddr.String()) - } - if err != nil { - return DNSExchangeResponse{err: err} - } - defer pc.Close() - if err := pc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil { - return DNSExchangeResponse{err: err} - } - binQuery, err = PrefixWithSize(binQuery) - if err != nil { - return DNSExchangeResponse{err: err} - } - if _, err := pc.Write(binQuery); err != nil { - return DNSExchangeResponse{err: err} - } - packet, err = ReadPrefixed(&pc) - if err != nil { - return DNSExchangeResponse{err: err} - } - rtt = time.Since(now) } msg := dns.Msg{} if err := msg.Unpack(packet); err != nil { @@ -510,3 +454,87 @@ func _dnsExchange( } return DNSExchangeResponse{response: &msg, rtt: rtt, err: nil} } + +func udpExchange(proxy *Proxy, serverAddress string, relay *DNSCryptRelay, query []byte) ([]byte, time.Duration, error) { + var rtt time.Duration + packet := []byte{} + udpAddr, err := net.ResolveUDPAddr("udp", serverAddress) + if err != nil { + return packet, rtt, err + } + upstreamAddr := udpAddr + if relay != nil { + proxy.prepareForRelay(udpAddr.IP, udpAddr.Port, &query) + upstreamAddr = relay.RelayUDPAddr + } + now := time.Now() + lAddr, err := net.ResolveUDPAddr("udp", ":0") + if err != nil { + return packet, rtt, err + } + pc, err := net.ListenUDP("udp", lAddr) + if err != nil { + return packet, rtt, err + } + defer pc.Close() + if err = pc.SetReadBuffer(MaxDNSPacketSize); err != nil { + return packet, rtt, err + } + if err = pc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil { + return packet, rtt, err + } + if _, err = pc.WriteToUDPAddrPort(query, upstreamAddr.AddrPort()); err != nil { + return packet, rtt, err + } + packet = make([]byte, MaxDNSPacketSize) + length, _, err := pc.ReadFromUDPAddrPort(packet) + if err != nil { + return packet, rtt, err + } + + rtt = time.Since(now) + packet = packet[:length] + return packet, rtt, err +} + +func tcpExchange(proxy *Proxy, serverAddress string, relay *DNSCryptRelay, query []byte) ([]byte, time.Duration, error) { + var rtt time.Duration + packet := []byte{} + tcpAddr, err := net.ResolveTCPAddr("tcp", serverAddress) + if err != nil { + return packet, rtt, err + } + upstreamAddr := tcpAddr + if relay != nil { + proxy.prepareForRelay(tcpAddr.IP, tcpAddr.Port, &query) + upstreamAddr = relay.RelayTCPAddr + } + now := time.Now() + var pc net.Conn + proxyDialer := proxy.xTransport.proxyDialer + if proxyDialer == nil { + pc, err = net.DialTCP("tcp", nil, upstreamAddr) + } else { + pc, err = (*proxyDialer).Dial("tcp", tcpAddr.String()) + } + if err != nil { + return packet, rtt, err + } + defer pc.Close() + if err := pc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil { + return packet, rtt, err + } + query, err = PrefixWithSize(query) + if err != nil { + return packet, rtt, err + } + if _, err := pc.Write(query); err != nil { + return packet, rtt, err + } + packet, err = ReadPrefixed(&pc) + if err != nil { + return packet, rtt, err + } + rtt = time.Since(now) + return packet, rtt, err +} diff --git a/dnscrypt-proxy/dnsutils_test.go b/dnscrypt-proxy/dnsutils_test.go new file mode 100644 index 00000000..9811f620 --- /dev/null +++ b/dnscrypt-proxy/dnsutils_test.go @@ -0,0 +1,197 @@ +package main + +import ( + "net" + "strings" + "sync" + "testing" + "time" + + "github.com/jedisct1/dlog" + "github.com/miekg/dns" + "github.com/powerman/check" +) + +func TestUDPExchangeIPv4(tt *testing.T) { + if testing.Verbose() { + dlog.SetLogLevel(dlog.SeverityDebug) + dlog.UseSyslog(false) + } + t := check.T(tt) + + us, err := startServerUDP(t, "udp4", dns.HandlerFunc(FakeServer1)) + t.Nil(err) + defer us.Shutdown() + + var relay *DNSCryptRelay + proxy := Proxy{timeout: 2 * time.Second} + + msg := dns.Msg{} + msg.SetQuestion("example.com.", dns.TypeA) + + query, err := msg.Pack() + t.Nil(err) + serverAddr, err := toServerAddr(us) + t.Nil(err) + r, rtt, err := udpExchange(&proxy, serverAddr, relay, query) + + t.Nil(err) + t.Must(len(r) > 0) + t.Must(rtt > 0) + resp := dns.Msg{} + resp.Unpack(r) + t.Must(resp.Rcode == dns.RcodeSuccess) +} + +func TestUDPExchangeIPv6(tt *testing.T) { + if testing.Verbose() { + dlog.SetLogLevel(dlog.SeverityDebug) + dlog.UseSyslog(false) + } + t := check.T(tt) + + us, err := startServerUDP(t, "udp6", dns.HandlerFunc(FakeServer1)) + t.Nil(err) + defer us.Shutdown() + + var relay *DNSCryptRelay + proxy := Proxy{timeout: 2 * time.Second} + + msg := dns.Msg{} + msg.SetQuestion("example.com.", dns.TypeA) + + query, err := msg.Pack() + t.Nil(err) + serverAddr, err := toServerAddr(us) + t.Nil(err) + r, rtt, err := udpExchange(&proxy, serverAddr, relay, query) + + t.Nil(err) + t.Must(len(r) > 0) + t.Must(rtt > 0) + resp := dns.Msg{} + resp.Unpack(r) + t.Must(resp.Rcode == dns.RcodeSuccess) +} + +func TestTCPExchangeIPv4(tt *testing.T) { + if testing.Verbose() { + dlog.SetLogLevel(dlog.SeverityDebug) + dlog.UseSyslog(false) + } + t := check.T(tt) + + us, err := startServerTCP(t, "tcp4", dns.HandlerFunc(FakeServer1)) + t.Nil(err) + defer us.Shutdown() + + var relay *DNSCryptRelay + proxy := Proxy{timeout: 2 * time.Second, xTransport: &XTransport{proxyDialer: nil}} + + msg := dns.Msg{} + msg.SetQuestion("example.com.", dns.TypeA) + + query, err := msg.Pack() + t.Nil(err) + serverAddr, err := toServerAddr(us) + t.Nil(err) + r, rtt, err := tcpExchange(&proxy, serverAddr, relay, query) + + t.Nil(err) + t.Must(len(r) > 0) + t.Must(rtt > 0) + resp := dns.Msg{} + resp.Unpack(r) + t.Must(resp.Rcode == dns.RcodeSuccess) +} + +func TestTCPExchangeIPv6(tt *testing.T) { + if testing.Verbose() { + dlog.SetLogLevel(dlog.SeverityDebug) + dlog.UseSyslog(false) + } + t := check.T(tt) + + us, err := startServerTCP(t, "tcp6", dns.HandlerFunc(FakeServer1)) + t.Nil(err) + defer us.Shutdown() + + var relay *DNSCryptRelay + proxy := Proxy{timeout: 2 * time.Second, xTransport: &XTransport{proxyDialer: nil}} + + msg := dns.Msg{} + msg.SetQuestion("example.com.", dns.TypeA) + + query, err := msg.Pack() + t.Nil(err) + serverAddr, err := toServerAddr(us) + t.Nil(err) + r, rtt, err := tcpExchange(&proxy, serverAddr, relay, query) + + t.Nil(err) + t.Must(len(r) > 0) + t.Must(rtt > 0) + resp := dns.Msg{} + resp.Unpack(r) + t.Must(resp.Rcode == dns.RcodeSuccess) +} + +func toServerAddr(s *dns.Server) (string, error) { + var h, p string + var err error + if strings.HasPrefix(s.Net, "udp") { + h, p, err = net.SplitHostPort(s.PacketConn.LocalAddr().String()) + } else { + h, p, err = net.SplitHostPort(s.Listener.Addr().String()) + } + if err != nil { + return "", err + } + if net.ParseIP(h).To4() == nil { + return "[::1]:" + p, nil + } + return "127.0.0.1:" + p, nil +} + +func startServerUDP(t *check.C, proto string, h dns.Handler) (*dns.Server, error) { + waitLock := sync.Mutex{} + addr := ":0" + if proto == "udp6" { + addr = "[::]:0" + } + server := &dns.Server{Addr: addr, Net: proto, ReadTimeout: time.Hour, WriteTimeout: time.Hour, NotifyStartedFunc: waitLock.Unlock, Handler: h} + waitLock.Lock() + + go func() { + err := server.ListenAndServe() + t.Nil(err) + }() + waitLock.Lock() + return server, nil +} + +func startServerTCP(t *check.C, proto string, h dns.Handler) (*dns.Server, error) { + waitLock := sync.Mutex{} + addr := ":0" + if proto == "tcp6" { + addr = "[::]:0" + } + server := &dns.Server{Addr: addr, Net: proto, ReadTimeout: time.Hour, WriteTimeout: time.Hour, NotifyStartedFunc: waitLock.Unlock, Handler: h} + waitLock.Lock() + + go func() { + err := server.ListenAndServe() + t.Nil(err) + }() + waitLock.Lock() + return server, nil +} + +func FakeServer1(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetReply(req) + + m.Extra = make([]dns.RR, 1) + m.Extra[0] = &dns.TXT{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} + w.WriteMsg(m) +} diff --git a/dnscrypt-proxy/plugin_forward.go b/dnscrypt-proxy/plugin_forward.go index e9cdda58..1f095fee 100644 --- a/dnscrypt-proxy/plugin_forward.go +++ b/dnscrypt-proxy/plugin_forward.go @@ -94,6 +94,10 @@ func (plugin *PluginForward) Eval(pluginsState *PluginsState, msg *dns.Msg) erro servers = candidate.servers break } + if "." == candidate.domain { + servers = candidate.servers + break + } } if len(servers) == 0 { return nil diff --git a/dnscrypt-proxy/proxy.go b/dnscrypt-proxy/proxy.go index c1b1e248..fcacd58a 100644 --- a/dnscrypt-proxy/proxy.go +++ b/dnscrypt-proxy/proxy.go @@ -630,6 +630,10 @@ func (proxy *Proxy) processIncomingQuery( if serverInfo != nil { serverName = serverInfo.Name needsEDNS0Padding = (serverInfo.Proto == stamps.StampProtoTypeDoH || serverInfo.Proto == stamps.StampProtoTypeTLS) + + if serverInfo.Proto == stamps.StampProtoTypePlain && serverInfo.knownBugs.fragmentsBlocked { + pluginsState.maxPayloadSize = pluginsState.maxUnencryptedUDPSafePayloadSize + } } query, _ = pluginsState.ApplyQueryPlugins(&proxy.pluginsGlobals, query, needsEDNS0Padding) if len(query) < MinDNSPacketSize || len(query) > MaxDNSPacketSize { @@ -786,6 +790,31 @@ func (proxy *Proxy) processIncomingQuery( serverInfo.noticeFailure(proxy) return response } + } else if serverInfo.Proto == stamps.StampProtoTypePlain { + serverInfo.noticeBegin(proxy) + var relay *DNSCryptRelay + if serverInfo.Relay != nil && serverInfo.Relay.Dnscrypt != nil { + relay = serverInfo.Relay.Dnscrypt + } + truncated := false + if clientProto == "udp" { + response, _, err = udpExchange(proxy, serverInfo.UDPAddr.String(), relay, query) + if err != nil { + dlog.Warnf("Failed to exchange query with plain server [%v] %v", serverName, err) + response = nil + } + if len(response) >= MinDNSPacketSize { + truncated = HasTCFlag(response) + } + } + + if truncated || clientProto == "tcp" { + response, _, err = tcpExchange(proxy, serverInfo.TCPAddr.String(), relay, query) + if err != nil { + dlog.Warnf("Failed to exchange query with plain server [%v] %v", serverName, err) + response = nil + } + } } else { dlog.Fatal("Unsupported protocol") } diff --git a/dnscrypt-proxy/serversInfo.go b/dnscrypt-proxy/serversInfo.go index edbfeb43..72ab5748 100644 --- a/dnscrypt-proxy/serversInfo.go +++ b/dnscrypt-proxy/serversInfo.go @@ -325,6 +325,8 @@ func fetchServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew return fetchDoHServerInfo(proxy, name, stamp, isNew) } else if stamp.Proto == stamps.StampProtoTypeODoHTarget { return fetchODoHTargetInfo(proxy, name, stamp, isNew) + } else if stamp.Proto == stamps.StampProtoTypePlain { + return fetchPlainServerInfo(proxy, name, stamp, isNew) } return ServerInfo{}, fmt.Errorf("Unsupported protocol for [%s]: [%s]", name, stamp.Proto.String()) } @@ -944,6 +946,49 @@ func fetchODoHTargetInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, is return serverInfo, err } +func fetchPlainServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew bool) (ServerInfo, error) { + relay, err := route(proxy, name, stamp.Proto) + if err != nil { + return ServerInfo{}, err + } + if relay != nil { + return ServerInfo{}, errors.New("Relay is not supported by plain DNS") + } + + remoteUDPAddr, err := net.ResolveUDPAddr("udp", stamp.ServerAddrStr) + if err != nil { + return ServerInfo{}, err + } + remoteTCPAddr, err := net.ResolveTCPAddr("tcp", stamp.ServerAddrStr) + if err != nil { + return ServerInfo{}, err + } + + testQuery := dns.Msg{} + testQuery.SetQuestion(".", dns.TypeNS) + testQuery.Id = 0xcafe + testQuery.MsgHdr.RecursionDesired = true + in, rtt, fragmentsBlocked, err := DNSExchange(proxy, "udp", &testQuery, stamp.ServerAddrStr, + nil, &name, false) + + if in != nil && in.Rcode != dns.RcodeSuccess { + dlog.Warnf("Plain Server Target Test Rcode: %v", in.Rcode) + } + if err != nil { + return ServerInfo{}, err + } + return ServerInfo{ + Proto: stamps.StampProtoTypePlain, + Name: name, + Timeout: proxy.timeout, + UDPAddr: remoteUDPAddr, + TCPAddr: remoteTCPAddr, + Relay: relay, + initialRtt: int(rtt.Nanoseconds() / 1000000), + knownBugs: ServerBugs{fragmentsBlocked: fragmentsBlocked}, + }, nil +} + func (serverInfo *ServerInfo) noticeFailure(proxy *Proxy) { proxy.serversInfo.Lock() serverInfo.rtt.Add(float64(proxy.timeout.Nanoseconds() / 1000000))