diff options
Diffstat (limited to 'src/fnettrace-dns/main.c')
-rw-r--r-- | src/fnettrace-dns/main.c | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/src/fnettrace-dns/main.c b/src/fnettrace-dns/main.c new file mode 100644 index 000000000..0281b5157 --- /dev/null +++ b/src/fnettrace-dns/main.c | |||
@@ -0,0 +1,162 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2022 Firejail Authors | ||
3 | * | ||
4 | * This file is part of firejail project | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | #include "fnettrace_dns.h" | ||
21 | #include <sys/ioctl.h> | ||
22 | #include <time.h> | ||
23 | #include <linux/filter.h> | ||
24 | #include <linux/if_ether.h> | ||
25 | #define MAX_BUF_SIZE (64 * 1024) | ||
26 | |||
27 | // pkt - start of DNS layer | ||
28 | void print_dns(uint32_t ip_src, unsigned char *pkt) { | ||
29 | assert(pkt); | ||
30 | |||
31 | char ip[30]; | ||
32 | sprintf(ip, "%d.%d.%d.%d", PRINT_IP(ip_src)); | ||
33 | time_t seconds = time(NULL); | ||
34 | struct tm *t = localtime(&seconds); | ||
35 | |||
36 | // expecting a single question count | ||
37 | if (pkt[4] != 0 || pkt[5] != 1) | ||
38 | goto errout; | ||
39 | |||
40 | // check cname | ||
41 | unsigned char *ptr = pkt + 12; | ||
42 | int len = 0; | ||
43 | while (*ptr != 0 && len < 255) { // 255 is the maximum length of a domain name including multiple '.' | ||
44 | if (*ptr > 63) // the name left of a '.' is 63 length maximum | ||
45 | goto errout; | ||
46 | |||
47 | int delta = *ptr + 1; | ||
48 | *ptr = '.'; | ||
49 | len += delta;; | ||
50 | ptr += delta; | ||
51 | } | ||
52 | |||
53 | printf("%02d:%02d:%02d %15s %s\n", t->tm_hour, t->tm_min, t->tm_sec, ip, pkt + 12 + 1); | ||
54 | return; | ||
55 | |||
56 | errout: | ||
57 | printf("%02d:%02d:%02d %15s Error: invalid DNS packet\n", t->tm_hour, t->tm_min, t->tm_sec, ip); | ||
58 | } | ||
59 | |||
60 | // https://www.kernel.org/doc/html/latest/networking/filter.html | ||
61 | static void custom_bpf(int sock) { | ||
62 | struct sock_filter code[] = { | ||
63 | // sudo tcpdump ip and udp and src port 53 -dd | ||
64 | { 0x28, 0, 0, 0x0000000c }, | ||
65 | { 0x15, 0, 8, 0x00000800 }, | ||
66 | { 0x30, 0, 0, 0x00000017 }, | ||
67 | { 0x15, 0, 6, 0x00000011 }, | ||
68 | { 0x28, 0, 0, 0x00000014 }, | ||
69 | { 0x45, 4, 0, 0x00001fff }, | ||
70 | { 0xb1, 0, 0, 0x0000000e }, | ||
71 | { 0x48, 0, 0, 0x0000000e }, | ||
72 | { 0x15, 0, 1, 0x00000035 }, | ||
73 | { 0x6, 0, 0, 0x00040000 }, | ||
74 | { 0x6, 0, 0, 0x00000000 }, | ||
75 | }; | ||
76 | |||
77 | struct sock_fprog bpf = { | ||
78 | .len = (unsigned short) sizeof(code) / sizeof(code[0]), | ||
79 | .filter = code, | ||
80 | }; | ||
81 | |||
82 | int rv = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)); | ||
83 | if (rv < 0) { | ||
84 | fprintf(stderr, "Error: cannot attach BPF filter\n"); | ||
85 | exit(1); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | static void run_trace(void) { | ||
90 | // grab all Ethernet packets and use a custom BPF filter to get only UDP from source port 53 | ||
91 | int s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); | ||
92 | if (s < 0) | ||
93 | errExit("socket"); | ||
94 | custom_bpf(s); | ||
95 | |||
96 | unsigned char buf[MAX_BUF_SIZE]; | ||
97 | while (1) { | ||
98 | fd_set rfds; | ||
99 | FD_ZERO(&rfds); | ||
100 | FD_SET(s, &rfds); | ||
101 | struct timeval tv; | ||
102 | tv.tv_sec = 1; | ||
103 | tv.tv_usec = 0; | ||
104 | int rv = select(s + 1, &rfds, NULL, NULL, &tv); | ||
105 | if (rv < 0) | ||
106 | errExit("select"); | ||
107 | else if (rv == 0) | ||
108 | continue; | ||
109 | unsigned bytes = recvfrom(s, buf, MAX_BUF_SIZE, 0, NULL, NULL); | ||
110 | |||
111 | if (bytes >= (14 + 20 + 8)) { // size of MAC + IP + UDP headers | ||
112 | uint8_t ip_hlen = (buf[14] & 0x0f) * 4; | ||
113 | uint16_t port_src; | ||
114 | memcpy(&port_src, buf + 14 + ip_hlen, 2); | ||
115 | port_src = ntohs(port_src); | ||
116 | uint8_t protocol = buf[14 + 9]; | ||
117 | uint32_t ip_src; | ||
118 | memcpy(&ip_src, buf + 14 + 12, 4); | ||
119 | ip_src = ntohl(ip_src); | ||
120 | |||
121 | // if DNS packet, extract the query | ||
122 | if (port_src == 53 && protocol == 0x11) // UDP protocol | ||
123 | print_dns(ip_src, buf + 14 + ip_hlen + 8); // IP and UDP header len | ||
124 | } | ||
125 | } | ||
126 | |||
127 | close(s); | ||
128 | } | ||
129 | |||
130 | |||
131 | static void usage(void) { | ||
132 | printf("Usage: fnettrace-dns [OPTIONS]\n"); | ||
133 | printf("Options:\n"); | ||
134 | printf(" --help, -? - this help screen\n"); | ||
135 | printf("\n"); | ||
136 | } | ||
137 | |||
138 | int main(int argc, char **argv) { | ||
139 | int i; | ||
140 | |||
141 | for (i = 1; i < argc; i++) { | ||
142 | if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-?") == 0) { | ||
143 | usage(); | ||
144 | return 0; | ||
145 | } | ||
146 | else { | ||
147 | fprintf(stderr, "Error: invalid argument\n"); | ||
148 | return 1; | ||
149 | } | ||
150 | } | ||
151 | |||
152 | if (getuid() != 0) { | ||
153 | fprintf(stderr, "Error: you need to be root to run this program\n"); | ||
154 | return 1; | ||
155 | } | ||
156 | |||
157 | time_t now = time(NULL); | ||
158 | printf("DNS trace for %s\n", ctime(&now)); | ||
159 | run_trace(); | ||
160 | |||
161 | return 0; | ||
162 | } | ||