dns: add code for snooping dns packets
[project/qosify.git] / dns.c
1 #include <netinet/if_ether.h>
2 #include <netinet/in.h>
3 #include <netinet/ip.h>
4 #include <netinet/ip6.h>
5 #include <netinet/udp.h>
6 #include <netpacket/packet.h>
7 #include <net/if.h>
8 #include <sys/socket.h>
9 #include <sys/types.h>
10 #include <errno.h>
11 #include <resolv.h>
12
13 #include <libubox/uloop.h>
14 #include <libubox/avl-cmp.h>
15
16 #define FLAG_RESPONSE 0x8000
17 #define FLAG_OPCODE 0x7800
18 #define FLAG_AUTHORATIVE 0x0400
19 #define FLAG_RCODE 0x000f
20
21 #define TYPE_A 0x0001
22 #define TYPE_CNAME 0x0005
23 #define TYPE_PTR 0x000c
24 #define TYPE_TXT 0x0010
25 #define TYPE_AAAA 0x001c
26 #define TYPE_SRV 0x0021
27 #define TYPE_ANY 0x00ff
28
29 #define IS_COMPRESSED(x) ((x & 0xc0) == 0xc0)
30
31 #define CLASS_FLUSH 0x8000
32 #define CLASS_UNICAST 0x8000
33 #define CLASS_IN 0x0001
34
35 #define MAX_NAME_LEN 256
36 #define MAX_DATA_LEN 8096
37
38 #include "qosify.h"
39
40 static struct uloop_fd ufd;
41 static struct uloop_timeout cname_gc_timer;
42 static AVL_TREE(cname_cache, avl_strcmp, false, NULL);
43
44 struct vlan_hdr {
45 uint16_t tci;
46 uint16_t proto;
47 };
48
49 struct packet {
50 void *buffer;
51 unsigned int len;
52 };
53
54 struct dns_header {
55 uint16_t id;
56 uint16_t flags;
57 uint16_t questions;
58 uint16_t answers;
59 uint16_t authority;
60 uint16_t additional;
61 } __packed;
62
63 struct dns_question {
64 uint16_t type;
65 uint16_t class;
66 } __packed;
67
68 struct dns_answer {
69 uint16_t type;
70 uint16_t class;
71 uint32_t ttl;
72 uint16_t rdlength;
73 } __packed;
74
75 struct cname_entry {
76 struct avl_node node;
77 uint8_t dscp;
78 uint8_t age;
79 };
80
81 static void *pkt_peek(struct packet *pkt, unsigned int len)
82 {
83 if (len > pkt->len)
84 return NULL;
85
86 return pkt->buffer;
87 }
88
89
90 static void *pkt_pull(struct packet *pkt, unsigned int len)
91 {
92 void *ret = pkt_peek(pkt, len);
93
94 if (!ret)
95 return NULL;
96
97 pkt->buffer += len;
98 pkt->len -= len;
99
100 return ret;
101 }
102
103 static int pkt_pull_name(struct packet *pkt, const void *hdr, char *dest)
104 {
105 int len;
106
107 if (dest)
108 len = dn_expand(hdr, pkt->buffer + pkt->len, pkt->buffer,
109 (void *)dest, MAX_NAME_LEN);
110 else
111 len = dn_skipname(pkt->buffer, pkt->buffer + pkt->len - 1);
112
113 if (len < 0 || !pkt_pull(pkt, len))
114 return -1;
115
116 return 0;
117 }
118
119 static bool
120 proto_is_vlan(uint16_t proto)
121 {
122 return proto == ETH_P_8021Q || proto == ETH_P_8021AD;
123 }
124
125 static void
126 cname_cache_set(const char *name, uint8_t dscp)
127 {
128 struct cname_entry *e;
129
130 e = avl_find_element(&cname_cache, name, e, node);
131 if (!e) {
132 char *name_buf;
133
134 e = calloc_a(sizeof(*e), &name_buf, strlen(name) + 1);
135 e->node.key = strcpy(name_buf, name);
136 avl_insert(&cname_cache, &e->node);
137 }
138
139 e->age = 0;
140 e->dscp = dscp;
141 }
142
143 static int
144 cname_cache_get(const char *name, uint8_t *dscp)
145 {
146 struct cname_entry *e;
147
148 e = avl_find_element(&cname_cache, name, e, node);
149 if (!e)
150 return -1;
151
152 *dscp = e->dscp;
153 return 0;
154 }
155
156 static int
157 dns_parse_question(struct packet *pkt, const void *hdr, uint8_t *dscp)
158 {
159 char qname[MAX_NAME_LEN];
160
161 if (pkt_pull_name(pkt, hdr, qname) ||
162 !pkt_pull(pkt, sizeof(struct dns_question)))
163 return -1;
164
165 cname_cache_get(qname, dscp);
166 qosify_map_lookup_dns_entry(qname, dscp);
167
168 return 0;
169 }
170
171 static int
172 dns_parse_answer(struct packet *pkt, void *hdr, uint8_t *dscp)
173 {
174 struct qosify_map_data data = {};
175 char cname[MAX_NAME_LEN];
176 struct dns_answer *a;
177 int prev_timeout;
178 void *rdata;
179 int len;
180
181 if (pkt_pull_name(pkt, hdr, NULL))
182 return -1;
183
184 a = pkt_pull(pkt, sizeof(*a));
185 if (!a)
186 return -1;
187
188 len = be16_to_cpu(a->rdlength);
189 rdata = pkt_pull(pkt, len);
190 if (!rdata)
191 return -1;
192
193 switch (be16_to_cpu(a->type)) {
194 case TYPE_CNAME:
195 if (dn_expand(hdr, pkt->buffer + pkt->len, rdata,
196 cname, sizeof(cname)) < 0)
197 return -1;
198
199 qosify_map_lookup_dns_entry(cname, dscp);
200 cname_cache_set(cname, *dscp);
201
202 return 0;
203 case TYPE_A:
204 data.id = CL_MAP_IPV4_ADDR;
205 memcpy(&data.addr, rdata, 4);
206 break;
207 case TYPE_AAAA:
208 data.id = CL_MAP_IPV6_ADDR;
209 memcpy(&data.addr, rdata, 16);
210 break;
211 default:
212 return 0;
213 }
214
215 data.user = true;
216 data.dscp = *dscp;
217
218 prev_timeout = qosify_map_timeout;
219 qosify_map_timeout = be32_to_cpu(a->ttl);
220 __qosify_map_set_entry(&data);
221 qosify_map_timeout = prev_timeout;
222
223 return 0;
224 }
225
226 static void
227 qosify_dns_data_cb(struct packet *pkt)
228 {
229 struct dns_header *h;
230 uint8_t dscp = 0xff;
231 int i;
232
233 h = pkt_pull(pkt, sizeof(*h));
234 if (!h)
235 return;
236
237 if ((h->flags & cpu_to_be16(FLAG_RESPONSE | FLAG_OPCODE | FLAG_RCODE)) !=
238 cpu_to_be16(FLAG_RESPONSE))
239 return;
240
241 if (h->questions != cpu_to_be16(1))
242 return;
243
244 if (dns_parse_question(pkt, h, &dscp))
245 return;
246
247 for (i = 0; i < be16_to_cpu(h->answers); i++)
248 if (dns_parse_answer(pkt, h, &dscp))
249 return;
250 }
251
252 static void
253 qosify_dns_packet_cb(struct packet *pkt)
254 {
255 struct ethhdr *eth;
256 struct ip6_hdr *ip6;
257 struct ip *ip;
258 uint16_t proto;
259
260 eth = pkt_pull(pkt, sizeof(*eth));
261 if (!eth)
262 return;
263
264 proto = be16_to_cpu(eth->h_proto);
265 if (proto_is_vlan(proto)) {
266 struct vlan_hdr *vlan;
267
268 vlan = pkt_pull(pkt, sizeof(*vlan));
269 if (!vlan)
270 return;
271
272 proto = vlan->proto;
273 }
274
275 switch (proto) {
276 case ETH_P_IP:
277 ip = pkt_peek(pkt, sizeof(struct ip));
278 if (!ip)
279 return;
280
281 if (!pkt_pull(pkt, ip->ip_hl * 4))
282 return;
283
284 proto = ip->ip_p;
285 break;
286 case ETH_P_IPV6:
287 ip6 = pkt_pull(pkt, sizeof(*ip6));
288 if (!ip6)
289 return;
290
291 proto = ip6->ip6_nxt;
292 break;
293 default:
294 return;
295 }
296
297 if (proto != IPPROTO_UDP)
298 return;
299
300 if (!pkt_pull(pkt, sizeof(struct udphdr)))
301 return;
302
303 qosify_dns_data_cb(pkt);
304 }
305
306 static void
307 qosify_dns_socket_cb(struct uloop_fd *fd, unsigned int events)
308 {
309 static uint8_t buf[8192];
310 struct packet pkt = {
311 .buffer = buf,
312 };
313 int len;
314
315 retry:
316 len = recvfrom(fd->fd, buf, sizeof(buf), MSG_DONTWAIT, NULL, NULL);
317 if (len < 0) {
318 if (errno == EINTR)
319 goto retry;
320 return;
321 }
322
323 if (!len)
324 return;
325
326 pkt.len = len;
327 qosify_dns_packet_cb(&pkt);
328 }
329
330 static void
331 qosify_cname_cache_gc(struct uloop_timeout *timeout)
332 {
333 struct cname_entry *e, *tmp;
334
335 avl_for_each_element_safe(&cname_cache, e, node, tmp) {
336 if (e->age++ < 5)
337 continue;
338
339 avl_delete(&cname_cache, &e->node);
340 free(e);
341 }
342
343 uloop_timeout_set(timeout, 1000);
344 }
345
346 static int
347 qosify_open_dns_socket(void)
348 {
349 struct sockaddr_ll sll = {
350 .sll_family = AF_PACKET,
351 .sll_protocol = htons(ETH_P_ALL),
352 };
353 int sock;
354
355 sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
356 if (sock == -1) {
357 ULOG_ERR("failed to create raw socket: %s\n", strerror(errno));
358 return -1;
359 }
360
361 sll.sll_ifindex = if_nametoindex(QOSIFY_DNS_IFNAME);
362 if (bind(sock, (struct sockaddr *)&sll, sizeof(sll))) {
363 ULOG_ERR("failed to bind socket to "QOSIFY_DNS_IFNAME": %s\n",
364 strerror(errno));
365 goto error;
366 }
367
368 ufd.fd = sock;
369 ufd.cb = qosify_dns_socket_cb;
370 uloop_fd_add(&ufd, ULOOP_READ);
371
372 return 0;
373
374 error:
375 close(sock);
376 return -1;
377 }
378
379 static void
380 qosify_dns_del_ifb(void)
381 {
382 qosify_run_cmd("ip link del ifb-dns type ifb", true);
383 }
384
385 int qosify_dns_init(void)
386 {
387 cname_gc_timer.cb = qosify_cname_cache_gc;
388 qosify_cname_cache_gc(&cname_gc_timer);
389
390 qosify_dns_del_ifb();
391
392 if (qosify_run_cmd("ip link add ifb-dns type ifb", false) ||
393 qosify_run_cmd("ip link set dev ifb-dns up", false) ||
394 qosify_open_dns_socket())
395 return -1;
396
397 return 0;
398 }
399
400 void qosify_dns_stop(void)
401 {
402 struct cname_entry *e, *tmp;
403
404 if (ufd.registered) {
405 uloop_fd_delete(&ufd);
406 close(ufd.fd);
407 }
408
409 qosify_dns_del_ifb();
410
411 avl_remove_all_elements(&cname_cache, e, node, tmp)
412 free(e);
413 }
414