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