blob: 84c94c847d1a460848349c2ebabb49d799673212 [file] [log] [blame]
// Copyright 2018 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 <inet6/inet6.h>
#include <zircon/assert.h>
#include <zircon/boot/netboot.h>
#include <zircon/device/ethernet.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <limits.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define ETH_MAC_SIZE 6
#define BUFSIZE 2048
typedef struct {
const char* device;
int pause_secs;
bool setting_promisc;
bool promisc_on;
bool dump_regs;
char* filter_macs;
int n_filter_macs;
} ethtool_options_t;
int usage(void) {
fprintf(stderr, "usage: ethtool <network-device> <time> <actions>\n");
fprintf(stderr, " network-device must start with '/dev/')\n");
fprintf(stderr, " time = how many seconds to hold the fd (before exiting)\n");
fprintf(stderr, "Actions: one of\n");
fprintf(stderr, " promisc on : Promiscuous mode on\n");
fprintf(stderr, " promisc off : Promiscuous mode off\n");
fprintf(stderr, " filter n.n.n.n.n.n n.n.n.n.n.n ... : multicast filter these addresses\n");
fprintf(stderr, " dump : Dump regs of chip\n");
fprintf(stderr, " (empty list is valid)\n");
fprintf(stderr, " --help : Show this help message\n");
return -1;
}
// Str should be nn.nn.nn.nn.nn.nn where nn is decimal 0..255 and there are ETH_MAC_SIZE (6) nn's
// Function returns non-zero if this isn't the case. (It's not fully paranoid, but shouldn't crash.)
// If input is good, first nn goes into mac[0], etc.
int parse_address(char* mac, const char* str) {
char* next_string;
for (int i = 0; i < 5; i++) {
mac[i] = (char)strtol(str, &next_string, 10);
if (next_string[0] != '.') {
return -1;
}
next_string++;
str = next_string;
}
mac[5] = (char)strtol(str, &next_string, 10);
if (next_string[0] != 0) {
return -1;
}
return 0;
}
int parse_args(int argc, const char** argv, ethtool_options_t* options) {
bool promisc_on = false;
bool promisc_off = false;
if (argc < 3) {
return usage();
}
if (strncmp(argv[0], "/dev/", strlen("/dev/"))) {
return usage();
}
options->device = argv[0];
argc--;
argv++;
char* remainder;
options->pause_secs = strtol(argv[0], &remainder, 10);
if (options->pause_secs < 0 || remainder[0] != 0) {
return usage();
}
argc--;
argv++;
if (!strcmp(argv[0], "promisc")) {
argc--;
argv++;
if (argc != 1) {
return usage();
}
if (!strcmp(argv[0], "on")) {
promisc_on = true;
} else if (!strcmp(argv[0], "off")) {
promisc_off = true;
} else {
return usage();
}
} else if (!strcmp(argv[0], "dump")) {
argc--;
argv++;
if (argc != 0) {
return usage();
}
options->dump_regs = true;
} else if (!strcmp(argv[0], "filter")) {
argc--;
argv++;
options->n_filter_macs = argc;
options->filter_macs = calloc(1, ETH_MAC_SIZE * argc);
char* addr_ptr = options->filter_macs;
while (argc--) {
if (parse_address(addr_ptr, *(argv++))) {
return usage();
}
addr_ptr += ETH_MAC_SIZE;
}
} else { // Includes --help, -h, --HELF, --42, etc.
return usage();
}
if (promisc_on && promisc_off) {
return usage();
}
if (promisc_on || promisc_off) {
options->setting_promisc = true;
options->promisc_on = promisc_on;
}
return 0;
}
int initialize_ethernet(ethtool_options_t* options) {
int fd;
if ((fd = open(options->device, O_RDWR)) < 0) {
fprintf(stderr, "ethtool: cannot open '%s': %d\n", options->device, fd);
return -1;
}
eth_fifos_t fifos;
zx_status_t status;
ssize_t r;
if ((r = ioctl_ethernet_get_fifos(fd, &fifos)) < 0) {
fprintf(stderr, "ethtool: failed to get fifos: %zd\n", r);
return r;
}
unsigned count = fifos.rx_depth / 2;
zx_handle_t iovmo;
// allocate shareable ethernet buffer data heap
if ((status = zx_vmo_create(count * BUFSIZE, ZX_VMO_NON_RESIZABLE, &iovmo)) < 0) {
return -1;
}
if ((r = ioctl_ethernet_set_iobuf(fd, &iovmo)) < 0) {
fprintf(stderr, "ethtool: failed to set iobuf: %zd\n", r);
return -1;
}
if ((r = ioctl_ethernet_set_client_name(fd, "ethtool", 7)) < 0) {
fprintf(stderr, "ethtool: failed to set client name %zd\n", r);
}
if (ioctl_ethernet_start(fd) < 0) {
fprintf(stderr, "ethtool: failed to start network interface\n");
return -1;
}
return fd;
}
int main(int argc, const char** argv) {
ethtool_options_t options;
memset(&options, 0, sizeof(options));
if (parse_args(argc - 1, argv + 1, &options)) {
return -1;
}
int fd = initialize_ethernet(&options);
ssize_t r;
if (options.setting_promisc) {
if ((r = ioctl_ethernet_set_promisc(fd, &options.promisc_on)) < 0) {
fprintf(stderr, "ethtool: failed to set promiscuous mode to %s: %zd\n",
options.promisc_on ? "on" : "off", r);
return -1;
} else {
fprintf(stderr, "ethtool: set %s promiscuous mode to %s\n",
options.device, options.promisc_on ? "on" : "off");
}
}
if (options.filter_macs) {
eth_multicast_config_t config;
memset(&config, 0, sizeof(config));
config.op = ETH_MULTICAST_TEST_FILTER;
if ((r = ioctl_ethernet_config_multicast(fd, &config)) < 0) {
fprintf(stderr, "ethtool: failed to config multicast test\n");
return -1;
}
config.op = ETH_MULTICAST_ADD_MAC;
for (int i = 0; i < options.n_filter_macs; i++) {
memcpy(config.mac, options.filter_macs + i * ETH_MAC_SIZE, ETH_MAC_SIZE);
printf("Sending addr %d %d %d %d %d %d\n", config.mac[0], config.mac[1],
config.mac[2], config.mac[3], config.mac[4], config.mac[5]);
if ((r = ioctl_ethernet_config_multicast(fd, &config)) < 0) {
fprintf(stderr, "ethtool: failed to add multicast addr\n");
return -1;
}
}
}
if (options.dump_regs) {
eth_multicast_config_t config;
memset(&config, 0, sizeof(config));
config.op = ETH_MULTICAST_DUMP_REGS;
if ((r = ioctl_ethernet_config_multicast(fd, &config)) < 0) {
fprintf(stderr, "ethtool: failed to request reg dump\n");
return -1;
}
}
zx_nanosleep(zx_deadline_after(ZX_SEC(options.pause_secs)));
return 0;
}