-
Notifications
You must be signed in to change notification settings - Fork 13
/
dnsflow.c
1527 lines (1344 loc) · 41.3 KB
/
dnsflow.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* dnsflow.c
*
* Copyright (c) 2011, DeepField Networks, Inc. <info@deepfield.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of DeepField Networks, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
/* DNS Flow Packet Format
Header:
version [1 bytes]
sets_count [1 bytes]
flags [2 bytes]
sequence_number [4 bytes]
sets [variable]
Data Set:
ip_vers [1 byte]
client_ip [4 bytes]/[16 bytes]
resolver_ip [4 bytes]/[16 bytes]
names_count [1 byte]
ips_count [1 byte]
ip6s_count [1 byte]
names_len [2 bytes]
names [variable] Each is a Nul terminated string.
ips [variable] Word-aligned, starts at names + names_len, each is 4 bytes
ip6s [variable] Word-aligned, starts at names + names_len, each is 16 bytes
Stats Set:
pkts_captured [4 bytes]
pkts_received [4 bytes]
pkts_dropped [4 bytes]
pkts_ifdropped [4 bytes] Only supported on some platforms.
sample_rate [4 bytes]
*/
#include <sys/file.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/sysinfo.h>
#if __linux__
#include <sys/prctl.h>
#endif
#include <inttypes.h>
#include <errno.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <err.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <pcap/pcap.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <ldns/ldns.h>
#include <event.h>
#include "dcap.h"
/* Define a MAX/MIN macros, if we don't already have then. */
#ifndef MAX
#define MAX(a, b) ((a) < (b) ? (b) : (a))
#endif
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#define DNSFLOW_FILTER_MAX_SRC_PORTS 10
#define DNSFLOW_PORT_SLOT_UNUSED -1
static int dnsflow_filter_src_ports[DNSFLOW_FILTER_MAX_SRC_PORTS];
static int dnsflow_port_override_used = 0;
#define DNSFLOW_MAX_PARSE 255
#define DNSFLOW_PKT_BUF_SIZE 65535
#define DNSFLOW_PKT_MAX_SIZE 1500
#define DNSFLOW_PKT_TARGET_SIZE 1200
#define DNSFLOW_VERSION 4
#define DNSFLOW_PORT 5300
#define DNSFLOW_UDP_MAX_DSTS 10
#define DNSFLOW_FLAG_STATS 0x0001
#define DNSFLOW_SETS_COUNT_MAX 255
#define IP6VERSION 0x60
#define IP6VERSIONMASK 0xf0
#define IP6PACKETHDRLEN 40
struct dnsflow_hdr {
uint8_t version;
uint8_t sets_count;
uint16_t flags;
uint32_t sequence_number;
};
#define DNSFLOW_NAMES_COUNT_MAX 255
#define DNSFLOW_IPS_COUNT_MAX 255
#define DNSFLOW_IP4_SET_HDR 40
#define DNSFLOW_IP6_SET_HDR 40
struct dnsflow_set_hdr {
uint8_t ip_vers;
uint8_t names_count;
uint8_t ips_count;
uint8_t ip6s_count;
uint16_t names_len;
union {
struct in_addr client_ip4;
struct in6_addr client_ip6;
} client_ip;
union {
struct in_addr resolver_ip4;
struct in6_addr resolver_ip6;
} resolver_ip;
};
struct dns_data_set {
uint8_t *names[DNSFLOW_MAX_PARSE];
int name_lens[DNSFLOW_MAX_PARSE];
int num_names;
struct in_addr ips[DNSFLOW_MAX_PARSE];
struct in6_addr ip6s[DNSFLOW_MAX_PARSE];
int num_ips;
int num_ip6s;
};
struct dnsflow_data_pkt {
/* Variable sized pkt, allocate maximum size when it's a data pkt. */
char pkt[1]; /* DNSFLOW_PKT_BUF_SIZE */
};
struct dnsflow_stats_pkt {
uint32_t pkts_captured;
uint32_t pkts_received;
uint32_t pkts_dropped;
uint32_t pkts_ifdropped; /* according to pcap, only supported
on some platforms */
uint32_t sample_rate;
};
enum dnsflow_buf_type {
DNSFLOW_DATA,
DNSFLOW_STATS,
};
struct dnsflow_buf {
uint32_t db_type; /* What's in the union */
uint32_t db_len; /* Size of what's in the pkt,
db_pkt_hdr and below. */
uint32_t db_loop_hdr; /* Holds PF_ type when dumping
straight to pcap file. */
struct dnsflow_hdr db_pkt_hdr;
union {
struct dnsflow_data_pkt data_pkt;
struct dnsflow_stats_pkt stats_pkt;
} DB_dat;
};
/* pcap record headers for saved files */
struct pcap_timeval {
bpf_int32 tv_sec; /* seconds */
bpf_int32 tv_usec; /* microseconds */
};
struct pcap_sf_pkthdr {
struct pcap_timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
/* From http://www.juniper.net/techpubs/en_US/junos/topics/concept/subscriber-management-subscriber-secure-policy-radius-header.html
* Note: looks like juniper has another slightly different mirror format:
* https://www.juniper.net/techpubs/en_US/junos/topics/concept/subscriber-management-subscriber-secure-policy-dtcp-header.html. Not sure when that would be
* used. */
struct jmirror_hdr {
uint32_t intercept_id;
uint32_t session_id;
};
#define db_data_pkt DB_dat.data_pkt
#define db_stats_pkt DB_dat.stats_pkt
/*** Globals ***/
/* pkt building */
static uint32_t sequence_number = 1;
static struct dnsflow_buf *data_buf = NULL;
static time_t last_send = 0;
static struct event push_ev;
static struct timeval push_tv = {1, 0};
static struct event stats_ev;
static struct timeval stats_tv = {10, 0};
#if !__linux__
static struct event check_parent_ev;
static struct timeval check_parent_tv = {1, 0};
#endif
static struct event sigterm_ev, sigint_ev, sigchld_ev;
/* config */
/* pcap-record dest port (*network* byte order) */
static int pcap_record_dst_port_enabled = 0;
static uint16_t pcap_record_dst_port = 0;
/* jmirror dest port (*network* byte order) - typically 30030 */
static int jmirror_dst_port_enabled = 0;
static uint16_t jmirror_dst_port = 0;
static int udp_num_dsts = 0;
static struct sockaddr_in dst_so_addrs[DNSFLOW_UDP_MAX_DSTS];
static pcap_t *pc_dump = NULL;
static pcap_dumper_t *pdump = NULL;
#define MAX_MPROC_CHILDREN 64
static pid_t mproc_children[MAX_MPROC_CHILDREN];
static int n_mproc_children = 0;
static pid_t my_pid;
static void
_log(const char *format, ...)
{
char buf[1024];
va_list args;
va_start(args, format);
vsnprintf(buf, sizeof(buf), format, args);
va_end(args);
fprintf(stderr, "[%d]: %s\n", my_pid, buf);
}
/* Add up to 1 sec of jitter. */
static struct timeval *
jitter_tv(struct timeval *tv)
{
tv->tv_usec = random() % 1000000;
return (tv);
}
static void
dnsflow_print_stats(struct dcap_stat *ds)
{
_log("%u packets captured", ds->captured);
if (ds->ps_valid) {
_log("%u packets received by filter", ds->ps_recv);
_log("%u packets dropped by kernel", ds->ps_drop);
_log("%u packets dropped by interface", ds->ps_ifdrop);
}
}
static void
clean_exit(struct dcap *dcap)
{
struct dcap_stat *ds;
int i;
if (n_mproc_children != 0) {
/* Tell children to exit. */
for (i = 0; i < n_mproc_children; i++) {
kill(mproc_children[i], SIGTERM);
}
}
_log("Shutting down.");
ds = dcap_get_stats(dcap);
dnsflow_print_stats(ds);
if (pdump != NULL) {
pcap_dump_close(pdump);
pcap_close(pc_dump);
}
exit(0);
}
#if !__linux__
static void
check_parent_cb(int fd, short event, void *arg)
{
struct dcap *dcap = (struct dcap *)arg;
if (getppid() == 1) {
/* orphaned */
_log("parent exited");
clean_exit(dcap);
}
evtimer_add(&check_parent_ev, &check_parent_tv);
}
#endif
/* When running in multi-proc mode, if the parent dies, want to make sure the
* children exit. */
static void
check_parent_setup(struct dcap *dcap)
{
#if __linux__
/* Linux provides a more efficient way to check for parent exit. */
if (prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) {
errx(1, "prctl failed");
}
#else
bzero(&check_parent_ev, sizeof(check_parent_ev));
evtimer_set(&check_parent_ev, check_parent_cb, dcap);
evtimer_add(&check_parent_ev, &check_parent_tv);
#endif
}
/* Returns the proc number for this process. The parent is always 1. */
static int
mproc_fork(int num_procs)
{
int proc_i;
pid_t pid;
if ((num_procs < 1) || (num_procs > MAX_MPROC_CHILDREN)) {
errx(1, "num_procs (%d) is not in range [1, %d]. "
"Recompile dnsflow.c with a larger value for "
"MAX_MPROC_CHILDREN to utilize more processes.",
num_procs, MAX_MPROC_CHILDREN);
}
if (num_procs > get_nprocs()) {
errx(1, "num_procs (%d) is more than "
"the number of available processors (%d). "
"This degrades performance, so it is not allowed.",
num_procs, get_nprocs());
}
/* proc_i is 1-based. 1 is parent; start at 2. */
for (proc_i = 2; proc_i <= num_procs; proc_i++) {
if((pid = fork()) < 0) {
errx(1, "fork error");
} else if (pid == 0) {
/* child */
n_mproc_children = 0;
return (proc_i);
} else {
/* parent */
/* XXX Use process group instead? */
mproc_children[n_mproc_children++] = pid;
}
}
/* parent gets slot 1. */
return (1);
}
/* encap_offset is the number of bytes between the end of the udp header
* and the start of the encapsulated ip header.
* Ie., the length of foo bar: ip udp (foo bar) ip udp dns
*
* proc_i and num_procs use 1-based numbering.
* */
static char *
build_pcap_filter(int encap_offset, int proc_i, int num_procs, int enable_mdns, int enable_ipv4_checksum_mproc_filter)
{
/* Note: according to pcap-filter(7), udp offsets only work for ipv4.
* (Would have to use ip6 offsets.) */
/* Offsets from start of udp. */
int udp_offset = 0; /* Offset from udp to encap udp. */
/* Label offsets used. */
int dns_flags_offset = 10;
/* Offsets from start of ip. */
int ip_offset = 0; /* Offset from ip to encap ip. */
int ip6_offset = 40; /* Offset of udp from ip6. */
/* Label offsets used. */
int dst_ip_offset = 16;
int checksum_udp_offset = 6;
/* Buffers to build pcap filter */
int *p_filter_port = NULL;
char port_filter[1024], *pf_cp;
char dns_resp_filter[1024];
char multi_proc_filter[1024];
/* The final filter returned in static buf. */
static char full_filter_ret[1024];
if (encap_offset != 0) {
/* udp, encap, ip, udp */
udp_offset = sizeof(struct udphdr) + encap_offset +
sizeof(struct ip);
/* ip, udp, encap, ip */
ip_offset = sizeof(struct ip) + sizeof(struct udphdr) +
encap_offset;
}
/* Port filter - Match src port 53 (and optionally 5353). */
if (dnsflow_port_override_used) {
pf_cp = port_filter;
p_filter_port = dnsflow_filter_src_ports;
while (p_filter_port != dnsflow_filter_src_ports + DNSFLOW_FILTER_MAX_SRC_PORTS) {
if (*p_filter_port != DNSFLOW_PORT_SLOT_UNUSED) {
if (p_filter_port == dnsflow_filter_src_ports) {
pf_cp += sprintf(pf_cp, "(src port %d", *p_filter_port);
} else {
pf_cp += sprintf(pf_cp, " or src port %d", *p_filter_port);
}
// handle edge case with -Y command-line option
if (*p_filter_port == 5353) {
enable_mdns = 0;
}
}
++p_filter_port;
}
// if this is still true, it has not already been added above
if (enable_mdns) {
pf_cp += sprintf(pf_cp, " or src port 5353");
}
*(pf_cp++) = ')';
*pf_cp = '\0';
} else if (enable_mdns) {
snprintf(port_filter, sizeof(port_filter),
"(src port 53 or src port 5353)");
} else {
snprintf(port_filter, sizeof(port_filter),
"src port 53");
}
/* Base dns filter - combine port and flag filters. */
/* Match valid recursive response flags.
* qr=1, rd=1, ra=1, rcode=0.
* XXX Could also pull out just A/AAAA. */
snprintf(dns_resp_filter, sizeof(dns_resp_filter),
"(udp and %s and (ip6 or (ip and udp[%d:1] & 0x80 = 0x80)))", port_filter, dns_flags_offset + udp_offset);
if (num_procs > 1) {
char *cp = NULL;
/* Multi-process filter:
* - For ip6, select based on modulo of udp checksum. Note that
* the 'udp' shorthand does not work in the ip6 context for the
* pcap filter language, so the checksum is found by offsetting
* appropriately from the ip6 accessor.
* - For ip4, since the udp checksum is optional (0 when not computed),
* the default filter selects based on modulo of client ip,
* which is the dst ip for dns responses. It does so using offset
* from ip, so ip options will break view into encap. Using the client
* ip as the load balance key may be useful to keep each client
* in same stream. Optionally, one can activate an ip4 filter
* that selects based on module of udp checksum when it is available,
* falling back to default ip4 filter when it is not.
*/
/* Note about performance:
* - We avoid explicit use of the % operator, and instead
* express (x % y) as (x - x/y*y) for integers x, y. According
* to the pcap-filter(7) man page, the % and ^ operators
* have limited support in some kernels (older or non-linux),
* which can have severe performance impacts.
*/
if (!enable_ipv4_checksum_mproc_filter) {
snprintf(multi_proc_filter, sizeof(multi_proc_filter),
"(ip and (%s) and ((ip[%d:4] - ip[%d:4]/%u*%u) = %u)) or ",
dns_resp_filter, dst_ip_offset + ip_offset,
dst_ip_offset + ip_offset, num_procs, num_procs, proc_i - 1);
cp = multi_proc_filter;
cp += strlen(multi_proc_filter);
} else {
snprintf(multi_proc_filter, sizeof(multi_proc_filter),
"(ip and (%s) and (((udp[%d:2] != 0) and ((udp[%d:2] - udp[%d:2]/%u*%u) = %u)) or ((udp[%d:2] = 0) and ((ip[%d:4] - ip[%d:4]/%u*%u) = %u)))) or ",
dns_resp_filter,
checksum_udp_offset, checksum_udp_offset, checksum_udp_offset, num_procs, num_procs, proc_i - 1,
checksum_udp_offset, dst_ip_offset + ip_offset, dst_ip_offset + ip_offset, num_procs, num_procs, proc_i - 1);
cp = multi_proc_filter;
cp += strlen(multi_proc_filter);
}
snprintf(cp, sizeof(multi_proc_filter) - (cp - multi_proc_filter),
"(ip6 and (%s) and ((ip6[%d:2] - ip6[%d:2]/%u*%u) = %u))",
dns_resp_filter, ip6_offset + checksum_udp_offset,
ip6_offset + checksum_udp_offset, num_procs, num_procs, proc_i - 1);
} else {
/* Just copy base dns filter. */
snprintf(multi_proc_filter, sizeof(multi_proc_filter), "%s", dns_resp_filter);
}
/* The final filter returned in static buf. Incorporates one level
* of vlan encapsulation. */
bzero(full_filter_ret, sizeof(full_filter_ret));
snprintf(full_filter_ret, sizeof(full_filter_ret),
"(%s) or (vlan and (%s))",
multi_proc_filter, multi_proc_filter);
return (full_filter_ret);
}
char *
ts_format(struct timeval *ts)
{
int sec, usec;
static char buf[256];
sec = ts->tv_sec % 86400;
usec = ts->tv_usec;
snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%06u",
sec / 3600, (sec % 3600) / 60, sec % 60, usec);
return buf;
}
/**
* Write and lock pid file.
*
* return 1 on success, 0 on failure.
*/
static int
write_pid_file(char *pid_file)
{
int fd;
FILE *fp;
fd = open(pid_file, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
perror(pid_file);
return 0;
}
if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
if (errno != EWOULDBLOCK) {
perror(pid_file);
}
return 0;
}
if (ftruncate(fd, 0) < 0) {
perror(pid_file);
return 0;
}
fp = fdopen(fd, "w");
fprintf(fp, "%u\n", getpid());
fflush(fp);
fsync(fileno(fp));
return 1;
}
static struct ip6_hdr *
ip6_check(int pkt_len, char *ip_pkt) {
struct ip6_hdr *ip6 = (struct ip6_hdr *)ip_pkt;
if (pkt_len < sizeof(struct ip6_hdr)) {
return (NULL);
}
if ((ip6->ip6_vfc & IP6VERSIONMASK) != IP6VERSION) {
return (NULL);
}
if (pkt_len < IP6PACKETHDRLEN) {
return (NULL);
}
if (pkt_len < ntohs(ip6->ip6_plen)) {
return (NULL);
}
return (ip6);
}
/* IP checks - version, header len, pkt len. */
static struct ip *
ip4_check(int pkt_len, char *ip_pkt) {
struct ip *ip = (struct ip *)ip_pkt;
if (pkt_len < sizeof(struct ip)) {
return (NULL);
}
if (ip->ip_v != IPVERSION) {
return (NULL);
}
if (pkt_len < (ip->ip_hl << 2)) {
return (NULL);
}
if (pkt_len < ntohs(ip->ip_len)) {
return (NULL);
}
if (ntohs(ip->ip_len) < (ip->ip_hl << 2)) {
return (NULL);
}
return (ip);
}
static struct udphdr *
udp_check(int pkt_len, struct ip *ip, struct ip6_hdr *ip6)
{
struct udphdr *udphdr;
struct ip6_ext *ip6_ext;
int ip_hdr_len;
int next_hdr;
int offset;
if (ip && ip->ip_p == IPPROTO_UDP) {
ip_hdr_len = ip->ip_hl << 2;
if (pkt_len < sizeof(struct ip) + sizeof(struct udphdr)) {
return (NULL);
}
udphdr = (struct udphdr *) (((u_char *) ip) + ip_hdr_len);
} else if (ip6) {
// Note that this loop does not provide much utility
// when the default base filter using `src port 53` is
// in use because the pcap-filter does not know to
// offset to the correct location to check for
// UDP.src_port when additional extension headers
// appear before the UDP section. Thus, we would
// likely never encounter a valid IPv6/UDP packet that
// would need to enter the while-loop
// below. Fortunately, most (if not all) IPv6/UDP/DNS
// responses that we are collecting do not have
// additional extension headers. We leave this loop in
// for exploratory analysis with custom filters.
next_hdr = ip6->ip6_nxt;
ip_hdr_len = sizeof(struct ip6_hdr);
ip6_ext = (struct ip6_ext *)((struct ip6_hdr *)(ip6 + 1));
while (next_hdr != IPPROTO_UDP) {
if (pkt_len < ip_hdr_len + sizeof(struct ip6_ext)) {
return (NULL);
}
next_hdr = ip6_ext->ip6e_nxt;
offset = (ip6_ext->ip6e_len + 1)*8;
// this may occur if we are dealing with garbage data,
// which may cause this loop to run forever
if (offset <= 0) {
return (NULL);
}
ip_hdr_len +=offset;
ip6_ext = (struct ip6_ext *)(((caddr_t)ip6_ext) + offset);
}
if (next_hdr == IPPROTO_UDP) {
if (pkt_len < ip_hdr_len + sizeof(struct udphdr)) {
return (NULL);
}
udphdr = (struct udphdr *)ip6_ext;
} else {
return (NULL);
}
} else {
return (NULL);
}
if (pkt_len < ip_hdr_len + ntohs(udphdr->uh_ulen)) {
return (NULL);
}
return (udphdr);
}
/* Sanity/buffer-len checks.
* Returns pointer to udp data, or NULL on error.
* On success, ip_ret and udphdr_ret will point to the headers. */
static char *
ip_udp_check(int pkt_len, char *ip_pkt,
struct ip **ip_ret, struct ip6_hdr **ip6_ret, struct udphdr **udphdr_ret)
{
char *udp_data = NULL;
struct ip *ip = NULL;
struct ip6_hdr *ip6 = NULL;
struct udphdr *udphdr = NULL;
*ip_ret = NULL;
*ip6_ret = NULL;
*udphdr_ret = NULL;
/* XXX Count/log number of bad pkts. */
/* XXX Need to pull in ip/udp checksumming and fragment handling. */
if (((ip = ip4_check(pkt_len, ip_pkt)) == NULL) &&
((ip6 = ip6_check(pkt_len, ip_pkt)) == NULL)) {
return NULL;
}
if ((udphdr = udp_check(pkt_len, ip, ip6)) == NULL) {
return NULL;
}
udp_data = (char *)udphdr + sizeof(struct udphdr);
*ip_ret = ip;
*ip6_ret = ip6;
*udphdr_ret = udphdr;
return (udp_data);
}
/* Handle encapsulated ip pkts.
* encap_hdr should point to the start of encapsulated pkt.
* ip_encap_offset is the number of bytes to reach the ip header.
* Returns pointer to udp data, or NULL on error.
* On success, ip_ret and udphdr_ret will point to the headers. */
static char *
ip_encap_check(int pkt_len, char *encap_hdr, int ip_encap_offset,
struct ip **ip_ret, struct ip6_hdr **ip6_ret, struct udphdr **udphdr_ret)
{
*ip_ret = NULL;
*ip6_ret = NULL;
*udphdr_ret = NULL;
if (pkt_len < ip_encap_offset) {
return (NULL);
}
return (ip_udp_check(pkt_len - ip_encap_offset,
encap_hdr + ip_encap_offset, ip_ret, ip6_ret, udphdr_ret));
}
static ldns_pkt *
dnsflow_dns_check(int pkt_len, char *dns_pkt)
{
ldns_status status;
ldns_pkt *lp;
ldns_rr *q_rr;
status = ldns_wire2pkt(&lp, (uint8_t *)dns_pkt, pkt_len);
if (status != LDNS_STATUS_OK) {
_log("Bad DNS pkt: %s", ldns_get_errorstr_by_id(status));
return (NULL);
}
/* Looking for valid recursive replies */
if (ldns_pkt_qr(lp) != 1 ||
ldns_pkt_rd(lp) != 1 ||
ldns_pkt_ra(lp) != 1 ||
ldns_pkt_get_rcode(lp) != LDNS_RCODE_NOERROR) {
ldns_pkt_free(lp);
return (NULL);
}
/* Check that there's only one question. Generally, this should be true
* as there's no way to reply to more than one question prior to
* proposals in EDNS1. */
if (ldns_pkt_qdcount(lp) != 1) {
ldns_pkt_free(lp);
return (NULL);
}
/* Only look at replies to A queries. Could possibly look at
* CNAME queries as well, but those aren't generally used. */
q_rr = ldns_rr_list_rr(ldns_pkt_question(lp), 0);
if (ldns_rr_get_type(q_rr) != LDNS_RR_TYPE_A &&
ldns_rr_get_type(q_rr) != LDNS_RR_TYPE_AAAA &&
ldns_rr_get_type(q_rr) != LDNS_RR_TYPE_ANY) {
ldns_pkt_free(lp);
return (NULL);
}
return (lp);
}
/* NOTE: The names in the returned dns_data_set point to data inside the
* ldns_pkt. So, don't free the packet until the names have been copied. */
static struct dns_data_set *
dnsflow_dns_extract(ldns_pkt *lp)
{
/* Statics */
static struct dns_data_set data[1];
ldns_rr_type rr_type;
ldns_rr *q_rr, *a_rr;
ldns_rdf *rdf;
int i, j;
struct in_addr *ip_ptr;
struct in6_addr *ip6_ptr;
data->num_names = 0;
data->num_ips = 0;
data->num_ip6s = 0;
q_rr = ldns_rr_list_rr(ldns_pkt_question(lp), 0);
if (ldns_rdf_size(ldns_rr_owner(q_rr)) > LDNS_MAX_DOMAINLEN) {
/* I believe this should never happen for valid DNS. */
_log("Invalid query string");
return (NULL);
}
data->names[data->num_names] = ldns_rdf_data(ldns_rr_owner(q_rr));
data->name_lens[data->num_names] = ldns_rdf_size(ldns_rr_owner(q_rr));
data->num_names++;
for (i = 0; i < ldns_pkt_ancount(lp); i++) {
a_rr = ldns_rr_list_rr(ldns_pkt_answer(lp), i);
rr_type = ldns_rr_get_type(a_rr);
/* XXX Not necessary, remove when we have more confidence. */
/*
str = ldns_rdf2str(ldns_rr_owner(a_rr));
if (strcmp(str, data->names[data->num_names - 1])) {
_log("XXX msg not in sequence");
ldns_pkt_print(stdout, lp);
}
LDNS_FREE(str);
*/
for (j = 0; j < ldns_rr_rd_count(a_rr); j++) {
rdf = ldns_rr_rdf(a_rr, j);
if (rr_type == LDNS_RR_TYPE_CNAME) {
if (data->num_names == DNSFLOW_MAX_PARSE) {
_log("Too many names");
continue;
}
if (ldns_rdf_size(rdf) > LDNS_MAX_DOMAINLEN) {
/* Again, I believe this should never
* happen. */
_log("Invalid name");
continue;
}
data->names[data->num_names] =
ldns_rdf_data(rdf);
data->name_lens[data->num_names] =
ldns_rdf_size(rdf);
data->num_names++;
} else if (rr_type == LDNS_RR_TYPE_A) {
if (data->num_ips == DNSFLOW_MAX_PARSE) {
_log("Too many ips");
continue;
}
ip_ptr = (struct in_addr *) ldns_rdf_data(rdf);
data->ips[data->num_ips++] = *ip_ptr;
} else if (rr_type == LDNS_RR_TYPE_AAAA) {
if (data->num_ip6s == DNSFLOW_MAX_PARSE) {
_log("Too many ips");
continue;
}
ip6_ptr = (struct in6_addr *) ldns_rdf_data(rdf);
data->ip6s[data->num_ip6s++] = *ip6_ptr;
} else {
/* XXX Only looking at A queries, so this is
* unexpected rdata. */
}
}
}
/* Sanity checks */
if (data->num_names == 0) {
return (NULL);
}
if (data->num_ips == 0 && data->num_ip6s == 0) {
return (NULL);
}
return (data);
}
static void
dnsflow_pkt_send(struct dnsflow_buf *dnsflow_buf)
{
static int udp_socket = 0;
struct pcap_pkthdr pkthdr;
int i;
if (pdump != NULL) {
uint8_t tmp[4096];
struct iphdr *ip;
struct udphdr *udp;
uint8_t *data;
struct sockaddr_in saddr, daddr;
u_int32_t *link;
uint32_t pf_type; /* Loopback "header" */
int linkheader_size = sizeof(pf_type);
memset(tmp, 0, sizeof(tmp));
pf_type = PF_INET;
link = (u_int32_t *) tmp;
ip = (struct iphdr *) (link + 1);
udp = (struct udphdr *) (ip+1);
data = (uint8_t *) (udp+1);
daddr.sin_family = AF_INET;
saddr.sin_family = AF_INET;
daddr.sin_port = htons(5300);
saddr.sin_port = htons(0);
inet_pton(AF_INET, "127.0.0.1", (struct in_addr *)&daddr.sin_addr.s_addr);
inet_pton(AF_INET, "127.0.0.1", (struct in_addr *)&saddr.sin_addr.s_addr);
ip->ihl = 5; /* header length, number of 32-bit words */
ip->version = 4;
ip->tos = 0x0;
ip->id = 0;
ip->frag_off = htons(0x4000); /* Don't fragment */
ip->ttl = 64;
ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + dnsflow_buf->db_len);
ip->protocol = IPPROTO_UDP;
ip->saddr = saddr.sin_addr.s_addr;
ip->daddr = daddr.sin_addr.s_addr;
udp->uh_sport = saddr.sin_port;
udp->uh_dport = daddr.sin_port;
udp->uh_ulen = htons(sizeof(struct udphdr) + dnsflow_buf->db_len);
udp->uh_sum = 0;
memcpy(link, &pf_type, linkheader_size);
memcpy(data, &dnsflow_buf->db_pkt_hdr, sizeof(tmp) - (data - tmp));
gettimeofday(&pkthdr.ts, NULL);
pkthdr.len = dnsflow_buf->db_len + 20 + 8 + 4; /* ip=20, udp=8, 4=loopback */
pkthdr.caplen = pkthdr.len;
pcap_dump((u_char *)pdump, &pkthdr, (u_char *) tmp);
return;
}
if (udp_num_dsts == 0) {
return;
}
if (udp_socket == 0) {
if ((udp_socket = socket(PF_INET, SOCK_DGRAM, 0)) < 1) {
err(1, "socket failed");
}
}
for (i = 0; i < udp_num_dsts; i++) {
if (sendto(udp_socket, &dnsflow_buf->db_pkt_hdr, dnsflow_buf->db_len, 0,
(struct sockaddr *)&dst_so_addrs[i],
sizeof(struct sockaddr_in)) < 0) {
warnx("send failed");
}
}
}
static void
dnsflow_pkt_send_data()
{
if (data_buf->db_len == 0) {
return;
}
data_buf->db_pkt_hdr.sequence_number = htonl(sequence_number++);
dnsflow_pkt_send(data_buf);
data_buf->db_len = 0;
last_send = time(NULL);
}
static void
dnsflow_push_cb(int fd, short event, void *arg)
{
time_t now = time(NULL);
if (now - last_send >= push_tv.tv_sec) {
dnsflow_pkt_send_data();
}
evtimer_add(&push_ev, jitter_tv(&push_tv));
}
/* XXX Need more care to prevent buffer overruns. */
static void
dnsflow_pkt_build(struct in_addr* client_ip, struct in6_addr* client_ip6, struct in_addr* resolver_ip, struct in6_addr* resolver_ip6, struct dns_data_set *dns_data)
{
struct dnsflow_hdr *dnsflow_hdr;
struct dnsflow_set_hdr *set_hdr;
char *pkt_start, *pkt_cur, *pkt_end, *names_start;
int i, j;
int header_len = 0;
struct in_addr *ip_ptr;
struct in6_addr *ip6_ptr;
int set_len, ips_len_total, ip6s_len_total;
int name_len_total = 0;
uint8_t names_count, ips_count, ip6s_count;
dnsflow_hdr = &data_buf->db_pkt_hdr;
pkt_start = (char *)dnsflow_hdr;