1package query
2
3import (
4	"fmt"
5	"net"
6	"strconv"
7
8	"github.com/nextdns/nextdns/arp"
9	"github.com/nextdns/nextdns/internal/dnsmessage"
10	"github.com/nextdns/nextdns/ndp"
11)
12
13type Query struct {
14	ID               uint16
15	Class            Class
16	Type             Type
17	RecursionDesired bool
18	MsgSize          uint16
19	Name             string
20	PeerIP           net.IP
21	MAC              net.HardwareAddr
22	Payload          []byte
23}
24
25type Class uint16
26
27const (
28	// ResourceHeader.Class and Question.Class
29	ClassINET   Class = 1
30	ClassCSNET  Class = 2
31	ClassCHAOS  Class = 3
32	ClassHESIOD Class = 4
33
34	// Question.Class
35	ClassANY Class = 255
36)
37
38var classNames = map[Class]string{
39	ClassINET:   "INET",
40	ClassCSNET:  "CSNET",
41	ClassCHAOS:  "CHAOS",
42	ClassHESIOD: "HESIOD",
43	ClassANY:    "ANY",
44}
45
46func (c Class) String() string {
47	s, found := classNames[c]
48	if !found {
49		s = strconv.FormatInt(int64(c), 10)
50	}
51	return s
52}
53
54type Type uint16
55
56const (
57	// ResourceHeader.Type and Question.Type
58	TypeA     Type = 1
59	TypeNS    Type = 2
60	TypeCNAME Type = 5
61	TypeSOA   Type = 6
62	TypePTR   Type = 12
63	TypeMX    Type = 15
64	TypeTXT   Type = 16
65	TypeAAAA  Type = 28
66	TypeSRV   Type = 33
67	TypeOPT   Type = 41
68
69	// Question.Type
70	TypeWKS   Type = 11
71	TypeHINFO Type = 13
72	TypeMINFO Type = 14
73	TypeAXFR  Type = 252
74	TypeALL   Type = 255
75)
76
77var typeNames = map[Type]string{
78	TypeA:     "A",
79	TypeNS:    "NS",
80	TypeCNAME: "CNAME",
81	TypeSOA:   "SOA",
82	TypePTR:   "PTR",
83	TypeMX:    "MX",
84	TypeTXT:   "TXT",
85	TypeAAAA:  "AAAA",
86	TypeSRV:   "SRV",
87	TypeOPT:   "OPT",
88	TypeWKS:   "WKS",
89	TypeHINFO: "HINFO",
90	TypeMINFO: "MINFO",
91	TypeAXFR:  "AXFR",
92	TypeALL:   "ALL",
93}
94
95func (t Type) String() string {
96	s, found := typeNames[t]
97	if !found {
98		s = strconv.FormatInt(int64(t), 10)
99	}
100	return s
101}
102
103const (
104	EDNS0_SUBNET = 0x8
105	EDNS0_MAC    = 0xfde9 // as defined by dnsmasq --add-mac feature
106)
107
108const maxDNSSize = 512
109
110// New lasily parses payload and extract the queried name, ip/MAC if
111// present in the query as EDNS0 extension. ARP queries are performed to find
112// MAC or IP depending on which one is present or not in the query.
113func New(payload []byte, peerIP net.IP) (Query, error) {
114	q := Query{
115		PeerIP:  peerIP,
116		MsgSize: maxDNSSize,
117		Payload: payload,
118	}
119
120	if !peerIP.IsLoopback() {
121		if peerIP.To4() != nil {
122			q.MAC = arp.SearchMAC(peerIP)
123		} else {
124			q.MAC = ndp.SearchMAC(peerIP)
125		}
126
127	}
128
129	if err := q.parse(); err != nil {
130		return q, err
131	}
132
133	if q.PeerIP.IsLoopback() && q.MAC != nil {
134		// MAC was sent in the request with a localhost client, it means we have
135		// a proxy like dnsmasq in front of us, not able to send the client IP
136		// using ECS. Let's search the IP in the arp and/or ndp tables.
137		if ip := arp.SearchIP(q.MAC); ip != nil {
138			q.PeerIP = ip
139		} else if ip := ndp.SearchIP(q.MAC); ip != nil {
140			q.PeerIP = ip
141		}
142	}
143
144	return q, nil
145}
146
147func (qry *Query) parse() error {
148	p := &dnsmessage.Parser{}
149	h, err := p.Start(qry.Payload)
150	if err != nil {
151		return fmt.Errorf("parse query: %v", err)
152	}
153
154	q, err := p.Question()
155	if err != nil {
156		return fmt.Errorf("parse question: %v", err)
157	}
158	qry.ID = h.ID
159	qry.RecursionDesired = h.RecursionDesired
160	qry.Class = Class(q.Class)
161	qry.Type = Type(q.Type)
162	qry.Name = q.Name.String()
163	_ = p.SkipAllQuestions()
164	_ = p.SkipAllAnswers()
165	_ = p.SkipAllAuthorities()
166	for {
167		h, err := p.AdditionalHeader()
168		if err != nil {
169			if err == dnsmessage.ErrSectionDone {
170				break
171			}
172			return fmt.Errorf("parse additional: %v", err)
173		}
174		if h.Type == dnsmessage.TypeOPT {
175			opt, err := p.OPTResource()
176			if err != nil {
177				return fmt.Errorf("parse OPT: %v", err)
178			}
179			qry.MsgSize = uint16(h.Class)
180			for _, o := range opt.Options {
181				switch o.Code {
182				case EDNS0_MAC:
183					qry.MAC = net.HardwareAddr(o.Data)
184				case EDNS0_SUBNET:
185					if len(o.Data) < 8 {
186						continue
187					}
188					switch o.Data[1] {
189					case 0x1: // IPv4
190						if o.Data[2] != 32 {
191							// Only consider full IPs
192							continue
193						}
194						qry.PeerIP = net.IP(o.Data[4:8])
195					case 0x2: // IPv6
196						if len(o.Data) < 20 {
197							continue
198						}
199						if o.Data[2] != 128 {
200							// Only consider full IPs
201							continue
202						}
203						qry.PeerIP = net.IP(o.Data[4:20])
204					}
205				}
206			}
207			break
208		}
209	}
210
211	return nil
212}
213