// Copyright 2017 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUFSIZE 2048 #define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1)) typedef struct { const char* device; bool raw; bool link_level; bool promisc; size_t packet_count; size_t verbose_level; int dumpfile; } netdump_options_t; typedef struct { uint32_t type; uint32_t blk_tot_len; uint32_t magic; uint16_t major; uint16_t minor; uint64_t section_len; // TODO(smklein): Add options here uint32_t blk_tot_len2; } __attribute__((packed)) pcap_shb_t; typedef struct { uint32_t type; uint32_t blk_tot_len; uint16_t linktype; uint16_t reserved; uint32_t snaplen; uint32_t blk_tot_len2; } __attribute__((packed)) pcap_idb_t; typedef struct { uint32_t type; uint32_t blk_tot_len; uint32_t pkt_len; } __attribute__((packed)) simple_pkt_t; #define SIMPLE_PKT_MIN_SIZE (sizeof(simple_pkt_t) + sizeof(uint32_t)) static void print_mac(const uint8_t mac[ETH_ALEN]) { printf("%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } static const char* ethtype_to_string(uint16_t ethtype) { switch (ethtype) { case ETH_P_IP: return "IPv4"; case ETH_P_ARP: return "ARP"; case ETH_P_IPV6: return "IPV6"; case ETH_P_8021Q: return "802.1Q"; default: return "Unknown"; } } static const char* protocol_to_string(uint8_t protocol) { switch (protocol) { case IPPROTO_HOPOPTS: return "HOPOPTS"; case IPPROTO_TCP: return "TCP"; case IPPROTO_UDP: return "UDP"; case IPPROTO_ICMP: return "ICMP"; case IPPROTO_ROUTING: return "ROUTING"; case IPPROTO_FRAGMENT: return "FRAGMENT"; case IPPROTO_ICMPV6: return "ICMPV6"; case IPPROTO_NONE: return "NONE"; default: return "Transport Unknown"; } } static const char* port_to_string(uint16_t port) { switch (port) { case 7: return "Echo"; case 20: return "FTP xfer"; case 21: return "FTP ctl"; case 22: return "SSH"; case 23: return "Telnet"; case 53: return "DNS"; case 69: return "TFTP"; case 80: return "HTTP"; case 115: return "SFTP"; case 123: return "NTP"; case 194: return "IRC"; case 443: return "HTTPS"; case DEBUGLOG_PORT: return "Netboot Debug"; case DEBUGLOG_ACK_PORT: return "Netboot Debug ack"; default: return ""; } } static void print_port(uint16_t port, size_t verbosity) { const char* str = port_to_string(port); if (verbosity && strcmp(str, "")) { printf(":%u (%s) ", port, str); } else { printf(":%u ", port); } } void parse_packet(void* packet, size_t length, netdump_options_t* options) { struct ethhdr* frame = (struct ethhdr*)(packet); if (length < ETH_ZLEN) { printf("Packet size (%lu) too small for ethernet frame\n", length); if (options->verbose_level == 2) { hexdump8_ex(packet, length, 0); } return; } uint16_t ethtype = htons(frame->h_proto); if (options->link_level) { print_mac(frame->h_source); printf(" > "); print_mac(frame->h_dest); printf(", ethertype %s (0x%x), ", ethtype_to_string(ethtype), ethtype); } struct iphdr* ipv4 = (struct iphdr*)(packet + sizeof(struct ethhdr)); char buf[256]; void* transport_packet = NULL; uint8_t transport_protocol; if (ipv4->version == 4) { printf("IP4 "); printf("%s > ", inet_ntop(AF_INET, &ipv4->saddr, buf, sizeof(buf))); printf("%s: ", inet_ntop(AF_INET, &ipv4->daddr, buf, sizeof(buf))); printf("%s, ", protocol_to_string(ipv4->protocol)); printf("length %u, ", ntohs(ipv4->tot_len)); transport_packet = (void*)((uintptr_t) ipv4 + sizeof(struct iphdr) + (ipv4->ihl > 5 ? ipv4->ihl * 4 : 0)); transport_protocol = ipv4->protocol; } else if (ipv4->version == 6) { ip6_hdr_t* ipv6 = (ip6_hdr_t*) ipv4; printf("IP6 "); printf("%s > ", inet_ntop(AF_INET6, &ipv6->src.u8, buf, sizeof(buf))); printf("%s: ", inet_ntop(AF_INET6, &ipv6->dst.u8, buf, sizeof(buf))); printf("%s, ", protocol_to_string(ipv6->next_header)); printf("length %u, ", ntohs(ipv6->length)); transport_packet = (void*)((uintptr_t) ipv6 + sizeof(ip6_hdr_t)); transport_protocol = ipv6->next_header; } else { printf("IP Version Unknown (or unhandled)"); } if (transport_packet != NULL) { if (transport_protocol == IPPROTO_TCP) { struct tcphdr* tcp = (struct tcphdr*) transport_packet; printf("Ports "); print_port(ntohs(tcp->source), options->verbose_level); printf("> "); print_port(ntohs(tcp->dest), options->verbose_level); } else if (transport_protocol == IPPROTO_UDP) { struct udphdr* udp = (struct udphdr*) transport_packet; printf("Ports "); print_port(ntohs(udp->uh_sport), options->verbose_level); printf("> "); print_port(ntohs(udp->uh_dport), options->verbose_level); } else { printf("Transport Version Unknown (or unhandled)"); } } printf("\n"); } int write_shb(int fd) { if (fd == -1) { return 0; } pcap_shb_t shb = { .type = 0x0A0D0D0A, .blk_tot_len = sizeof(pcap_shb_t), .magic = 0x1A2B3C4D, .major = 1, .minor = 0, .section_len = 0xFFFFFFFFFFFFFFFF, .blk_tot_len2 = sizeof(pcap_shb_t), }; if (write(fd, &shb, sizeof(shb)) != sizeof(shb)) { fprintf(stderr, "Couldn't write PCAP Section Header block\n"); return -1; } return 0; } int write_idb(int fd) { if (fd == -1) { return 0; } pcap_idb_t idb = { .type = 0x00000001, .blk_tot_len = sizeof(pcap_idb_t), .linktype = 1, .reserved = 0, // We can't use a zero here, but tcpdump also rejects 2^32 - 1. Try 2^16 - 1. // See http://seclists.org/tcpdump/2012/q2/8. .snaplen = 0xFFFF, .blk_tot_len2 = sizeof(pcap_idb_t), }; if (write(fd, &idb, sizeof(idb)) != sizeof(idb)) { fprintf(stderr, "Couldn't write PCAP Interface Description Block\n"); return -1; } return 0; } int write_packet(int fd, void* data, size_t len) { if (fd == -1) { return 0; } size_t padded_len = ROUNDUP(len, 4); simple_pkt_t pkt = { .type = 0x00000003, .blk_tot_len = SIMPLE_PKT_MIN_SIZE + padded_len, .pkt_len = len, }; // TODO(tkilbourn): rewrite this to offload writing to another thread, and also deal with // partial writes if (write(fd, &pkt, sizeof(pkt)) != sizeof(pkt)) { fprintf(stderr, "Couldn't write packet header\n"); return -1; } if (write(fd, data, len) != (ssize_t) len) { fprintf(stderr, "Couldn't write packet\n"); return -1; } if (padded_len > len) { size_t padding = padded_len - len; ZX_DEBUG_ASSERT(padding <= 3); static const uint32_t zero = 0; if (write(fd, &zero, padding) != (ssize_t) padding) { fprintf(stderr, "Couldn't write padding\n"); return -1; } } if (write(fd, &pkt.blk_tot_len, sizeof(pkt.blk_tot_len)) != sizeof(pkt.blk_tot_len)) { fprintf(stderr, "Couldn't write packet footer\n"); return -1; } return 0; } void handle_rx(zx_handle_t rx_fifo, char* iobuf, unsigned count, netdump_options_t* options) { eth_fifo_entry_t entries[count]; if (write_shb(options->dumpfile)) { return; } if (write_idb(options->dumpfile)) { return; } for (;;) { size_t n; zx_status_t status; if ((status = zx_fifo_read(rx_fifo, sizeof(entries[0]), entries, countof(entries), &n)) < 0) { if (status == ZX_ERR_SHOULD_WAIT) { zx_object_wait_one(rx_fifo, ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, ZX_TIME_INFINITE, NULL); continue; } fprintf(stderr, "netdump: failed to read rx packets: %d\n", status); return; } eth_fifo_entry_t* e = entries; for (size_t i = 0; i < n; i++, e++) { if (e->flags & ETH_FIFO_RX_OK) { if (options->raw) { printf("---\n"); hexdump8_ex(iobuf + e->offset, e->length, 0); } else { parse_packet(iobuf + e->offset, e->length, options); } if (write_packet(options->dumpfile, iobuf + e->offset, e->length)) { return; } options->packet_count--; if (options->packet_count == 0) { return; } } e->length = BUFSIZE; e->flags = 0; if ((status = zx_fifo_write(rx_fifo, sizeof(*e), e, 1, NULL)) < 0) { fprintf(stderr, "netdump: failed to queue rx packet: %d\n", status); break; } } } } int usage(void) { fprintf(stderr, "usage: netdump [