text: // 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 <fuchsia/hardware/ethernet/c/fidl.h>
  #include <inet6/inet6.h>
  #include <lib/fdio/fd.h>
  #include <lib/fdio/fdio.h>
  #include <lib/fdio/directory.h>
  #include <zircon/assert.h>
  #include <zircon/boot/netboot.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;
  }
  
  zx_handle_t 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 ZX_HANDLE_INVALID;
      }
  
      zx_handle_t svc;
      zx_status_t status = fdio_get_service_handle(fd, &svc);
      if (status != ZX_OK) {
          fprintf(stderr, "ethtool: failed to get service handle\n");
          return ZX_HANDLE_INVALID;
      }
  
      fuchsia_hardware_ethernet_Fifos fifos;
  
      zx_status_t call_status = ZX_OK;
      status = fuchsia_hardware_ethernet_DeviceGetFifos(svc, &call_status, &fifos);
      if (status != ZX_OK || call_status != ZX_OK) {
          fprintf(stderr, "ethtool: failed to get fifos: %d, %d\n", status, call_status);
          return ZX_HANDLE_INVALID;
      }
  
      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 ZX_HANDLE_INVALID;
      }
  
      status = fuchsia_hardware_ethernet_DeviceSetIOBuffer(svc, iovmo, &call_status);
      if (status != ZX_OK || call_status != ZX_OK) {
          fprintf(stderr, "ethtool: failed to set iobuf: %d, %d\n", status, call_status);
          return ZX_HANDLE_INVALID;
      }
  
      status = fuchsia_hardware_ethernet_DeviceSetClientName(svc, "ethtool", 7, &call_status);
      if (status != ZX_OK || call_status != ZX_OK) {
          fprintf(stderr, "ethtool: failed to set client name %d, %d\n", status, call_status);
      }
  
      status = fuchsia_hardware_ethernet_DeviceStart(svc, &call_status);
      if (status != ZX_OK || call_status != ZX_OK) {
          fprintf(stderr, "ethtool: failed to start network interface\n");
          return ZX_HANDLE_INVALID;
      }
  
      return svc;
  }
  
  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;
      }
  
      zx_handle_t svc = initialize_ethernet(&options);
      if (svc == ZX_HANDLE_INVALID) {
          return -1;
      }
      zx_status_t status, call_status;
      if (options.setting_promisc) {
          status = fuchsia_hardware_ethernet_DeviceSetPromiscuousMode(svc, options.promisc_on,
                                                                      &call_status);
          if (status != ZX_OK || call_status != ZX_OK) {
              fprintf(stderr, "ethtool: failed to set promiscuous mode to %s: %d, %d\n",
                      options.promisc_on ? "on" : "off", status, call_status);
              return -1;
          } else {
              fprintf(stderr, "ethtool: set %s promiscuous mode to %s\n",
                      options.device, options.promisc_on ? "on" : "off");
          }
      }
      if (options.filter_macs) {
          status = fuchsia_hardware_ethernet_DeviceConfigMulticastTestFilter(svc, &call_status);
          if (status != ZX_OK || call_status != ZX_OK) {
              fprintf(stderr, "ethtool: failed to config multicast test\n");
              return -1;
          }
          for (int i = 0; i < options.n_filter_macs; i++) {
              fuchsia_hardware_ethernet_MacAddress addr;
              memcpy(&addr.octets, options.filter_macs + i * ETH_MAC_SIZE, ETH_MAC_SIZE);
              printf("Sending addr %d %d %d %d %d %d\n", addr.octets[0], addr.octets[1],
                     addr.octets[2], addr.octets[3], addr.octets[4], addr.octets[5]);
              status =
                  fuchsia_hardware_ethernet_DeviceConfigMulticastAddMac(svc, &addr, &call_status);
              if (status != ZX_OK || call_status != ZX_OK) {
                  fprintf(stderr, "ethtool: failed to add multicast addr\n");
                  return -1;
              }
          }
      }
      if (options.dump_regs) {
          status = fuchsia_hardware_ethernet_DeviceDumpRegisters(svc, &call_status);
          if (status != ZX_OK || call_status != ZX_OK) {
              fprintf(stderr, "ethtool: failed to request reg dump\n");
              return -1;
          }
      }
      zx_nanosleep(zx_deadline_after(ZX_SEC(options.pause_secs)));
      return 0;
  }
  