blob: f4ce9dfcc1295668b599d07af321dbf4294c3632 [file] [log] [blame]
// 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 <arpa/inet.h>
#include <fcntl.h>
#include <fuchsia/hardware/ethernet/c/fidl.h>
#include <inttypes.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/zx/channel.h>
#include <lib/zx/clock.h>
#include <lib/zx/fifo.h>
#include <lib/zx/vmo.h>
#include <limits.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <stdlib.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/boot/netboot.h>
#include <zircon/device/ethernet.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
#include <pretty/hexdump.h>
#include "filter_builder_impl.h"
static constexpr size_t BUFSIZE = 2048;
static constexpr size_t STRBUFSIZE = 256;
static constexpr int64_t DEFAULT_TIMEOUT_SECONDS = 60;
namespace netdump {
enum class PrintFormat {
LOG,
HEXDUMP,
PCAPNG,
};
class NetdumpOptions {
public:
std::string device;
PrintFormat print_format = PrintFormat::LOG;
bool link_level = false;
bool promisc = false;
std::optional<uint64_t> packet_count = std::nullopt;
size_t verbose_level = 0;
int dumpfile_fd = -1;
zx::time timeout_deadline = zx::time::infinite();
Tokenizer tokenizer{};
parser::Parser parser{tokenizer};
FilterPtr filter = nullptr;
FilterPtr highlight_filter = nullptr;
};
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;
// Statistics tracking netdump operations
struct Stats {
// Packet counts
uint64_t pkts_filtered_in;
uint64_t pkts_filtered_out;
uint64_t pkts_len_small; // Too small to be parsed.
uint64_t pkts_eth_rx_fail; // ETH_FIFO_RX_OK flag unset
// Byte counts
uint64_t bytes_filtered_in;
uint64_t bytes_filtered_out;
uint64_t bytes_len_small;
uint64_t bytes_eth_rx_fail;
// File dump statistics
uint64_t pkts_write_ok;
uint64_t pkts_write_fail;
// Extend below.
};
static constexpr size_t SIMPLE_PKT_MIN_SIZE = sizeof(simple_pkt_t) + sizeof(uint32_t);
static std::string mac_to_string(const uint8_t mac[ETH_ALEN]) {
std::stringstream stream;
stream.flags(std::ios::hex);
stream.fill('0');
// clang-format off
// Cast is required to make `stream` not interpret input as a char.
stream << std::setw(2) << static_cast<uint16_t>(mac[0]) << ":"
<< std::setw(2) << static_cast<uint16_t>(mac[1]) << ":"
<< std::setw(2) << static_cast<uint16_t>(mac[2]) << ":"
<< std::setw(2) << static_cast<uint16_t>(mac[3]) << ":"
<< std::setw(2) << static_cast<uint16_t>(mac[4]) << ":"
<< std::setw(2) << static_cast<uint16_t>(mac[5]);
// clang-format on
return stream.str();
}
static std::string 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 std::string 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 std::string 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 inline std::string port_string_by_verbosity(uint16_t port, size_t verbosity) {
std::string port_name = port_to_string(port);
std::stringstream stream;
stream << port;
if (verbosity && !port_name.empty()) {
stream << " (" << port_name << ")";
}
return stream.str();
}
// Test if the packet should be highlit.
inline bool to_highlight(const Packet& packet, const NetdumpOptions& options) {
return (options.highlight_filter != nullptr && options.highlight_filter->match(packet));
}
// Write link level information to `stream` and return the ethtype.
uint16_t parse_l2_packet(const Packet& packet, const NetdumpOptions& options,
std::stringstream* stream) {
ZX_ASSERT(stream != nullptr);
uint16_t ethtype = ntohs(packet.eth->h_proto);
if (options.link_level) {
*stream << mac_to_string(packet.eth->h_source) << " > " << mac_to_string(packet.eth->h_dest)
<< ", ethertype " << ethtype_to_string(ethtype) << " (0x" << std::hex << ethtype
<< std::dec << "), ";
}
return ethtype;
}
// Write L3 information to `stream` and return the transport protocol number if L3 protocol is IP.
uint8_t parse_l3_packet(uint16_t ethtype, const Packet& packet, const NetdumpOptions& options,
std::stringstream* stream) {
ZX_ASSERT(stream != nullptr);
const struct iphdr* ip = packet.ip;
char buf[STRBUFSIZE];
switch (ip->version) {
case 4: {
*stream << "IP4 " << inet_ntop(AF_INET, &ip->saddr, buf, sizeof(buf)) << " > "
<< inet_ntop(AF_INET, &ip->daddr, buf, sizeof(buf)) << ": "
<< protocol_to_string(ip->protocol) << ", "
<< "length " << ntohs(ip->tot_len) << ", ";
return ip->protocol;
}
case 6: {
const struct ip6_hdr* ipv6 = packet.ipv6;
*stream << "IP6 " << inet_ntop(AF_INET6, &ipv6->ip6_src.s6_addr, buf, sizeof(buf)) << " > "
<< inet_ntop(AF_INET6, &ipv6->ip6_dst.s6_addr, buf, sizeof(buf)) << ": "
<< protocol_to_string(ipv6->ip6_nxt) << ", "
<< "length " << ntohs(ipv6->ip6_plen) << ", ";
return ipv6->ip6_nxt;
}
default: {
ZX_DEBUG_ASSERT_MSG(false, "Ethtype recognized but IP headers malformed.");
return 0;
}
}
}
void parse_l4_packet(uint8_t transport_protocol, const Packet& packet,
const NetdumpOptions& options, std::stringstream* stream) {
ZX_ASSERT(stream != nullptr);
switch (transport_protocol) {
case IPPROTO_TCP: {
const struct tcphdr* tcp = packet.tcp;
*stream << "Ports: " << port_string_by_verbosity(ntohs(tcp->source), options.verbose_level)
<< " > " << port_string_by_verbosity(ntohs(tcp->dest), options.verbose_level);
return;
}
case IPPROTO_UDP: {
const struct udphdr* udp = packet.udp;
*stream << "Ports: " << port_string_by_verbosity(ntohs(udp->uh_sport), options.verbose_level)
<< " > " << port_string_by_verbosity(ntohs(udp->uh_dport), options.verbose_level);
return;
}
default: {
ZX_DEBUG_ASSERT_MSG(false, "Transport protocol recognized but headers malformed.");
return;
}
}
}
void parse_packet(const Packet& packet, const NetdumpOptions& options) {
std::stringstream stream;
auto is_highlit = to_highlight(packet, options);
if (is_highlit) {
stream << parser::ANSI_HIGHLIGHT;
}
uint16_t ethtype = parse_l2_packet(packet, options, &stream);
if (packet.ip != nullptr) {
uint8_t transport_protocol = parse_l3_packet(ethtype, packet, options, &stream);
if (packet.transport != nullptr) {
parse_l4_packet(transport_protocol, packet, options, &stream);
} else {
stream << "L4 headers incomplete or unhandled"; // Protocol is displayed in L3 parsing.
}
} else {
stream << "L3 headers incomplete or unhandled"; // Ethtype is displayed in L2 parsing.
}
if (is_highlit) {
std::cout << stream.str() << parser::ANSI_RESET << std::endl;
} else {
std::cout << stream.str() << std::endl;
}
}
inline bool filter_packet(const NetdumpOptions& options, Packet* packet) {
if (options.filter == nullptr) {
return true;
}
return options.filter->match(*packet);
}
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)) {
std::cerr << "Couldn't write PCAP Section Header block" << std::endl;
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)) {
std::cerr << "Couldn't write PCAP Interface Description block" << std::endl;
return -1;
}
return 0;
}
inline size_t roundup(size_t a, size_t b) { return ((a) + ((b)-1)) & ~((b)-1); }
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 = static_cast<uint32_t>(SIMPLE_PKT_MIN_SIZE + padded_len),
.pkt_len = static_cast<uint32_t>(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)) {
std::cerr << "Couldn't write packet header" << std::endl;
return -1;
}
if (write(fd, data, len) != static_cast<ssize_t>(len)) {
std::cerr << "Couldn't write packet" << std::endl;
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) != static_cast<ssize_t>(padding)) {
std::cerr << "Couldn't write padding" << std::endl;
return -1;
}
}
if (write(fd, &pkt.blk_tot_len, sizeof(pkt.blk_tot_len)) != sizeof(pkt.blk_tot_len)) {
std::cerr << "Couldn't write packet footer" << std::endl;
return -1;
}
return 0;
}
void handle_rx(const zx::fifo& rx_fifo, char* iobuf, unsigned count, const NetdumpOptions& options,
Stats* stats) {
if (stats == nullptr) {
std::cerr << "Couldn't track statistics. Bail out" << std::endl;
return;
}
if (!count) {
std::cerr << "Expected non-zero count value. Bail out" << std::endl;
return;
}
eth_fifo_entry_t entries[count];
bool dumpfile_write_error =
write_shb(options.dumpfile_fd) < 0 || write_idb(options.dumpfile_fd) < 0;
bool livedump_write_error = (options.print_format == PrintFormat::PCAPNG) &&
(write_shb(STDOUT_FILENO) < 0 || write_idb(STDOUT_FILENO) < 0);
if (dumpfile_write_error || livedump_write_error) {
return;
}
Packet packet{};
uint64_t packets_remaining =
(options.packet_count == std::nullopt ? std::numeric_limits<uint64_t>::max()
: *options.packet_count);
for (; packets_remaining > 0;) {
size_t n;
zx_status_t status;
if ((status = rx_fifo.read(sizeof(entries[0]), entries, countof(entries), &n)) < 0) {
if (status == ZX_ERR_SHOULD_WAIT) {
rx_fifo.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, options.timeout_deadline, nullptr);
if (zx::clock::get_monotonic() >= options.timeout_deadline) {
return;
}
continue;
}
std::cerr << "netdump: failed to read rx packets: " << status << std::endl;
return;
}
eth_fifo_entry_t* e = entries;
for (size_t i = 0; i < n; ++i, ++e) {
if (e->flags & ETH_FIFO_RX_OK) {
void* buffer = iobuf + e->offset;
uint16_t length = e->length;
bool do_write = true; // Whether the packet is included for write-out to dumpfile.
packet.populate(buffer, length);
if (packet.eth == nullptr) {
stats->pkts_len_small++;
stats->bytes_len_small += length;
// Not setting `do_write` to false in order to record small frames.
switch (options.print_format) {
case PrintFormat::LOG:
[[fallthrough]];
case PrintFormat::HEXDUMP:
if (options.verbose_level == 2 || options.print_format == PrintFormat::HEXDUMP) {
hexdump8_very_ex(buffer, length, 0, hexdump_stdio_printf, stdout);
}
break;
case PrintFormat::PCAPNG:
if (write_packet(STDOUT_FILENO, buffer, length) < 0) {
stats->pkts_write_fail += (n - i);
return;
}
stats->pkts_write_ok++;
break;
default:
ZX_ASSERT_MSG(false, "Unknown print format.");
break;
}
} else {
// pkts_eth_rx_ok++
// bytes_eth_rx_ok += length
if (filter_packet(options, &packet)) {
stats->pkts_filtered_in++;
stats->bytes_filtered_in += length;
switch (options.print_format) {
case PrintFormat::LOG:
parse_packet(packet, options);
break;
case PrintFormat::HEXDUMP:
std::cout << "---" << std::endl;
hexdump8_very_ex(buffer, length, 0, hexdump_stdio_printf, stdout);
break;
case PrintFormat::PCAPNG:
if (write_packet(STDOUT_FILENO, buffer, length) < 0) {
stats->pkts_write_fail += (n - i);
return;
}
stats->pkts_write_ok++;
break;
default:
ZX_ASSERT_MSG(false, "Unknown print format.");
break;
}
--packets_remaining;
} else {
stats->pkts_filtered_out++;
stats->bytes_filtered_out += length;
do_write = false;
}
}
if (do_write && options.dumpfile_fd != -1) {
auto write_status = write_packet(options.dumpfile_fd, buffer, length);
if (write_status < 0) {
stats->pkts_write_fail += (n - i);
return;
}
stats->pkts_write_ok++;
}
if (packets_remaining == 0 || zx::clock::get_monotonic() >= options.timeout_deadline) {
// Normal end condition
return;
}
} else {
stats->pkts_eth_rx_fail++;
}
e->length = BUFSIZE;
e->flags = 0;
if ((status = rx_fifo.write(sizeof(*e), e, 1, nullptr)) < 0) {
std::cerr << "netdump: failed to queue rx packet: " << status << std::endl;
break;
}
}
}
}
int usage() {
// clang-format off
std::cerr << "usage: netdump [ <option>* ] <network-device>" << std::endl
<< " -t {sec} : Exit after sec seconds, default " << DEFAULT_TIMEOUT_SECONDS
<< std::endl
<< " -w file : Write packet output to file in pcapng format" << std::endl
<< " -c count : Exit after receiving count packets" << std::endl
<< " -e : Print link-level header information" << std::endl
<< " -f filter : Capture only packets specified by filter" << std::endl
<< " -i filter : Highlight packets specified by filter" << std::endl
<< " -p : Use promiscuous mode" << std::endl
<< " -v : Print verbose output" << std::endl
<< " -vv : Print extra verbose output" << std::endl
<< " --pcapdump : Print packet output in pcapng format, "
<< "can be combined with -w" << std::endl
<< " --hexdump : Print packet output in hexdump format" << std::endl
<< " --help-filter : Show filter syntax usage" << std::endl
<< " --help : Show this help message" << std::endl
<< "Filter syntax usage:" << std::endl;
// clang-format on
parser::parser_syntax(&std::cerr);
return -1;
}
using StringIterator = std::vector<const std::string>::iterator;
int parse_args(StringIterator begin, StringIterator end, NetdumpOptions* options) {
if (begin >= end) {
return usage();
}
--end;
std::string last = *end;
for (; begin < end; ++begin) {
std::string arg = *begin;
if (arg == "-c") {
++begin;
if (begin == end) {
return usage();
}
size_t num_end;
int64_t packet_count = stoll(*begin, &num_end, 10);
if (packet_count < 0 || num_end < begin->length()) {
return usage();
}
options->packet_count = std::optional(static_cast<uint64_t>(packet_count));
} else if (arg == "-e") {
options->link_level = true;
} else if (arg == "-f" || arg == "-i") {
++begin;
if (begin == end) {
return usage();
}
parser::FilterTreeBuilder builder(options->tokenizer);
auto parsed = options->parser.parse(*begin, &builder);
if (auto error = std::get_if<parser::ParseError>(&parsed)) {
std::cerr << *error << "Use '--help-filter' to see the filter syntax." << std::endl;
return -1;
}
if (arg == "-f") {
options->filter = std::move(std::get<FilterPtr>(parsed));
} else {
options->highlight_filter = std::move(std::get<FilterPtr>(parsed));
}
} else if (arg == "-p") {
options->promisc = true;
} else if (arg == "-w") {
++begin;
if (begin == end || options->dumpfile_fd != -1) {
return usage();
}
options->dumpfile_fd = open(begin->c_str(), O_WRONLY | O_CREAT);
if (options->dumpfile_fd < 0) {
std::cerr << "Error: Could not output to file: " << *begin << std::endl;
return usage();
}
} else if (arg == "-v") {
options->verbose_level = 1;
} else if (!arg.compare(0, sizeof("-vv"), "-vv")) {
// Since this is the max verbosity, adding extra 'v's does nothing.
options->verbose_level = 2;
} else if (arg == "--hexdump") {
options->print_format = PrintFormat::HEXDUMP;
} else if (arg == "--pcapdump") {
options->print_format = PrintFormat::PCAPNG;
} else if (arg == "-t") {
int64_t timeout_seconds = DEFAULT_TIMEOUT_SECONDS;
if (begin < end - 1) {
size_t num_end;
int64_t input = stoll(begin[1], &num_end, 10);
if (num_end == begin[1].length()) {
// Valid number.
if (input < 0) {
return usage();
}
timeout_seconds = input;
++begin;
}
}
options->timeout_deadline = zx::clock::get_monotonic() + zx::sec(timeout_seconds);
} else {
return usage();
}
}
if (last == "--help-filter") {
parser::parser_syntax(&std::cerr);
return -1;
}
if (last == "--help") {
return usage();
}
options->device = last;
return 0;
}
} // namespace netdump
void show_stats(const netdump::Stats& stats) {
// TODO(porce): Integration test with the emulated network.
auto pkts_len_ok = stats.pkts_filtered_in + stats.pkts_filtered_out;
auto pkts_eth_rx_ok = pkts_len_ok + stats.pkts_len_small;
auto pkts_seen = pkts_eth_rx_ok + stats.pkts_eth_rx_fail;
auto bytes_len_ok = stats.bytes_filtered_in + stats.bytes_filtered_out;
auto bytes_eth_rx_ok = bytes_len_ok + stats.bytes_len_small;
auto bytes_seen = bytes_eth_rx_ok + stats.bytes_eth_rx_fail;
// Use stderr for auxiliary info not to disturb PCAPNG live streaming.
fprintf(stderr, "\n --\nPacket capture statistics\n");
fprintf(stderr, " pkts_seen : %" PRIu64 "\n", pkts_seen);
fprintf(stderr, " pkts_filtered_in : %" PRIu64 "\n", stats.pkts_filtered_in);
fprintf(stderr, " pkts_filtered_out : %" PRIu64 "\n", stats.pkts_filtered_out);
fprintf(stderr, " pkts_eth_rx_fail : %" PRIu64 "\n", stats.pkts_eth_rx_fail);
fprintf(stderr, " pkts_len_small : %" PRIu64 "\n", stats.pkts_len_small);
fprintf(stderr, " bytes_seen : %" PRIu64 "\n", bytes_seen);
fprintf(stderr, " bytes_filtered_in : %" PRIu64 "\n", stats.bytes_filtered_in);
fprintf(stderr, " bytes_filtered_out : %" PRIu64 "\n", stats.bytes_filtered_out);
fprintf(stderr, " bytes_eth_rx_fail : %" PRIu64 "\n", stats.bytes_eth_rx_fail);
fprintf(stderr, " bytes_len_small : %" PRIu64 "\n", stats.bytes_len_small);
fprintf(stderr, " pkts_write_ok : %" PRIu64 "\n", stats.pkts_write_ok);
fprintf(stderr, " pkts_write_fail : %" PRIu64 "\n", stats.pkts_write_fail);
fprintf(stderr, "\n");
}
int main(int argc, const char** argv) {
netdump::NetdumpOptions options;
std::vector<std::string> args(argv + 1, argv + argc);
if (parse_args(args.begin(), args.end(), &options)) {
return -1;
}
int fd;
if ((fd = open(options.device.c_str(), O_RDWR)) < 0) {
std::cerr << "netdump: cannot open '" << options.device << "'" << std::endl;
return -1;
}
zx::channel svc;
zx_status_t status = fdio_get_service_handle(fd, svc.reset_and_get_address());
if (status != ZX_OK) {
std::cerr << "netdump: failed to get service handle" << std::endl;
return -1;
}
fuchsia_hardware_ethernet_Fifos fifos;
zx_status_t call_status = ZX_OK;
status = fuchsia_hardware_ethernet_DeviceGetFifos(svc.get(), &call_status, &fifos);
if (status != ZX_OK || call_status != ZX_OK) {
std::cerr << "netdump: failed to get fifos: " << status << ", " << call_status << std::endl;
return -1;
}
zx::fifo rx_fifo = zx::fifo(fifos.rx);
unsigned count = fifos.rx_depth / 2;
zx::vmo iovmo;
// Allocate shareable ethernet buffer data heap with no options. Non-resizable by default.
if ((status = zx::vmo::create(count * BUFSIZE, 0, &iovmo)) < 0) {
return -1;
}
char* iobuf;
if ((status = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, iovmo.get(),
0, count * BUFSIZE, reinterpret_cast<uintptr_t*>(&iobuf))) < 0) {
return -1;
}
status = fuchsia_hardware_ethernet_DeviceSetIOBuffer(svc.get(), iovmo.get(), &call_status);
if (status != ZX_OK || call_status != ZX_OK) {
std::cerr << "netdump: failed to set iobuf: " << status << ", " << call_status << std::endl;
return -1;
}
status = fuchsia_hardware_ethernet_DeviceSetClientName(svc.get(), "netdump", 7, &call_status);
if (status != ZX_OK || call_status != ZX_OK) {
std::cerr << "netdump: failed to set client name: " << status << ", " << call_status
<< std::endl;
}
if (options.promisc) {
status = fuchsia_hardware_ethernet_DeviceSetPromiscuousMode(svc.get(), true, &call_status);
if (status != ZX_OK || call_status != ZX_OK) {
std::cerr << "netdump: failed to set promisc mode: " << status << ", " << call_status
<< std::endl;
}
}
// assign data chunks to ethbufs
for (unsigned n = 0; n < count; ++n) {
eth_fifo_entry_t entry = {
.offset = static_cast<uint32_t>(n * BUFSIZE),
.length = BUFSIZE,
.flags = 0,
.cookie = 0,
};
if ((status = zx_fifo_write(fifos.rx, sizeof(entry), &entry, 1, nullptr)) < 0) {
std::cerr << "netdump: failed to queue rx packet: " << status << std::endl;
return -1;
}
}
status = fuchsia_hardware_ethernet_DeviceStart(svc.get(), &call_status);
if (status != ZX_OK || call_status != ZX_OK) {
std::cerr << "netdump: failed to start network interface" << std::endl;
return -1;
}
status = fuchsia_hardware_ethernet_DeviceListenStart(svc.get(), &call_status);
if (status != ZX_OK || call_status != ZX_OK) {
std::cerr << "netdump: failed to start listening" << std::endl;
return -1;
}
zx_handle_t directory_request = zx_take_startup_handle(PA_DIRECTORY_REQUEST);
if (directory_request != ZX_HANDLE_INVALID) {
// We don't serve anything, we can close the directory request immediately.
// This is used in integration tests as a signal that netdump is running and attached to the
// device; if one day netdump needs to serve anything the integration tests need to be updated.
zx_handle_close(directory_request);
}
netdump::Stats stats = {};
handle_rx(rx_fifo, iobuf, count, options, &stats);
if (options.dumpfile_fd != -1) {
close(options.dumpfile_fd);
}
if (options.print_format == netdump::PrintFormat::LOG ||
options.print_format == netdump::PrintFormat::HEXDUMP) {
show_stats(stats);
}
return 0;
}