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 }