1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <fuchsia/hardware/ethernet/c/fidl.h>
6 #include <inet6/inet6.h>
7 #include <lib/fdio/util.h>
8 #include <pretty/hexdump.h>
9 #include <zircon/assert.h>
10 #include <zircon/boot/netboot.h>
11 #include <zircon/process.h>
12 #include <zircon/syscalls.h>
13
14 #include <arpa/inet.h>
15 #include <fcntl.h>
16 #include <limits.h>
17 #include <netinet/if_ether.h>
18 #include <netinet/tcp.h>
19 #include <netinet/udp.h>
20 #include <netinet/ip.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #define BUFSIZE 2048
27 #define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1))
28
29 typedef struct {
30 const char* device;
31 bool raw;
32 bool link_level;
33 bool promisc;
34 size_t packet_count;
35 size_t verbose_level;
36 int dumpfile;
37 } netdump_options_t;
38
39 typedef struct {
40 uint32_t type;
41 uint32_t blk_tot_len;
42 uint32_t magic;
43 uint16_t major;
44 uint16_t minor;
45 uint64_t section_len;
46 // TODO(smklein): Add options here
47 uint32_t blk_tot_len2;
48 } __attribute__((packed)) pcap_shb_t;
49
50 typedef struct {
51 uint32_t type;
52 uint32_t blk_tot_len;
53 uint16_t linktype;
54 uint16_t reserved;
55 uint32_t snaplen;
56 uint32_t blk_tot_len2;
57 } __attribute__((packed)) pcap_idb_t;
58
59 typedef struct {
60 uint32_t type;
61 uint32_t blk_tot_len;
62 uint32_t pkt_len;
63 } __attribute__((packed)) simple_pkt_t;
64
65 #define SIMPLE_PKT_MIN_SIZE (sizeof(simple_pkt_t) + sizeof(uint32_t))
66
print_mac(const uint8_t mac[ETH_ALEN])67 static void print_mac(const uint8_t mac[ETH_ALEN]) {
68 printf("%02x:%02x:%02x:%02x:%02x:%02x",
69 mac[0], mac[1], mac[2],
70 mac[3], mac[4], mac[5]);
71 }
72
ethtype_to_string(uint16_t ethtype)73 static const char* ethtype_to_string(uint16_t ethtype) {
74 switch (ethtype) {
75 case ETH_P_IP: return "IPv4";
76 case ETH_P_ARP: return "ARP";
77 case ETH_P_IPV6: return "IPV6";
78 case ETH_P_8021Q: return "802.1Q";
79 default: return "Unknown";
80 }
81 }
82
protocol_to_string(uint8_t protocol)83 static const char* protocol_to_string(uint8_t protocol) {
84 switch (protocol) {
85 case IPPROTO_HOPOPTS: return "HOPOPTS";
86 case IPPROTO_TCP: return "TCP";
87 case IPPROTO_UDP: return "UDP";
88 case IPPROTO_ICMP: return "ICMP";
89 case IPPROTO_ROUTING: return "ROUTING";
90 case IPPROTO_FRAGMENT: return "FRAGMENT";
91 case IPPROTO_ICMPV6: return "ICMPV6";
92 case IPPROTO_NONE: return "NONE";
93 default: return "Transport Unknown";
94 }
95 }
96
port_to_string(uint16_t port)97 static const char* port_to_string(uint16_t port) {
98 switch (port) {
99 case 7: return "Echo";
100 case 20: return "FTP xfer";
101 case 21: return "FTP ctl";
102 case 22: return "SSH";
103 case 23: return "Telnet";
104 case 53: return "DNS";
105 case 69: return "TFTP";
106 case 80: return "HTTP";
107 case 115: return "SFTP";
108 case 123: return "NTP";
109 case 194: return "IRC";
110 case 443: return "HTTPS";
111 case DEBUGLOG_PORT: return "Netboot Debug";
112 case DEBUGLOG_ACK_PORT: return "Netboot Debug ack";
113 default: return "";
114 }
115 }
116
print_port(uint16_t port,size_t verbosity)117 static void print_port(uint16_t port, size_t verbosity) {
118 const char* str = port_to_string(port);
119 if (verbosity && strcmp(str, "")) {
120 printf(":%u (%s) ", port, str);
121 } else {
122 printf(":%u ", port);
123 }
124 }
125
parse_packet(void * packet,size_t length,netdump_options_t * options)126 void parse_packet(void* packet, size_t length, netdump_options_t* options) {
127 struct ethhdr* frame = (struct ethhdr*)(packet);
128 if (length < ETH_ZLEN) {
129 printf("Packet size (%lu) too small for ethernet frame\n", length);
130 if (options->verbose_level == 2) {
131 hexdump8_ex(packet, length, 0);
132 }
133 return;
134 }
135 uint16_t ethtype = htons(frame->h_proto);
136
137 if (options->link_level) {
138 print_mac(frame->h_source);
139 printf(" > ");
140 print_mac(frame->h_dest);
141 printf(", ethertype %s (0x%x), ", ethtype_to_string(ethtype), ethtype);
142 }
143
144 struct iphdr* ipv4 = (struct iphdr*)(packet + sizeof(struct ethhdr));
145 char buf[256];
146
147 void* transport_packet = NULL;
148 uint8_t transport_protocol;
149
150 if (ipv4->version == 4) {
151 printf("IP4 ");
152 printf("%s > ", inet_ntop(AF_INET, &ipv4->saddr, buf, sizeof(buf)));
153 printf("%s: ", inet_ntop(AF_INET, &ipv4->daddr, buf, sizeof(buf)));
154 printf("%s, ", protocol_to_string(ipv4->protocol));
155 printf("length %u, ", ntohs(ipv4->tot_len));
156 transport_packet = (void*)((uintptr_t) ipv4 + sizeof(struct iphdr) +
157 (ipv4->ihl > 5 ? ipv4->ihl * 4 : 0));
158 transport_protocol = ipv4->protocol;
159 } else if (ipv4->version == 6) {
160 ip6_hdr_t* ipv6 = (ip6_hdr_t*) ipv4;
161 printf("IP6 ");
162 printf("%s > ", inet_ntop(AF_INET6, &ipv6->src.u8, buf, sizeof(buf)));
163 printf("%s: ", inet_ntop(AF_INET6, &ipv6->dst.u8, buf, sizeof(buf)));
164 printf("%s, ", protocol_to_string(ipv6->next_header));
165 printf("length %u, ", ntohs(ipv6->length));
166 transport_packet = (void*)((uintptr_t) ipv6 + sizeof(ip6_hdr_t));
167 transport_protocol = ipv6->next_header;
168 } else {
169 printf("IP Version Unknown (or unhandled)");
170 }
171
172 if (transport_packet != NULL) {
173 if (transport_protocol == IPPROTO_TCP) {
174 struct tcphdr* tcp = (struct tcphdr*) transport_packet;
175 printf("Ports ");
176 print_port(ntohs(tcp->source), options->verbose_level);
177 printf("> ");
178 print_port(ntohs(tcp->dest), options->verbose_level);
179 } else if (transport_protocol == IPPROTO_UDP) {
180 struct udphdr* udp = (struct udphdr*) transport_packet;
181 printf("Ports ");
182 print_port(ntohs(udp->uh_sport), options->verbose_level);
183 printf("> ");
184 print_port(ntohs(udp->uh_dport), options->verbose_level);
185 } else {
186 printf("Transport Version Unknown (or unhandled)");
187 }
188 }
189
190 printf("\n");
191 }
192
write_shb(int fd)193 int write_shb(int fd) {
194 if (fd == -1) {
195 return 0;
196 }
197 pcap_shb_t shb = {
198 .type = 0x0A0D0D0A,
199 .blk_tot_len = sizeof(pcap_shb_t),
200 .magic = 0x1A2B3C4D,
201 .major = 1,
202 .minor = 0,
203 .section_len = 0xFFFFFFFFFFFFFFFF,
204 .blk_tot_len2 = sizeof(pcap_shb_t),
205 };
206
207 if (write(fd, &shb, sizeof(shb)) != sizeof(shb)) {
208 fprintf(stderr, "Couldn't write PCAP Section Header block\n");
209 return -1;
210 }
211 return 0;
212 }
213
write_idb(int fd)214 int write_idb(int fd) {
215 if (fd == -1) {
216 return 0;
217 }
218 pcap_idb_t idb = {
219 .type = 0x00000001,
220 .blk_tot_len = sizeof(pcap_idb_t),
221 .linktype = 1,
222 .reserved = 0,
223 // We can't use a zero here, but tcpdump also rejects 2^32 - 1. Try 2^16 - 1.
224 // See http://seclists.org/tcpdump/2012/q2/8.
225 .snaplen = 0xFFFF,
226 .blk_tot_len2 = sizeof(pcap_idb_t),
227 };
228
229 if (write(fd, &idb, sizeof(idb)) != sizeof(idb)) {
230 fprintf(stderr, "Couldn't write PCAP Interface Description Block\n");
231 return -1;
232 }
233
234 return 0;
235 }
236
write_packet(int fd,void * data,size_t len)237 int write_packet(int fd, void* data, size_t len) {
238 if (fd == -1) {
239 return 0;
240 }
241
242 size_t padded_len = ROUNDUP(len, 4);
243 simple_pkt_t pkt = {
244 .type = 0x00000003,
245 .blk_tot_len = SIMPLE_PKT_MIN_SIZE + padded_len,
246 .pkt_len = len,
247 };
248
249 // TODO(tkilbourn): rewrite this to offload writing to another thread, and also deal with
250 // partial writes
251 if (write(fd, &pkt, sizeof(pkt)) != sizeof(pkt)) {
252 fprintf(stderr, "Couldn't write packet header\n");
253 return -1;
254 }
255 if (write(fd, data, len) != (ssize_t) len) {
256 fprintf(stderr, "Couldn't write packet\n");
257 return -1;
258 }
259 if (padded_len > len) {
260 size_t padding = padded_len - len;
261 ZX_DEBUG_ASSERT(padding <= 3);
262 static const uint32_t zero = 0;
263 if (write(fd, &zero, padding) != (ssize_t) padding) {
264 fprintf(stderr, "Couldn't write padding\n");
265 return -1;
266 }
267 }
268 if (write(fd, &pkt.blk_tot_len, sizeof(pkt.blk_tot_len)) != sizeof(pkt.blk_tot_len)) {
269 fprintf(stderr, "Couldn't write packet footer\n");
270 return -1;
271 }
272
273 return 0;
274 }
275
handle_rx(zx_handle_t rx_fifo,char * iobuf,unsigned count,netdump_options_t * options)276 void handle_rx(zx_handle_t rx_fifo, char* iobuf, unsigned count, netdump_options_t* options) {
277 fuchsia_hardware_ethernet_FifoEntry entries[count];
278
279 if (write_shb(options->dumpfile)) {
280 return;
281 }
282 if (write_idb(options->dumpfile)) {
283 return;
284 }
285
286 for (;;) {
287 size_t n;
288 zx_status_t status;
289 if ((status = zx_fifo_read(rx_fifo, sizeof(entries[0]), entries, countof(entries), &n)) < 0) {
290 if (status == ZX_ERR_SHOULD_WAIT) {
291 zx_object_wait_one(rx_fifo, ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, ZX_TIME_INFINITE, NULL);
292 continue;
293 }
294 fprintf(stderr, "netdump: failed to read rx packets: %d\n", status);
295 return;
296 }
297
298 fuchsia_hardware_ethernet_FifoEntry* e = entries;
299 for (size_t i = 0; i < n; i++, e++) {
300 if (e->flags & fuchsia_hardware_ethernet_FIFO_RX_OK) {
301 if (options->raw) {
302 printf("---\n");
303 hexdump8_ex(iobuf + e->offset, e->length, 0);
304 } else {
305 parse_packet(iobuf + e->offset, e->length, options);
306 }
307
308 if (write_packet(options->dumpfile, iobuf + e->offset, e->length)) {
309 return;
310 }
311
312 options->packet_count--;
313 if (options->packet_count == 0) {
314 return;
315 }
316 }
317
318 e->length = BUFSIZE;
319 e->flags = 0;
320 if ((status = zx_fifo_write(rx_fifo, sizeof(*e), e, 1, NULL)) < 0) {
321 fprintf(stderr, "netdump: failed to queue rx packet: %d\n", status);
322 break;
323 }
324 }
325 }
326 }
327
usage(void)328 int usage(void) {
329 fprintf(stderr, "usage: netdump [ <option>* ] <network-device>\n");
330 fprintf(stderr, " -w file : Write packet output to file in pcapng format\n");
331 fprintf(stderr, " -c count: Exit after receiving count packets\n");
332 fprintf(stderr, " -e : Print link-level header information\n");
333 fprintf(stderr, " -p : Use promiscuous mode\n");
334 fprintf(stderr, " -v : Print verbose output\n");
335 fprintf(stderr, " -vv : Print extra verbose output\n");
336 fprintf(stderr, " --raw : Print raw bytes of all incoming packets\n");
337 fprintf(stderr, " --help : Show this help message\n");
338 return -1;
339 }
340
parse_args(int argc,const char ** argv,netdump_options_t * options)341 int parse_args(int argc, const char** argv, netdump_options_t* options) {
342 while (argc > 1) {
343 if (!strncmp(argv[0], "-c", strlen("-c"))) {
344 argv++;
345 argc--;
346 if (argc < 1) {
347 return usage();
348 }
349 char* endptr;
350 options->packet_count = strtol(argv[0], &endptr, 10);
351 if (*endptr != '\0') {
352 return usage();
353 }
354 argv++;
355 argc--;
356 } else if (!strcmp(argv[0], "-e")) {
357 argv++;
358 argc--;
359 options->link_level = true;
360 } else if (!strcmp(argv[0], "-p")) {
361 argv++;
362 argc--;
363 options->promisc = true;
364 } else if (!strcmp(argv[0], "-w")) {
365 argv++;
366 argc--;
367 if (argc < 1 || options->dumpfile != -1) {
368 return usage();
369 }
370 options->dumpfile = open(argv[0], O_WRONLY | O_CREAT);
371 if (options->dumpfile < 0) {
372 fprintf(stderr, "Error: Could not output to file: %s\n", argv[0]);
373 return usage();
374 }
375 argv++;
376 argc--;
377 } else if (!strcmp(argv[0], "-v")) {
378 argv++;
379 argc--;
380 options->verbose_level = 1;
381 } else if (!strncmp(argv[0], "-vv", sizeof("-vv"))) {
382 // Since this is the max verbosity, adding extra 'v's does nothing.
383 argv++;
384 argc--;
385 options->verbose_level = 2;
386 } else if (!strcmp(argv[0], "--raw")) {
387 argv++;
388 argc--;
389 options->raw = true;
390 } else {
391 return usage();
392 }
393 }
394
395 if (argc == 0) {
396 return usage();
397 } else if (!strcmp(argv[0], "--help")) {
398 return usage();
399 }
400
401 options->device = argv[0];
402 return 0;
403 }
404
main(int argc,const char ** argv)405 int main(int argc, const char** argv) {
406 netdump_options_t options;
407 memset(&options, 0, sizeof(options));
408 options.dumpfile = -1;
409 if (parse_args(argc - 1, argv + 1, &options)) {
410 return -1;
411 }
412
413 int fd;
414 if ((fd = open(options.device, O_RDWR)) < 0) {
415 fprintf(stderr, "netdump: cannot open '%s'\n", options.device);
416 return -1;
417 }
418
419 zx_handle_t svc;
420 zx_status_t status = fdio_get_service_handle(fd, &svc);
421 if (status != ZX_OK) {
422 fprintf(stderr, "netdump: failed to get service handle\n");
423 return -1;
424 }
425
426 fuchsia_hardware_ethernet_Fifos fifos;
427 zx_status_t call_status = ZX_OK;
428 status = fuchsia_hardware_ethernet_DeviceGetFifos(svc, &call_status, &fifos);
429 if (status != ZX_OK || call_status != ZX_OK) {
430 fprintf(stderr, "netdump: failed to get fifos: %d, %d\n", status, call_status);
431 return -1;
432 }
433
434 unsigned count = fifos.rx_depth / 2;
435 zx_handle_t iovmo;
436 // allocate shareable ethernet buffer data heap
437 if ((status = zx_vmo_create(count * BUFSIZE, ZX_VMO_NON_RESIZABLE, &iovmo)) < 0) {
438 return -1;
439 }
440
441 char* iobuf;
442 if ((status = zx_vmar_map(zx_vmar_root_self(),
443 ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
444 0, iovmo, 0, count * BUFSIZE, (uintptr_t*)&iobuf)) < 0) {
445 return -1;
446 }
447
448 status = fuchsia_hardware_ethernet_DeviceSetIOBuffer(svc, iovmo, &call_status);
449 if (status != ZX_OK || call_status != ZX_OK) {
450 fprintf(stderr, "netdump: failed to set iobuf: %d, %d\n", status, call_status);
451 return -1;
452 }
453
454 status = fuchsia_hardware_ethernet_DeviceSetClientName(svc, "netdump", 7, &call_status);
455 if (status != ZX_OK || call_status != ZX_OK) {
456 fprintf(stderr, "netdump: failed to set client name %d, %d\n", status, call_status);
457 }
458
459 if (options.promisc) {
460 status = fuchsia_hardware_ethernet_DeviceSetPromiscuousMode(svc, true, &call_status);
461 if (status != ZX_OK || call_status != ZX_OK) {
462 fprintf(stderr, "netdump: failed to set promisc mode: %d, %d\n", status, call_status);
463 }
464 }
465
466 // assign data chunks to ethbufs
467 for (unsigned n = 0; n < count; n++) {
468 fuchsia_hardware_ethernet_FifoEntry entry = {
469 .offset = n * BUFSIZE,
470 .length = BUFSIZE,
471 .flags = 0,
472 .cookie = 0,
473 };
474 if ((status = zx_fifo_write(fifos.rx, sizeof(entry), &entry, 1, NULL)) < 0) {
475 fprintf(stderr, "netdump: failed to queue rx packet: %d\n", status);
476 return -1;
477 }
478 }
479
480 status = fuchsia_hardware_ethernet_DeviceStart(svc, &call_status);
481 if (status != ZX_OK || call_status != ZX_OK) {
482 fprintf(stderr, "netdump: failed to start network interface\n");
483 return -1;
484 }
485
486 status = fuchsia_hardware_ethernet_DeviceListenStart(svc, &call_status);
487 if (status != ZX_OK || call_status != ZX_OK) {
488 fprintf(stderr, "netdump: failed to start listening\n");
489 return -1;
490 }
491
492 handle_rx(fifos.rx, iobuf, count, &options);
493
494 zx_handle_close(fifos.rx);
495 if (options.dumpfile != -1) {
496 close(options.dumpfile);
497 }
498 return 0;
499 }
500