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