Try harder to work around Cisco and Quad9 bugs

This commit is contained in:
Frank Denis 2020-03-25 20:09:46 +01:00
parent 64935c9b92
commit 7424f1a8b7
7 changed files with 116 additions and 85 deletions

View file

@ -1,9 +1,7 @@
* Version 2.0.42 * Version 2.0.42
- Quad9 was put back into the list of broken implementations. They - Quad9 was put back into the list of broken implementations. They
drop responses larger than questions instead of truncating them. drop responses larger than questions instead of truncating them.
- Queries for servers that don't properly handle padding are now - More workarounds were implemented for servers dropping UDP fragments.
padded to 1472 bytes. This mitigates the issue with Quad9 while
still working around the limitations of Cisco resolvers.
* Version 2.0.41 * Version 2.0.41
- Precompiled ARM binaries are compatible with ARMv5 CPUs. The - Precompiled ARM binaries are compatible with ARMv5 CPUs. The

View file

@ -134,7 +134,8 @@ func newConfig() Config {
LBEstimator: true, LBEstimator: true,
BlockedQueryResponse: "hinfo", BlockedQueryResponse: "hinfo",
BrokenImplementations: BrokenImplementationsConfig{ BrokenImplementations: BrokenImplementationsConfig{
BrokenQueryPadding: []string{"cisco", "cisco-ipv6", "cisco-familyshield", "quad9-dnscrypt-ip4-filter-alt", "quad9-dnscrypt-ip4-filter-pri", "quad9-dnscrypt-ip4-nofilter-alt", "quad9-dnscrypt-ip4-nofilter-pri", "quad9-dnscrypt-ip6-filter-alt", "quad9-dnscrypt-ip6-filter-pri", "quad9-dnscrypt-ip6-nofilter-alt", "quad9-dnscrypt-ip6-nofilter-pri"}, FragmentsBlocked: []string{"cisco", "cisco-ipv6", "cisco-familyshield", "quad9-dnscrypt-ip4-filter-alt", "quad9-dnscrypt-ip4-filter-pri", "quad9-dnscrypt-ip4-nofilter-alt", "quad9-dnscrypt-ip4-nofilter-pri", "quad9-dnscrypt-ip6-filter-alt", "quad9-dnscrypt-ip6-filter-pri", "quad9-dnscrypt-ip6-nofilter-alt", "quad9-dnscrypt-ip6-nofilter-pri"},
LargerResponsesDropped: []string{"quad9-dnscrypt-ip4-filter-alt", "quad9-dnscrypt-ip4-filter-pri", "quad9-dnscrypt-ip4-nofilter-alt", "quad9-dnscrypt-ip4-nofilter-pri", "quad9-dnscrypt-ip6-filter-alt", "quad9-dnscrypt-ip6-filter-pri", "quad9-dnscrypt-ip6-nofilter-alt", "quad9-dnscrypt-ip6-nofilter-pri"},
}, },
} }
} }
@ -192,7 +193,9 @@ type AnonymizedDNSConfig struct {
} }
type BrokenImplementationsConfig struct { type BrokenImplementationsConfig struct {
BrokenQueryPadding []string `toml:"broken_query_padding"` BrokenQueryPadding []string `toml:"broken_query_padding"`
FragmentsBlocked []string `toml:"fragments_blocked"`
LargerResponsesDropped []string `toml:"larger_responses_dropped"`
} }
type LocalDoHConfig struct { type LocalDoHConfig struct {
@ -502,7 +505,12 @@ func ConfigLoad(proxy *Proxy, flags *ConfigFlags) error {
} }
proxy.dohCreds = &creds proxy.dohCreds = &creds
proxy.serversWithBrokenQueryPadding = config.BrokenImplementations.BrokenQueryPadding // Backwards compatibility
config.BrokenImplementations.FragmentsBlocked = append(config.BrokenImplementations.FragmentsBlocked, config.BrokenImplementations.BrokenQueryPadding...)
config.BrokenImplementations.LargerResponsesDropped = append(config.BrokenImplementations.LargerResponsesDropped, config.BrokenImplementations.BrokenQueryPadding...)
proxy.serversBlockingFragments = config.BrokenImplementations.FragmentsBlocked
proxy.serversDroppingLargerResponses = config.BrokenImplementations.LargerResponsesDropped
if *flags.ListAll { if *flags.ListAll {
config.ServerNames = nil config.ServerNames = nil

View file

@ -80,18 +80,24 @@ func (proxy *Proxy) Encrypt(serverInfo *ServerInfo, packet []byte, proto string)
} }
minQuestionSize := QueryOverhead + len(packet) minQuestionSize := QueryOverhead + len(packet)
if proto == "udp" { if proto == "udp" {
if serverInfo.knownBugs.incorrectPadding { minQuestionSize = Max(proxy.questionSizeEstimator.MinQuestionSize(), minQuestionSize)
// XXX - Note: Cisco's broken implementation doesn't accept more than 1472 bytes
minQuestionSize = Max(1472, minQuestionSize)
} else {
minQuestionSize = Max(proxy.questionSizeEstimator.MinQuestionSize(), minQuestionSize)
}
} else { } else {
var xpad [1]byte var xpad [1]byte
rand.Read(xpad[:]) rand.Read(xpad[:])
minQuestionSize += int(xpad[0]) minQuestionSize += int(xpad[0])
} }
paddedLength := Min(MaxDNSUDPPacketSize, (Max(minQuestionSize, QueryOverhead)+1+63) & ^63) paddedLength := Min(MaxDNSUDPPacketSize, (Max(minQuestionSize, QueryOverhead)+1+63) & ^63)
if proto == "udp" {
if serverInfo.knownBugs.fragmentsBlocked {
if serverInfo.knownBugs.largerQueriesDropped {
paddedLength = MaxDNSUDPSafePacketSize
} else {
paddedLength = Min(MaxDNSUDPSafePacketSize, paddedLength)
}
} else if serverInfo.knownBugs.largerQueriesDropped {
paddedLength = MaxDNSUDPPacketSize
}
}
if serverInfo.RelayUDPAddr != nil && proto == "tcp" { if serverInfo.RelayUDPAddr != nil && proto == "tcp" {
paddedLength = MaxDNSPacketSize paddedLength = MaxDNSPacketSize
} }

View file

@ -38,7 +38,7 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk
relayUDPAddr, relayTCPAddr = nil, nil relayUDPAddr, relayTCPAddr = nil, nil
} }
tryFragmentsSupport := true tryFragmentsSupport := true
if knownBugs.incorrectPadding { if knownBugs.fragmentsBlocked {
tryFragmentsSupport = false tryFragmentsSupport = false
} }
in, rtt, fragmentsBlocked, err := dnsExchange(proxy, proto, &query, serverAddress, relayUDPAddr, relayTCPAddr, serverName, tryFragmentsSupport) in, rtt, fragmentsBlocked, err := dnsExchange(proxy, proto, &query, serverAddress, relayUDPAddr, relayTCPAddr, serverName, tryFragmentsSupport)

