package main import ( crypto_rand "crypto/rand" "encoding/binary" "net" "os" "runtime" "strings" "sync/atomic" "time" "github.com/jedisct1/dlog" clocksmith "github.com/jedisct1/go-clocksmith" stamps "github.com/jedisct1/go-dnsstamps" "github.com/miekg/dns" "golang.org/x/crypto/curve25519" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) type Proxy struct { udpListeners []*net.UDPConn tcpListeners []*net.TCPListener localDoHListeners []*net.TCPListener userName string child bool proxyPublicKey [32]byte proxySecretKey [32]byte ephemeralKeys bool questionSizeEstimator QuestionSizeEstimator serversInfo ServersInfo timeout time.Duration certRefreshDelay time.Duration certRefreshDelayAfterFailure time.Duration certIgnoreTimestamp bool mainProto string listenAddresses []string localDoHListenAddresses []string localDoHPath string localDoHCertFile string localDoHCertKeyFile string daemonize bool registeredServers []RegisteredServer registeredRelays []RegisteredServer pluginBlockIPv6 bool pluginBlockUnqualified bool pluginBlockUndelegated bool cache bool cacheSize int cacheNegMinTTL uint32 cacheNegMaxTTL uint32 cacheMinTTL uint32 cacheMaxTTL uint32 rejectTTL uint32 cloakTTL uint32 queryLogFile string queryLogFormat string queryLogIgnoredQtypes []string nxLogFile string nxLogFormat string blockNameFile string whitelistNameFile string blockNameLogFile string whitelistNameLogFile string blockNameFormat string whitelistNameFormat string blockIPFile string blockIPLogFile string blockIPFormat string forwardFile string cloakFile string captivePortalFile string pluginsGlobals PluginsGlobals sources []*Source clientsCount uint32 maxClients uint32 xTransport *XTransport allWeeklyRanges *map[string]WeeklyRanges logMaxSize int logMaxAge int logMaxBackups int blockedQueryResponse string queryMeta []string routes *map[string][]string serversBlockingFragments []string showCerts bool dohCreds *map[string]DOHClientCreds skipAnonIncompatbibleResolvers bool anonDirectCertFallback bool dns64Prefixes []string dns64Resolvers []string } func (proxy *Proxy) registerUDPListener(conn *net.UDPConn) { proxy.udpListeners = append(proxy.udpListeners, conn) } func (proxy *Proxy) registerTCPListener(listener *net.TCPListener) { proxy.tcpListeners = append(proxy.tcpListeners, listener) } func (proxy *Proxy) registerLocalDoHListener(listener *net.TCPListener) { proxy.localDoHListeners = append(proxy.localDoHListeners, listener) } func (proxy *Proxy) addDNSListener(listenAddrStr string) { listenUDPAddr, err := net.ResolveUDPAddr("udp", listenAddrStr) if err != nil { dlog.Fatal(err) } listenTCPAddr, err := net.ResolveTCPAddr("tcp", listenAddrStr) if err != nil { dlog.Fatal(err) } // if 'userName' is not set, continue as before if len(proxy.userName) <= 0 { if err := proxy.udpListenerFromAddr(listenUDPAddr); err != nil { dlog.Fatal(err) } if err := proxy.tcpListenerFromAddr(listenTCPAddr); err != nil { dlog.Fatal(err) } return } // if 'userName' is set and we are the parent process if !proxy.child { // parent listenerUDP, err := net.ListenUDP("udp", listenUDPAddr) if err != nil { dlog.Fatal(err) } listenerTCP, err := net.ListenTCP("tcp", listenTCPAddr) if err != nil { dlog.Fatal(err) } fdUDP, err := listenerUDP.File() // On Windows, the File method of UDPConn is not implemented. if err != nil { dlog.Fatalf("Unable to switch to a different user: %v", err) } fdTCP, err := listenerTCP.File() // On Windows, the File method of TCPListener is not implemented. if err != nil { dlog.Fatalf("Unable to switch to a different user: %v", err) } defer listenerUDP.Close() defer listenerTCP.Close() FileDescriptors = append(FileDescriptors, fdUDP) FileDescriptors = append(FileDescriptors, fdTCP) return } // child listenerUDP, err := net.FilePacketConn(os.NewFile(InheritedDescriptorsBase+FileDescriptorNum, "listenerUDP")) if err != nil { dlog.Fatalf("Unable to switch to a different user: %v", err) } FileDescriptorNum++ listenerTCP, err := net.FileListener(os.NewFile(InheritedDescriptorsBase+FileDescriptorNum, "listenerTCP")) if err != nil { dlog.Fatalf("Unable to switch to a different user: %v", err) } FileDescriptorNum++ dlog.Noticef("Now listening to %v [UDP]", listenUDPAddr) proxy.registerUDPListener(listenerUDP.(*net.UDPConn)) dlog.Noticef("Now listening to %v [TCP]", listenAddrStr) proxy.registerTCPListener(listenerTCP.(*net.TCPListener)) } func (proxy *Proxy) addLocalDoHListener(listenAddrStr string) { listenTCPAddr, err := net.ResolveTCPAddr("tcp", listenAddrStr) if err != nil { dlog.Fatal(err) } // if 'userName' is not set, continue as before if len(proxy.userName) <= 0 { if err := proxy.localDoHListenerFromAddr(listenTCPAddr); err != nil { dlog.Fatal(err) } return } // if 'userName' is set and we are the parent process if !proxy.child { // parent listenerTCP, err := net.ListenTCP("tcp", listenTCPAddr) if err != nil { dlog.Fatal(err) } fdTCP, err := listenerTCP.File() // On Windows, the File method of TCPListener is not implemented. if err != nil { dlog.Fatalf("Unable to switch to a different user: %v", err) } defer listenerTCP.Close() FileDescriptors = append(FileDescriptors, fdTCP) return } // child listenerTCP, err := net.FileListener(os.NewFile(InheritedDescriptorsBase+FileDescriptorNum, "listenerTCP")) if err != nil { dlog.Fatalf("Unable to switch to a different user: %v", err) } FileDescriptorNum++ proxy.registerLocalDoHListener(listenerTCP.(*net.TCPListener)) dlog.Noticef("Now listening to https://%v%v [DoH]", listenAddrStr, proxy.localDoHPath) } func (proxy *Proxy) StartProxy() { proxy.questionSizeEstimator = NewQuestionSizeEstimator() if _, err := crypto_rand.Read(proxy.proxySecretKey[:]); err != nil { dlog.Fatal(err) } curve25519.ScalarBaseMult(&proxy.proxyPublicKey, &proxy.proxySecretKey) for _, registeredServer := range proxy.registeredServers { proxy.serversInfo.registerServer(registeredServer.name, registeredServer.stamp) } proxy.startAcceptingClients() liveServers, err := proxy.serversInfo.refresh(proxy) if liveServers > 0 { proxy.certIgnoreTimestamp = false } if proxy.showCerts { os.Exit(0) } if liveServers > 0 { dlog.Noticef("dnscrypt-proxy is ready - live servers: %d", liveServers) if !proxy.child { if err := ServiceManagerReadyNotify(); err != nil { dlog.Fatal(err) } } } else if err != nil { dlog.Error(err) dlog.Notice("dnscrypt-proxy is waiting for at least one server to be reachable") } go func() { for { clocksmith.Sleep(PrefetchSources(proxy.xTransport, proxy.sources)) runtime.GC() } }() if len(proxy.serversInfo.registeredServers) > 0 { go func() { for { delay := proxy.certRefreshDelay if liveServers == 0 { delay = proxy.certRefreshDelayAfterFailure } clocksmith.Sleep(delay) liveServers, _ = proxy.serversInfo.refresh(proxy) if liveServers > 0 { proxy.certIgnoreTimestamp = false } runtime.GC() } }() } } type XConn struct { conn interface{} } func (xConn *XConn) Write(p []byte) (n int, err error) { return xConn.conn.(net.Conn).Write(p) } func (xConn *XConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { return xConn.conn.(net.PacketConn).WriteTo(p, addr) } type XNetUDPConn struct { packetConnV4 *ipv4.PacketConn packetConnV6 *ipv6.PacketConn controlMessage *XControlMessage } type XControlMessage struct { controlMessageV4 *ipv4.ControlMessage controlMessageV6 *ipv6.ControlMessage } func (xNetConn *XNetUDPConn) fixSourceAddress(xControlMessage *XControlMessage) { if xControlMessage == nil { return } if xControlMessage.controlMessageV4 != nil { xControlMessage.controlMessageV4.Src = xControlMessage.controlMessageV4.Dst } else if xControlMessage.controlMessageV6 != nil { xControlMessage.controlMessageV6.Src = xControlMessage.controlMessageV6.Dst } xNetConn.controlMessage = xControlMessage } func (xNetConn *XNetUDPConn) Close() error { if xNetConn.packetConnV4 != nil { return xNetConn.packetConnV4.Close() } return xNetConn.packetConnV6.Close() } func (xNetConn *XNetUDPConn) ReadFrom(b []byte) (n int, cm *XControlMessage, src net.Addr, err error) { if xNetConn.packetConnV4 != nil { xn, xcm, xsrc, xerr := xNetConn.packetConnV4.ReadFrom(b) return xn, &XControlMessage{controlMessageV4: xcm}, xsrc, xerr } xn, xcm, xsrc, xerr := xNetConn.packetConnV6.ReadFrom(b) return xn, &XControlMessage{controlMessageV6: xcm}, xsrc, xerr } func (xNetConn *XNetUDPConn) WriteTo(b []byte, cm *XControlMessage, dst net.Addr) (n int, err error) { if xNetConn.packetConnV4 != nil { return xNetConn.packetConnV4.WriteTo(b, cm.controlMessageV4, dst) } return xNetConn.packetConnV6.WriteTo(b, cm.controlMessageV6, dst) } func (proxy *Proxy) udpListener(clientPc net.PacketConn) { isIPv6 := strings.HasPrefix(clientPc.LocalAddr().String(), "[") var xClientPc XNetUDPConn if !isIPv6 { xClientPc = XNetUDPConn{packetConnV4: ipv4.NewPacketConn(clientPc)} xClientPc.packetConnV4.SetControlMessage(ipv4.FlagDst, true) } else { xClientPc = XNetUDPConn{packetConnV6: ipv6.NewPacketConn(clientPc)} xClientPc.packetConnV6.SetControlMessage(ipv6.FlagDst, true) } defer xClientPc.Close() for { buffer := make([]byte, MaxDNSPacketSize-1) length, metadata, clientAddr, err := xClientPc.ReadFrom(buffer) if err != nil { return } xClientPc.fixSourceAddress(metadata) packet := buffer[:length] go func() { start := time.Now() if !proxy.clientsCountInc() { dlog.Warnf("Too many incoming connections (max=%d)", proxy.maxClients) return } defer proxy.clientsCountDec() proxy.processIncomingQuery("udp", proxy.mainProto, packet, &clientAddr, &XConn{conn: clientPc}, start) }() } } func (proxy *Proxy) udpListenerFromAddr(listenAddr *net.UDPAddr) error { listener, err := net.ListenUDP("udp", listenAddr) if err != nil { return err } proxy.registerUDPListener(listener) dlog.Noticef("Now listening to %v [UDP]", listenAddr) return nil } func (proxy *Proxy) tcpListener(acceptPc *net.TCPListener) { defer acceptPc.Close() for { clientPc, err := acceptPc.Accept() if err != nil { continue } go func() { start := time.Now() defer clientPc.Close() if !proxy.clientsCountInc() { dlog.Warnf("Too many incoming connections (max=%d)", proxy.maxClients) return } defer proxy.clientsCountDec() if err := clientPc.SetDeadline(time.Now().Add(proxy.timeout)); err != nil { return } packet, err := ReadPrefixed(&clientPc) if err != nil { return } clientAddr := clientPc.RemoteAddr() proxy.processIncomingQuery("tcp", "tcp", packet, &clientAddr, &XConn{conn: clientPc}, start) }() } } func (proxy *Proxy) tcpListenerFromAddr(listenAddr *net.TCPAddr) error { acceptPc, err := net.ListenTCP("tcp", listenAddr) if err != nil { return err } proxy.registerTCPListener(acceptPc) dlog.Noticef("Now listening to %v [TCP]", listenAddr) return nil } func (proxy *Proxy) localDoHListenerFromAddr(listenAddr *net.TCPAddr) error { acceptPc, err := net.ListenTCP("tcp", listenAddr) if err != nil { return err } proxy.registerLocalDoHListener(acceptPc) dlog.Noticef("Now listening to https://%v%v [DoH]", listenAddr, proxy.localDoHPath) return nil } func (proxy *Proxy) startAcceptingClients() { for _, clientPc := range proxy.udpListeners { go proxy.udpListener(clientPc) } proxy.udpListeners = nil for _, acceptPc := range proxy.tcpListeners { go proxy.tcpListener(acceptPc) } proxy.tcpListeners = nil for _, acceptPc := range proxy.localDoHListeners { go proxy.localDoHListener(acceptPc) } proxy.localDoHListeners = nil } func (proxy *Proxy) prepareForRelay(ip net.IP, port int, encryptedQuery *[]byte) { anonymizedDNSHeader := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00} relayedQuery := append(anonymizedDNSHeader, ip.To16()...) var tmp [2]byte binary.BigEndian.PutUint16(tmp[0:2], uint16(port)) relayedQuery = append(relayedQuery, tmp[:]...) relayedQuery = append(relayedQuery, *encryptedQuery...) *encryptedQuery = relayedQuery } func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, sharedKey *[32]byte, encryptedQuery []byte, clientNonce []byte) ([]byte, error) { upstreamAddr := serverInfo.UDPAddr if serverInfo.RelayUDPAddr != nil { upstreamAddr = serverInfo.RelayUDPAddr } var err error var pc net.Conn proxyDialer := proxy.xTransport.proxyDialer if proxyDialer == nil { pc, err = net.DialUDP("udp", nil, upstreamAddr) } else { pc, err = (*proxyDialer).Dial("udp", upstreamAddr.String()) } if err != nil { return nil, err } defer pc.Close() if err := pc.SetDeadline(time.Now().Add(serverInfo.Timeout)); err != nil { return nil, err } if serverInfo.RelayUDPAddr != nil { proxy.prepareForRelay(serverInfo.UDPAddr.IP, serverInfo.UDPAddr.Port, &encryptedQuery) } encryptedResponse := make([]byte, MaxDNSPacketSize) for tries := 2; tries > 0; tries-- { if _, err := pc.Write(encryptedQuery); err != nil { return nil, err } length, err := pc.Read(encryptedResponse) if err == nil { encryptedResponse = encryptedResponse[:length] break } dlog.Debugf("[%v] Retry on timeout", serverInfo.Name) } return proxy.Decrypt(serverInfo, sharedKey, encryptedResponse, clientNonce) } func (proxy *Proxy) exchangeWithTCPServer(serverInfo *ServerInfo, sharedKey *[32]byte, encryptedQuery []byte, clientNonce []byte) ([]byte, error) { upstreamAddr := serverInfo.TCPAddr if serverInfo.RelayUDPAddr != nil { upstreamAddr = serverInfo.RelayTCPAddr } var err error var pc net.Conn proxyDialer := proxy.xTransport.proxyDialer if proxyDialer == nil { pc, err = net.DialTCP("tcp", nil, upstreamAddr) } else { pc, err = (*proxyDialer).Dial("tcp", upstreamAddr.String()) } if err != nil { return nil, err } defer pc.Close() if err := pc.SetDeadline(time.Now().Add(serverInfo.Timeout)); err != nil { return nil, err } if serverInfo.RelayTCPAddr != nil { proxy.prepareForRelay(serverInfo.TCPAddr.IP, serverInfo.TCPAddr.Port, &encryptedQuery) } encryptedQuery, err = PrefixWithSize(encryptedQuery) if err != nil { return nil, err } if _, err := pc.Write(encryptedQuery); err != nil { return nil, err } encryptedResponse, err := ReadPrefixed(&pc) if err != nil { return nil, err } return proxy.Decrypt(serverInfo, sharedKey, encryptedResponse, clientNonce) } func (proxy *Proxy) clientsCountInc() bool { for { count := atomic.LoadUint32(&proxy.clientsCount) if count >= proxy.maxClients { return false } if atomic.CompareAndSwapUint32(&proxy.clientsCount, count, count+1) { dlog.Debugf("clients count: %d", count+1) return true } } } func (proxy *Proxy) clientsCountDec() { for { if count := atomic.LoadUint32(&proxy.clientsCount); count == 0 || atomic.CompareAndSwapUint32(&proxy.clientsCount, count, count-1) { break } } } func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string, query []byte, clientAddr *net.Addr, clientPc *XConn, start time.Time) (response []byte) { if len(query) < MinDNSPacketSize { return } pluginsState := NewPluginsState(proxy, clientProto, clientAddr, serverProto, start) serverName := "-" needsEDNS0Padding := false serverInfo := proxy.serversInfo.getOne() if serverInfo != nil { serverName = serverInfo.Name needsEDNS0Padding = (serverInfo.Proto == stamps.StampProtoTypeDoH || serverInfo.Proto == stamps.StampProtoTypeTLS) } query, _ = pluginsState.ApplyQueryPlugins(&proxy.pluginsGlobals, query, needsEDNS0Padding) if len(query) < MinDNSPacketSize || len(query) > MaxDNSPacketSize { return } if pluginsState.action == PluginsActionDrop { pluginsState.returnCode = PluginsReturnCodeDrop pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) return } var err error if pluginsState.synthResponse != nil { response, err = pluginsState.synthResponse.PackBuffer(response) if err != nil { pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) return } } if len(response) == 0 && serverInfo != nil { var ttl *uint32 pluginsState.serverName = serverName if serverInfo.Proto == stamps.StampProtoTypeDNSCrypt { sharedKey, encryptedQuery, clientNonce, err := proxy.Encrypt(serverInfo, query, serverProto) if err != nil && serverProto == "udp" { dlog.Debug("Unable to pad for UDP, re-encrypting query for TCP") serverProto = "tcp" sharedKey, encryptedQuery, clientNonce, err = proxy.Encrypt(serverInfo, query, serverProto) } if err != nil { pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) return } serverInfo.noticeBegin(proxy) if serverProto == "udp" { response, err = proxy.exchangeWithUDPServer(serverInfo, sharedKey, encryptedQuery, clientNonce) retryOverTCP := false if err == nil && len(response) >= MinDNSPacketSize && response[2]&0x02 == 0x02 { retryOverTCP = true } else if neterr, ok := err.(net.Error); ok && neterr.Timeout() { dlog.Debugf("[%v] Retry over TCP after UDP timeouts", serverName) retryOverTCP = true } if retryOverTCP { serverProto = "tcp" sharedKey, encryptedQuery, clientNonce, err = proxy.Encrypt(serverInfo, query, serverProto) if err != nil { pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) return } response, err = proxy.exchangeWithTCPServer(serverInfo, sharedKey, encryptedQuery, clientNonce) } } else { response, err = proxy.exchangeWithTCPServer(serverInfo, sharedKey, encryptedQuery, clientNonce) } if err != nil { if stale, ok := pluginsState.sessionData["stale"]; ok { dlog.Debug("Serving stale response") response, err = (stale.(*dns.Msg)).Pack() } } if err != nil { if neterr, ok := err.(net.Error); ok && neterr.Timeout() { pluginsState.returnCode = PluginsReturnCodeServerTimeout } else { pluginsState.returnCode = PluginsReturnCodeNetworkError } pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) serverInfo.noticeFailure(proxy) return } } else if serverInfo.Proto == stamps.StampProtoTypeDoH { tid := TransactionID(query) SetTransactionID(query, 0) serverInfo.noticeBegin(proxy) serverResponse, tls, _, err := proxy.xTransport.DoHQuery(serverInfo.useGet, serverInfo.URL, query, proxy.timeout) SetTransactionID(query, tid) if err == nil || tls == nil || !tls.HandshakeComplete { response = nil } else if stale, ok := pluginsState.sessionData["stale"]; ok { dlog.Debug("Serving stale response") response, err = (stale.(*dns.Msg)).Pack() } if err != nil { pluginsState.returnCode = PluginsReturnCodeNetworkError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) serverInfo.noticeFailure(proxy) return } if response == nil { response = serverResponse } if len(response) >= MinDNSPacketSize { SetTransactionID(response, tid) } } else { dlog.Fatal("Unsupported protocol") } if len(response) < MinDNSPacketSize || len(response) > MaxDNSPacketSize { pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) serverInfo.noticeFailure(proxy) return } response, err = pluginsState.ApplyResponsePlugins(&proxy.pluginsGlobals, response, ttl) if err != nil { pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) serverInfo.noticeFailure(proxy) return } if pluginsState.action == PluginsActionDrop { pluginsState.returnCode = PluginsReturnCodeDrop pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) return } if pluginsState.synthResponse != nil { response, err = pluginsState.synthResponse.PackBuffer(response) if err != nil { pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) return } } if rcode := Rcode(response); rcode == dns.RcodeServerFailure { // SERVFAIL if pluginsState.dnssec { dlog.Debug("A response had an invalid DNSSEC signature") } else { dlog.Infof("Server [%v] returned temporary error code SERVFAIL -- Invalid DNSSEC signature received or server may be experiencing connectivity issues", serverInfo.Name) serverInfo.noticeFailure(proxy) } } else { serverInfo.noticeSuccess(proxy) } } if len(response) < MinDNSPacketSize || len(response) > MaxDNSPacketSize { pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) if serverInfo != nil { serverInfo.noticeFailure(proxy) } return } if clientProto == "udp" { if len(response) > pluginsState.maxUnencryptedUDPSafePayloadSize { response, err = TruncatedResponse(response) if err != nil { pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) return } } clientPc.WriteTo(response, *clientAddr) if HasTCFlag(response) { proxy.questionSizeEstimator.blindAdjust() } else { proxy.questionSizeEstimator.adjust(ResponseOverhead + len(response)) } } else if clientProto == "tcp" { response, err = PrefixWithSize(response) if err != nil { pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) if serverInfo != nil { serverInfo.noticeFailure(proxy) } return } if clientPc != nil { clientPc.Write(response) } } pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) return response } func NewProxy() *Proxy { return &Proxy{ serversInfo: NewServersInfo(), } }