...

Source file src/edge-infra.dev/pkg/sds/k8s/daemonsetdns/daemonsetdns.go

Documentation: edge-infra.dev/pkg/sds/k8s/daemonsetdns

     1  package daemonsetdns
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"strings"
     7  	"time"
     8  
     9  	guuid "github.com/google/uuid"
    10  	"github.com/miekg/dns"
    11  
    12  	"edge-infra.dev/pkg/lib/fog"
    13  	"edge-infra.dev/pkg/sds/k8s/iplookup"
    14  )
    15  
    16  const dnsTimeout = 30 * time.Second
    17  
    18  var (
    19  	errDNSLen  = errors.New("fewer DNS labels than expected")
    20  	errDNSName = errors.New("unexpected DNS name format")
    21  )
    22  
    23  // Extracts the node name, daemonset name and namespace of a given namespace
    24  // from the DNS request following the <nodeName>.<daemonsetName>.<Namespace>.pod-locator.
    25  // format. domainName must be a syntactically valid domain name.
    26  func extractName(domainName string) (nodeName string, daemonset string, namespace string, err error) {
    27  	nameParts := dns.SplitDomainName(domainName)
    28  	labelCount := len(nameParts)
    29  	// DNS request must have at least 4 labels. The node name can optionally be more than one label
    30  	if labelCount < 4 {
    31  		return "", "", "", errDNSLen
    32  	}
    33  
    34  	// top-level domain for the daemonset dns should always be pod-locator
    35  	if nameParts[labelCount-1] != "pod-locator" {
    36  		return "", "", "", errDNSName
    37  	}
    38  
    39  	namespace = nameParts[labelCount-2]
    40  	daemonset = nameParts[labelCount-3]
    41  	nodeNameParts := nameParts[:(labelCount - 3)]
    42  	nodeName = strings.Join(nodeNameParts, ".")
    43  
    44  	return nodeName, daemonset, namespace, nil
    45  }
    46  
    47  func handleResponse(ipclient *iplookup.IPLookup) func(w dns.ResponseWriter, r *dns.Msg) {
    48  	return func(w dns.ResponseWriter, r *dns.Msg) {
    49  		ctx, cancelFunc := context.WithTimeout(context.Background(), dnsTimeout)
    50  		defer cancelFunc()
    51  
    52  		log := fog.New().
    53  			WithName("daemonsetdns").
    54  			WithName("resolver").
    55  			WithValues(fog.OperationFields(guuid.New().String())...)
    56  
    57  		ctx = fog.IntoContext(ctx, log)
    58  
    59  		var rr dns.RR
    60  
    61  		m := new(dns.Msg)
    62  		m.SetReply(r)
    63  
    64  		dnsName := r.Question[0].Name
    65  
    66  		log.Info("Incoming DNS request", "Name", dnsName, "type", r.Question[0].Qtype)
    67  
    68  		nodeName, daemonset, namespace, err := extractName(dnsName)
    69  		if err != nil {
    70  			log.Error(err, "Unable to extract daemonset properties from dns request")
    71  			if err := w.WriteMsg(m); err != nil {
    72  				log.Error(err, "Failed to write response")
    73  			}
    74  			return
    75  		}
    76  
    77  		query := iplookup.Query{
    78  			Namespace: namespace,
    79  			Daemonset: daemonset,
    80  		}
    81  
    82  		log.Info("Discovering IP address", "Node Name", nodeName, "Namespace", namespace, "Daemonset", daemonset)
    83  
    84  		ip, err := ipclient.GetDaemonsetPodIP(ctx, nodeName, query)
    85  		if err != nil {
    86  			log.Error(err, "Error occurred when discovering IP")
    87  			if err := w.WriteMsg(m); err != nil {
    88  				log.Error(err, "Failed to write response")
    89  			}
    90  			return
    91  		}
    92  
    93  		// Create DNS answer section
    94  		if ip.To4() != nil {
    95  			rr = &dns.A{
    96  				Hdr: dns.RR_Header{Name: dnsName, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0},
    97  				A:   ip,
    98  			}
    99  		} else {
   100  			rr = &dns.AAAA{
   101  				Hdr:  dns.RR_Header{Name: dnsName, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0},
   102  				AAAA: ip,
   103  			}
   104  		}
   105  
   106  		// Apply DNS answer section when question and answer record types match
   107  		switch r.Question[0].Qtype {
   108  		case dns.TypeA:
   109  			if ip.To4() == nil {
   110  				log.Error(nil, "Record type and returned ip version do not match")
   111  				break
   112  			}
   113  			m.Answer = append(m.Answer, rr)
   114  		default:
   115  			log.Info("Unsupported request type")
   116  		}
   117  
   118  		// Write the DNS response
   119  		if err := w.WriteMsg(m); err != nil {
   120  			log.Error(err, "Failed to write response")
   121  		}
   122  	}
   123  }
   124  
   125  // Return a dns.Server for a given net protocol which can be used to find the
   126  // ipv4 address of a pod in a daemonset
   127  func Dnsserver(net string, ipclient *iplookup.IPLookup) *dns.Server {
   128  	log := fog.New().WithName("daemonsetdns").WithName("serve")
   129  	log.Info("Starting server", "protocol", net)
   130  	dns.HandleFunc("pod-locator.", handleResponse(ipclient))
   131  
   132  	server := &dns.Server{
   133  		Addr: "[::]:5353", Net: net, TsigSecret: nil, ReusePort: false,
   134  	}
   135  	return server
   136  }
   137  

View as plain text