New upstream version 4
Sudip Mukherjee
3 years ago
0 | #nsntrace | |
0 | # nsntrace | |
1 | 1 | > Perform network trace of a single process by using network namespaces. |
2 | 2 | |
3 | 3 | This application uses Linux network namespaces to perform network traces of a single application. The traces are saved as pcap files. And can later be analyzed by for instance Wireshark. |
28 | 28 | Another limitation is, that since we are using iptables and since |
29 | 29 | we are tracing raw sockets. This application needs to be run as root. |
30 | 30 | |
31 | On many systems today the nameserver functionality is handled by an | |
32 | application such as systemd-resolved or dnsmasq and the nameserver | |
33 | address is a loopback address (like 127.0.0.53) where that application | |
34 | listens for incoming DNS queries. | |
35 | ||
36 | This will not work for us in this network namespace environment, since | |
37 | we have our own namespaced loopback device. To work around this one can use the | |
38 | `--use-public-dns` option to override resolv.conf in the namespace. Then nsntrace | |
39 | will use nameservers from Quad9 (9.9.9.9), Cloudflare (1.1.1.1), Google (8.8.8.8) and | |
40 | OpenDNS (208.67.222.222) to perform DNS queries. | |
41 | ||
31 | 42 | ## usage |
32 | > nsntrace | |
43 | $ nsntrace | |
33 | 44 | usage: nsntrace [options] program [arguments] |
34 | 45 | Perform network trace of a single process by using network namespaces. |
35 | 46 | |
36 | 47 | Options: |
37 | -o file send trace output to file (default nsntrace.pcap) | |
38 | -d device the network device to trace | |
39 | -f filter an optional capture filter | |
40 | -u username run program as username/uid | |
48 | -o file send trace output to file (default nsntrace.pcap) | |
49 | -d device the network device to trace | |
50 | -f filter an optional capture filter | |
51 | -u username run program as username/uid | |
52 | --use-public-dns override resolv.conf to use public nameservers from | |
53 | Quad9, Cloudflare, Google and OpenDNS | |
41 | 54 | |
42 | 55 | ## example |
43 | > sudo nsntrace -d eth1 wget www.google.com | |
44 | Starting network trace of 'wget' on interface eth1. | |
45 | Your IP address in this trace is 172.16.42.255. | |
46 | Use ctrl-c to end at any time. | |
56 | ``` | |
57 | $ sudo nsntrace -d eth1 wget www.google.com | |
58 | Starting network trace of 'wget' on interface eth1. | |
59 | Your IP address in this trace is 172.16.42.255. | |
60 | Use ctrl-c to end at any time. | |
47 | 61 | |
48 | --2016-07-15 12:12:17-- http://www.google.com/ | |
49 | Location: http://www.google.se/?gfe_rd=cr&ei=AbeIV5zZHcaq8wfTlrjgCA [following] | |
50 | --2016-07-15 12:12:17-- http://www.google.se/?gfe_rd=cr&ei=AbeIV5zZHcaq8wfTlrjgCA | |
51 | Length: unspecified [text/html] | |
52 | Saving to: ‘index.html’ | |
62 | --2016-07-15 12:12:17-- http://www.google.com/ | |
63 | Location: http://www.google.se/?gfe_rd=cr&ei=AbeIV5zZHcaq8wfTlrjgCA [following] | |
64 | --2016-07-15 12:12:17-- http://www.google.se/?gfe_rd=cr&ei=AbeIV5zZHcaq8wfTlrjgCA | |
65 | Length: unspecified [text/html] | |
66 | Saving to: ‘index.html’ | |
53 | 67 | |
54 | index.html [ <=> ] 10.72K --.-KB/s in 0.001s | |
68 | index.html [ <=> ] 10.72K --.-KB/s in 0.001s | |
55 | 69 | |
56 | 2016-07-15 12:12:17 (15.3 MB/s) - ‘index.html’ saved [10980] | |
70 | 2016-07-15 12:12:17 (15.3 MB/s) - ‘index.html’ saved [10980] | |
57 | 71 | |
58 | Finished capturing 42 packets. | |
72 | Finished capturing 42 packets. | |
59 | 73 | |
60 | > tshark -r nsntrace.pcap -Y 'http.response or http.request' | |
61 | 16 0.998839 172.16.42.255 -> 195.249.146.104 HTTP 229 GET http://www.google.com/ HTTP/1.1 | |
62 | 20 1.010671 195.249.146.104 -> 172.16.42.255 HTTP 324 HTTP/1.1 302 Moved Temporarily (text/html) | |
63 | 22 1.010898 172.16.42.255 -> 195.249.146.104 HTTP 263 GET http://www.google.se/?gfe_rd=cr&ei=AbeIV5zZHcaq8wfTlrjgCA HTTP/1.1 | |
64 | 31 1.051006 195.249.146.104 -> 172.16.42.255 HTTP 71 HTTP/1.1 200 OK (text/html) | |
74 | $ tshark -r nsntrace.pcap -Y 'http.response or http.request' | |
75 | 16 0.998839 172.16.42.255 -> 195.249.146.104 HTTP 229 GET http://www.google.com/ HTTP/1.1 | |
76 | 20 1.010671 195.249.146.104 -> 172.16.42.255 HTTP 324 HTTP/1.1 302 Moved Temporarily (text/html) | |
77 | 22 1.010898 172.16.42.255 -> 195.249.146.104 HTTP 263 GET http://www.google.se/?gfe_rd=cr&ei=AbeIV5zZHcaq8wfTlrjgCA HTTP/1.1 | |
78 | 31 1.051006 195.249.146.104 -> 172.16.42.255 HTTP 71 HTTP/1.1 200 OK (text/html) | |
79 | ``` | |
80 | ||
81 | ### live capture using tshark | |
82 | ``` | |
83 | $ sudo nsntrace -f tcp -o - wget www.google.com 2> /dev/null | tshark -r - | |
84 | 1 0.000000 172.16.42.255 → 142.250.74.36 TCP 74 51088 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1 TSval=1362504482 TSecr=0 WS=128 | |
85 | 2 0.014010 142.250.74.36 → 172.16.42.255 TCP 74 80 → 51088 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1430 SACK_PERM=1 TSval=2846449454 Secr=1362504482 WS=256 | |
86 | 3 0.014078 172.16.42.255 → 142.250.74.36 TCP 66 51088 → 80 [ACK] Seq=1 Ack=1 Win=64256 Len=0 TSval=1362504496 TSecr=2846449454 | |
87 | 4 0.014221 172.16.42.255 → 142.250.74.36 HTTP 207 GET / HTTP/1.1 | |
88 | ||
89 | 5 0.033935 142.250.74.36 → 172.16.42.255 TCP 66 80 → 51088 [ACK] Seq=1 Ack=142 Win=66816 Len=0 TSval=2846449475 TSecr=1362504496 | |
90 | 6 0.093989 142.250.74.36 → 172.16.42.255 TCP 1484 HTTP/1.1 200 OK [TCP segment of a reassembled PDU] | |
91 | 7 0.094022 172.16.42.255 → 142.250.74.36 TCP 66 51088 → 80 [ACK] Seq=142 Ack=1419 Win=63360 Len=0 TSval=1362504576 TSecr=2846449532 | |
92 | 8 0.096447 142.250.74.36 → 172.16.42.255 TCP 2902 HTTP/1.1 200 OK [TCP segment of a reassembled PDU] | |
93 | 9 0.096478 172.16.42.255 → 142.250.74.36 TCP 66 51088 → 80 [ACK] Seq=142 Ack=4255 Win=62208 Len=0 TSval=1362504578 TSecr=2846449532 | |
94 | 10 0.099871 142.250.74.36 → 172.16.42.255 HTTP 9626 Continuation[Packet size limited during capture] | |
95 | 11 0.099936 172.16.42.255 → 142.250.74.36 TCP 66 51088 → 80 [ACK] Seq=142 Ack=13815 Win=56320 Len=0 TSval=1362504582 TSecr=2846449532 | |
96 | 12 0.100743 172.16.42.255 → 142.250.74.36 TCP 66 51088 → 80 [FIN, ACK] Seq=142 Ack=13815 Win=64128 Len=0 TSval=1362504583 TSecr=2846449532 | |
97 | 13 0.113167 142.250.74.36 → 172.16.42.255 TCP 66 80 → 51088 [FIN, ACK] Seq=13815 Ack=143 Win=66816 Len=0 TSval=2846449554 TSecr=1362504583 | |
98 | 14 0.113190 172.16.42.255 → 142.250.74.36 TCP 66 51088 → 80 [ACK] Seq=143 Ack=13816 Win=64128 Len=0 TSval=1362504595 TSecr=2846449554 | |
99 | ``` |
2 | 2 | # [bug reports], |
3 | 3 | # [tar-ball name]) |
4 | 4 | AC_INIT([nsntrace], |
5 | [3], | |
5 | [4], | |
6 | 6 | [https://github.com/jonasdn/nsntrace/issues], |
7 | 7 | [nsntrace]) |
8 | 8 |
76 | 76 | </para></listitem> |
77 | 77 | </varlistentry> |
78 | 78 | |
79 | ||
80 | <varlistentry> | |
81 | <term><option>--use-public-dns</option></term> | |
82 | <listitem><para> | |
83 | Override resolv.conf in namespace to use public nameservers from | |
84 | Quad9 (9.9.9.9), Cloudflare (1.1.1.1), Google (8.8.8.8) and | |
85 | OpenDNS (208.67.222.222). | |
86 | </para></listitem> | |
87 | </varlistentry> | |
88 | ||
79 | 89 | <varlistentry> |
80 | 90 | <term><option>--outfile <replaceable>file</replaceable></option></term> |
81 | 91 | <term><option>-o <replaceable>file</replaceable></option></term> |
82 | 92 | |
83 | 93 | <listitem><para> |
84 | 94 | Write the trace output to the file <replaceable>file</replaceable>. |
85 | Default is nsntrace.pcap. | |
95 | Default is nsntrace.pcap. Use '-' for stdout. | |
86 | 96 | </para></listitem> |
87 | 97 | </varlistentry> |
88 | 98 |
0 | name: nsntrace | |
1 | adopt-info: nsntrace | |
2 | base: core20 | |
3 | summary: Namespaced network tracer | |
4 | description: | | |
5 | Perform network trace of a single process by using network namespaces. | |
6 | ||
7 | grade: stable | |
8 | confinement: classic | |
9 | ||
10 | parts: | |
11 | nsntrace: | |
12 | plugin: autotools | |
13 | source-type: git | |
14 | override-pull: | # This override tells snapcraft to use latest release tag | |
15 | snapcraftctl pull | |
16 | last_tag="$(git describe --tags --abbrev=0 --match 'v*')" | |
17 | git fetch | |
18 | git checkout "${last_tag}" | |
19 | snapcraftctl set-version "${last_tag}" | |
20 | source: https://github.com/nsntrace/nsntrace.git | |
21 | build-packages: | |
22 | - libpcap-dev | |
23 | - libnl-route-3-dev | |
24 | - xsltproc | |
25 | - iptables | |
26 | - pkg-config | |
27 | - docbook-xsl | |
28 | - docbook-xml | |
29 | stage-packages: | |
30 | - libpcap0.8 | |
31 | ||
32 | apps: | |
33 | nsntrace: | |
34 | command: usr/local/bin/nsntrace |
15 | 15 | nsntrace_SOURCES = $(headers) $(sources) |
16 | 16 | nsntrace_CPPFLAGS = $(LIBNL_CFLAGS) $(warnings) |
17 | 17 | nsntrace_LDFLAGS = -pthread |
18 | nsntrace_LDADD = $(LIBNL_LIBS) | |
18 | nsntrace_LDADD = $(LIBNL_LIBS) -lresolv |
12 | 12 | * with nsntraces; if not, see <https://www.gnu.org/licenses/>. |
13 | 13 | */ |
14 | 14 | |
15 | #include <linux/if.h> | |
15 | 16 | #include <pcap.h> |
16 | 17 | #include <pthread.h> |
17 | 18 | #include <stdlib.h> |
57 | 58 | nsntrace_capture_stop() |
58 | 59 | { |
59 | 60 | pcap_breakloop(handle); |
60 | pthread_join(capture_thread, NULL); | |
61 | 61 | } |
62 | 62 | |
63 | 63 | int |
64 | 64 | nsntrace_capture_start(const char *iface, |
65 | 65 | const char *filter, |
66 | const char *outfile) | |
66 | FILE *fp) | |
67 | 67 | { |
68 | 68 | char errbuf[PCAP_ERRBUF_SIZE]; |
69 | struct bpf_program fp; | |
69 | struct bpf_program bpf_fp; | |
70 | 70 | int ret; |
71 | 71 | |
72 | 72 | if (!(handle = pcap_open_live(iface, BUFSIZ, 1, 1000, errbuf))) { |
75 | 75 | } |
76 | 76 | |
77 | 77 | if (filter) { |
78 | ret = pcap_compile(handle, &fp, filter, | |
78 | ret = pcap_compile(handle, &bpf_fp, filter, | |
79 | 79 | 0, PCAP_NETMASK_UNKNOWN); |
80 | 80 | if (ret < 0) { |
81 | 81 | fprintf(stderr, "Failed to set filter: %s\n", filter); |
82 | 82 | return EXIT_FAILURE; |
83 | 83 | } else { |
84 | pcap_setfilter(handle, &fp); | |
84 | pcap_setfilter(handle, &bpf_fp); | |
85 | 85 | } |
86 | 86 | } |
87 | 87 | |
88 | if (!(pcap_dumper = pcap_dump_open(handle, outfile))) { | |
89 | fprintf(stderr, "Couldn't open output: %s: %s\n", | |
90 | outfile, pcap_geterr(handle)); | |
88 | if (!(pcap_dumper = pcap_dump_fopen(handle, fp))) { | |
89 | fprintf(stderr, "Couldn't open output: %s\n", pcap_geterr(handle)); | |
91 | 90 | return EXIT_FAILURE; |
92 | 91 | } |
93 | 92 | |
116 | 115 | char * |
117 | 116 | nsntrace_capture_default_device() |
118 | 117 | { |
119 | return pcap_lookupdev(NULL); | |
118 | pcap_if_t *interfaces; | |
119 | char *name; | |
120 | ||
121 | if (pcap_findalldevs(&interfaces, NULL) < 0) { | |
122 | return NULL; | |
123 | } | |
124 | ||
125 | name = strndup(interfaces->name, IFNAMSIZ); | |
126 | pcap_freealldevs(interfaces); | |
127 | ||
128 | return name; | |
120 | 129 | } |
121 | 130 | |
122 | 131 | int |
124 | 133 | { |
125 | 134 | pcap_if_t *dev; |
126 | 135 | pcap_if_t *devList; |
136 | int ret = EXIT_FAILURE; | |
127 | 137 | |
128 | 138 | char errbuf[PCAP_ERRBUF_SIZE]; |
129 | 139 | |
134 | 144 | |
135 | 145 | for (dev = devList; dev != NULL; dev = dev->next) { |
136 | 146 | if (strcmp(dev->name, iface) == 0) { |
137 | return EXIT_SUCCESS; | |
147 | ret = EXIT_SUCCESS; | |
148 | goto out; | |
138 | 149 | } |
139 | 150 | } |
140 | 151 | |
141 | 152 | fprintf(stderr, "Unknown interface: %s\n", iface); |
142 | ||
143 | return EXIT_FAILURE; | |
153 | out: | |
154 | pcap_freealldevs(devList); | |
155 | return ret; | |
144 | 156 | } |
17 | 17 | |
18 | 18 | int nsntrace_capture_start(const char *iface, |
19 | 19 | const char *filter, |
20 | const char *outfile); | |
20 | FILE *fp); | |
21 | 21 | |
22 | 22 | void nsntrace_capture_stop(); |
23 | 23 |
13 | 13 | * |
14 | 14 | */ |
15 | 15 | |
16 | #define _GNU_SOURCE | |
17 | #include <errno.h> | |
16 | 18 | #include <fcntl.h> |
17 | 19 | #include <sys/socket.h> |
18 | 20 | #include <linux/if.h> |
26 | 28 | #include <netlink/route/link/bonding.h> |
27 | 29 | #include <netlink/route/link/veth.h> |
28 | 30 | #include <netlink/route/route.h> |
31 | #include <resolv.h> | |
32 | #include <sched.h> | |
29 | 33 | #include <sys/ioctl.h> |
34 | #include <sys/mount.h> | |
30 | 35 | #include <sys/socket.h> |
36 | #include <sys/stat.h> | |
31 | 37 | #include <unistd.h> |
32 | 38 | |
33 | 39 | #include "cmd.h" |
40 | #include "net.h" | |
34 | 41 | |
35 | 42 | /* |
36 | 43 | * Netlink is an IPC mechanism used between the kernel and user space |
274 | 281 | } |
275 | 282 | |
276 | 283 | /* |
284 | * This function will create a namespace local resolv.conf that will | |
285 | * take precedence over the system-wide one. It uses the black magic | |
286 | * of mount namespaces. | |
287 | * | |
288 | * It is a best-effort thing, so it will not return any error, but report | |
289 | * when things go bad. | |
290 | */ | |
291 | static void | |
292 | _nsntrace_net_create_resolv_conf() | |
293 | { | |
294 | int fd; | |
295 | const char *resolv = "nameserver 9.9.9.9\n" /* Quad9 */ | |
296 | "nameserver 1.1.1.1\n" /* Cloudflare */ | |
297 | "nameserver 8.8.8.8\n" /* Google */ | |
298 | "nameserver 208.67.222.222\n"; /* OpenDNS */ | |
299 | ||
300 | if (mkdir(NSNTRACE_RUN_DIR, 0644) < 0) { | |
301 | if (errno != EEXIST) { | |
302 | perror("mkdir"); | |
303 | return; | |
304 | } | |
305 | } | |
306 | ||
307 | char resolv_path[] = NSNTRACE_RUN_DIR "/resolv.confXXXXXX"; | |
308 | if ((fd = mkstemp(resolv_path)) < 0) { | |
309 | perror("mkstemp"); | |
310 | return; | |
311 | } | |
312 | ||
313 | if (write(fd, resolv, strlen(resolv) + 1) < 0) { | |
314 | perror("write"); | |
315 | close(fd); | |
316 | return; | |
317 | } | |
318 | ||
319 | close(fd); | |
320 | ||
321 | /* | |
322 | * Here we will get a bit tricky. In order to have our own nameservers | |
323 | * for our network namespace we will bind mount our own resolv.conf | |
324 | * over the system-wide one at /etc/resolv.conf. | |
325 | * | |
326 | * But we do not want to affect others outside this namespace. So we | |
327 | * enter a mount namespace (CLONE_NEWNS) before we do the bind mount. This, | |
328 | * however, is not enough. Before the bind mount we need to remount the | |
329 | * root partition with the MS_SLAVE and MS_REC flags to make sure our | |
330 | * changes are not propagated to the outside mount namespace. As some | |
331 | * systems have that as the default behavior. | |
332 | */ | |
333 | ||
334 | /* enter new mount namespace */ | |
335 | if (unshare(CLONE_NEWNS) < 0) { | |
336 | perror("unshare"); | |
337 | return; | |
338 | } | |
339 | ||
340 | /* remount root to avoid propagating changes to subtrees */ | |
341 | if (mount("", "/", "none", MS_SLAVE | MS_REC, NULL)) { | |
342 | perror("mount"); | |
343 | return; | |
344 | } | |
345 | ||
346 | /* bind mount our resolv.conf over system-wide */ | |
347 | if (mount(resolv_path, "/etc/resolv.conf", "none", MS_BIND, NULL) < 0) { | |
348 | perror("mount"); | |
349 | return; | |
350 | } | |
351 | } | |
352 | ||
353 | /* | |
354 | * Here we use the resolver(3) family of functions to iterate through the | |
355 | * configured nameservers. | |
356 | * | |
357 | * On many systems today the nameserver functionality is handled by an | |
358 | * application such as systemd-resolved or dnsmasq and the nameserver address | |
359 | * is a loopback address (like 127.0.0.53) where that application listens for | |
360 | * incoming DNS queries. | |
361 | * | |
362 | * This will not work for us in this network namespace environment, since | |
363 | * we have our own namespaced loopback device. | |
364 | */ | |
365 | static void | |
366 | _nsntrace_net_check_dns() | |
367 | { | |
368 | struct __res_state resolv_state = { 0, }; | |
369 | ||
370 | if (res_ninit(&resolv_state) < 0) { | |
371 | return; | |
372 | } | |
373 | ||
374 | /* iterate configured nameservers */ | |
375 | for (int i = 0; i < MAXNS; i++) { | |
376 | struct in_addr addr = resolv_state.nsaddr_list[i].sin_addr; | |
377 | if (addr.s_addr == 0) { | |
378 | continue; | |
379 | } | |
380 | /* s_addr is a 32 bit integer representing the ipv4 address */ | |
381 | unsigned char first = addr.s_addr & 0x000000FF; | |
382 | if (first == 127) | |
383 | continue; | |
384 | return; /* non-loopback nameserver found */ | |
385 | } | |
386 | ||
387 | /* we end up here if we did not find any non-loopback nameserver */ | |
388 | fprintf(stderr, | |
389 | "Warning: only loopback (127.x.y.z) nameservers found.\n" | |
390 | "This means we will probably not be able to resolve hostnames.\n" | |
391 | "Consider passing --use-public-dns to use public nameservers from\n" | |
392 | "Quad9, Cloudflare, Google and OpenDNS\n\n"); | |
393 | } | |
394 | ||
395 | /* | |
277 | 396 | * Set up the environment needed from the root network namespace point |
278 | 397 | * of view. Create virtual ethernet interface (see above) and set our side |
279 | 398 | * of it up and set address. |
329 | 448 | * Set up the namespaced net infrastructure needed. |
330 | 449 | */ |
331 | 450 | int |
332 | nsntrace_net_ns_init() | |
451 | nsntrace_net_ns_init(int use_public_dns) | |
333 | 452 | { |
334 | 453 | int ret = 0; |
335 | 454 | |
341 | 460 | return EXIT_FAILURE; |
342 | 461 | } |
343 | 462 | |
463 | if (!use_public_dns) { | |
464 | _nsntrace_net_check_dns(); | |
465 | } else { | |
466 | _nsntrace_net_create_resolv_conf(); | |
467 | } | |
344 | 468 | |
345 | 469 | return ret; |
346 | 470 | } |
15 | 15 | #ifndef _NSNTRACE_NET_H_ |
16 | 16 | #define _NSNTRACE_NET_H |
17 | 17 | |
18 | #define NSNTRACE_RUN_DIR "/run/nsntrace" | |
19 | ||
18 | 20 | int nsntrace_net_init(pid_t ns_pid, |
19 | 21 | const char *device); |
20 | 22 | |
21 | 23 | int nsntrace_net_deinit(const char *device); |
22 | 24 | |
23 | int nsntrace_net_ns_init(); | |
25 | int nsntrace_net_ns_init(int use_public_dns); | |
24 | 26 | |
25 | 27 | int nsntrace_net_ip_forward_enabled(); |
26 | 28 |
13 | 13 | * |
14 | 14 | */ |
15 | 15 | #define _GNU_SOURCE |
16 | #include <errno.h> | |
17 | #include <fcntl.h> | |
18 | #include <ftw.h> | |
16 | 19 | #include <grp.h> |
17 | 20 | #include <getopt.h> |
21 | #include <linux/limits.h> | |
18 | 22 | #include <sched.h> |
19 | 23 | #include <signal.h> |
20 | 24 | #include <stdio.h> |
21 | 25 | #include <stdlib.h> |
22 | 26 | #include <string.h> |
27 | #include <sys/mount.h> | |
23 | 28 | #include <sys/wait.h> |
29 | #include <sys/stat.h> | |
24 | 30 | #include <time.h> |
25 | 31 | #include <unistd.h> |
26 | 32 | #include <pwd.h> |
61 | 67 | |
62 | 68 | #define STACK_SIZE (1024 * 64) /* 64 kB stack */ |
63 | 69 | #define DEFAULT_OUTFILE "nsntrace.pcap" |
70 | #define NETNS_RUN_DIR "/run/netns" | |
71 | #define PROC_SELF_NETNS_PATH "/proc/self/ns/net" | |
72 | ||
73 | static FILE *where; | |
64 | 74 | |
65 | 75 | struct nsntrace_options { |
66 | 76 | char *outfile; |
67 | 77 | char *device; |
78 | int use_public_dns; | |
68 | 79 | char *user; |
69 | 80 | char *filter; |
70 | 81 | char * const *args; |
71 | 82 | }; |
72 | 83 | |
84 | #define PUBLIC_DNS 1000 | |
73 | 85 | static const char *short_opt = "+o:d:u:f:h"; |
74 | 86 | static struct option long_opt[] = { |
75 | 87 | { "outfile", required_argument, NULL, 'o' }, |
76 | 88 | { "device", required_argument, NULL, 'd' }, |
89 | { "use-public-dns", no_argument, NULL, PUBLIC_DNS }, | |
77 | 90 | { "user", required_argument, NULL, 'u' }, |
78 | 91 | { "filter", required_argument, NULL, 'f' }, |
79 | 92 | { "help", required_argument, NULL, 'h' }, |
121 | 134 | } |
122 | 135 | } |
123 | 136 | |
137 | static int _nsntrace_unlink_cb(const char *fpath, | |
138 | const struct stat *sb, | |
139 | int typeflag, | |
140 | struct FTW *ftwbuf) | |
141 | { | |
142 | int rv = remove(fpath); | |
143 | if (rv) | |
144 | perror(fpath); | |
145 | return rv; | |
146 | } | |
147 | ||
148 | static void _nsntrace_remove_ns() | |
149 | { | |
150 | char netns_path[PATH_MAX]; | |
151 | ||
152 | snprintf(netns_path, sizeof(netns_path), "%s/nsntrace-%d", NETNS_RUN_DIR, getpid()); | |
153 | umount2(netns_path, MNT_DETACH); | |
154 | if (unlink(netns_path) < 0) { | |
155 | perror("unlink"); | |
156 | } | |
157 | ||
158 | /* clean up run-time files */ | |
159 | nftw(NSNTRACE_RUN_DIR, _nsntrace_unlink_cb, 64, FTW_DEPTH | FTW_PHYS); | |
160 | } | |
161 | ||
124 | 162 | static void |
125 | 163 | _nsntrace_cleanup_ns() |
126 | 164 | { |
127 | 165 | kill(child_pid, SIGTERM); |
128 | 166 | waitpid(child_pid, NULL, 0); |
129 | 167 | |
130 | printf("Finished capturing %lu packets.\n", | |
131 | nsntrace_capture_packet_count()); | |
168 | _nsntrace_remove_ns(); | |
169 | ||
170 | fprintf(where, "Finished capturing %lu packets.\n", | |
171 | nsntrace_capture_packet_count()); | |
132 | 172 | nsntrace_capture_flush(); |
133 | 173 | } |
134 | 174 | |
146 | 186 | * terminating signals. We need to clean up after |
147 | 187 | * our children. |
148 | 188 | */ |
149 | printf("Capture interrupted, cleaning up\n"); | |
150 | ||
151 | /* wait for our children */ | |
152 | 189 | wait(NULL); |
190 | } | |
191 | ||
192 | /* | |
193 | * This will give the network namespace a name in the eyes of tools like | |
194 | * ip-netns. We need to make sure we clean up after us, since a bind mount | |
195 | * of /proc/<pid>/ns/net will keep the network namespace alive after the | |
196 | * process exits. | |
197 | */ | |
198 | static void | |
199 | _nsntrace_set_name() | |
200 | { | |
201 | char netns_path[PATH_MAX]; | |
202 | int fd; | |
203 | int ret = 0; | |
204 | ||
205 | /* Create the base netns directory if it doesn't exist */ | |
206 | if ((ret = mkdir(NETNS_RUN_DIR, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) < 0) { | |
207 | if (errno != EEXIST) { | |
208 | goto out; | |
209 | } | |
210 | } | |
211 | ||
212 | snprintf(netns_path, PATH_MAX, "%s/nsntrace-%d", NETNS_RUN_DIR, getpid()); | |
213 | fd = open(netns_path, O_RDONLY|O_CREAT|O_EXCL, 0); | |
214 | if (fd < 0) { | |
215 | ret = fd; | |
216 | goto out; | |
217 | } | |
218 | close(fd); | |
219 | ||
220 | ret = mount(PROC_SELF_NETNS_PATH, netns_path, "none", MS_BIND, NULL); | |
221 | out: | |
222 | if (ret < 0) { | |
223 | fprintf(stderr, "failed to set namespace name\n"); | |
224 | } | |
153 | 225 | } |
154 | 226 | |
155 | 227 | static void |
159 | 231 | const char *ip; |
160 | 232 | const char *interface; |
161 | 233 | |
234 | /* | |
235 | * If outfile is a lone dash ("-"), the user wants us to output to STDOUT, | |
236 | * in that case we avoid printing anything else to STDOUT and use STDERR | |
237 | * instead. | |
238 | */ | |
239 | FILE *fp; | |
240 | if (options->outfile[0] == '-' && options->outfile[1] == '\0') { | |
241 | where = stderr; | |
242 | fp = stdout; | |
243 | } else { | |
244 | where = stdout; | |
245 | fp = fopen(options->outfile, "w"); | |
246 | if (!fp) { | |
247 | perror("fopen"); | |
248 | exit(EXIT_FAILURE); | |
249 | } | |
250 | } | |
251 | ||
162 | 252 | ip = nsntrace_net_get_capture_ip(); |
163 | 253 | interface = nsntrace_net_get_capture_interface(); |
164 | 254 | |
165 | printf("Starting network trace of '%s' on interface %s.\n" | |
166 | "Your IP address in this trace is %s.\n" | |
167 | "Use ctrl-c to end at any time.\n\n", | |
168 | options->args[0], options->device, ip); | |
169 | ret = nsntrace_capture_start(interface, options->filter, options->outfile); | |
255 | fprintf(where, "Starting network trace of '%s' on interface %s.\n" | |
256 | "Your IP address in this trace is %s.\n" | |
257 | "Use ctrl-c to end at any time.\n\n", | |
258 | options->args[0], options->device, ip); | |
259 | ret = nsntrace_capture_start(interface, options->filter, fp); | |
170 | 260 | if (ret != 0) { |
171 | 261 | exit(ret); |
172 | 262 | } |
234 | 324 | exit(EXIT_FAILURE); |
235 | 325 | } |
236 | 326 | |
327 | /* | |
328 | * If outfile is a lone dash we output only PCAP data on STDOUT. | |
329 | * We use the dup2 syscall to redirect the tracee output to STDERR. | |
330 | */ | |
331 | if (options->outfile[0] == '-' && options->outfile[1] == '\0') { | |
332 | dup2(STDERR_FILENO, STDOUT_FILENO); | |
333 | } | |
334 | ||
237 | 335 | /* launch the application to trace */ |
238 | 336 | if (execvp(options->args[0], options->args) < 0) { |
239 | 337 | fprintf(stderr, "Unable to start '%s'\n", options->args[0]); |
247 | 345 | int ret = EXIT_SUCCESS; |
248 | 346 | struct nsntrace_options *options = (struct nsntrace_options *) arg; |
249 | 347 | |
250 | if (nsntrace_net_ns_init() < 0) { | |
348 | if (nsntrace_net_ns_init(options->use_public_dns) < 0) { | |
251 | 349 | fprintf(stderr, "failed to setup network environment\n"); |
252 | 350 | return EXIT_FAILURE; |
253 | 351 | } |
254 | 352 | |
255 | 353 | _nsntrace_handle_signals(_nsntrace_cleanup_ns_signal_callback); |
256 | 354 | _nsntrace_start_tracer(options); |
355 | ||
356 | _nsntrace_set_name(); | |
257 | 357 | |
258 | 358 | child_pid = fork(); |
259 | 359 | if (child_pid < 0) { |
292 | 392 | static void |
293 | 393 | _nsntrace_usage() |
294 | 394 | { |
295 | printf("usage: nsntrace [options] program [arguments]\n" | |
395 | fprintf(stderr, "usage: nsntrace [options] program [arguments]\n" | |
296 | 396 | "Perform network trace of a single process by using " |
297 | 397 | "network namespaces.\n\n" |
298 | 398 | "Options:\n" |
299 | "-o file\t\tsend trace output to file (default nsntrace.pcap)\n" | |
300 | "-d device\tthe network device to trace\n" | |
301 | "-f filter\tan optional capture filter\n" | |
302 | "-u username\trun PROG as username/uid\n"); | |
399 | "-o file\t\t\tsend trace output to file (default nsntrace.pcap), use '-' for stdout\n" | |
400 | "-d device\t\tthe network device to trace\n" | |
401 | "-f filter\t\tan optional capture filter\n" | |
402 | "-u username\t\trun PROG as username/uid\n" | |
403 | "--use-public-dns\toverride resolv.conf to use public nameservers from\n" | |
404 | "\t\t\tQuad9, Cloudflare, Google and OpenDNS\n"); | |
303 | 405 | } |
304 | 406 | |
305 | 407 | static void |
331 | 433 | options->filter = strdup(optarg); |
332 | 434 | break; |
333 | 435 | |
436 | case PUBLIC_DNS: | |
437 | options->use_public_dns = 1; | |
438 | break; | |
439 | ||
334 | 440 | case 'h': |
335 | 441 | _nsntrace_usage(); |
336 | 442 | exit(EXIT_SUCCESS); |
344 | 450 | } |
345 | 451 | |
346 | 452 | if (!options->device) { |
347 | options->device = strdup(nsntrace_capture_default_device()); | |
453 | options->device = nsntrace_capture_default_device(); | |
348 | 454 | } |
349 | 455 | if (!options->outfile) { |
350 | 456 | options->outfile = strdup(DEFAULT_OUTFILE); |
396 | 502 | if ((ret = nsntrace_net_init(pid, options.device)) < 0 || |
397 | 503 | (ret = nsntrace_capture_check_device(options.device))) { |
398 | 504 | fprintf(stderr, "Failed to setup networking environment\n"); |
399 | kill(pid, SIGTERM); | |
505 | kill(pid, SIGKILL); | |
400 | 506 | goto out; |
401 | 507 | } |
402 | 508 |