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
24
25
26 func extractName(domainName string) (nodeName string, daemonset string, namespace string, err error) {
27 nameParts := dns.SplitDomainName(domainName)
28 labelCount := len(nameParts)
29
30 if labelCount < 4 {
31 return "", "", "", errDNSLen
32 }
33
34
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
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
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
119 if err := w.WriteMsg(m); err != nil {
120 log.Error(err, "Failed to write response")
121 }
122 }
123 }
124
125
126
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