Merge branch 'master' of github.com:jedisct1/dnscrypt-proxy

* 'master' of github.com:jedisct1/dnscrypt-proxy:
  Reduce contention
  Don't name different things "ttl" to avoid confusion
  Reduce lock contention
  Nits
  Rename negTTL to rejectTTL to avoid confusion with cacheNegTTL
  feature: Add neg_ttl for rejected entries and cloak_ttl for cloaking-rules entries
  feature: xtransport: Expire CachedIPs, split resolve function from Fetch
  fix: xtransport: Check 'fallback_resolver'
  fix: xtransport: Ensure we strip [] from host/ip before net.ParseIP
  cleanup: xtransport: group all consts and alike
This commit is contained in:
Frank Denis 2019-10-22 11:51:38 +02:00
commit 32b691a5c7
8 changed files with 182 additions and 91 deletions

View File

@ -11,6 +11,7 @@ import (
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@ -51,6 +52,8 @@ type Config struct {
CacheNegMaxTTL uint32 `toml:"cache_neg_max_ttl"`
CacheMinTTL uint32 `toml:"cache_min_ttl"`
CacheMaxTTL uint32 `toml:"cache_max_ttl"`
RejectTTL uint32 `toml:"reject_ttl"`
CloakTTL uint32 `toml:"cloak_ttl"`
QueryLog QueryLogConfig `toml:"query_log"`
NxLog NxLogConfig `toml:"nx_log"`
BlockName BlockNameConfig `toml:"blacklist"`
@ -102,6 +105,8 @@ func newConfig() Config {
CacheNegMaxTTL: 600,
CacheMinTTL: 60,
CacheMaxTTL: 86400,
RejectTTL: 600,
CloakTTL: 600,
SourceRequireNoLog: true,
SourceRequireNoFilter: true,
SourceIPv4: true,
@ -272,11 +277,14 @@ func ConfigLoad(proxy *Proxy, svcFlag *string) error {
proxy.xTransport = NewXTransport()
proxy.xTransport.tlsDisableSessionTickets = config.TLSDisableSessionTickets
proxy.xTransport.tlsCipherSuite = config.TLSCipherSuite
proxy.xTransport.fallbackResolver = config.FallbackResolver
proxy.xTransport.mainProto = proxy.mainProto
if len(config.FallbackResolver) > 0 {
if err := CheckResolver(config.FallbackResolver); err != nil {
dlog.Fatalf("fallback_resolver [%v]", err)
}
proxy.xTransport.ignoreSystemDNS = config.IgnoreSystemDNS
}
proxy.xTransport.fallbackResolver = config.FallbackResolver
proxy.xTransport.useIPv4 = config.SourceIPv4
proxy.xTransport.useIPv6 = config.SourceIPv6
proxy.xTransport.keepAlive = time.Duration(config.KeepAlive) * time.Second
@ -361,6 +369,8 @@ func ConfigLoad(proxy *Proxy, svcFlag *string) error {
proxy.cacheMinTTL = config.CacheMinTTL
proxy.cacheMaxTTL = config.CacheMaxTTL
proxy.rejectTTL = config.RejectTTL
proxy.cloakTTL = config.CloakTTL
proxy.queryMeta = config.QueryMeta
@ -690,3 +700,15 @@ func cdLocal() {
}
os.Chdir(filepath.Dir(exeFileName))
}
func CheckResolver(resolver string) error {
host, port := ExtractHostAndPort(resolver, -1)
if ip := ParseIP(host); ip == nil {
return fmt.Errorf("Host does not parse as IP '%s'", resolver)
} else if port == -1 {
return fmt.Errorf("Port missing '%s'", resolver)
} else if _, err := strconv.ParseUint(strconv.Itoa(port), 10, 16); err != nil {
return fmt.Errorf("Port does not parse '%s' [%v]", resolver, err)
}
return nil
}

View File

@ -266,6 +266,12 @@ log_files_max_backups = 1
block_ipv6 = false
## TTL for synthetic responses sent when a request has been blocked (due to
## IPv6 or blacklists).
reject_ttl = 600
##################################################################################
# Route queries for specific domains to a dedicated set of servers #
@ -293,6 +299,9 @@ block_ipv6 = false
# cloaking_rules = 'cloaking-rules.txt'
## TTL used when serving entries in cloaking-rules.txt
# cloak_ttl = 600
###########################

View File

@ -65,14 +65,15 @@ func (plugin *PluginCacheResponse) Eval(pluginsState *PluginsState, msg *dns.Msg
msg: *msg,
}
plugin.cachedResponses.Lock()
defer plugin.cachedResponses.Unlock()
if plugin.cachedResponses.cache == nil {
plugin.cachedResponses.cache, err = lru.NewARC(pluginsState.cacheSize)
if err != nil {
plugin.cachedResponses.Unlock()
return err
}
}
plugin.cachedResponses.cache.Add(cacheKey, cachedResponse)
plugin.cachedResponses.Unlock()
updateTTL(msg, cachedResponse.expiration)
return nil

View File

@ -41,7 +41,7 @@ func (plugin *PluginCloak) Init(proxy *Proxy) error {
if err != nil {
return err
}
plugin.ttl = proxy.cacheMinTTL
plugin.ttl = proxy.cloakTTL
plugin.patternMatcher = NewPatternPatcher()
cloakedNames := make(map[string]*CloakedName)
for lineNo, line := range strings.Split(string(bin), "\n") {

View File

@ -76,6 +76,7 @@ type PluginsState struct {
cacheNegMaxTTL uint32
cacheMinTTL uint32
cacheMaxTTL uint32
rejectTTL uint32
questionMsg *dns.Msg
requestStart time.Time
requestEnd time.Time
@ -221,6 +222,7 @@ func NewPluginsState(proxy *Proxy, clientProto string, clientAddr *net.Addr, sta
cacheNegMaxTTL: proxy.cacheNegMaxTTL,
cacheMinTTL: proxy.cacheMinTTL,
cacheMaxTTL: proxy.cacheMaxTTL,
rejectTTL: proxy.rejectTTL,
questionMsg: nil,
requestStart: start,
maxUnencryptedUDPSafePayloadSize: MaxDNSUDPSafePacketSize,
@ -249,7 +251,7 @@ func (pluginsState *PluginsState) ApplyQueryPlugins(pluginsGlobals *PluginsGloba
return packet, err
}
if pluginsState.action == PluginsActionReject {
synth, err := RefusedResponseFromMessage(&msg, pluginsGlobals.refusedCodeInResponses, pluginsGlobals.respondWithIPv4, pluginsGlobals.respondWithIPv6, pluginsState.cacheMinTTL)
synth, err := RefusedResponseFromMessage(&msg, pluginsGlobals.refusedCodeInResponses, pluginsGlobals.respondWithIPv4, pluginsGlobals.respondWithIPv6, pluginsState.rejectTTL)
if err != nil {
return nil, err
}
@ -296,7 +298,7 @@ func (pluginsState *PluginsState) ApplyResponsePlugins(pluginsGlobals *PluginsGl
return packet, err
}
if pluginsState.action == PluginsActionReject {
synth, err := RefusedResponseFromMessage(&msg, pluginsGlobals.refusedCodeInResponses, pluginsGlobals.respondWithIPv4, pluginsGlobals.respondWithIPv6, pluginsState.cacheMinTTL)
synth, err := RefusedResponseFromMessage(&msg, pluginsGlobals.refusedCodeInResponses, pluginsGlobals.respondWithIPv4, pluginsGlobals.respondWithIPv6, pluginsState.rejectTTL)
if err != nil {
return nil, err
}

View File

@ -41,6 +41,8 @@ type Proxy struct {
cacheNegMaxTTL uint32
cacheMinTTL uint32
cacheMaxTTL uint32
rejectTTL uint32
cloakTTL uint32
queryLogFile string
queryLogFormat string
queryLogIgnoredQtypes []string

View File

@ -191,9 +191,9 @@ func (serversInfo *ServersInfo) estimatorUpdate() {
func (serversInfo *ServersInfo) getOne() *ServerInfo {
serversInfo.Lock()
defer serversInfo.Unlock()
serversCount := len(serversInfo.inner)
if serversCount <= 0 {
serversInfo.Unlock()
return nil
}
if serversInfo.lbEstimator {
@ -211,6 +211,7 @@ func (serversInfo *ServersInfo) getOne() *ServerInfo {
candidate = rand.Intn(Min(serversCount, 2))
}
serverInfo := serversInfo.inner[candidate]
serversInfo.Unlock()
dlog.Debugf("Using candidate [%s] RTT: %d", (*serverInfo).Name, int((*serverInfo).rtt.Value()))
return serverInfo
@ -328,9 +329,9 @@ func fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp
func fetchDoHServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew bool) (ServerInfo, error) {
if len(stamp.ServerAddrStr) > 0 {
ipOnly, _ := ExtractHostAndPort(stamp.ServerAddrStr, -1)
proxy.xTransport.cachedIPs.Lock()
proxy.xTransport.cachedIPs.cache[stamp.ProviderName] = ipOnly
proxy.xTransport.cachedIPs.Unlock()
if ip := ParseIP(ipOnly); ip != nil {
proxy.xTransport.saveCachedIP(stamp.ProviderName, ip, -1*time.Second)
}
}
url := &url.URL{
Scheme: "https",

View File

@ -8,8 +8,8 @@ import (
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
@ -25,11 +25,21 @@ import (
netproxy "golang.org/x/net/proxy"
)
const DefaultFallbackResolver = "9.9.9.9:53"
const (
DefaultFallbackResolver = "9.9.9.9:53"
DefaultKeepAlive = 5 * time.Second
DefaultTimeout = 30 * time.Second
SystemResolverTTL = 24 * time.Hour
)
type CachedIPItem struct {
ip net.IP
expiration *time.Time
}
type CachedIPs struct {
sync.RWMutex
cache map[string]string
cache map[string]*CachedIPItem
}
type XTransport struct {
@ -48,12 +58,12 @@ type XTransport struct {
httpProxyFunction func(*http.Request) (*url.URL, error)
}
var DefaultKeepAlive = 5 * time.Second
var DefaultTimeout = 30 * time.Second
func NewXTransport() *XTransport {
if err := CheckResolver(DefaultFallbackResolver); err != nil {
panic("DefaultFallbackResolver does not parse")
}
xTransport := XTransport{
cachedIPs: CachedIPs{cache: make(map[string]string)},
cachedIPs: CachedIPs{cache: make(map[string]*CachedIPItem)},
keepAlive: DefaultKeepAlive,
timeout: DefaultTimeout,
fallbackResolver: DefaultFallbackResolver,
@ -67,11 +77,41 @@ func NewXTransport() *XTransport {
return &xTransport
}
func (xTransport *XTransport) clearCache() {
func ParseIP(ipStr string) net.IP {
return net.ParseIP(strings.TrimRight(strings.TrimLeft(ipStr, "["), "]"))
}
// If ttl < 0, never expire
// Otherwise, ttl is set to max(ttl, xTransport.timeout)
func (xTransport *XTransport) saveCachedIP(host string, ip net.IP, ttl time.Duration) {
item := &CachedIPItem{ip: ip, expiration: nil}
if ttl >= 0 {
if ttl < xTransport.timeout {
ttl = xTransport.timeout
}
expiration := time.Now().Add(ttl)
item.expiration = &expiration
}
xTransport.cachedIPs.Lock()
xTransport.cachedIPs.cache = make(map[string]string)
xTransport.cachedIPs.cache[host] = item
xTransport.cachedIPs.Unlock()
dlog.Info("IP cache cleared")
}
func (xTransport *XTransport) loadCachedIP(host string, deleteIfExpired bool) (net.IP, bool) {
xTransport.cachedIPs.RLock()
item, ok := xTransport.cachedIPs.cache[host]
xTransport.cachedIPs.RUnlock()
if !ok {
return nil, false
}
expiration := item.expiration
if deleteIfExpired && expiration != nil && time.Until(*expiration) < 0 {
xTransport.cachedIPs.Lock()
delete(xTransport.cachedIPs.cache, host)
xTransport.cachedIPs.Unlock()
return nil, false
}
return item.ip, ok
}
func (xTransport *XTransport) rebuildTransport() {
@ -91,11 +131,9 @@ func (xTransport *XTransport) rebuildTransport() {
DialContext: func(ctx context.Context, network, addrStr string) (net.Conn, error) {
host, port := ExtractHostAndPort(addrStr, stamps.DefaultPort)
ipOnly := host
xTransport.cachedIPs.RLock()
cachedIP := xTransport.cachedIPs.cache[host]
xTransport.cachedIPs.RUnlock()
if len(cachedIP) > 0 {
ipOnly = cachedIP
cachedIP, ok := xTransport.loadCachedIP(host, false)
if ok {
ipOnly = cachedIP.String()
} else {
dlog.Debugf("[%s] IP address was not cached", host)
}
@ -127,47 +165,54 @@ func (xTransport *XTransport) rebuildTransport() {
xTransport.transport = transport
}
func (xTransport *XTransport) resolveUsingSystem(host string) (*string, error) {
foundIPs, err := net.LookupHost(host)
func (xTransport *XTransport) resolveUsingSystem(host string) (ip net.IP, ttl time.Duration, err error) {
ttl = SystemResolverTTL
var foundIPs []string
foundIPs, err = net.LookupHost(host)
if err != nil {
return nil, err
return
}
ips := make([]net.IP, 0)
for _, ip := range foundIPs {
foundIP := net.ParseIP(ip)
if foundIP == nil {
continue
}
if xTransport.useIPv4 {
if ipv4 := foundIP.To4(); ipv4 != nil {
foundIPx := foundIP.String()
return &foundIPx, nil
if foundIP := net.ParseIP(ip); foundIP != nil {
if xTransport.useIPv4 {
if ipv4 := foundIP.To4(); ipv4 != nil {
ips = append(ips, foundIP)
}
}
}
if xTransport.useIPv6 {
if ipv6 := foundIP.To16(); ipv6 != nil {
foundIPx := "[" + foundIP.String() + "]"
return &foundIPx, nil
if xTransport.useIPv6 {
if ipv6 := foundIP.To16(); ipv6 != nil {
ips = append(ips, foundIP)
}
}
}
}
return nil, err
if len(ips) > 0 {
ip = ips[rand.Intn(len(ips))]
}
return
}
func (xTransport *XTransport) resolveUsingResolver(dnsClient *dns.Client, host string, resolver string) (*string, error) {
var err error
func (xTransport *XTransport) resolveUsingResolver(proto, host string, resolver string) (ip net.IP, ttl time.Duration, err error) {
dnsClient := dns.Client{Net: proto}
if xTransport.useIPv4 {
msg := new(dns.Msg)
msg.SetQuestion(dns.Fqdn(host), dns.TypeA)
msg.SetEdns0(uint16(MaxDNSPacketSize), true)
var in *dns.Msg
in, _, err = dnsClient.Exchange(msg, resolver)
if err == nil {
if in, _, err = dnsClient.Exchange(msg, resolver); err == nil {
answers := make([]dns.RR, 0)
for _, answer := range in.Answer {
if answer.Header().Rrtype == dns.TypeA {
foundIP := answer.(*dns.A).A.String()
return &foundIP, nil
answers = append(answers, answer)
}
}
if len(answers) > 0 {
answer := answers[rand.Intn(len(answers))]
ip = answer.(*dns.A).A
ttl = time.Duration(answer.Header().Ttl) * time.Second
return
}
}
}
if xTransport.useIPv6 {
@ -175,17 +220,62 @@ func (xTransport *XTransport) resolveUsingResolver(dnsClient *dns.Client, host s
msg.SetQuestion(dns.Fqdn(host), dns.TypeAAAA)
msg.SetEdns0(uint16(MaxDNSPacketSize), true)
var in *dns.Msg
in, _, err = dnsClient.Exchange(msg, resolver)
if err == nil {
if in, _, err = dnsClient.Exchange(msg, resolver); err == nil {
answers := make([]dns.RR, 0)
for _, answer := range in.Answer {
if answer.Header().Rrtype == dns.TypeAAAA {
foundIP := "[" + answer.(*dns.AAAA).AAAA.String() + "]"
return &foundIP, nil
answers = append(answers, answer)
}
}
if len(answers) > 0 {
answer := answers[rand.Intn(len(answers))]
ip = answer.(*dns.AAAA).AAAA
ttl = time.Duration(answer.Header().Ttl) * time.Second
return
}
}
}
return nil, err
return
}
func (xTransport *XTransport) resolveHost(host string) (err error) {
if xTransport.proxyDialer != nil || xTransport.httpProxyFunction != nil {
return
}
if ParseIP(host) != nil {
return
}
if _, ok := xTransport.loadCachedIP(host, true); ok {
return
}
var foundIP net.IP
var ttl time.Duration
if !xTransport.ignoreSystemDNS {
foundIP, ttl, err = xTransport.resolveUsingSystem(host)
}
if xTransport.ignoreSystemDNS || err != nil {
protos := []string{"udp", "tcp"}
if xTransport.mainProto == "tcp" {
protos = []string{"tcp", "udp"}
}
for _, proto := range protos {
if err != nil {
dlog.Noticef("System DNS configuration not usable yet, exceptionally resolving [%s] using resolver %s[%s]", host, proto, xTransport.fallbackResolver)
} else {
dlog.Debugf("Resolving [%s] using resolver %s[%s]", host, proto, xTransport.fallbackResolver)
}
foundIP, ttl, err = xTransport.resolveUsingResolver(proto, host, xTransport.fallbackResolver)
if err == nil {
break
}
}
}
if err != nil {
return
}
xTransport.saveCachedIP(host, foundIP, ttl)
dlog.Debugf("[%s] IP address [%s] added to the cache, valid until %v", host, foundIP, ttl)
return
}
func (xTransport *XTransport) Fetch(method string, url *url.URL, accept string, contentType string, body *[]byte, timeout time.Duration, padding *string) (*http.Response, time.Duration, error) {
@ -215,6 +305,9 @@ func (xTransport *XTransport) Fetch(method string, url *url.URL, accept string,
if xTransport.proxyDialer == nil && strings.HasSuffix(host, ".onion") {
return nil, 0, errors.New("Onion service is not reachable without Tor")
}
if err := xTransport.resolveHost(host); err != nil {
return nil, 0, err
}
req := &http.Request{
Method: method,
URL: url,
@ -225,45 +318,6 @@ func (xTransport *XTransport) Fetch(method string, url *url.URL, accept string,
req.ContentLength = int64(len(*body))
req.Body = ioutil.NopCloser(bytes.NewReader(*body))
}
var err error
resolveByProxy := false
if xTransport.proxyDialer != nil || xTransport.httpProxyFunction != nil {
resolveByProxy = true
}
var foundIP *string
if !resolveByProxy && net.ParseIP(host) == nil {
xTransport.cachedIPs.RLock()
cachedIP := xTransport.cachedIPs.cache[host]
xTransport.cachedIPs.RUnlock()
if len(cachedIP) > 0 {
foundIP = &cachedIP
} else {
if !xTransport.ignoreSystemDNS {
foundIP, err = xTransport.resolveUsingSystem(host)
} else {
dlog.Debug("Ignoring system DNS")
}
if xTransport.ignoreSystemDNS || err != nil {
if xTransport.ignoreSystemDNS {
dlog.Debugf("Resolving [%s] using fallback resolver [%s]", host, xTransport.fallbackResolver)
} else {
dlog.Noticef("System DNS configuration not usable yet, exceptionally resolving [%s] using fallback resolver [%s]", host, xTransport.fallbackResolver)
}
dnsClient := dns.Client{Net: xTransport.mainProto}
foundIP, err = xTransport.resolveUsingResolver(&dnsClient, host, xTransport.fallbackResolver)
}
if foundIP == nil {
return nil, 0, fmt.Errorf("No IP found for [%s]", host)
}
if err != nil {
return nil, 0, err
}
xTransport.cachedIPs.Lock()
xTransport.cachedIPs.cache[host] = *foundIP
xTransport.cachedIPs.Unlock()
dlog.Debugf("[%s] IP address [%s] added to the cache", host, *foundIP)
}
}
start := time.Now()
resp, err := client.Do(req)
rtt := time.Since(start)