| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665 |
- /*
- * Copyright (c) 2022, Psiphon Inc.
- * All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- // Package resolver implements a DNS stub resolver, or DNS client, which
- // resolves domain names.
- //
- // The resolver is Psiphon-specific and oriented towards blocking resistance.
- // See ResolveIP for more details.
- package resolver
- import (
- "context"
- "encoding/hex"
- "fmt"
- "net"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/transforms"
- lrucache "github.com/cognusion/go-cache-lru"
- "github.com/miekg/dns"
- )
- const (
- resolverCacheDefaultTTL = 1 * time.Minute
- resolverCacheReapFrequency = 1 * time.Minute
- resolverCacheMaxEntries = 10000
- resolverServersUpdateTTL = 5 * time.Second
- resolverDefaultAttemptsPerServer = 2
- resolverDefaultRequestTimeout = 5 * time.Second
- resolverDefaultAwaitTimeout = 10 * time.Millisecond
- resolverDefaultAnswerTTL = 1 * time.Minute
- resolverDNSPort = "53"
- udpPacketBufferSize = 1232
- )
- // NetworkConfig specifies network-level configuration for a Resolver.
- type NetworkConfig struct {
- // GetDNSServers returns a list of system DNS server addresses (IP:port, or
- // IP only with port 53 assumed), as determined via OS APIs, in priority
- // order. GetDNSServers may be nil.
- GetDNSServers func() []string
- // BindToDevice should ensure the input file descriptor, a UDP socket, is
- // excluded from VPN routing. BindToDevice may be nil.
- BindToDevice func(fd int) (string, error)
- // AllowDefaultResolverWithBindToDevice indicates that it's safe to use
- // the default resolver when BindToDevice is configured, as the host OS
- // will automatically exclude DNS requests from the VPN.
- AllowDefaultResolverWithBindToDevice bool
- // IPv6Synthesize should apply NAT64 synthesis to the input IPv4 address,
- // returning a synthesized IPv6 address that will route to the same
- // endpoint. IPv6Synthesize may be nil.
- IPv6Synthesize func(IPv4 string) string
- // HasIPv6Route should return true when the host has an IPv6 route.
- // Resolver has an internal implementation, hasRoutableIPv6Interface, to
- // determine this, but it can fail on some platforms ("route ip+net:
- // netlinkrib: permission denied" on Android, for example; see Go issue
- // 40569). When HasIPv6Route is nil, the internal implementation is used.
- HasIPv6Route func() bool
- // LogWarning is an optional callback which is used to log warnings and
- // transient errors which would otherwise not be recorded or returned.
- LogWarning func(error)
- // LogHostnames indicates whether to log hostname in errors or not.
- LogHostnames bool
- // CacheExtensionInitialTTL specifies a minimum TTL to use when caching
- // domain resolution results. This minimum will override any TTL in the
- // DNS response. CacheExtensionInitialTTL is off when 0.
- CacheExtensionInitialTTL time.Duration
- // CacheExtensionVerifiedTTL specifies the minimum TTL to set for a cached
- // domain resolution result after the result has been verified.
- // CacheExtensionVerifiedTTL is off when 0.
- //
- // DNS cache extension is a workaround to partially mitigate issues with
- // obtaining underlying system DNS server IPs on platforms such as iOS
- // once a VPN is running and after network changes, such as changing from
- // Wi-Fi to mobile. While ResolveParameters.AlternateDNSServer can be
- // used to specify a known public DNS server, it may be the case that
- // public DNS servers are blocked or always falling back to a public DNS
- // server creates unusual traffic. And while it may be possible to use
- // the default system resolver, it lacks certain circumvention
- // capabilities.
- //
- // Extending the TTL for cached responses allows Psiphon to redial domains
- // using recently successful IPs.
- //
- // CacheExtensionInitialTTL allows for a greater initial minimum TTL, so
- // that the response entry remains in the cache long enough for a dial to
- // fully complete and verify the endpoint. Psiphon will call
- // Resolver.VerifyExtendCacheTTL once a dial has authenticated, for
- // example, the destination Psiphon server. VerifyCacheExtension will
- // further extend the corresponding TTL to CacheExtensionVerifiedTTL, a
- // longer TTL. CacheExtensionInitialTTL is intended to be on the order of
- // minutes and CacheExtensionVerifiedTTL may be on the order of hours.
- //
- // When CacheExtensionVerifiedTTL is on, the DNS cache is not flushed on
- // network changes, to allow for the previously cached entries to remain
- // available in the problematic scenario. Like adjusting TTLs, this is an
- // explicit trade-off which doesn't adhere to standard best practise, but
- // is expected to be more blocking resistent; this approach also assumes
- // that endpoints such as CDN IPs are typically available on any network.
- CacheExtensionVerifiedTTL time.Duration
- }
- func (c *NetworkConfig) allowDefaultResolver() bool {
- // When BindToDevice is configured, the standard library resolver is not
- // used, as the system resolver may not route outside of the VPN.
- return c.BindToDevice == nil || c.AllowDefaultResolverWithBindToDevice
- }
- func (c *NetworkConfig) logWarning(err error) {
- if c.LogWarning != nil {
- c.LogWarning(err)
- }
- }
- // ResolveParameters specifies the configuration and behavior of a single
- // ResolveIP call, a single domain name resolution.
- //
- // New ResolveParameters may be generated by calling MakeResolveParameters,
- // which takes tactics parameters as an input.
- //
- // ResolveParameters may be persisted for replay.
- type ResolveParameters struct {
- // AttemptsPerServer specifies how many requests to send to each DNS
- // server before trying the next server. IPv4 and IPv6 requests are sent
- // concurrently and count as one attempt.
- AttemptsPerServer int
- // AttemptsPerPreferredServer is AttemptsPerServer for a preferred
- // alternate DNS server.
- AttemptsPerPreferredServer int
- // RequestTimeout specifies how long to wait for a valid response before
- // moving on to the next attempt.
- RequestTimeout time.Duration
- // AwaitTimeout specifies how long to await an additional response after
- // the first response is received. This additional wait time applies only
- // when there is either no IPv4 or IPv6 response.
- AwaitTimeout time.Duration
- // PreresolvedIPAddress specifies an IP address result to be used in place
- // of making a request.
- PreresolvedIPAddress string
- // AlternateDNSServer specifies an alterate DNS server (IP:port, or IP
- // only with port 53 assumed) to be used when either no system DNS
- // servers are available or when PreferAlternateDNSServer is set.
- AlternateDNSServer string
- // PreferAlternateDNSServer indicates whether to prioritize using the
- // AlternateDNSServer. When set, the AlternateDNSServer is attempted
- // before any system DNS servers.
- PreferAlternateDNSServer bool
- // ProtocolTransformName specifies the name associated with
- // ProtocolTransformSpec and is used for metrics.
- ProtocolTransformName string
- // ProtocolTransformSpec specifies a transform to apply to the DNS request packet.
- // See: "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/transforms".
- //
- // As transforms operate on strings and DNS requests are binary,
- // transforms should be expressed using hex characters.
- //
- // DNS transforms include strategies discovered by the Geneva team,
- // https://geneva.cs.umd.edu.
- ProtocolTransformSpec transforms.Spec
- // ProtocolTransformSeed specifies the seed to use for generating random
- // data in the ProtocolTransformSpec transform. To replay a transform,
- // specify the same seed.
- ProtocolTransformSeed *prng.Seed
- // IncludeEDNS0 indicates whether to include the EDNS(0) UDP maximum
- // response size extension in DNS requests. The resolver can handle
- // responses larger than 512 bytes (RFC 1035 maximum) regardless of
- // whether the extension is included; the extension may be included as
- // part of appearing similar to other DNS traffic.
- IncludeEDNS0 bool
- firstAttemptWithAnswer int32
- }
- // GetFirstAttemptWithAnswer returns the index of the first request attempt
- // that received a valid response, for the most recent ResolveIP call using
- // this ResolveParameters. This information is used for logging metrics. The
- // first attempt has index 1. GetFirstAttemptWithAnswer return 0 when no
- // request attempt has reported a valid response.
- //
- // The caller is responsible for synchronizing use of a ResolveParameters
- // instance (e.g, use a distinct ResolveParameters per ResolveIP to ensure
- // GetFirstAttemptWithAnswer refers to a specific ResolveIP).
- func (r *ResolveParameters) GetFirstAttemptWithAnswer() int {
- return int(atomic.LoadInt32(&r.firstAttemptWithAnswer))
- }
- func (r *ResolveParameters) setFirstAttemptWithAnswer(attempt int) {
- atomic.StoreInt32(&r.firstAttemptWithAnswer, int32(attempt))
- }
- // Implementation note: Go's standard net.Resolver supports specifying a
- // custom Dial function. This could be used to implement at least a large
- // subset of the Resolver functionality on top of Go's standard library
- // resolver. However, net.Resolver is limited to using the CGO resolver on
- // Android, https://github.com/golang/go/issues/8877, in which case the
- // custom Dial function is not used. Furthermore, the the pure Go resolver in
- // net/dnsclient_unix.go appears to not be used on Windows at this time.
- //
- // Go also provides golang.org/x/net/dns/dnsmessage, a DNS message marshaller,
- // which could potentially be used in place of github.com/miekg/dns.
- // Resolver is a DNS stub resolver, or DNS client, which resolves domain
- // names. A Resolver instance maintains a cache, a network state snapshot,
- // and metrics. All ResolveIP calls will share the same cache and state.
- // Multiple concurrent ResolveIP calls are supported.
- type Resolver struct {
- networkConfig *NetworkConfig
- mutex sync.Mutex
- networkID string
- hasIPv6Route bool
- systemServers []string
- lastServersUpdate time.Time
- cache *lrucache.Cache
- metrics resolverMetrics
- }
- type resolverMetrics struct {
- resolves int
- cacheHits int
- verifiedCacheExtensions int
- requestsIPv4 int
- requestsIPv6 int
- responsesIPv4 int
- responsesIPv6 int
- defaultResolves int
- defaultSuccesses int
- peakInFlight int64
- minRTT time.Duration
- maxRTT time.Duration
- }
- func newResolverMetrics() resolverMetrics {
- return resolverMetrics{minRTT: -1}
- }
- // NewResolver creates a new Resolver instance.
- func NewResolver(networkConfig *NetworkConfig, networkID string) *Resolver {
- r := &Resolver{
- networkConfig: networkConfig,
- metrics: newResolverMetrics(),
- }
- // updateNetworkState will initialize the cache and network state,
- // including system DNS servers.
- r.updateNetworkState(networkID)
- return r
- }
- // Stop clears the Resolver cache and resets metrics. Stop must be called only
- // after ceasing all in-flight ResolveIP goroutines, or else the cache or
- // metrics may repopulate. A Resolver may be resumed after calling Stop, but
- // Update must be called first.
- func (r *Resolver) Stop() {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- // r.networkConfig is not set to nil to avoid possible nil pointer
- // dereferences by concurrent ResolveIP calls.
- r.networkID = ""
- r.hasIPv6Route = false
- r.systemServers = nil
- r.cache.Flush()
- r.metrics = newResolverMetrics()
- }
- // MakeResolveParameters generates ResolveParameters using the input tactics
- // parameters and optional frontingProviderID context.
- func (r *Resolver) MakeResolveParameters(
- p parameters.ParametersAccessor,
- frontingProviderID string) (*ResolveParameters, error) {
- params := &ResolveParameters{
- AttemptsPerServer: p.Int(parameters.DNSResolverAttemptsPerServer),
- AttemptsPerPreferredServer: p.Int(parameters.DNSResolverAttemptsPerPreferredServer),
- RequestTimeout: p.Duration(parameters.DNSResolverRequestTimeout),
- AwaitTimeout: p.Duration(parameters.DNSResolverAwaitTimeout),
- }
- // When a frontingProviderID is specified, generate a pre-resolved IP
- // address, based on tactics configuration.
- if frontingProviderID != "" {
- if p.WeightedCoinFlip(parameters.DNSResolverPreresolvedIPAddressProbability) {
- CIDRs := p.LabeledCIDRs(parameters.DNSResolverPreresolvedIPAddressCIDRs, frontingProviderID)
- if len(CIDRs) > 0 {
- CIDR := CIDRs[prng.Intn(len(CIDRs))]
- IP, err := generateIPAddressFromCIDR(CIDR)
- if err != nil {
- return nil, errors.Trace(err)
- }
- params.PreresolvedIPAddress = IP.String()
- }
- }
- }
- // When PreresolvedIPAddress is set, there's no DNS request and the
- // following params can be skipped.
- if params.PreresolvedIPAddress != "" {
- return params, nil
- }
- // When preferring an alternate DNS server, select the alternate from
- // DNSResolverPreferredAlternateServers. This list is for circumvention
- // operations, such as using a public DNS server with a protocol
- // transform. Otherwise, select from DNSResolverAlternateServers, which
- // is a fallback list of DNS servers to be used when the system DNS
- // servers cannot be obtained.
- preferredServers := p.Strings(parameters.DNSResolverPreferredAlternateServers)
- preferAlternateDNSServer := len(preferredServers) > 0 && p.WeightedCoinFlip(
- parameters.DNSResolverPreferAlternateServerProbability)
- alternateServers := preferredServers
- if !preferAlternateDNSServer {
- alternateServers = p.Strings(parameters.DNSResolverAlternateServers)
- }
- // Select an alternate DNS server, typically a public DNS server. Ensure
- // tactics is configured with an empty DNSResolverAlternateServers list
- // in cases where attempts to public DNS server are unwanted.
- if len(alternateServers) > 0 {
- alternateServer := alternateServers[prng.Intn(len(alternateServers))]
- // Check that the alternateServer has a well-formed IP address; and add
- // a default port if none it present.
- host, _, err := net.SplitHostPort(alternateServer)
- if err != nil {
- // Assume the SplitHostPort error is due to missing port.
- host = alternateServer
- alternateServer = net.JoinHostPort(alternateServer, resolverDNSPort)
- }
- if net.ParseIP(host) == nil {
- // Log warning and proceed without this DNS server.
- r.networkConfig.logWarning(
- errors.TraceNew("invalid alternate DNS server IP address"))
- } else {
- params.AlternateDNSServer = alternateServer
- params.PreferAlternateDNSServer = preferAlternateDNSServer
- }
- }
- // Select a DNS transform. DNS request transforms are "scoped" by
- // alternate DNS server (IP address without port); that is, when an
- // alternate DNS server is certain to be attempted first, a transform
- // associated with and known to work with that DNS server will be
- // selected. Otherwise, a transform from the default scope
- // (transforms.SCOPE_ANY == "") is selected.
- //
- // In any case, ResolveIP will only apply a transform on the first request
- // attempt.
- if p.WeightedCoinFlip(parameters.DNSResolverProtocolTransformProbability) {
- specs := p.ProtocolTransformSpecs(
- parameters.DNSResolverProtocolTransformSpecs)
- scopedSpecNames := p.ProtocolTransformScopedSpecNames(
- parameters.DNSResolverProtocolTransformScopedSpecNames)
- // The alternate DNS server will be the first attempt if
- // PreferAlternateDNSServer or the list of system DNS servers is empty.
- //
- // Limitation: the system DNS server list may change, due to a later
- // Resolver.update call when ResolveIP is called with these
- // ResolveParameters.
- _, systemServers := r.getNetworkState()
- scope := transforms.SCOPE_ANY
- if params.AlternateDNSServer != "" &&
- (params.PreferAlternateDNSServer || len(systemServers) == 0) {
- // Remove the port number, as the scope key is an IP address only.
- //
- // TODO: when we only just added the default port above, which is
- // the common case, we could avoid this extra split.
- host, _, err := net.SplitHostPort(params.AlternateDNSServer)
- if err != nil {
- return nil, errors.Trace(err)
- }
- scope = host
- }
- name, spec := specs.Select(scope, scopedSpecNames)
- if spec != nil {
- params.ProtocolTransformName = name
- params.ProtocolTransformSpec = spec
- var err error
- params.ProtocolTransformSeed, err = prng.NewSeed()
- if err != nil {
- return nil, errors.Trace(err)
- }
- }
- }
- if p.WeightedCoinFlip(parameters.DNSResolverIncludeEDNS0Probability) {
- params.IncludeEDNS0 = true
- }
- return params, nil
- }
- // ResolveAddress splits the input host:port address, calls ResolveIP to
- // resolve the IP address of the host, selects an IP if there are multiple,
- // and returns a rejoined IP:port.
- func (r *Resolver) ResolveAddress(
- ctx context.Context,
- networkID string,
- params *ResolveParameters,
- address string) (string, error) {
- hostname, port, err := net.SplitHostPort(address)
- if err != nil {
- return "", errors.Trace(err)
- }
- IPs, err := r.ResolveIP(ctx, networkID, params, hostname)
- if err != nil {
- return "", errors.Trace(err)
- }
- return net.JoinHostPort(IPs[prng.Intn(len(IPs))].String(), port), nil
- }
- // ResolveIP resolves a domain name.
- //
- // The input params may be nil, in which case default timeouts are used.
- //
- // ResolveIP performs concurrent A and AAAA lookups, returns any valid
- // response IPs, and caches results. An error is returned when there are
- // no valid response IPs.
- //
- // ResolveIP is not a general purpose resolver and is Psiphon-specific. For
- // example, resolved domains are expected to exist; ResolveIP does not
- // fallback to TCP; does not consult any "hosts" file; does not perform RFC
- // 3484 sorting logic (see Go issue 18518); only implements a subset of
- // Go/glibc/resolv.conf(5) resolver parameters (attempts and timeouts, but
- // not rotate, single-request etc.) ResolveIP does not implement singleflight
- // logic, as the Go resolver does, and allows multiple concurrent request for
- // the same domain -- Psiphon won't often resolve the exact same domain
- // multiple times concurrently, and, when it does, there's a circumvention
- // benefit to attempting different DNS servers and protocol transforms.
- //
- // ResolveIP does not currently support DoT, DoH, or TCP; those protocols are
- // often blocked or less common. Instead, ResolveIP makes a best effort to
- // evade plaintext UDP DNS interference by ignoring invalid responses and by
- // optionally applying protocol transforms that may evade blocking.
- func (r *Resolver) ResolveIP(
- ctx context.Context,
- networkID string,
- params *ResolveParameters,
- hostname string) ([]net.IP, error) {
- // ResolveIP does _not_ lock r.mutex for the lifetime of the function, to
- // ensure many ResolveIP calls can run concurrently.
- // If the hostname is already an IP address, just return that. For
- // metrics, this does not count as a resolve, as the caller may invoke
- // ResolveIP for all dials.
- IP := net.ParseIP(hostname)
- if IP != nil {
- return []net.IP{IP}, nil
- }
- // Count all resolves of an actual domain, including cached and
- // pre-resolved cases.
- r.updateMetricResolves()
- // Call updateNetworkState immediately before resolving, as a best effort
- // to ensure that system DNS servers and IPv6 routing network state
- // reflects the current network. updateNetworkState locks the Resolver
- // mutex for its duration, and so concurrent ResolveIP calls may block at
- // this point. However, all updateNetworkState operations are local to
- // the host or device; and, if the networkID is unchanged since the last
- // call, updateNetworkState may not perform any operations; and after the
- // updateNetworkState call, ResolveIP proceeds without holding the mutex
- // lock. As a result, this step should not prevent ResolveIP concurrency.
- r.updateNetworkState(networkID)
- if params == nil {
- // Supply default ResolveParameters
- params = &ResolveParameters{
- AttemptsPerServer: resolverDefaultAttemptsPerServer,
- AttemptsPerPreferredServer: resolverDefaultAttemptsPerServer,
- RequestTimeout: resolverDefaultRequestTimeout,
- AwaitTimeout: resolverDefaultAwaitTimeout,
- }
- }
- // When PreresolvedIPAddress is set, tactics parameters determined the IP address
- // in this case.
- if params.PreresolvedIPAddress != "" {
- IP := net.ParseIP(params.PreresolvedIPAddress)
- if IP == nil {
- // Unexpected case, as MakeResolveParameters selects the IP address.
- return nil, errors.TraceNew("invalid IP address")
- }
- return []net.IP{IP}, nil
- }
- // Use a snapshot of the current network state, including IPv6 routing and
- // system DNS servers.
- //
- // Limitation: these values are used even if the network changes in the
- // middle of a ResolveIP call; ResolveIP is not interrupted if the
- // network changes.
- hasIPv6Route, systemServers := r.getNetworkState()
- // Use the standard library resolver when there's no GetDNSServers, or the
- // system server list is otherwise empty, and no alternate DNS server is
- // configured.
- //
- // Note that in the case where there are no system DNS servers and there
- // is an AlternateDNSServer, if the AlternateDNSServer attempt fails,
- // control does not flow back to defaultResolverLookupIP. On platforms
- // without GetDNSServers, the caller must arrange for distinct attempts
- // that try a AlternateDNSServer, or just use the standard library
- // resolver.
- //
- // ResolveIP should always be called, even when defaultResolverLookupIP is
- // expected to be used, to ensure correct metrics counts and ensure a
- // consistent error message log stack for all DNS-related failures.
- //
- if len(systemServers) == 0 &&
- params.AlternateDNSServer == "" &&
- r.networkConfig.allowDefaultResolver() {
- IPs, err := defaultResolverLookupIP(ctx, hostname, r.networkConfig.LogHostnames)
- r.updateMetricDefaultResolver(err == nil)
- if err != nil {
- return nil, errors.Trace(err)
- }
- return IPs, err
- }
- // Consult the cache before making queries. This comes after the standard
- // library case, to allow the standard library to provide its own caching
- // logic.
- IPs := r.getCache(hostname)
- if IPs != nil {
- return IPs, nil
- }
- // Set the list of DNS servers to attempt. AlternateDNSServer is used
- // first when PreferAlternateDNSServer is set; otherwise
- // AlternateDNSServer is used only when there is no system DNS server.
- var servers []string
- if params.AlternateDNSServer != "" &&
- (len(systemServers) == 0 || params.PreferAlternateDNSServer) {
- servers = []string{params.AlternateDNSServer}
- }
- servers = append(servers, systemServers...)
- if len(servers) == 0 {
- return nil, errors.TraceNew("no DNS servers")
- }
- // Set the request timeout and set up a reusable timer for handling
- // request and await timeouts.
- //
- // We expect to always have a request timeout. Handle the unexpected no
- // timeout, 0, case by setting the longest timeout possible, ~290 years;
- // always having a non-zero timeout makes the following code marginally
- // simpler.
- requestTimeout := params.RequestTimeout
- if requestTimeout == 0 {
- requestTimeout = 1<<63 - 1
- }
- var timer *time.Timer
- timerDrained := true
- resetTimer := func(timeout time.Duration) {
- if timer == nil {
- timer = time.NewTimer(timeout)
- } else {
- if !timerDrained && !timer.Stop() {
- <-timer.C
- }
- timer.Reset(timeout)
- }
- timerDrained = false
- }
- // Orchestrate the DNS requests
- resolveCtx, cancelFunc := context.WithCancel(ctx)
- defer cancelFunc()
- waitGroup := new(sync.WaitGroup)
- conns := common.NewConns()
- type answer struct {
- attempt int
- IPs []net.IP
- TTLs []time.Duration
- }
- var maxAttempts int
- if params.PreferAlternateDNSServer {
- maxAttempts = params.AttemptsPerPreferredServer
- maxAttempts += (len(servers) - 1) * params.AttemptsPerServer
- } else {
- maxAttempts = len(servers) * params.AttemptsPerServer
- }
- answerChan := make(chan *answer, maxAttempts*2)
- inFlight := int64(0)
- awaitA := int32(1)
- awaitAAAA := int32(1)
- if !hasIPv6Route {
- awaitAAAA = 0
- }
- var result *answer
- var lastErr atomic.Value
- stop := false
- for i := 0; !stop && i < maxAttempts; i++ {
- var index int
- if params.PreferAlternateDNSServer {
- if i < params.AttemptsPerPreferredServer {
- index = 0
- } else {
- index = 1 + ((i - params.AttemptsPerPreferredServer) / params.AttemptsPerServer)
- }
- } else {
- index = i / params.AttemptsPerServer
- }
- server := servers[index]
- // Only the first attempt pair tries transforms, as it's not certain
- // the transforms will be compatible with DNS servers.
- useProtocolTransform := (i == 0 && params.ProtocolTransformSpec != nil)
- // Send A and AAAA requests concurrently.
- questionTypes := []resolverQuestionType{resolverQuestionTypeA, resolverQuestionTypeAAAA}
- if !hasIPv6Route {
- questionTypes = questionTypes[0:1]
- }
- for _, questionType := range questionTypes {
- waitGroup.Add(1)
- // For metrics, track peak concurrent in-flight requests for
- // a _single_ ResolveIP. inFlight for this ResolveIP is also used
- // to determine whether to await additional responses once the
- // first, valid response is received. For that logic to be
- // correct, we must increment inFlight in this outer goroutine to
- // ensure the await logic sees either inFlight > 0 or an answer
- // in the channel.
- r.updateMetricPeakInFlight(atomic.AddInt64(&inFlight, 1))
- go func(attempt int, questionType resolverQuestionType, useProtocolTransform bool) {
- defer waitGroup.Done()
- // We must decrement inFlight only after sending an answer and
- // setting awaitA or awaitAAAA to ensure that the await logic
- // in the outer goroutine will see inFlight 0 only once those
- // operations are complete.
- //
- // We cannot wait and decrement inFlight when the outer
- // goroutine receives answers, as no answer is sent in some
- // cases, such as when the resolve fails due to NXDOMAIN.
- defer atomic.AddInt64(&inFlight, -1)
- // The request count metric counts the _intention_ to send
- // requests, as there's a possibility that newResolverConn or
- // performDNSQuery fail locally before sending a request packet.
- switch questionType {
- case resolverQuestionTypeA:
- r.updateMetricRequestsIPv4()
- case resolverQuestionTypeAAAA:
- r.updateMetricRequestsIPv6()
- }
- // While it's possible, and potentially more optimal, to use
- // the same UDP socket for both the A and AAAA request, we
- // use a distinct socket per request, as common DNS clients do.
- conn, err := r.newResolverConn(r.networkConfig.logWarning, server)
- if err != nil {
- lastErr.Store(errors.Trace(err))
- return
- }
- defer conn.Close()
- // There's no context.Context support in the underlying API
- // used by performDNSQuery, so instead collect all the
- // request conns so that they can be closed, and any blocking
- // network I/O interrupted, below, if resolveCtx is done.
- if !conns.Add(conn) {
- // Add fails when conns is already closed.
- return
- }
- // performDNSQuery will send the request and read a response.
- // performDNSQuery will continue reading responses until it
- // receives a valid response, which can mitigate a subset of
- // DNS injection attacks (to the limited extent possible for
- // plaintext DNS).
- //
- // For IPv4, NXDOMAIN or a response with no IPs is not
- // expected for domains resolved by Psiphon, so
- // performDNSQuery treats such a response as invalid. For
- // IPv6, a response with no IPs, may be valid(even though the
- // response could be forged); the resolver will continue its
- // attempts loop if it has no other IPs.
- //
- // Each performDNSQuery has no timeout and runs
- // until it has read a valid response or the requestCtx is
- // done. This allows for slow arriving, valid responses to
- // eventually succeed, even if the read time exceeds
- // requestTimeout, as long as the read time is less than the
- // requestCtx timeout.
- //
- // With this approach, the overall ResolveIP call may have
- // more than 2 performDNSQuery requests in-flight at a time,
- // as requestTimeout is used to schedule sending the next
- // attempt but not cancel the current attempt. For
- // connectionless UDP, the resulting network traffic should
- // be similar to common DNS clients which do cancel request
- // before beginning the next attempt.
- IPs, TTLs, RTT, err := performDNSQuery(
- resolveCtx,
- r.networkConfig.logWarning,
- params,
- useProtocolTransform,
- conn,
- questionType,
- hostname)
- // Update the min/max RTT metric when reported (>=0) even if
- // the result is an error; i.e., the even if there was an
- // invalid response.
- //
- // Limitation: since individual requests aren't cancelled
- // after requestTimeout, RTT metrics won't reflect
- // no-response cases, although request and response count
- // disparities will still show up in the metrics.
- if RTT >= 0 {
- r.updateMetricRTT(RTT)
- }
- if err != nil {
- lastErr.Store(errors.Trace(err))
- return
- }
- if len(IPs) > 0 {
- select {
- case answerChan <- &answer{attempt: attempt, IPs: IPs, TTLs: TTLs}:
- default:
- }
- }
- // Mark no longer awaiting A or AAAA as long as there is a
- // valid response, even if there are no IPs in the IPv6 case.
- switch questionType {
- case resolverQuestionTypeA:
- r.updateMetricResponsesIPv4()
- atomic.StoreInt32(&awaitA, 0)
- case resolverQuestionTypeAAAA:
- r.updateMetricResponsesIPv6()
- atomic.StoreInt32(&awaitAAAA, 0)
- default:
- }
- }(i+1, questionType, useProtocolTransform)
- }
- resetTimer(requestTimeout)
- select {
- case result = <-answerChan:
- // When the first answer, a response with valid IPs, arrives, exit
- // the attempts loop. The following await branch may collect
- // additional answers.
- params.setFirstAttemptWithAnswer(result.attempt)
- stop = true
- case <-timer.C:
- // When requestTimeout arrives, loop around and launch the next
- // attempt; leave the existing requests running in case they
- // eventually respond.
- timerDrained = true
- case <-resolveCtx.Done():
- // When resolveCtx is done, exit the attempts loop.
- //
- // Append the existing lastErr, which may convey useful
- // information to be reported in a failed_tunnel error message.
- lastErr.Store(errors.Tracef("%v (lastErr: %v)", ctx.Err(), lastErr.Load()))
- stop = true
- }
- }
- // Receive any additional answers, now present in the channel, which
- // arrived concurrent with the first answer. This receive avoids a race
- // condition where inFlight may now be 0, with additional answers
- // enqueued, in which case the following await branch is not taken.
- //
- // It's possible for the attempts loop to exit with no received answer due
- // to timeouts or cancellation while, concurrently, an answer is sent to
- // the channel. In this case, when result == nil, we ignore the answers
- // and leave this as a failed resolve.
- if result != nil {
- for loop := true; loop; {
- select {
- case nextAnswer := <-answerChan:
- result.IPs = append(result.IPs, nextAnswer.IPs...)
- result.TTLs = append(result.TTLs, nextAnswer.TTLs...)
- default:
- loop = false
- }
- }
- }
- // When we have an answer, await -- for a short time,
- // params.AwaitTimeout -- extra answers from any remaining in-flight
- // requests. Only await if the request isn't cancelled and we don't
- // already have at least one IPv4 and one IPv6 response; only await AAAA
- // if it was sent; note that a valid AAAA response may include no IPs
- // lastErr is not set in timeout/cancelled cases here, since we already
- // have an answer.
- if result != nil &&
- resolveCtx.Err() == nil &&
- atomic.LoadInt64(&inFlight) > 0 &&
- (atomic.LoadInt32(&awaitA) != 0 || atomic.LoadInt32(&awaitAAAA) != 0) &&
- params.AwaitTimeout > 0 {
- resetTimer(params.AwaitTimeout)
- for {
- stop := false
- select {
- case nextAnswer := <-answerChan:
- result.IPs = append(result.IPs, nextAnswer.IPs...)
- result.TTLs = append(result.TTLs, nextAnswer.TTLs...)
- case <-timer.C:
- timerDrained = true
- stop = true
- case <-resolveCtx.Done():
- stop = true
- }
- if stop ||
- atomic.LoadInt64(&inFlight) == 0 ||
- (atomic.LoadInt32(&awaitA) == 0 && atomic.LoadInt32(&awaitAAAA) == 0) {
- break
- }
- }
- }
- if timer != nil {
- timer.Stop()
- }
- // Interrupt all workers.
- cancelFunc()
- conns.CloseAll()
- waitGroup.Wait()
- // When there's no answer, return the last error.
- if result == nil {
- err := lastErr.Load()
- if err == nil {
- err = errors.TraceNew("unexpected missing error")
- }
- if r.networkConfig.LogHostnames {
- err = fmt.Errorf("resolve %s : %w", hostname, err.(error))
- }
- return nil, errors.Trace(err.(error))
- }
- if len(result.IPs) == 0 {
- // Unexpected, since a len(IPs) > 0 check precedes sending to answerChan.
- return nil, errors.TraceNew("unexpected no IPs")
- }
- // Update the cache now, after all results are gathered.
- r.setCache(hostname, result.IPs, result.TTLs)
- return result.IPs, nil
- }
- // VerifyCacheExtension extends the TTL for any cached result for the
- // specified hostname to at least NetworkConfig.CacheExtensionVerifiedTTL.
- func (r *Resolver) VerifyCacheExtension(hostname string) {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- if r.networkConfig.CacheExtensionVerifiedTTL == 0 {
- return
- }
- if net.ParseIP(hostname) != nil {
- return
- }
- entry, expires, ok := r.cache.GetWithExpiration(hostname)
- if !ok {
- return
- }
- // Change the TTL only if the entry expires and the existing TTL isn't
- // longer than the extension.
- neverExpires := time.Time{}
- if expires == neverExpires ||
- expires.After(time.Now().Add(r.networkConfig.CacheExtensionVerifiedTTL)) {
- return
- }
- r.cache.Set(hostname, entry, r.networkConfig.CacheExtensionVerifiedTTL)
- r.metrics.verifiedCacheExtensions += 1
- }
- // GetMetrics returns a summary of DNS metrics.
- func (r *Resolver) GetMetrics() string {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- // When r.metrics.minRTT < 0, min/maxRTT is unset.
- minRTT := "n/a"
- maxRTT := minRTT
- if r.metrics.minRTT >= 0 {
- minRTT = fmt.Sprintf("%d", r.metrics.minRTT/time.Millisecond)
- maxRTT = fmt.Sprintf("%d", r.metrics.maxRTT/time.Millisecond)
- }
- extend := ""
- if r.networkConfig.CacheExtensionVerifiedTTL > 0 {
- extend = fmt.Sprintf("| extend %d ", r.metrics.verifiedCacheExtensions)
- }
- defaultResolves := ""
- if r.networkConfig.allowDefaultResolver() {
- defaultResolves = fmt.Sprintf(
- " | def %d/%d", r.metrics.defaultResolves, r.metrics.defaultSuccesses)
- }
- // Note that the number of system resolvers is a point-in-time value,
- // while the others are cumulative.
- return fmt.Sprintf("resolves %d | hit %d %s| req v4/v6 %d/%d | resp %d/%d | peak %d | rtt %s - %s ms. | sys %d%s",
- r.metrics.resolves,
- r.metrics.cacheHits,
- extend,
- r.metrics.requestsIPv4,
- r.metrics.requestsIPv6,
- r.metrics.responsesIPv4,
- r.metrics.responsesIPv6,
- r.metrics.peakInFlight,
- minRTT,
- maxRTT,
- len(r.systemServers),
- defaultResolves)
- }
- // updateNetworkState updates the system DNS server list, IPv6 state, and the
- // cache.
- //
- // Any errors that occur while querying network state are logged; in error
- // conditions the functionality of the resolver may be reduced, but the
- // resolver remains operational.
- func (r *Resolver) updateNetworkState(networkID string) {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- // Only perform blocking/expensive update operations when necessary.
- updateAll := false
- updateIPv6Route := false
- updateServers := false
- flushCache := false
- // If r.cache is nil, this is the first update call in NewResolver. Create
- // the cache and perform all updates.
- if r.cache == nil {
- r.cache = lrucache.NewWithLRU(
- resolverCacheDefaultTTL,
- resolverCacheReapFrequency,
- resolverCacheMaxEntries)
- updateAll = true
- }
- // Perform all updates when the networkID has changed, which indicates a
- // different network.
- if r.networkID != networkID {
- updateAll = true
- }
- if updateAll {
- updateIPv6Route = true
- updateServers = true
- flushCache = true
- }
- // Even when the networkID has not changed, update DNS servers
- // periodically. This is similar to how other DNS clients
- // poll /etc/resolv.conf, including the period of 5s.
- if time.Since(r.lastServersUpdate) > resolverServersUpdateTTL {
- updateServers = true
- }
- // Update hasIPv6Route, which indicates whether the current network has an
- // IPv6 route and so if DNS requests for AAAA records will be sent.
- // There's no use for AAAA records on IPv4-only networks; and other
- // common DNS clients omit AAAA requests on IPv4-only records, so these
- // requests would otherwise be unusual.
- //
- // There's no hasIPv4Route as we always need to resolve A records,
- // particularly for IPv4-only endpoints; for IPv6-only networks,
- // NetworkConfig.IPv6Synthesize should be used to accomodate IPv4 DNS
- // server addresses, and dials performed outside the Resolver will
- // similarly use NAT 64 (on iOS; on Android, 464XLAT will handle this
- // transparently).
- if updateIPv6Route {
- if r.networkConfig.HasIPv6Route != nil {
- r.hasIPv6Route = r.networkConfig.HasIPv6Route()
- } else {
- hasIPv6Route, err := hasRoutableIPv6Interface()
- if err != nil {
- // Log warning and proceed without IPv6.
- r.networkConfig.logWarning(
- errors.Tracef("unable to determine IPv6 route: %v", err))
- hasIPv6Route = false
- }
- r.hasIPv6Route = hasIPv6Route
- }
- }
- // Update the list of system DNS servers. It's not an error condition here
- // if the list is empty: a subsequent ResolveIP may use
- // ResolveParameters which specifies an AlternateDNSServer.
- if updateServers && r.networkConfig.GetDNSServers != nil {
- systemServers := []string{}
- for _, systemServer := range r.networkConfig.GetDNSServers() {
- host, _, err := net.SplitHostPort(systemServer)
- if err != nil {
- // Assume the SplitHostPort error is due to systemServer being
- // an IP only, and append the default port, 53. If
- // systemServer _isn't_ an IP, the following ParseIP will fail.
- host = systemServer
- systemServer = net.JoinHostPort(systemServer, resolverDNSPort)
- }
- if net.ParseIP(host) == nil {
- // Log warning and proceed without this DNS server.
- r.networkConfig.logWarning(
- errors.TraceNew("invalid DNS server IP address"))
- continue
- }
- systemServers = append(systemServers, systemServer)
- }
- // Check if the list of servers has changed, including order. If
- // changed, flush the cache even if the networkID has not changed.
- // Cached results are only considered valid as long as the system DNS
- // configuration remains the same.
- equal := len(r.systemServers) == len(systemServers)
- if equal {
- for i := 0; i < len(r.systemServers); i++ {
- if r.systemServers[i] != systemServers[i] {
- equal = false
- break
- }
- }
- }
- flushCache = flushCache || !equal
- // Concurrency note: once the r.systemServers slice is set, the
- // contents of the backing array must not be modified due to
- // concurrent ResolveIP calls.
- r.systemServers = systemServers
- r.lastServersUpdate = time.Now()
- }
- // Skip cache flushes when the extended DNS caching mechanism is enabled.
- // TODO: retain only verified cache entries?
- if flushCache && r.networkConfig.CacheExtensionVerifiedTTL == 0 {
- r.cache.Flush()
- }
- // Set r.networkID only after all operations complete without errors; if
- // r.networkID were set earlier, a subsequent
- // ResolveIP/updateNetworkState call might proceed as if the network
- // state were updated for the specified network ID.
- r.networkID = networkID
- }
- func (r *Resolver) getNetworkState() (bool, []string) {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- return r.hasIPv6Route, r.systemServers
- }
- func (r *Resolver) setCache(hostname string, IPs []net.IP, TTLs []time.Duration) {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- // The shortest TTL is used. In some cases, a DNS server may omit the TTL
- // or set a 0 TTL, in which case the default is used.
- TTL := resolverDefaultAnswerTTL
- for _, answerTTL := range TTLs {
- if answerTTL > 0 && answerTTL < TTL {
- TTL = answerTTL
- }
- }
- // When NetworkConfig.CacheExtensionInitialTTL configured, ensure the TTL
- // is no shorter than CacheExtensionInitialTTL.
- if r.networkConfig.CacheExtensionInitialTTL != 0 &&
- TTL < r.networkConfig.CacheExtensionInitialTTL {
- TTL = r.networkConfig.CacheExtensionInitialTTL
- }
- // Limitation: with concurrent ResolveIPs for the same domain, the last
- // setCache call determines the cache value. The results are not merged.
- r.cache.Set(hostname, IPs, TTL)
- }
- func (r *Resolver) getCache(hostname string) []net.IP {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- entry, ok := r.cache.Get(hostname)
- if !ok {
- return nil
- }
- r.metrics.cacheHits += 1
- return entry.([]net.IP)
- }
- // newResolverConn creates a UDP socket that will send packets to serverAddr.
- // serverAddr is an IP:port, which allows specifying the port for testing or
- // in rare cases where the port isn't 53.
- func (r *Resolver) newResolverConn(
- logWarning func(error),
- serverAddr string) (retConn net.Conn, retErr error) {
- defer func() {
- if retErr != nil {
- logWarning(retErr)
- }
- }()
- // When configured, attempt to synthesize an IPv6 address from
- // an IPv4 address for compatibility on DNS64/NAT64 networks.
- // If synthesize fails, try the original address.
- if r.networkConfig.IPv6Synthesize != nil {
- serverIPStr, port, err := net.SplitHostPort(serverAddr)
- if err != nil {
- return nil, errors.Trace(err)
- }
- serverIP := net.ParseIP(serverIPStr)
- if serverIP != nil && serverIP.To4() != nil {
- synthesized := r.networkConfig.IPv6Synthesize(serverIPStr)
- if synthesized != "" && net.ParseIP(synthesized) != nil {
- serverAddr = net.JoinHostPort(synthesized, port)
- }
- }
- }
- dialer := &net.Dialer{}
- if r.networkConfig.BindToDevice != nil {
- dialer.Control = func(_, _ string, c syscall.RawConn) error {
- var controlErr error
- err := c.Control(func(fd uintptr) {
- _, err := r.networkConfig.BindToDevice(int(fd))
- if err != nil {
- controlErr = errors.Tracef("BindToDevice failed: %v", err)
- return
- }
- })
- if controlErr != nil {
- return errors.Trace(controlErr)
- }
- return errors.Trace(err)
- }
- }
- // context.Background is ok in this case as the UDP dial is just a local
- // syscall to create the socket.
- conn, err := dialer.DialContext(context.Background(), "udp", serverAddr)
- if err != nil {
- return nil, errors.Trace(err)
- }
- return conn, nil
- }
- func (r *Resolver) updateMetricResolves() {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- r.metrics.resolves += 1
- }
- func (r *Resolver) updateMetricRequestsIPv4() {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- r.metrics.requestsIPv4 += 1
- }
- func (r *Resolver) updateMetricRequestsIPv6() {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- r.metrics.requestsIPv6 += 1
- }
- func (r *Resolver) updateMetricResponsesIPv4() {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- r.metrics.responsesIPv4 += 1
- }
- func (r *Resolver) updateMetricResponsesIPv6() {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- r.metrics.responsesIPv6 += 1
- }
- func (r *Resolver) updateMetricDefaultResolver(success bool) {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- r.metrics.defaultResolves += 1
- if success {
- r.metrics.defaultSuccesses += 1
- }
- }
- func (r *Resolver) updateMetricPeakInFlight(inFlight int64) {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- if inFlight > r.metrics.peakInFlight {
- r.metrics.peakInFlight = inFlight
- }
- }
- func (r *Resolver) updateMetricRTT(rtt time.Duration) {
- r.mutex.Lock()
- defer r.mutex.Unlock()
- if rtt < 0 {
- // Ignore invalid input.
- return
- }
- // When r.metrics.minRTT < 0, min/maxRTT is unset.
- if r.metrics.minRTT < 0 || rtt < r.metrics.minRTT {
- r.metrics.minRTT = rtt
- }
- if rtt > r.metrics.maxRTT {
- r.metrics.maxRTT = rtt
- }
- }
- func hasRoutableIPv6Interface() (bool, error) {
- interfaces, err := net.Interfaces()
- if err != nil {
- return false, errors.Trace(err)
- }
- for _, in := range interfaces {
- if (in.Flags&net.FlagUp == 0) ||
- (in.Flags&(net.FlagLoopback|net.FlagPointToPoint)) != 0 {
- continue
- }
- addrs, err := in.Addrs()
- if err != nil {
- return false, errors.Trace(err)
- }
- for _, addr := range addrs {
- if IPNet, ok := addr.(*net.IPNet); ok &&
- IPNet.IP.To4() == nil &&
- !IPNet.IP.IsLinkLocalUnicast() {
- return true, nil
- }
- }
- }
- return false, nil
- }
- func generateIPAddressFromCIDR(CIDR string) (net.IP, error) {
- _, IPNet, err := net.ParseCIDR(CIDR)
- if err != nil {
- return nil, errors.Trace(err)
- }
- // A retry is required, since a CIDR may include broadcast IPs (a.b.c.0) or
- // other invalid values. The number of retries is limited to ensure we
- // don't hang in the case of a misconfiguration.
- for i := 0; i < 10; i++ {
- randBytes := prng.Bytes(len(IPNet.IP))
- IP := make(net.IP, len(IPNet.IP))
- // The 1 bits in the mask must apply to the IP in the CIDR and the 0
- // bits in the mask are available to randomize.
- for i := 0; i < len(IP); i++ {
- IP[i] = (IPNet.IP[i] & IPNet.Mask[i]) | (randBytes[i] & ^IPNet.Mask[i])
- }
- if IP.IsGlobalUnicast() && !common.IsBogon(IP) {
- return IP, nil
- }
- }
- return nil, errors.TraceNew("failed to generate random IP")
- }
- type resolverQuestionType int
- const (
- resolverQuestionTypeA = 0
- resolverQuestionTypeAAAA = 1
- )
- func performDNSQuery(
- resolveCtx context.Context,
- logWarning func(error),
- params *ResolveParameters,
- useProtocolTransform bool,
- conn net.Conn,
- questionType resolverQuestionType,
- hostname string) ([]net.IP, []time.Duration, time.Duration, error) {
- if useProtocolTransform {
- if params.ProtocolTransformSpec == nil ||
- params.ProtocolTransformSeed == nil {
- return nil, nil, -1, errors.TraceNew("invalid protocol transform configuration")
- }
- // miekg/dns expects conn to be a net.PacketConn or else it writes the
- // TCP length prefix
- udpConn, ok := conn.(*net.UDPConn)
- if !ok {
- return nil, nil, -1, errors.TraceNew("conn is not a *net.UDPConn")
- }
- conn = &transformDNSPacketConn{
- UDPConn: udpConn,
- transform: params.ProtocolTransformSpec,
- seed: params.ProtocolTransformSeed,
- }
- }
- // UDPSize sets the receive buffer to > 512, even when we don't include
- // EDNS(0), which will mitigate issues with RFC 1035 non-compliant
- // servers. See Go issue 51127.
- dnsConn := &dns.Conn{
- Conn: conn,
- UDPSize: udpPacketBufferSize,
- }
- defer dnsConn.Close()
- // SetQuestion initializes request.MsgHdr.Id to a random value
- request := &dns.Msg{MsgHdr: dns.MsgHdr{RecursionDesired: true}}
- switch questionType {
- case resolverQuestionTypeA:
- request.SetQuestion(dns.Fqdn(hostname), dns.TypeA)
- case resolverQuestionTypeAAAA:
- request.SetQuestion(dns.Fqdn(hostname), dns.TypeAAAA)
- default:
- return nil, nil, -1, errors.TraceNew("unknown DNS request question type")
- }
- if params.IncludeEDNS0 {
- // miekg/dns: "RFC 6891, Section 6.1.1 allows the OPT record to appear
- // anywhere in the additional record section, but it's usually at the
- // end..."
- request.SetEdns0(udpPacketBufferSize, false)
- }
- startTime := time.Now()
- // Send the DNS request
- dnsConn.WriteMsg(request)
- // Read and process the DNS response
- var IPs []net.IP
- var TTLs []time.Duration
- var lastErr error
- RTT := time.Duration(-1)
- for {
- // Stop when resolveCtx is done; the caller, ResolveIP, will also
- // close conn, which will interrupt a blocking dnsConn.ReadMsg.
- if resolveCtx.Err() != nil {
- // ResolveIP, which calls performDNSQuery, already records the
- // context error (e.g., context timeout), so instead report
- // lastErr, when present, as it may contain more useful
- // information about why a response was rejected.
- err := lastErr
- if err == nil {
- err = errors.Trace(resolveCtx.Err())
- }
- return nil, nil, RTT, err
- }
- // Read a response. RTT is the elapsed time between sending the
- // request and reading the last received response.
- response, err := dnsConn.ReadMsg()
- RTT = time.Since(startTime)
- if err == nil && response.MsgHdr.Id != request.MsgHdr.Id {
- err = dns.ErrId
- }
- if err != nil {
- // Try reading again, in case the first response packet failed to
- // unmarshal or had an invalid ID. The Go resolver also does this;
- // see Go issue 13281.
- if resolveCtx.Err() == nil {
- // Only log if resolveCtx is not done; otherwise the error could
- // be due to conn being closed by ResolveIP.
- lastErr = errors.Tracef("invalid response: %v", err)
- logWarning(lastErr)
- }
- continue
- }
- // Check the RCode.
- //
- // For IPv4, we expect RCodeSuccess as Psiphon will typically only
- // resolve domains that exist and have a valid IP (when this isn't
- // the case, and we retry, the overall ResolveIP and its parent dial
- // will still abort after resolveCtx is done, or RequestTimeout
- // expires for maxAttempts).
- //
- // For IPv6, we should also expect RCodeSuccess even if there is no
- // AAAA record, as long as the domain exists and has an A record.
- // However, per RFC 6147 section 5.1.2, we may receive
- // NXDOMAIN: "...some servers respond with RCODE=3 to a AAAA query
- // even if there is an A record available for that owner name. Those
- // servers are in clear violation of the meaning of RCODE 3...". In
- // this case, we coalesce NXDOMAIN into success to treat the response
- // the same as success with no AAAA record.
- //
- // All other RCodes, which are unexpected, lead to a read retry.
- if response.MsgHdr.Rcode != dns.RcodeSuccess &&
- !(questionType == resolverQuestionTypeAAAA && response.MsgHdr.Rcode == dns.RcodeNameError) {
- errMsg, ok := dns.RcodeToString[response.MsgHdr.Rcode]
- if !ok {
- errMsg = fmt.Sprintf("Rcode: %d", response.MsgHdr.Rcode)
- }
- lastErr = errors.Tracef("unexpected RCode: %v", errMsg)
- logWarning(lastErr)
- continue
- }
- // Extract all IP answers, along with corresponding TTLs for caching.
- // Perform additional validation, which may lead to another read
- // retry. However, if _any_ valid IP is found, stop reading and
- // return that result. Again, the validation is only best effort.
- checkFailed := false
- for _, answer := range response.Answer {
- haveAnswer := false
- var IP net.IP
- var TTLSec uint32
- switch questionType {
- case resolverQuestionTypeA:
- if a, ok := answer.(*dns.A); ok {
- IP = a.A
- TTLSec = a.Hdr.Ttl
- haveAnswer = true
- }
- case resolverQuestionTypeAAAA:
- if aaaa, ok := answer.(*dns.AAAA); ok {
- IP = aaaa.AAAA
- TTLSec = aaaa.Hdr.Ttl
- haveAnswer = true
- }
- }
- if !haveAnswer {
- continue
- }
- err := checkDNSAnswerIP(IP)
- if err != nil {
- checkFailed = true
- lastErr = errors.Tracef("invalid IP: %v", err)
- logWarning(lastErr)
- // Check the next answer
- continue
- }
- IPs = append(IPs, IP)
- TTLs = append(TTLs, time.Duration(TTLSec)*time.Second)
- }
- // For IPv4, an IP is expected, as noted in the comment above.
- //
- // In potential cases where we resolve a domain that has only an IPv6
- // address, the concurrent AAAA request will deliver its result to
- // ResolveIP, and that answer will be selected, so only the "await"
- // logic will delay the parent dial in that case.
- if questionType == resolverQuestionTypeA && len(IPs) == 0 && !checkFailed {
- checkFailed = true
- lastErr = errors.TraceNew("unexpected empty A response")
- logWarning(lastErr)
- }
- // Retry if there are no valid IPs and any error; if no error, this
- // may be a valid AAAA response with no IPs, in which case return the
- // result.
- if len(IPs) == 0 && checkFailed {
- continue
- }
- return IPs, TTLs, RTT, nil
- }
- }
- func checkDNSAnswerIP(IP net.IP) error {
- if IP == nil {
- return errors.TraceNew("IP is nil")
- }
- // Limitation: this could still be a phony/injected response, it's not
- // possible to verify with plaintext DNS, but a "bogon" IP is clearly
- // invalid.
- if common.IsBogon(IP) {
- return errors.TraceNew("IP is bogon")
- }
- // Create a temporary socket bound to the destination IP. This checks
- // thats the local host has a route to this IP. If not, we'll reject the
- // IP. This prevents selecting an IP which is guaranteed to fail to dial.
- // Use UDP as this results in no network traffic; the destination port is
- // arbitrary. The Go resolver performs a similar operation.
- //
- // Limitations:
- // - We may cache the IP and reuse it without checking routability again;
- // the cache should be flushed when network state changes.
- // - Given that the AAAA is requested only when the host has an IPv6
- // route, we don't expect this to often fail with a _valid_ response.
- // However, this remains a possibility and in this case,
- // performDNSQuery will keep awaiting a response which can trigger
- // the "await" logic.
- conn, err := net.DialUDP("udp", nil, &net.UDPAddr{IP: IP, Port: 443})
- if err != nil {
- return errors.Trace(err)
- }
- conn.Close()
- return nil
- }
- func defaultResolverLookupIP(
- ctx context.Context, hostname string, logHostnames bool) ([]net.IP, error) {
- addrs, err := net.DefaultResolver.LookupIPAddr(ctx, hostname)
- if err != nil && !logHostnames {
- // Remove domain names from "net" error messages.
- err = common.RedactNetError(err)
- }
- if err != nil {
- return nil, errors.Trace(err)
- }
- ips := make([]net.IP, len(addrs))
- for i, addr := range addrs {
- ips[i] = addr.IP
- }
- return ips, nil
- }
- // transformDNSPacketConn wraps a *net.UDPConn, intercepting Write calls and
- // applying the specified protocol transform.
- //
- // As transforms operate on strings and DNS requests are binary, the transform
- // should be expressed using hex characters. The DNS packet to be written
- // (input the Write) is converted to hex, transformed, and converted back to
- // binary and then actually written to the UDP socket.
- type transformDNSPacketConn struct {
- *net.UDPConn
- transform transforms.Spec
- seed *prng.Seed
- }
- func (conn *transformDNSPacketConn) Write(b []byte) (int, error) {
- // Limitation: there is no check that a transformed packet remains within
- // the network packet MTU.
- input := hex.EncodeToString(b)
- output, err := conn.transform.Apply(conn.seed, input)
- if err != nil {
- return 0, errors.Trace(err)
- }
- packet, err := hex.DecodeString(output)
- if err != nil {
- return 0, errors.Trace(err)
- }
- _, err = conn.UDPConn.Write(packet)
- if err != nil {
- // In the error case, don't report bytes written as the number could
- // exceed the pre-transform length.
- return 0, errors.Trace(err)
- }
- // Report the pre-transform length as bytes written, as the caller may check
- // that the requested len(b) bytes were written.
- return len(b), nil
- }
|