From e81f1ca1dba7e643e74b52eace78032015f180ff Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Tue, 9 Feb 2021 17:26:33 +0900 Subject: [PATCH] Support: relay randomization (removed unncessary file. thanks @lifenjoiner) --- dnscrypt-proxy/config.go | 6 ++ dnscrypt-proxy/dnsutils.go | 25 ++++- dnscrypt-proxy/example-dnscrypt-proxy.toml | 4 +- dnscrypt-proxy/proxy.go | 20 +++- dnscrypt-proxy/serversInfo.go | 104 +++++++++++++++------ 5 files changed, 121 insertions(+), 38 deletions(-) diff --git a/dnscrypt-proxy/config.go b/dnscrypt-proxy/config.go index de61059206..9d92b0c6d3 100644 --- a/dnscrypt-proxy/config.go +++ b/dnscrypt-proxy/config.go @@ -152,6 +152,7 @@ func newConfig() Config { }, AnonymizedDNS: AnonymizedDNSConfig{ DirectCertFallback: true, + RelayRandomization: false, }, } } @@ -232,6 +233,7 @@ type AnonymizedDNSConfig struct { Routes []AnonymizedDNSRouteConfig `toml:"routes"` SkipIncompatible bool `toml:"skip_incompatible"` DirectCertFallback bool `toml:"direct_cert_fallback"` + RelayRandomization bool `toml:"relay_randomization"` } type BrokenImplementationsConfig struct { @@ -614,6 +616,7 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error { } proxy.skipAnonIncompatibleResolvers = config.AnonymizedDNS.SkipIncompatible proxy.anonDirectCertFallback = config.AnonymizedDNS.DirectCertFallback + proxy.anonRelayRandomization = config.AnonymizedDNS.RelayRandomization if config.DoHClientX509AuthLegacy.Creds != nil { return errors.New("[tls_client_auth] has been renamed to [doh_client_x509_auth] - Update your config file") @@ -733,6 +736,9 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error { dlog.Noticef("Anonymized DNS: routing everything via %v", via) } } + if proxy.anonRelayRandomization { + dlog.Noticef("Anonymized DNS: relay randomization turned on") + } } if *flags.Check { dlog.Notice("Configuration successfully checked") diff --git a/dnscrypt-proxy/dnsutils.go b/dnscrypt-proxy/dnsutils.go index de4e5f3a01..256b5dc00b 100644 --- a/dnscrypt-proxy/dnsutils.go +++ b/dnscrypt-proxy/dnsutils.go @@ -3,6 +3,7 @@ package main import ( "encoding/binary" "errors" + "math/rand" "net" "strings" "time" @@ -372,7 +373,7 @@ func DNSExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress strin } return nil, 0, false, err } - dlog.Infof("Unable to get the public key for [%v] via relay [%v], retrying over a direct connection", *serverName, relay.RelayUDPAddr.IP) + dlog.Infof("Unable to get the public key for [%v] via relay [%v], retrying over a direct connection", *serverName, relay.RelayUDPAddrs) relay = nil } } @@ -403,9 +404,16 @@ func _dnsExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress stri return DNSExchangeResponse{err: err} } upstreamAddr := udpAddr - if relay != nil { + if relay != nil && len(relay.RelayUDPAddrs) > 0 { + var relayIdx int + if proxy.anonRelayRandomization { + relayIdx = rand.Intn(len(relay.RelayUDPAddrs)) + } else { + relayIdx = 0 + } proxy.prepareForRelay(udpAddr.IP, udpAddr.Port, &binQuery) - upstreamAddr = relay.RelayUDPAddr + upstreamAddr = relay.RelayUDPAddrs[relayIdx] + dlog.Debugf("[%v] _dnsExchange: via relay [%v] (UDP)", serverAddress, relay.RelayUDPAddrs[relayIdx].IP) } now := time.Now() pc, err := net.DialUDP("udp", nil, upstreamAddr) @@ -436,9 +444,16 @@ func _dnsExchange(proxy *Proxy, proto string, query *dns.Msg, serverAddress stri return DNSExchangeResponse{err: err} } upstreamAddr := tcpAddr - if relay != nil { + if relay != nil && len(relay.RelayTCPAddrs) > 0 { + var relayIdx int + if proxy.anonRelayRandomization { + relayIdx = rand.Intn(len(relay.RelayTCPAddrs)) + } else { + relayIdx = 0 + } proxy.prepareForRelay(tcpAddr.IP, tcpAddr.Port, &binQuery) - upstreamAddr = relay.RelayTCPAddr + upstreamAddr = relay.RelayTCPAddrs[relayIdx] + dlog.Debugf("[%v] _dnsExchange: via relay [%v] (TCP)", serverAddress, relay.RelayTCPAddrs[relayIdx].IP) } now := time.Now() var pc net.Conn diff --git a/dnscrypt-proxy/example-dnscrypt-proxy.toml b/dnscrypt-proxy/example-dnscrypt-proxy.toml index 12d9bdecd7..0cdfe41cbd 100644 --- a/dnscrypt-proxy/example-dnscrypt-proxy.toml +++ b/dnscrypt-proxy/example-dnscrypt-proxy.toml @@ -778,7 +778,9 @@ skip_incompatible = false # direct_cert_fallback = false - +# If multiple relays are specified, one of them are randomly chosen when a query get issued. +# Default value is false (non-randomized). +# relay_randomization = true ############################### # DNS64 # diff --git a/dnscrypt-proxy/proxy.go b/dnscrypt-proxy/proxy.go index ec578808b8..40d7b38a48 100644 --- a/dnscrypt-proxy/proxy.go +++ b/dnscrypt-proxy/proxy.go @@ -4,6 +4,7 @@ import ( "context" crypto_rand "crypto/rand" "encoding/binary" + "math/rand" "net" "os" "runtime" @@ -91,6 +92,7 @@ type Proxy struct { certIgnoreTimestamp bool skipAnonIncompatibleResolvers bool anonDirectCertFallback bool + anonRelayRandomization bool pluginBlockUndelegated bool child bool daemonize bool @@ -477,7 +479,14 @@ func (proxy *Proxy) prepareForRelay(ip net.IP, port int, encryptedQuery *[]byte) func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, sharedKey *[32]byte, encryptedQuery []byte, clientNonce []byte) ([]byte, error) { upstreamAddr := serverInfo.UDPAddr if serverInfo.Relay != nil && serverInfo.Relay.Dnscrypt != nil { - upstreamAddr = serverInfo.Relay.Dnscrypt.RelayUDPAddr + var relayIdx int + if proxy.anonRelayRandomization { + relayIdx = rand.Intn(len(serverInfo.Relay.Dnscrypt.RelayUDPAddrs)) + } else { + relayIdx = 0 + } + upstreamAddr = serverInfo.Relay.Dnscrypt.RelayUDPAddrs[relayIdx] + dlog.Debugf("[%v] exchangeWithUDPServer: via relay [%v]", serverInfo.Name, serverInfo.Relay.Dnscrypt.RelayUDPAddrs[relayIdx].IP) } var err error var pc net.Conn @@ -515,7 +524,14 @@ func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, sharedKey *[32 func (proxy *Proxy) exchangeWithTCPServer(serverInfo *ServerInfo, sharedKey *[32]byte, encryptedQuery []byte, clientNonce []byte) ([]byte, error) { upstreamAddr := serverInfo.TCPAddr if serverInfo.Relay != nil && serverInfo.Relay.Dnscrypt != nil { - upstreamAddr = serverInfo.Relay.Dnscrypt.RelayTCPAddr + var relayIdx int + if proxy.anonRelayRandomization { + relayIdx = rand.Intn(len(serverInfo.Relay.Dnscrypt.RelayTCPAddrs)) + } else { + relayIdx = 0 + } + upstreamAddr = serverInfo.Relay.Dnscrypt.RelayTCPAddrs[relayIdx] + dlog.Debugf("[%v] exchangeWithTCPServer: via relay [%v]", serverInfo.Name, serverInfo.Relay.Dnscrypt.RelayTCPAddrs[relayIdx].IP) } var err error var pc net.Conn diff --git a/dnscrypt-proxy/serversInfo.go b/dnscrypt-proxy/serversInfo.go index 6f631fd88d..1dcf687098 100644 --- a/dnscrypt-proxy/serversInfo.go +++ b/dnscrypt-proxy/serversInfo.go @@ -100,8 +100,8 @@ func (LBStrategyRandom) getCandidate(serversCount int) int { var DefaultLBStrategy = LBStrategyP2{} type DNSCryptRelay struct { - RelayUDPAddr *net.UDPAddr - RelayTCPAddr *net.TCPAddr + RelayUDPAddrs []*net.UDPAddr + RelayTCPAddrs []*net.TCPAddr } type ODoHRelay struct{} @@ -281,7 +281,7 @@ func fetchServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew return ServerInfo{}, fmt.Errorf("Unsupported protocol for [%s]: [%s]", name, stamp.Proto.String()) } -func findFarthestRoute(proxy *Proxy, name string, relayStamps []stamps.ServerStamp) *stamps.ServerStamp { +func findFarthestRoute(proxy *Proxy, name string, relayStamps []*stamps.ServerStamp) *stamps.ServerStamp { serverIdx := -1 proxy.serversInfo.RLock() for i, registeredServer := range proxy.serversInfo.registeredServers { @@ -305,8 +305,8 @@ func findFarthestRoute(proxy *Proxy, name string, relayStamps []stamps.ServerSta } bestRelayIdxs := make([]int, 0) bestRelaySamePrefixBits := 128 - for relayIdx, relayStamp := range relayStamps { - relayAddrStr, _ := ExtractHostAndPort(relayStamp.ServerAddrStr, 443) + for relayIdx := range relayStamps { + relayAddrStr, _ := ExtractHostAndPort(relayStamps[relayIdx].ServerAddrStr, 443) relayAddr := net.ParseIP(relayAddrStr) if relayAddr == nil { continue @@ -332,10 +332,11 @@ func findFarthestRoute(proxy *Proxy, name string, relayStamps []stamps.ServerSta bestRelayIdxs = append(bestRelayIdxs, relayIdx) } } - return &relayStamps[bestRelayIdxs[rand.Intn(len(bestRelayIdxs))]] + return relayStamps[bestRelayIdxs[rand.Intn(len(bestRelayIdxs))]] } -func route(proxy *Proxy, name string) (*Relay, error) { +// Called only when DNSCrypt at this point (Jan. 14, 2021), ODoH needs to be handled by an arg of StampProtoType. +func route(proxy *Proxy, name string, serverProto stamps.StampProtoType) (*Relay, error) { routes := proxy.routes if routes == nil { return nil, nil @@ -378,40 +379,83 @@ func route(proxy *Proxy, name string) (*Relay, error) { proxy.serversInfo.RUnlock() } } + if len(relayStamps) == 0 { return nil, fmt.Errorf("Empty relay set for [%v]", name) } - var relayCandidateStamp *stamps.ServerStamp - if !wildcard || len(relayStamps) == 1 { - relayCandidateStamp = &relayStamps[rand.Intn(len(relayStamps))] + + dlog.Debugf( + "Choosing relay candidates for [%v] (%v) - wildcard: %v, relayRandomization: %v", + name, serverProto.String(), wildcard, proxy.anonRelayRandomization, + ) + + // filtering with serverProto + var filteredStamps [](*stamps.ServerStamp) + for i, stamp := range relayStamps { + if serverProto == stamp.Proto || + (serverProto == stamps.StampProtoTypeDNSCrypt && stamp.Proto == stamps.StampProtoTypeDNSCryptRelay) || + (serverProto == stamps.StampProtoTypeODoHTarget && stamp.Proto == stamps.StampProtoTypeODoHRelay) { + filteredStamps = append(filteredStamps, &relayStamps[i]) + } + } + dlog.Debugf("Relay candidates supporting [%v]: %v", serverProto.String(), filteredStamps) + + var relayCandidateStamps [](*stamps.ServerStamp) + if proxy.anonRelayRandomization { + for i := range filteredStamps { + relayCandidateStamps = append(relayCandidateStamps, filteredStamps[i]) + } + } else if !wildcard || len(filteredStamps) == 1 { + relayCandidateStamps = append(relayCandidateStamps, filteredStamps[rand.Intn(len(filteredStamps))]) } else { - relayCandidateStamp = findFarthestRoute(proxy, name, relayStamps) + farthest := findFarthestRoute(proxy, name, filteredStamps) + relayCandidateStamps = append(relayCandidateStamps, farthest) } - if relayCandidateStamp == nil { + if len(relayCandidateStamps) == 0 { return nil, fmt.Errorf("No valid relay for server [%v]", name) } - relayName := relayCandidateStamp.ServerAddrStr + dlog.Debugf("Stamps of chosen relay candidates for [%v]: %v", name, relayCandidateStamps) + + /// maybe not required? only used for print. + var relayNamesForPrint []string proxy.serversInfo.RLock() - for _, registeredServer := range proxy.serversInfo.registeredRelays { - if registeredServer.stamp.ServerAddrStr == relayCandidateStamp.ServerAddrStr { - relayName = registeredServer.name - break + for i := range relayCandidateStamps { + relayName := relayCandidateStamps[i].ServerAddrStr + for _, registeredServer := range proxy.serversInfo.registeredRelays { + if registeredServer.stamp.ServerAddrStr == relayCandidateStamps[i].ServerAddrStr { + relayName = registeredServer.name + break + } } + relayNamesForPrint = append(relayNamesForPrint, relayName) } + proxy.serversInfo.RUnlock() - switch relayCandidateStamp.Proto { - case stamps.StampProtoTypeDNSCrypt, stamps.StampProtoTypeDNSCryptRelay: - relayUDPAddr, err := net.ResolveUDPAddr("udp", relayCandidateStamp.ServerAddrStr) - if err != nil { - return nil, err - } - relayTCPAddr, err := net.ResolveTCPAddr("tcp", relayCandidateStamp.ServerAddrStr) - if err != nil { - return nil, err + switch serverProto { + case stamps.StampProtoTypeDNSCrypt: + var relayUDPAddrs [](*net.UDPAddr) + var relayTCPAddrs [](*net.TCPAddr) + for _, stamp := range relayCandidateStamps { + relayUDPAddr, err := net.ResolveUDPAddr("udp", stamp.ServerAddrStr) + if err != nil { + return nil, err + } + relayUDPAddrs = append(relayUDPAddrs, relayUDPAddr) + relayTCPAddr, err := net.ResolveTCPAddr("tcp", stamp.ServerAddrStr) + if err != nil { + return nil, err + } + relayTCPAddrs = append(relayTCPAddrs, relayTCPAddr) } - dlog.Noticef("Anonymizing queries for [%v] via [%v]", name, relayName) - return &Relay{Proto: stamps.StampProtoTypeDNSCryptRelay, Dnscrypt: &DNSCryptRelay{RelayUDPAddr: relayUDPAddr, RelayTCPAddr: relayTCPAddr}}, nil - case stamps.StampProtoTypeODoHRelay: + dlog.Noticef("Anonymizing queries for [%v] via [%v]", name, relayNamesForPrint) + return &Relay{ + Proto: stamps.StampProtoTypeDNSCryptRelay, + Dnscrypt: &DNSCryptRelay{ + RelayUDPAddrs: relayUDPAddrs, + RelayTCPAddrs: relayTCPAddrs, + }, + }, nil + case stamps.StampProtoTypeODoHTarget: return &Relay{Proto: stamps.StampProtoTypeODoHRelay, ODoH: &ODoHRelay{}}, nil } return nil, fmt.Errorf("Invalid relay set for server [%v]", name) @@ -434,7 +478,7 @@ func fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp break } } - relay, err := route(proxy, name) + relay, err := route(proxy, name, stamp.Proto) if err != nil { return ServerInfo{}, err }