github.com/elfadel/cilium@v1.6.12/pkg/fqdn/dnsproxy/proxy.go (about)

     1  // Copyright 2018 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package dnsproxy
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"math"
    22  	"net"
    23  	"regexp"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/cilium/cilium/pkg/datapath/linux/linux_defaults"
    29  	"github.com/cilium/cilium/pkg/endpoint"
    30  	"github.com/cilium/cilium/pkg/fqdn/matchpattern"
    31  	"github.com/cilium/cilium/pkg/identity"
    32  	"github.com/cilium/cilium/pkg/ipcache"
    33  	"github.com/cilium/cilium/pkg/lock"
    34  	"github.com/cilium/cilium/pkg/logging/logfields"
    35  	"github.com/cilium/cilium/pkg/option"
    36  	"github.com/cilium/cilium/pkg/policy"
    37  	"github.com/cilium/cilium/pkg/spanstat"
    38  
    39  	"github.com/miekg/dns"
    40  	"github.com/sirupsen/logrus"
    41  )
    42  
    43  const (
    44  	// ProxyForwardTimeout is the maximum time to wait for DNS responses to
    45  	// forwarded DNS requests. This is needed since UDP queries have no way to
    46  	// indicate that the client has stopped expecting a response.
    47  	ProxyForwardTimeout = 10 * time.Second
    48  
    49  	// ProxyBindTimeout is how long we wait for a successful bind to the bindaddr.
    50  	// Note: This must be divisible by 5 without going to 0
    51  	ProxyBindTimeout = 20 * time.Second
    52  
    53  	// ProxyBindRetryInterval is how long to wait between attempts to bind to the
    54  	// proxy address:port
    55  	ProxyBindRetryInterval = ProxyBindTimeout / 5
    56  )
    57  
    58  // DNSProxy is a L7 proxy for DNS traffic. It keeps a list of allowed DNS
    59  // lookups that can be regexps and blocks lookups that are not allowed.
    60  // A singleton is always running inside cilium-agent.
    61  // Note: All public fields are read only and do not require locking
    62  type DNSProxy struct {
    63  	// BindAddr is the local address the server is using to listen for DNS
    64  	// requests. This is a read-only value and reflects the actual value. Passing
    65  	// ":0" to StartDNSProxy will allow the kernel to set the port, and that can
    66  	// be read here.
    67  	BindAddr string
    68  
    69  	// BindPort is the port in BindAddr.
    70  	BindPort uint16
    71  
    72  	// LookupEndpointIDByIP is a provided callback that returns the endpoint ID
    73  	// as a uint16.
    74  	// Note: this is a little pointless since this proxy is in-process but it is
    75  	// intended to allow us to switch to an external proxy process by forcing the
    76  	// design now.
    77  	LookupEndpointIDByIP LookupEndpointIDByIPFunc
    78  
    79  	// LookupSecIDByIP is a provided callback that returns the IP's security ID
    80  	// from the ipcache.
    81  	// Note: this is a little pointless since this proxy is in-process but it is
    82  	// intended to allow us to switch to an external proxy process by forcing the
    83  	// design now.
    84  	LookupSecIDByIP LookupSecIDByIPFunc
    85  
    86  	// NotifyOnDNSMsg is a provided callback by which the proxy can emit DNS
    87  	// response data. It is intended to wire into a DNS cache and a
    88  	// fqdn.NameManager.
    89  	// Note: this is a little pointless since this proxy is in-process but it is
    90  	// intended to allow us to switch to an external proxy process by forcing the
    91  	// design now.
    92  	NotifyOnDNSMsg NotifyOnDNSMsgFunc
    93  
    94  	// UDPServer, TCPServer are the miekg/dns server instances. They handle DNS
    95  	// parsing etc. for us.
    96  	UDPServer, TCPServer *dns.Server
    97  
    98  	// UDPClient, TCPClient are the miekg/dns client instances. Forwarded
    99  	// requests are made with these clients but are sent to the originally
   100  	// intended DNS server.
   101  	// Note: The DNS request ID is randomized but when seeing a lot of traffic we
   102  	// may still exhaust the 16-bit ID space for our (source IP, source Port) and
   103  	// this may cause DNS disruption. A client pool may be better.
   104  	UDPClient, TCPClient *dns.Client
   105  
   106  	// lookupTargetDNSServer extracts the originally intended target of a DNS
   107  	// query. It is always set to lookupTargetDNSServer in
   108  	// helpers.go but is modified during testing.
   109  	lookupTargetDNSServer func(w dns.ResponseWriter) (serverIP net.IP, serverPort uint16, addrStr string, err error)
   110  
   111  	// this mutex protects variables below this point
   112  	lock.Mutex
   113  
   114  	// allowed tracks all allowed L7 DNS rules by endpointID, destination port,
   115  	// and L3 Selector. All must match for a query to be allowed.
   116  	//
   117  	// matchNames with no regexp wildcards are still compiled, internally.
   118  	// Note: Simple DNS names, e.g. bar.foo.com, will treat the "." as a literal.
   119  	allowed perEPAllow
   120  
   121  	// rejectReply is the OPCode send from the DNS-proxy to the endpoint if the
   122  	// DNS request is invalid
   123  	rejectReply int
   124  }
   125  
   126  // perEPAllow maps EndpointIDs to ports + selectors + rules
   127  type perEPAllow map[uint64]portToSelectorAllow
   128  
   129  // portToSelectorAllow maps port numbers to selectors + rules
   130  type portToSelectorAllow map[uint16]cachedSelectorREEntry
   131  
   132  // cachedSelectorREEntry maps port numbers to selectors to rules, mirroring
   133  // policy.L7DataMap but the DNS rules are compiled into a single regexp
   134  type cachedSelectorREEntry map[policy.CachedSelector]*regexp.Regexp
   135  
   136  // setPortRulesForID sets the matching rules for endpointID and destPort for
   137  // later lookups. It converts newRules into a unified regexp that can be reused
   138  // later.
   139  func (allow perEPAllow) setPortRulesForID(endpointID uint64, destPort uint16, newRules policy.L7DataMap) error {
   140  	// This is the delete case
   141  	if len(newRules) == 0 {
   142  		epPorts := allow[endpointID]
   143  		delete(epPorts, destPort)
   144  		if len(epPorts) == 0 {
   145  			delete(allow, endpointID)
   146  		}
   147  		return nil
   148  	}
   149  
   150  	newRE := make(cachedSelectorREEntry)
   151  	for selector, l7Rules := range newRules {
   152  		reStrings := make([]string, 0, len(l7Rules.DNS))
   153  		for _, dnsRule := range l7Rules.DNS {
   154  			if len(dnsRule.MatchName) > 0 {
   155  				dnsRuleName := strings.ToLower(dns.Fqdn(dnsRule.MatchName))
   156  				dnsPatternAsRE := matchpattern.ToRegexp(dnsRuleName)
   157  				reStrings = append(reStrings, "("+dnsPatternAsRE+")")
   158  			}
   159  			if len(dnsRule.MatchPattern) > 0 {
   160  				dnsPattern := matchpattern.Sanitize(dnsRule.MatchPattern)
   161  				dnsPatternAsRE := matchpattern.ToRegexp(dnsPattern)
   162  				reStrings = append(reStrings, "("+dnsPatternAsRE+")")
   163  			}
   164  		}
   165  		re, err := regexp.Compile(strings.Join(reStrings, "|"))
   166  		if err != nil {
   167  			return err
   168  		}
   169  		newRE[selector] = re
   170  	}
   171  
   172  	epPorts, exist := allow[endpointID]
   173  	if !exist {
   174  		epPorts = make(portToSelectorAllow)
   175  		allow[endpointID] = epPorts
   176  	}
   177  
   178  	epPorts[destPort] = newRE
   179  	return nil
   180  }
   181  
   182  // getPortRulesForID returns a precompiled regex representing DNS rules for the
   183  // passed-in endpointID and destPort with setPortRulesForID
   184  func (allow perEPAllow) getPortRulesForID(endpointID uint64, destPort uint16) (rules cachedSelectorREEntry, exists bool) {
   185  	rules, exists = allow[endpointID][destPort]
   186  	return rules, exists
   187  }
   188  
   189  // LookupEndpointIDByIPFunc wraps logic to lookup an endpoint with any backend.
   190  // See DNSProxy.LookupEndpointIDByIP for usage.
   191  type LookupEndpointIDByIPFunc func(ip net.IP) (endpoint *endpoint.Endpoint, err error)
   192  
   193  // LookupSecIDByIPFunc Func wraps logic to lookup an IP's security ID from the
   194  // ipcache.
   195  // See DNSProxy.LookupSecIDByIP for usage.
   196  type LookupSecIDByIPFunc func(ip net.IP) (secID ipcache.Identity, exists bool, err error)
   197  
   198  // NotifyOnDNSMsgFunc handles propagating DNS response data
   199  // See DNSProxy.LookupEndpointIDByIP for usage.
   200  type NotifyOnDNSMsgFunc func(lookupTime time.Time, ep *endpoint.Endpoint, epIPPort string, serverAddr string, msg *dns.Msg, protocol string, allowed bool, stat ProxyRequestContext) error
   201  
   202  // ProxyRequestContext proxy dns request context struct to send in the callback
   203  type ProxyRequestContext struct {
   204  	ProcessingTime spanstat.SpanStat // This is going to happen at the end of the second callback.
   205  	// Error is a enum of [timeout, allow, denied, proxyerr].
   206  	UpstreamTime spanstat.SpanStat
   207  	Success      bool
   208  	Err          error
   209  }
   210  
   211  // IsTimeout return true if the ProxyRequest timeout
   212  func (proxyStat *ProxyRequestContext) IsTimeout() bool {
   213  	netErr, isNetErr := proxyStat.Err.(net.Error)
   214  	if isNetErr && netErr.Timeout() {
   215  		return true
   216  
   217  	}
   218  	return false
   219  }
   220  
   221  // StartDNSProxy starts a proxy used for DNS L7 redirects that listens on
   222  // address and port.
   223  // address is the bind address to listen on. Empty binds to all local
   224  // addresses.
   225  // port is the port to bind to for both UDP and TCP. 0 causes the kernel to
   226  // select a free port.
   227  // lookupEPFunc will be called with the source IP of DNS requests, and expects
   228  // a unique identifier for the endpoint that made the request.
   229  // notifyFunc will be called with DNS response data that is returned to a
   230  // requesting endpoint. Note that denied requests will not trigger this
   231  // callback.
   232  func StartDNSProxy(address string, port uint16, lookupEPFunc LookupEndpointIDByIPFunc, lookupSecIDFunc LookupSecIDByIPFunc, notifyFunc NotifyOnDNSMsgFunc) (*DNSProxy, error) {
   233  	if port == 0 {
   234  		log.Debug("DNS Proxy port is configured to 0. A random port will be assigned by the OS.")
   235  	}
   236  
   237  	if lookupEPFunc == nil || notifyFunc == nil {
   238  		return nil, errors.New("DNS proxy must have lookupEPFunc and notifyFunc provided")
   239  	}
   240  
   241  	p := &DNSProxy{
   242  		LookupEndpointIDByIP:  lookupEPFunc,
   243  		LookupSecIDByIP:       lookupSecIDFunc,
   244  		NotifyOnDNSMsg:        notifyFunc,
   245  		lookupTargetDNSServer: lookupTargetDNSServer,
   246  		allowed:               make(perEPAllow),
   247  		rejectReply:           dns.RcodeRefused,
   248  	}
   249  
   250  	// Start the DNS listeners on UDP and TCP
   251  	var (
   252  		UDPConn                *net.UDPConn
   253  		TCPListener            *net.TCPListener
   254  		err                    error
   255  		EnableIPv4, EnableIPv6 = option.Config.EnableIPv4, option.Config.EnableIPv6
   256  	)
   257  
   258  	start := time.Now()
   259  	for time.Since(start) < ProxyBindTimeout {
   260  		UDPConn, TCPListener, err = bindToAddr(address, port, EnableIPv4, EnableIPv6)
   261  		if err == nil {
   262  			break
   263  		}
   264  		log.WithError(err).Warnf("Attempt to bind DNS Proxy failed, retrying in %v", ProxyBindRetryInterval)
   265  		time.Sleep(ProxyBindRetryInterval)
   266  	}
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  
   271  	p.BindAddr = UDPConn.LocalAddr().String()
   272  	p.BindPort = uint16(UDPConn.LocalAddr().(*net.UDPAddr).Port)
   273  	p.UDPServer = &dns.Server{PacketConn: UDPConn, Addr: p.BindAddr, Net: "udp", Handler: p,
   274  		SessionUDPFactory: &sessionUDPFactory{ipv4Enabled: EnableIPv4, ipv6Enabled: EnableIPv6},
   275  	}
   276  	p.TCPServer = &dns.Server{Listener: TCPListener, Addr: p.BindAddr, Net: "tcp", Handler: p}
   277  	log.WithField("address", p.BindAddr).Debug("DNS Proxy bound to address")
   278  
   279  	for _, s := range []*dns.Server{p.UDPServer, p.TCPServer} {
   280  		go func(server *dns.Server) {
   281  			// try 5 times during a single ProxyBindTimeout period. We fatal here
   282  			// because we have no other way to indicate failure this late.
   283  			start := time.Now()
   284  			for time.Since(start) < ProxyBindTimeout {
   285  				if err := server.ActivateAndServe(); err != nil {
   286  					log.WithError(err).Errorf("Failed to start the %s DNS proxy on %s", server.Net, server.Addr)
   287  				}
   288  				time.Sleep(ProxyBindRetryInterval)
   289  			}
   290  			log.Fatalf("Failed to start %s DNS Proxy on %s", server.Net, server.Addr)
   291  		}(s)
   292  	}
   293  
   294  	// Bind the DNS forwarding clients on UDP and TCP
   295  	// Note: SingleInFlight should remain disabled. When enabled it folds DNS
   296  	// retries into the previous lookup, suppressing them.
   297  	p.UDPClient = &dns.Client{Net: "udp", Timeout: ProxyForwardTimeout, SingleInflight: false}
   298  	p.TCPClient = &dns.Client{Net: "tcp", Timeout: ProxyForwardTimeout, SingleInflight: false}
   299  
   300  	return p, nil
   301  }
   302  
   303  // UpdateAllowed sets newRules for endpointID and destPort. It compiles the DNS
   304  // rules into regexes that are then used in CheckAllowed.
   305  func (p *DNSProxy) UpdateAllowed(endpointID uint64, destPort uint16, newRules policy.L7DataMap) error {
   306  	p.Lock()
   307  	defer p.Unlock()
   308  
   309  	return p.allowed.setPortRulesForID(endpointID, destPort, newRules)
   310  }
   311  
   312  // CheckAllowed checks endpointID, destPort, destID, and name against the rules
   313  // added to the proxy, and only returns true if this all match something that
   314  // was added (via SetAllowed) previously.
   315  func (p *DNSProxy) CheckAllowed(endpointID uint64, destPort uint16, destID identity.NumericIdentity, name string) (allowed bool) {
   316  	name = strings.ToLower(dns.Fqdn(name))
   317  	p.Lock()
   318  	defer p.Unlock()
   319  
   320  	epAllow, exists := p.allowed.getPortRulesForID(endpointID, destPort)
   321  	if !exists {
   322  		return false
   323  	}
   324  
   325  	for selector, re := range epAllow {
   326  		// The port was matched in getPortRulesForID, above.
   327  		if selector.Selects(destID) && re.MatchString(name) {
   328  			return true
   329  		}
   330  	}
   331  
   332  	return false
   333  }
   334  
   335  // ServeDNS handles individual DNS requests forwarded to the proxy, and meets
   336  // the dns.Handler interface.
   337  // It will:
   338  //  - Look up the endpoint that sent the request by IP, via LookupEndpointIDByIP.
   339  //  - Look up the Sec ID of the destination server, via LookupSecIDByIP.
   340  //  - Check that the endpoint ID, destination Sec ID, destination port and the
   341  //  qname all match a rule. If not, the request is dropped.
   342  //  - The allowed request is forwarded to the originally intended DNS server IP
   343  //  - The response is shared via NotifyOnDNSMsg (this will go to a
   344  //  fqdn/NameManager instance).
   345  //  - Write the response to the endpoint.
   346  func (p *DNSProxy) ServeDNS(w dns.ResponseWriter, request *dns.Msg) {
   347  	stat := ProxyRequestContext{}
   348  	stat.ProcessingTime.Start()
   349  	requestID := request.Id // keep the original request ID
   350  	qname := string(request.Question[0].Name)
   351  	protocol := w.LocalAddr().Network()
   352  	scopedLog := log.WithFields(logrus.Fields{
   353  		logfields.DNSName:      qname,
   354  		logfields.IPAddr:       w.RemoteAddr(),
   355  		logfields.DNSRequestID: request.Id})
   356  	scopedLog.Debug("Handling DNS query from endpoint")
   357  
   358  	epIPPort := w.RemoteAddr().String()
   359  	addr, _, err := net.SplitHostPort(epIPPort)
   360  	if err != nil {
   361  		scopedLog.WithError(err).Error("cannot extract endpoint IP from DNS request")
   362  		stat.Err = fmt.Errorf("Cannot extract endpoint IP from DNS request: %s", err)
   363  		stat.ProcessingTime.End(false)
   364  		p.NotifyOnDNSMsg(time.Now(), nil, epIPPort, "", request, protocol, false, stat)
   365  		p.sendRefused(scopedLog, w, request)
   366  		return
   367  	}
   368  	ep, err := p.LookupEndpointIDByIP(net.ParseIP(addr))
   369  	if err != nil {
   370  		scopedLog.WithError(err).Error("cannot extract endpoint ID from DNS request")
   371  		stat.Err = fmt.Errorf("Cannot extract endpoint ID from DNS request: %s", err)
   372  		stat.ProcessingTime.End(false)
   373  		p.NotifyOnDNSMsg(time.Now(), nil, epIPPort, "", request, protocol, false, stat)
   374  		p.sendRefused(scopedLog, w, request)
   375  		return
   376  	}
   377  
   378  	scopedLog = scopedLog.WithField(logfields.EndpointID, ep.StringID())
   379  
   380  	targetServerIP, targetServerPort, targetServerAddr, err := p.lookupTargetDNSServer(w)
   381  	if err != nil {
   382  		log.WithError(err).Error("cannot extract destination IP:port from DNS request")
   383  		stat.Err = fmt.Errorf("Cannot extract destination IP:port from DNS request: %s", err)
   384  		stat.ProcessingTime.End(false)
   385  		p.NotifyOnDNSMsg(time.Now(), ep, epIPPort, targetServerAddr, request, protocol, false, stat)
   386  		p.sendRefused(scopedLog, w, request)
   387  		return
   388  	}
   389  
   390  	serverSecID, exists, err := p.LookupSecIDByIP(targetServerIP)
   391  	if !exists || err != nil {
   392  		scopedLog.WithError(err).WithField("server", targetServerAddr).Debug("cannot find server ip in ipcache")
   393  		stat.Err = fmt.Errorf("Cannot find server ip in ipcache: %s", err)
   394  		stat.ProcessingTime.End(false)
   395  		p.NotifyOnDNSMsg(time.Now(), ep, epIPPort, targetServerAddr, request, protocol, false, stat)
   396  		p.sendRefused(scopedLog, w, request)
   397  		return
   398  	}
   399  	scopedLog.WithField("server", targetServerAddr).Debugf("Found target server to of DNS request secID %+v", serverSecID)
   400  
   401  	// The allowed check is first because we don't want to use DNS responses that
   402  	// endpoints are not allowed to see.
   403  	// Note: The cache doesn't know about the source of the DNS data (yet) and so
   404  	// it won't enforce any separation between results from different endpoints.
   405  	// This isn't ideal but we are trusting the DNS responses anyway.
   406  	if allowed := p.CheckAllowed(uint64(ep.ID), targetServerPort, serverSecID.ID, qname); !allowed {
   407  		scopedLog.Debug("Rejecting DNS query from endpoint due to policy")
   408  		stat.Err = p.sendRefused(scopedLog, w, request)
   409  		stat.ProcessingTime.End(true)
   410  		p.NotifyOnDNSMsg(time.Now(), ep, epIPPort, targetServerAddr, request, protocol, false, stat)
   411  		return
   412  	}
   413  
   414  	scopedLog.Debug("Forwarding DNS request for a name that is allowed")
   415  	p.NotifyOnDNSMsg(time.Now(), ep, epIPPort, targetServerAddr, request, protocol, true, stat)
   416  
   417  	// Keep the same L4 protocol. This handles DNS re-requests over TCP, for
   418  	// requests that were too large for UDP.
   419  	var client *dns.Client
   420  	switch protocol {
   421  	case "udp":
   422  		client = p.UDPClient
   423  	case "tcp":
   424  		client = p.TCPClient
   425  	default:
   426  		scopedLog.Error("Cannot parse DNS proxy client network to select forward client")
   427  		stat.Err = fmt.Errorf("Cannot parse DNS proxy client network to select forward client: %s", err)
   428  		stat.ProcessingTime.End(false)
   429  		p.NotifyOnDNSMsg(time.Now(), ep, epIPPort, targetServerAddr, request, protocol, false, stat)
   430  		p.sendRefused(scopedLog, w, request)
   431  		return
   432  	}
   433  	stat.ProcessingTime.End(true)
   434  	stat.UpstreamTime.Start()
   435  
   436  	request.Id = dns.Id() // force a random new ID for this request
   437  	response, _, err := client.Exchange(request, targetServerAddr)
   438  	stat.UpstreamTime.End(err == nil)
   439  	if err != nil {
   440  		stat.Err = err
   441  		if stat.IsTimeout() {
   442  			scopedLog.WithError(err).Warn("Timeout waiting for response to forwarded proxied DNS lookup")
   443  		} else {
   444  			scopedLog.WithError(err).Error("Cannot forward proxied DNS lookup")
   445  			p.sendRefused(scopedLog, w, request)
   446  			stat.Err = fmt.Errorf("Cannot forward proxied DNS lookup: %s", err)
   447  		}
   448  		p.NotifyOnDNSMsg(time.Now(), ep, epIPPort, targetServerAddr, request, protocol, false, stat)
   449  		return
   450  	}
   451  
   452  	scopedLog.WithField(logfields.Response, response).Debug("Received DNS response to proxied lookup")
   453  	stat.Success = true
   454  
   455  	scopedLog.Debug("Notifying with DNS response to original DNS query")
   456  	p.NotifyOnDNSMsg(time.Now(), ep, epIPPort, targetServerAddr, response, protocol, true, stat)
   457  
   458  	scopedLog.Debug("Responding to original DNS query")
   459  	// restore the ID to the one in the initial request so it matches what the requester expects.
   460  	response.Id = requestID
   461  	err = w.WriteMsg(response)
   462  	if err != nil {
   463  		scopedLog.WithError(err).Error("Cannot forward proxied DNS response")
   464  		stat.Err = fmt.Errorf("Cannot forward proxied DNS response: %s", err)
   465  		p.NotifyOnDNSMsg(time.Now(), ep, epIPPort, targetServerAddr, response, protocol, true, stat)
   466  	}
   467  }
   468  
   469  // sendRefused creates and sends a REFUSED response for request to w
   470  // The returned error is logged with scopedLog and is returned for convenience
   471  func (p *DNSProxy) sendRefused(scopedLog *logrus.Entry, w dns.ResponseWriter, request *dns.Msg) (err error) {
   472  	refused := new(dns.Msg)
   473  	refused.SetRcode(request, p.rejectReply)
   474  
   475  	if err = w.WriteMsg(refused); err != nil {
   476  		scopedLog.WithError(err).Error("Cannot send REFUSED response")
   477  		err = fmt.Errorf("cannot send REFUSED response: %s", err)
   478  	}
   479  	return err
   480  }
   481  
   482  // SetRejectReply sets the default reject reply on denied dns responses.
   483  func (p *DNSProxy) SetRejectReply(opt string) {
   484  	switch strings.ToLower(opt) {
   485  	case strings.ToLower(option.FQDNProxyDenyWithNameError):
   486  		p.rejectReply = dns.RcodeNameError
   487  	case strings.ToLower(option.FQDNProxyDenyWithRefused):
   488  		p.rejectReply = dns.RcodeRefused
   489  	default:
   490  		log.Infof("DNS reject response '%s' is not valid, available options are '%v'",
   491  			opt, option.FQDNRejectOptions)
   492  		return
   493  	}
   494  }
   495  
   496  // ExtractMsgDetails extracts a canonical query name, any IPs in a response,
   497  // the lowest applicable TTL, rcode, anwer rr types and question types
   498  // When a CNAME is returned the chain is collapsed down, keeping the lowest TTL,
   499  // and CNAME targets are returned.
   500  func ExtractMsgDetails(msg *dns.Msg) (qname string, responseIPs []net.IP, TTL uint32, CNAMEs []string, rcode int, answerTypes []uint16, qTypes []uint16, err error) {
   501  	if len(msg.Question) == 0 {
   502  		return "", nil, 0, nil, 0, nil, nil, errors.New("Invalid DNS message")
   503  	}
   504  	qname = strings.ToLower(string(msg.Question[0].Name))
   505  
   506  	// rrName is the name the next RR should include.
   507  	// This will change when we see CNAMEs.
   508  	rrName := strings.ToLower(qname)
   509  
   510  	TTL = math.MaxUint32 // a TTL must exist in the RRs
   511  
   512  	answerTypes = make([]uint16, 0, len(msg.Answer))
   513  	for _, ans := range msg.Answer {
   514  		// Ensure we have records for DNS names we expect
   515  		if strings.ToLower(ans.Header().Name) != rrName {
   516  			return qname, nil, 0, nil, 0, nil, nil, fmt.Errorf("Unexpected name (%s) in RRs for %s (query for %s)", ans, rrName, qname)
   517  		}
   518  
   519  		// Handle A, AAAA and CNAME records by accumulating IPs and lowest TTL
   520  		switch ans := ans.(type) {
   521  		case *dns.A:
   522  			responseIPs = append(responseIPs, ans.A)
   523  			if TTL > ans.Hdr.Ttl {
   524  				TTL = ans.Hdr.Ttl
   525  			}
   526  		case *dns.AAAA:
   527  			responseIPs = append(responseIPs, ans.AAAA)
   528  			if TTL > ans.Hdr.Ttl {
   529  				TTL = ans.Hdr.Ttl
   530  			}
   531  		case *dns.CNAME:
   532  			// We still track the TTL because the lowest TTL in the chain
   533  			// determines the valid caching time for the whole response.
   534  			if TTL > ans.Hdr.Ttl {
   535  				TTL = ans.Hdr.Ttl
   536  			}
   537  			rrName = strings.ToLower(ans.Target)
   538  			CNAMEs = append(CNAMEs, ans.Target)
   539  		}
   540  		answerTypes = append(answerTypes, ans.Header().Rrtype)
   541  	}
   542  
   543  	qTypes = make([]uint16, 0, len(msg.Question))
   544  	for _, q := range msg.Question {
   545  		qTypes = append(qTypes, q.Qtype)
   546  	}
   547  
   548  	return qname, responseIPs, TTL, CNAMEs, msg.Rcode, answerTypes, qTypes, nil
   549  }
   550  
   551  // bindToAddr attempts to bind to address and port for both UDP and TCP. If
   552  // port is 0 a random open port is assigned and the same one is used for UDP
   553  // and TCP.
   554  // Note: This mimics what the dns package does EXCEPT for setting reuseport.
   555  // This is ok for now but it would simplify proxy management in the future to
   556  // have it set.
   557  func bindToAddr(address string, port uint16, ipv4, ipv6 bool) (*net.UDPConn, *net.TCPListener, error) {
   558  	var err error
   559  	var listener net.Listener
   560  	var conn net.PacketConn
   561  	defer func() {
   562  		if err != nil {
   563  			if listener != nil {
   564  				listener.Close()
   565  			}
   566  			if conn != nil {
   567  				conn.Close()
   568  			}
   569  		}
   570  	}()
   571  
   572  	bindAddr := net.JoinHostPort(address, strconv.Itoa(int(port)))
   573  
   574  	listener, err = listenConfig(linux_defaults.MagicMarkEgress, ipv4, ipv6).Listen(context.Background(),
   575  		"tcp", bindAddr)
   576  	if err != nil {
   577  		return nil, nil, err
   578  	}
   579  
   580  	conn, err = listenConfig(linux_defaults.MagicMarkEgress, ipv4, ipv6).ListenPacket(context.Background(),
   581  		"udp", listener.Addr().String())
   582  	if err != nil {
   583  		return nil, nil, err
   584  	}
   585  
   586  	return conn.(*net.UDPConn), listener.(*net.TCPListener), nil
   587  }