Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support: Relay randomization #1584

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions dnscrypt-proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ func newConfig() Config {
},
AnonymizedDNS: AnonymizedDNSConfig{
DirectCertFallback: true,
RelayRandomization: false,
},
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
25 changes: 20 additions & 5 deletions dnscrypt-proxy/dnsutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"encoding/binary"
"errors"
"math/rand"
"net"
"strings"
"time"
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion dnscrypt-proxy/example-dnscrypt-proxy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 #
Expand Down
20 changes: 18 additions & 2 deletions dnscrypt-proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
crypto_rand "crypto/rand"
"encoding/binary"
"math/rand"
"net"
"os"
"runtime"
Expand Down Expand Up @@ -91,6 +92,7 @@ type Proxy struct {
certIgnoreTimestamp bool
skipAnonIncompatibleResolvers bool
anonDirectCertFallback bool
anonRelayRandomization bool
pluginBlockUndelegated bool
child bool
daemonize bool
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
104 changes: 74 additions & 30 deletions dnscrypt-proxy/serversInfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
Expand Down