View file

@ -624,14 +624,19 @@ cache_neg_max_ttl = 600
# Cisco servers currently cannot handle queries larger than 1472 bytes, and don't # Cisco servers currently cannot handle queries larger than 1472 bytes, and don't
# truncate reponses larger than questions as expected by the DNSCrypt protocol. # truncate reponses larger than questions as expected by the DNSCrypt protocol.
# Quad9 ignores the query instead of sending a truncated response when the
# response is larger than the question.
# This prevents large responses from being received over UDP, and breaks relaying. # This prevents large responses from being received over UDP, and breaks relaying.
# A workaround for the first issue will be applied to servers in list below. # A workaround for the first issue will be applied to servers in list below.
# Relaying cannot be reliable until the servers are fixed. # Relaying cannot be reliable until the servers are fixed.
# Do not change that list until the bugs are fixed server-side. # Do not change that list until the bugs are fixed server-side.
broken_query_padding = ['cisco', 'cisco-ipv6', 'cisco-familyshield', 'quad9-dnscrypt-ip4-filter-alt', 'quad9-dnscrypt-ip4-filter-pri', 'quad9-dnscrypt-ip4-nofilter-alt', 'quad9-dnscrypt-ip4-nofilter-pri', 'quad9-dnscrypt-ip6-filter-alt', 'quad9-dnscrypt-ip6-filter-pri', 'quad9-dnscrypt-ip6-nofilter-alt', 'quad9-dnscrypt-ip6-nofilter-pri'] fragments_blocked = ['cisco', 'cisco-ipv6', 'cisco-familyshield', 'quad9-dnscrypt-ip4-filter-alt', 'quad9-dnscrypt-ip4-filter-pri', 'quad9-dnscrypt-ip4-nofilter-alt', 'quad9-dnscrypt-ip4-nofilter-pri', 'quad9-dnscrypt-ip6-filter-alt', 'quad9-dnscrypt-ip6-filter-pri', 'quad9-dnscrypt-ip6-nofilter-alt', 'quad9-dnscrypt-ip6-nofilter-pri']
# Quad9 ignores the query instead of sending a truncated response when the
# response is larger than the question.
# Do not change that list until the bugs are fixed server-side.
larger_responses_dropped = ['quad9-dnscrypt-ip4-filter-alt', 'quad9-dnscrypt-ip4-filter-pri', 'quad9-dnscrypt-ip4-nofilter-alt', 'quad9-dnscrypt-ip4-nofilter-pri', 'quad9-dnscrypt-ip6-filter-alt', 'quad9-dnscrypt-ip6-filter-pri', 'quad9-dnscrypt-ip6-nofilter-alt', 'quad9-dnscrypt-ip6-nofilter-pri']

