diff options
author | 2021-12-28 11:04:59 -0500 | |
---|---|---|
committer | 2021-12-28 11:04:59 -0500 | |
commit | 54b28a0b6392f9921fad98ba9e22470c900efb03 (patch) | |
tree | b84db698ff24816e8c67b9002de860a2fccaeb0f /src/fnettrace/main.c | |
parent | Merge pull request #4802 from jose1711/clipgrab_fix (diff) | |
download | firejail-54b28a0b6392f9921fad98ba9e22470c900efb03.tar.gz firejail-54b28a0b6392f9921fad98ba9e22470c900efb03.tar.zst firejail-54b28a0b6392f9921fad98ba9e22470c900efb03.zip |
nettrace/netlock
Diffstat (limited to 'src/fnettrace/main.c')
-rw-r--r-- | src/fnettrace/main.c | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/src/fnettrace/main.c b/src/fnettrace/main.c new file mode 100644 index 000000000..f036d0c9e --- /dev/null +++ b/src/fnettrace/main.c | |||
@@ -0,0 +1,433 @@ | |||
1 | #include "fnettrace.h" | ||
2 | #define MAX_BUF_SIZE (64 * 1024) | ||
3 | |||
4 | static int arg_netfilter = 0; | ||
5 | static char *arg_log = NULL; | ||
6 | |||
7 | typedef struct hlist_t { | ||
8 | struct hlist_t *next; | ||
9 | uint32_t ip_src; | ||
10 | uint32_t ip_dst; | ||
11 | uint16_t port_src; | ||
12 | uint64_t bytes; | ||
13 | int instance; | ||
14 | #define MAX_TTL 20 // 20 * DISPLAY_INTERVAL = 1 minute | ||
15 | short ttl; | ||
16 | uint8_t protocol; | ||
17 | } HList; | ||
18 | |||
19 | #define HMAX 256 | ||
20 | HList *htable[HMAX] = {NULL}; | ||
21 | static int htable_empty = 1; | ||
22 | |||
23 | static void hlist_add(uint32_t ip_src, uint32_t ip_dst, uint8_t protocol, uint16_t port_src, uint64_t bytes) { | ||
24 | uint8_t h = hash(ip_src); | ||
25 | htable_empty = 0; | ||
26 | |||
27 | // find | ||
28 | int instance = 0; | ||
29 | HList *ptr = htable[h]; | ||
30 | while (ptr) { | ||
31 | if (ptr->ip_src == ip_src) { | ||
32 | instance++; | ||
33 | if (ptr->ip_dst == ip_dst && ptr->port_src == port_src && ptr->protocol == protocol) { | ||
34 | ptr->bytes += bytes; | ||
35 | ptr->ttl = MAX_TTL; | ||
36 | return; | ||
37 | } | ||
38 | } | ||
39 | ptr = ptr->next; | ||
40 | } | ||
41 | |||
42 | HList *hnew = malloc(sizeof(HList)); | ||
43 | hnew->ip_src = ip_src; | ||
44 | hnew->ip_dst = ip_dst; | ||
45 | hnew->port_src = port_src; | ||
46 | hnew->protocol = protocol; | ||
47 | hnew->next = NULL; | ||
48 | hnew->bytes = bytes; | ||
49 | hnew->ttl = MAX_TTL; | ||
50 | hnew->instance = instance + 1; | ||
51 | if (htable[h] == NULL) | ||
52 | htable[h] = hnew; | ||
53 | else { | ||
54 | hnew->next = htable[h]; | ||
55 | htable[h] = hnew; | ||
56 | } | ||
57 | |||
58 | ansi_clrline(1); | ||
59 | logprintf(" %u.%u.%u.%u\n", PRINT_IP(hnew->ip_src)); | ||
60 | } | ||
61 | |||
62 | // remove entries with a ttl <= 0 | ||
63 | static void hlist_clean_ttl() { | ||
64 | if (htable_empty) | ||
65 | return; | ||
66 | |||
67 | int i; | ||
68 | for (i = 0; i < HMAX; i++) { | ||
69 | HList *ptr = htable[i]; | ||
70 | HList *parent = NULL; | ||
71 | while (ptr) { | ||
72 | if (--ptr->ttl <= 0) { | ||
73 | HList *tmp = ptr; | ||
74 | ptr = ptr->next; | ||
75 | if (parent) | ||
76 | parent->next = ptr; | ||
77 | else | ||
78 | htable[i] = ptr; | ||
79 | free(tmp); | ||
80 | } | ||
81 | else { | ||
82 | parent = ptr; | ||
83 | ptr = ptr->next; | ||
84 | } | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | |||
89 | static void hlist_print() { | ||
90 | ansi_clrscr(0); | ||
91 | if (htable_empty) | ||
92 | return; | ||
93 | if (arg_netfilter) | ||
94 | printf("\n\n"); | ||
95 | static int clear_cnt = 0; | ||
96 | |||
97 | int i; | ||
98 | int cnt = 0; | ||
99 | int cnt_printed = 0; | ||
100 | for (i = 0; i < HMAX; i++) { | ||
101 | HList *ptr = htable[i]; | ||
102 | while (ptr) { | ||
103 | if (ptr->bytes) { | ||
104 | cnt_printed++; | ||
105 | char ip_src[30]; | ||
106 | sprintf(ip_src, "%u.%u.%u.%u:%u", PRINT_IP(ptr->ip_src), ptr->port_src); | ||
107 | char ip_dst[30]; | ||
108 | sprintf(ip_dst, "%u.%u.%u.%u", PRINT_IP(ptr->ip_dst)); | ||
109 | printf("%-25s => %-25s\t%s:", | ||
110 | ip_src, | ||
111 | ip_dst, | ||
112 | (ptr->protocol == 6)? "TCP": "UDP"); | ||
113 | |||
114 | if (ptr->bytes > (DISPLAY_INTERVAL * 1024 * 2)) // > 2 KB/second | ||
115 | printf(" %lu KB/sec\n", | ||
116 | ptr->bytes / (DISPLAY_INTERVAL * 1024)); | ||
117 | else | ||
118 | printf(" %lu B/sec\n", | ||
119 | ptr->bytes / DISPLAY_INTERVAL); | ||
120 | ptr->bytes = 0; | ||
121 | } | ||
122 | |||
123 | ptr = ptr->next; | ||
124 | cnt++; | ||
125 | } | ||
126 | } | ||
127 | |||
128 | if (cnt_printed < 7) { | ||
129 | for (i = 0; i < 7 - cnt_printed; i++) | ||
130 | printf("\n"); | ||
131 | } | ||
132 | |||
133 | if (!arg_netfilter) { | ||
134 | printf("(%d %s in the last one minute)\n", cnt, (cnt == 1)? "stream": "streams"); | ||
135 | hlist_clean_ttl(); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | static void run_trace(void) { | ||
140 | logprintf("accumulating traffic for %d seconds...\n", NETLOCK_INTERVAL); | ||
141 | |||
142 | // trace only rx ipv4 tcp and upd | ||
143 | int s1 = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); | ||
144 | int s2 = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); | ||
145 | if (s1 < 0 || s2 < 0) | ||
146 | errExit("socket"); | ||
147 | |||
148 | unsigned start = time(NULL); | ||
149 | unsigned last_print_traces = 0; | ||
150 | unsigned last_print_remaining = 0; | ||
151 | unsigned char buf[MAX_BUF_SIZE]; | ||
152 | int progress_cnt = 0; | ||
153 | while (1) { | ||
154 | unsigned end = time(NULL); | ||
155 | if (arg_netfilter && end - start >= NETLOCK_INTERVAL) { | ||
156 | ansi_clrline(1); | ||
157 | break; | ||
158 | } | ||
159 | if (end % DISPLAY_INTERVAL == 1 && last_print_traces != end) { // first print after 1 second | ||
160 | hlist_print(); | ||
161 | last_print_traces = end; | ||
162 | } | ||
163 | if (arg_netfilter && last_print_remaining != end) { | ||
164 | ansi_clrline(1); | ||
165 | int secs = NETLOCK_INTERVAL - (end - start); | ||
166 | logprintf("%d %s remaining ", secs, (secs == 1)? "second": "seconds"); | ||
167 | last_print_remaining = end; | ||
168 | } | ||
169 | |||
170 | fd_set rfds; | ||
171 | FD_ZERO(&rfds); | ||
172 | FD_SET(s1, &rfds); | ||
173 | FD_SET(s2, &rfds); | ||
174 | int maxfd = (s1 > s2) ? s1 : s2; | ||
175 | maxfd++; | ||
176 | struct timeval tv; | ||
177 | tv.tv_sec = 1; | ||
178 | tv.tv_usec = 0; | ||
179 | int rv = select(maxfd, &rfds, NULL, NULL, &tv); | ||
180 | if (rv < 0) | ||
181 | errExit("select"); | ||
182 | else if (rv == 0) | ||
183 | continue; | ||
184 | |||
185 | |||
186 | |||
187 | int sock = (FD_ISSET(s1, &rfds)) ? s1 : s2; | ||
188 | |||
189 | unsigned char buf[MAX_BUF_SIZE]; | ||
190 | unsigned bytes = recvfrom(sock, buf, MAX_BUF_SIZE, 0, NULL, NULL); | ||
191 | if (bytes >= 20) { // size of IP header | ||
192 | // filter out loopback traffic | ||
193 | if (buf[12] != 127) { | ||
194 | uint32_t ip_src; | ||
195 | memcpy(&ip_src, buf + 12, 4); | ||
196 | ip_src = ntohl(ip_src); | ||
197 | |||
198 | uint32_t ip_dst; | ||
199 | memcpy(&ip_dst, buf + 16, 4); | ||
200 | ip_dst = ntohl(ip_dst); | ||
201 | |||
202 | uint8_t hlen = (buf[0] & 0x0f) * 4; | ||
203 | uint16_t port_src; | ||
204 | memcpy(&port_src, buf + hlen, 2); | ||
205 | port_src = ntohs(port_src); | ||
206 | |||
207 | hlist_add(ip_src, ip_dst, buf[9], port_src, (uint64_t) bytes); | ||
208 | } | ||
209 | } | ||
210 | } | ||
211 | |||
212 | close(s1); | ||
213 | close(s2); | ||
214 | } | ||
215 | |||
216 | static char *filter_start = | ||
217 | "*filter\n" | ||
218 | ":INPUT DROP [0:0]\n" | ||
219 | ":FORWARD DROP [0:0]\n" | ||
220 | ":OUTPUT DROP [0:0]\n"; | ||
221 | |||
222 | // return 1 if error | ||
223 | static int print_filter(FILE *fp) { | ||
224 | if (htable_empty) | ||
225 | return 1; | ||
226 | fprintf(fp, "%s\n", filter_start); | ||
227 | fprintf(fp, "-A INPUT -s 127.0.0.0/8 -j ACCEPT\n"); | ||
228 | fprintf(fp, "-A OUTPUT -d 127.0.0.0/8 -j ACCEPT\n"); | ||
229 | fprintf(fp, "\n"); | ||
230 | |||
231 | int i; | ||
232 | for (i = 0; i < HMAX; i++) { | ||
233 | HList *ptr = htable[i]; | ||
234 | while (ptr) { | ||
235 | if (ptr->instance == 1) { | ||
236 | char *protocol = (ptr->protocol == 6)? "tcp": "udp"; | ||
237 | fprintf(fp, "-A INPUT -s %u.%u.%u.%u -sport %u -p %s -j ACCEPT\n", | ||
238 | PRINT_IP(ptr->ip_src), | ||
239 | ptr->port_src, | ||
240 | protocol); | ||
241 | fprintf(fp, "-A OUTPUT -d %u.%u.%u.%u -dport %u -p %s -j ACCEPT\n", | ||
242 | PRINT_IP(ptr->ip_src), | ||
243 | ptr->port_src, | ||
244 | protocol); | ||
245 | fprintf(fp, "\n"); | ||
246 | } | ||
247 | ptr = ptr->next; | ||
248 | } | ||
249 | } | ||
250 | fprintf(fp, "COMMIT\n"); | ||
251 | |||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | static char *flush_rules[] = { | ||
256 | "-P INPUT ACCEPT", | ||
257 | "-P FORWARD ACCEPT", | ||
258 | "-P OUTPUT ACCEPT", | ||
259 | "-F", | ||
260 | "-X", | ||
261 | "-t nat -F", | ||
262 | "-t nat -X", | ||
263 | "-t mangle -F", | ||
264 | "-t mangle -X", | ||
265 | "iptables -t raw -F", | ||
266 | "-t raw -X", | ||
267 | NULL | ||
268 | }; | ||
269 | |||
270 | static void flush_netfilter(void) { | ||
271 | // find iptables command | ||
272 | struct stat s; | ||
273 | char *iptables = NULL; | ||
274 | if (stat("/sbin/iptables", &s) == 0) | ||
275 | iptables = "/sbin/iptables"; | ||
276 | else if (stat("/usr/sbin/iptables", &s) == 0) | ||
277 | iptables = "/usr/sbin/iptables"; | ||
278 | if (iptables == NULL) { | ||
279 | fprintf(stderr, "Error: iptables command not found, netfilter not configured\n"); | ||
280 | exit(1); | ||
281 | } | ||
282 | |||
283 | int i = 0; | ||
284 | while (flush_rules[i]) { | ||
285 | char *cmd; | ||
286 | if (asprintf(&cmd, "%s %s", iptables, flush_rules[i]) == -1) | ||
287 | errExit("asprintf"); | ||
288 | int rv = system(cmd); | ||
289 | (void) rv; | ||
290 | free(cmd); | ||
291 | i++; | ||
292 | } | ||
293 | } | ||
294 | |||
295 | static void deploy_netfilter(void) { | ||
296 | int rv; | ||
297 | char *cmd; | ||
298 | |||
299 | // create temporary file | ||
300 | char fname[] = "/tmp/firejail-XXXXXX"; | ||
301 | int fd = mkstemp(fname); | ||
302 | if (fd == -1) { | ||
303 | fprintf(stderr, "Error: cannot create temporary configuration file\n"); | ||
304 | exit(1); | ||
305 | } | ||
306 | |||
307 | FILE* fp = fdopen(fd, "w"); | ||
308 | if (!fp) { | ||
309 | rv = unlink(fname); | ||
310 | (void) rv; | ||
311 | fprintf(stderr, "Error: cannot create temporary configuration file\n"); | ||
312 | exit(1); | ||
313 | } | ||
314 | print_filter(fp); | ||
315 | fclose(fp); | ||
316 | |||
317 | if (arg_log) { | ||
318 | logprintf("\n"); | ||
319 | logprintf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); | ||
320 | if (asprintf(&cmd, "cat %s >> %s", fname, arg_log) == -1) | ||
321 | errExit("asprintf"); | ||
322 | rv = system(cmd); | ||
323 | (void) rv; | ||
324 | free(cmd); | ||
325 | logprintf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); | ||
326 | } | ||
327 | |||
328 | // find iptables command | ||
329 | struct stat s; | ||
330 | char *iptables = NULL; | ||
331 | char *iptables_restore = NULL; | ||
332 | if (stat("/sbin/iptables", &s) == 0) { | ||
333 | iptables = "/sbin/iptables"; | ||
334 | iptables_restore = "/sbin/iptables-restore"; | ||
335 | } | ||
336 | else if (stat("/usr/sbin/iptables", &s) == 0) { | ||
337 | iptables = "/usr/sbin/iptables"; | ||
338 | iptables_restore = "/usr/sbin/iptables-restore"; | ||
339 | } | ||
340 | if (iptables == NULL || iptables_restore == NULL) { | ||
341 | fprintf(stderr, "Error: iptables command not found, netfilter not configured\n"); | ||
342 | rv = unlink(fname); | ||
343 | (void) rv; | ||
344 | exit(1); | ||
345 | } | ||
346 | |||
347 | // configuring | ||
348 | if (asprintf(&cmd, "%s %s", iptables_restore, fname) == -1) | ||
349 | errExit("asprintf"); | ||
350 | rv = system(cmd); | ||
351 | if (rv) | ||
352 | fprintf(stdout, "Warning: possible netfilter problem!"); | ||
353 | free(cmd); | ||
354 | |||
355 | sleep(1); | ||
356 | if (asprintf(&cmd, "%s %s", iptables_restore, fname) == -1) | ||
357 | errExit("asprintf"); | ||
358 | rv = system(cmd); | ||
359 | free(cmd); | ||
360 | |||
361 | printf("Current firewall configuration:\n\n"); | ||
362 | if (asprintf(&cmd, "%s -vL -n", iptables) == -1) | ||
363 | errExit("asprintf"); | ||
364 | rv = system(cmd); | ||
365 | |||
366 | rv = unlink(fname); | ||
367 | (void) rv; | ||
368 | logprintf("\nfirewall deployed\n"); | ||
369 | } | ||
370 | |||
371 | void logprintf(char* fmt, ...) { | ||
372 | if (!arg_log) | ||
373 | return; | ||
374 | |||
375 | FILE *fp = fopen(arg_log, "a"); | ||
376 | if (fp) { // disregard if error | ||
377 | va_list args; | ||
378 | va_start(args,fmt); | ||
379 | vfprintf(fp, fmt, args); | ||
380 | va_end(args); | ||
381 | fclose(fp); | ||
382 | } | ||
383 | } | ||
384 | |||
385 | static void usage(void) { | ||
386 | printf("Usage: fnetlock [OPTIONS]\n"); | ||
387 | printf("Options:\n"); | ||
388 | printf(" --help, -? - this help screen\n"); | ||
389 | printf(" --netfilter - build the firewall rules and commit them.\n"); | ||
390 | printf(" --log=filename - logfile\n"); | ||
391 | printf("\n"); | ||
392 | } | ||
393 | |||
394 | int main(int argc, char **argv) { | ||
395 | int i; | ||
396 | printf("\n\n"); | ||
397 | |||
398 | if (getuid() != 0) { | ||
399 | fprintf(stderr, "Error: you need to be root to run this program\n"); | ||
400 | return 1; | ||
401 | } | ||
402 | |||
403 | for (i = 1; i < argc; i++) { | ||
404 | if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-?") == 0) { | ||
405 | usage(); | ||
406 | return 0; | ||
407 | } | ||
408 | else if (strcmp(argv[i], "--netfilter") == 0) | ||
409 | arg_netfilter = 1; | ||
410 | else if (strncmp(argv[i], "--log=", 6) == 0) | ||
411 | arg_log = argv[i] + 6; | ||
412 | else { | ||
413 | fprintf(stderr, "Error: invalid argument\n"); | ||
414 | return 1; | ||
415 | } | ||
416 | } | ||
417 | |||
418 | if (arg_netfilter) { | ||
419 | logprintf("starting network lockdown\n"); | ||
420 | flush_netfilter(); | ||
421 | } | ||
422 | |||
423 | ansi_clrscr(0); | ||
424 | run_trace(); | ||
425 | if (arg_netfilter) { | ||
426 | deploy_netfilter(); | ||
427 | sleep(3); | ||
428 | if (arg_log) | ||
429 | unlink(arg_log); | ||
430 | } | ||
431 | |||
432 | return 0; | ||
433 | } | ||