1// SPDX-License-Identifier: MIT
2
3package dnsfwd
4
5import (
6 "encoding/binary"
7 "net/netip"
8 "strings"
9)
10
11const (
12 typeA = 1
13 classIN = 1
14 // overrideTTL is short: the sandbox lives for one job.
15 overrideTTL = 30
16)
17
18// overrideName returns Override normalised for comparison: lower case, without
19// a trailing dot.
20func (f *Forwarder) overrideName() string {
21 return strings.TrimSuffix(strings.ToLower(f.Override), ".")
22}
23
24// questionName reads the name of the question that starts at q[hdrLen]. It
25// returns the lower-cased name without trailing dot and the offset just after
26// the name. Compression pointers and over-long labels are refused (a question
27// never uses them): ok is false and the query is forwarded as usual.
28func questionName(q []byte) (name string, end int, ok bool) {
29 var sb strings.Builder
30 i := hdrLen
31 for {
32 if i >= len(q) {
33 return "", 0, false
34 }
35 n := int(q[i])
36 i++
37 if n == 0 {
38 return sb.String(), i, true
39 }
40 if n&0xC0 != 0 || i+n > len(q) {
41 return "", 0, false
42 }
43 if sb.Len() > 0 {
44 sb.WriteByte('.')
45 }
46 sb.WriteString(strings.ToLower(string(q[i : i+n])))
47 i += n
48 if sb.Len() > 253 {
49 return "", 0, false
50 }
51 }
52}
53
54// overrideAnswer builds the local answer to q when q asks for the overridden
55// name in class IN, else nil. A gets addr; every other type gets NOERROR with
56// no data (so AAAA is an empty answer: sandboxes have no IPv6, and an upstream
57// NXDOMAIN or CNAME for the name must not leak in).
58func (f *Forwarder) overrideAnswer(addr netip.Addr, q []byte) []byte {
59 want := f.overrideName()
60 if want == "" || len(q) < hdrLen {
61 return nil
62 }
63 // A plain query: QR=0, opcode 0, one question, nothing else but additional
64 // records (EDNS).
65 if q[2]&0xF8 != 0 || binary.BigEndian.Uint16(q[4:]) != 1 ||
66 binary.BigEndian.Uint16(q[6:]) != 0 || binary.BigEndian.Uint16(q[8:]) != 0 {
67 return nil
68 }
69 name, end, ok := questionName(q)
70 if !ok || name != want || end+4 > len(q) {
71 return nil
72 }
73 qtype := binary.BigEndian.Uint16(q[end:])
74 if binary.BigEndian.Uint16(q[end+2:]) != classIN {
75 return nil
76 }
77 end += 4
78 withA := qtype == typeA && addr.Is4()
79
80 r := make([]byte, 0, end+16+11)
81 r = append(r, q[0], q[1])
82 r = append(r, 0x80|(q[2]&0x01), 0x80) // QR, RD copied; RA; NOERROR
83 r = append(r, 0, 1) // QDCOUNT
84 if withA {
85 r = append(r, 0, 1)
86 } else {
87 r = append(r, 0, 0)
88 }
89 r = append(r, 0, 0) // NSCOUNT
90 // An EDNS query gets an OPT record back (RFC 6891), without options.
91 edns := binary.BigEndian.Uint16(q[10:]) >= 1 && len(q) >= end+11 &&
92 q[end] == 0 && binary.BigEndian.Uint16(q[end+1:]) == 41
93 if edns {
94 r = append(r, 0, 1)
95 } else {
96 r = append(r, 0, 0)
97 }
98 r = append(r, q[hdrLen:end]...)
99 if withA {
100 r = append(r, 0xC0, hdrLen, 0, typeA, 0, classIN)
101 r = binary.BigEndian.AppendUint32(r, overrideTTL)
102 a := addr.As4()
103 r = append(r, 0, 4, a[0], a[1], a[2], a[3])
104 }
105 if edns {
106 r = append(r, 0, 0, 41, 0x10, 0, 0, 0, 0, 0, 0, 0) // root, OPT, 4096 bytes, no options
107 }
108 return r
109}