View file

@ -16,68 +16,69 @@ import (
) )
type Proxy struct { type Proxy struct {
userName string userName string
child bool child bool
proxyPublicKey [32]byte proxyPublicKey [32]byte
proxySecretKey [32]byte proxySecretKey [32]byte
ephemeralKeys bool ephemeralKeys bool
questionSizeEstimator QuestionSizeEstimator questionSizeEstimator QuestionSizeEstimator
serversInfo ServersInfo serversInfo ServersInfo
timeout time.Duration timeout time.Duration
certRefreshDelay time.Duration certRefreshDelay time.Duration
certRefreshDelayAfterFailure time.Duration certRefreshDelayAfterFailure time.Duration
certIgnoreTimestamp bool certIgnoreTimestamp bool
mainProto string mainProto string
listenAddresses []string listenAddresses []string
localDoHListenAddresses []string localDoHListenAddresses []string
localDoHPath string localDoHPath string
localDoHCertFile string localDoHCertFile string
localDoHCertKeyFile string localDoHCertKeyFile string
daemonize bool daemonize bool
registeredServers []RegisteredServer registeredServers []RegisteredServer
registeredRelays []RegisteredServer registeredRelays []RegisteredServer
pluginBlockIPv6 bool pluginBlockIPv6 bool
pluginBlockUnqualified bool pluginBlockUnqualified bool
pluginBlockUndelegated bool pluginBlockUndelegated bool
cache bool cache bool
cacheSize int cacheSize int
cacheNegMinTTL uint32 cacheNegMinTTL uint32
cacheNegMaxTTL uint32 cacheNegMaxTTL uint32
cacheMinTTL uint32 cacheMinTTL uint32
cacheMaxTTL uint32 cacheMaxTTL uint32
rejectTTL uint32 rejectTTL uint32
cloakTTL uint32 cloakTTL uint32
queryLogFile string queryLogFile string
queryLogFormat string queryLogFormat string
queryLogIgnoredQtypes []string queryLogIgnoredQtypes []string
nxLogFile string nxLogFile string
nxLogFormat string nxLogFormat string
blockNameFile string blockNameFile string
whitelistNameFile string whitelistNameFile string
blockNameLogFile string blockNameLogFile string
whitelistNameLogFile string whitelistNameLogFile string
blockNameFormat string blockNameFormat string
whitelistNameFormat string whitelistNameFormat string
blockIPFile string blockIPFile string
blockIPLogFile string blockIPLogFile string
blockIPFormat string blockIPFormat string
forwardFile string forwardFile string
cloakFile string cloakFile string
pluginsGlobals PluginsGlobals pluginsGlobals PluginsGlobals
sources []*Source sources []*Source
clientsCount uint32 clientsCount uint32
maxClients uint32 maxClients uint32
xTransport *XTransport xTransport *XTransport
allWeeklyRanges *map[string]WeeklyRanges allWeeklyRanges *map[string]WeeklyRanges
logMaxSize int logMaxSize int
logMaxAge int logMaxAge int
logMaxBackups int logMaxBackups int
blockedQueryResponse string blockedQueryResponse string
queryMeta []string queryMeta []string
routes *map[string][]string routes *map[string][]string
serversWithBrokenQueryPadding []string serversBlockingFragments []string
showCerts bool serversDroppingLargerResponses []string
dohCreds *map[string]DOHClientCreds showCerts bool
dohCreds *map[string]DOHClientCreds
} }
func (proxy *Proxy) addDNSListener(listenAddrStr string) { func (proxy *Proxy) addDNSListener(listenAddrStr string) {
@ -473,6 +474,11 @@ func (proxy *Proxy) processIncomingQuery(clientProto string, serverProto string,
pluginsState.serverName = serverName pluginsState.serverName = serverName
if serverInfo.Proto == stamps.StampProtoTypeDNSCrypt { if serverInfo.Proto == stamps.StampProtoTypeDNSCrypt {
sharedKey, encryptedQuery, clientNonce, err := proxy.Encrypt(serverInfo, query, serverProto) 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 { if err != nil {
pluginsState.returnCode = PluginsReturnCodeParseError pluginsState.returnCode = PluginsReturnCodeParseError
pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals) pluginsState.ApplyLoggingPlugins(&proxy.pluginsGlobals)

View file

@ -32,7 +32,8 @@ type RegisteredServer struct {
} }
type ServerBugs struct { type ServerBugs struct {
incorrectPadding bool fragmentsBlocked bool
largerQueriesDropped bool
} }
type DOHClientCreds struct { type DOHClientCreds struct {
@ -319,10 +320,17 @@ func fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp
stamp.ServerPk = serverPk stamp.ServerPk = serverPk
} }
knownBugs := ServerBugs{} knownBugs := ServerBugs{}
for _, buggyServerName := range proxy.serversWithBrokenQueryPadding { for _, buggyServerName := range proxy.serversBlockingFragments {
if buggyServerName == name { if buggyServerName == name {
knownBugs.incorrectPadding = true knownBugs.fragmentsBlocked = true
dlog.Infof("Known bug in [%v]: padding is not correctly handled", name) dlog.Infof("Known bug in [%v]: fragmented questions over UDP are blocked", name)
break
}
}
for _, buggyServerName := range proxy.serversDroppingLargerResponses {
if buggyServerName == name {
knownBugs.largerQueriesDropped = true
dlog.Infof("Known bug in [%v]: truncated responses are not sent when a response is larger than the query", name)
break break
} }
} }
@ -331,11 +339,11 @@ func fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp
return ServerInfo{}, err return ServerInfo{}, err
} }
certInfo, rtt, fragmentsBlocked, err := FetchCurrentDNSCryptCert(proxy, &name, proxy.mainProto, stamp.ServerPk, stamp.ServerAddrStr, stamp.ProviderName, isNew, relayUDPAddr, relayTCPAddr, knownBugs) certInfo, rtt, fragmentsBlocked, err := FetchCurrentDNSCryptCert(proxy, &name, proxy.mainProto, stamp.ServerPk, stamp.ServerAddrStr, stamp.ProviderName, isNew, relayUDPAddr, relayTCPAddr, knownBugs)
if !knownBugs.incorrectPadding && fragmentsBlocked { if !knownBugs.fragmentsBlocked && fragmentsBlocked {
dlog.Debugf("[%v] drops fragmented queries", name) dlog.Debugf("[%v] drops fragmented queries", name)
knownBugs.incorrectPadding = true knownBugs.fragmentsBlocked = true
} }
if knownBugs.incorrectPadding && (relayUDPAddr != nil || relayTCPAddr != nil) { if knownBugs.fragmentsBlocked && (relayUDPAddr != nil || relayTCPAddr != nil) {
dlog.Warnf("[%v] is incompatible with anonymization", name) dlog.Warnf("[%v] is incompatible with anonymization", name)
relayTCPAddr, relayUDPAddr = nil, nil relayTCPAddr, relayUDPAddr = nil, nil
} }