1 // Copyright 2018 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 <zircon/assert.h>
9 #include <zircon/boot/netboot.h>
10 #include <zircon/process.h>
11 #include <zircon/syscalls.h>
12 
13 #include <arpa/inet.h>
14 #include <fcntl.h>
15 #include <limits.h>
16 #include <netinet/if_ether.h>
17 #include <netinet/ip.h>
18 #include <netinet/tcp.h>
19 #include <netinet/udp.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 
25 #define ETH_MAC_SIZE 6
26 #define BUFSIZE 2048
27 
28 typedef struct {
29     const char* device;
30     int pause_secs;
31     bool setting_promisc;
32     bool promisc_on;
33     bool dump_regs;
34     char* filter_macs;
35     int n_filter_macs;
36 } ethtool_options_t;
37 
usage(void)38 int usage(void) {
39     fprintf(stderr, "usage: ethtool <network-device> <time> <actions>\n");
40     fprintf(stderr, "  network-device must start with '/dev/')\n");
41     fprintf(stderr, "  time = how many seconds to hold the fd (before exiting)\n");
42     fprintf(stderr, "Actions: one of\n");
43     fprintf(stderr, "  promisc on     : Promiscuous mode on\n");
44     fprintf(stderr, "  promisc off    : Promiscuous mode off\n");
45     fprintf(stderr, "  filter n.n.n.n.n.n n.n.n.n.n.n ...    : multicast filter these addresses\n");
46     fprintf(stderr, "  dump           : Dump regs of chip\n");
47     fprintf(stderr, "    (empty list is valid)\n");
48     fprintf(stderr, "  --help  : Show this help message\n");
49     return -1;
50 }
51 
52 // Str should be nn.nn.nn.nn.nn.nn where nn is decimal 0..255 and there are ETH_MAC_SIZE (6) nn's
53 // Function returns non-zero if this isn't the case. (It's not fully paranoid, but shouldn't crash.)
54 // If input is good, first nn goes into mac[0], etc.
parse_address(char * mac,const char * str)55 int parse_address(char* mac, const char* str) {
56     char* next_string;
57     for (int i = 0; i < 5; i++) {
58         mac[i] = (char)strtol(str, &next_string, 10);
59         if (next_string[0] != '.') {
60             return -1;
61         }
62         next_string++;
63         str = next_string;
64     }
65     mac[5] = (char)strtol(str, &next_string, 10);
66     if (next_string[0] != 0) {
67         return -1;
68     }
69     return 0;
70 }
71 
parse_args(int argc,const char ** argv,ethtool_options_t * options)72 int parse_args(int argc, const char** argv, ethtool_options_t* options) {
73     bool promisc_on = false;
74     bool promisc_off = false;
75 
76     if (argc < 3) {
77         return usage();
78     }
79     if (strncmp(argv[0], "/dev/", strlen("/dev/"))) {
80         return usage();
81     }
82     options->device = argv[0];
83     argc--;
84     argv++;
85 
86     char* remainder;
87     options->pause_secs = strtol(argv[0], &remainder, 10);
88     if (options->pause_secs < 0 || remainder[0] != 0) {
89         return usage();
90     }
91     argc--;
92     argv++;
93 
94     if (!strcmp(argv[0], "promisc")) {
95         argc--;
96         argv++;
97         if (argc != 1) {
98             return usage();
99         }
100         if (!strcmp(argv[0], "on")) {
101             promisc_on = true;
102         } else if (!strcmp(argv[0], "off")) {
103             promisc_off = true;
104         } else {
105             return usage();
106         }
107     } else if (!strcmp(argv[0], "dump")) {
108         argc--;
109         argv++;
110         if (argc != 0) {
111             return usage();
112         }
113         options->dump_regs = true;
114     } else if (!strcmp(argv[0], "filter")) {
115         argc--;
116         argv++;
117         options->n_filter_macs = argc;
118         options->filter_macs = calloc(1, ETH_MAC_SIZE * argc);
119         char* addr_ptr = options->filter_macs;
120         while (argc--) {
121             if (parse_address(addr_ptr, *(argv++))) {
122                 return usage();
123             }
124             addr_ptr += ETH_MAC_SIZE;
125         }
126     } else { // Includes --help, -h, --HELF, --42, etc.
127         return usage();
128     }
129 
130     if (promisc_on && promisc_off) {
131         return usage();
132     }
133     if (promisc_on || promisc_off) {
134         options->setting_promisc = true;
135         options->promisc_on = promisc_on;
136     }
137 
138     return 0;
139 }
140 
initialize_ethernet(ethtool_options_t * options)141 zx_handle_t initialize_ethernet(ethtool_options_t* options) {
142     int fd;
143     if ((fd = open(options->device, O_RDWR)) < 0) {
144         fprintf(stderr, "ethtool: cannot open '%s': %d\n", options->device, fd);
145         return ZX_HANDLE_INVALID;
146     }
147 
148     zx_handle_t svc;
149     zx_status_t status = fdio_get_service_handle(fd, &svc);
150     if (status != ZX_OK) {
151         fprintf(stderr, "ethtool: failed to get service handle\n");
152         return ZX_HANDLE_INVALID;
153     }
154 
155     fuchsia_hardware_ethernet_Fifos fifos;
156 
157     zx_status_t call_status = ZX_OK;
158     status = fuchsia_hardware_ethernet_DeviceGetFifos(svc, &call_status, &fifos);
159     if (status != ZX_OK || call_status != ZX_OK) {
160         fprintf(stderr, "ethtool: failed to get fifos: %d, %d\n", status, call_status);
161         return ZX_HANDLE_INVALID;
162     }
163 
164     unsigned count = fifos.rx_depth / 2;
165     zx_handle_t iovmo;
166     // allocate shareable ethernet buffer data heap
167     if ((status = zx_vmo_create(count * BUFSIZE, ZX_VMO_NON_RESIZABLE, &iovmo)) < 0) {
168         return ZX_HANDLE_INVALID;
169     }
170 
171     status = fuchsia_hardware_ethernet_DeviceSetIOBuffer(svc, iovmo, &call_status);
172     if (status != ZX_OK || call_status != ZX_OK) {
173         fprintf(stderr, "ethtool: failed to set iobuf: %d, %d\n", status, call_status);
174         return ZX_HANDLE_INVALID;
175     }
176 
177     status = fuchsia_hardware_ethernet_DeviceSetClientName(svc, "ethtool", 7, &call_status);
178     if (status != ZX_OK || call_status != ZX_OK) {
179         fprintf(stderr, "ethtool: failed to set client name %d, %d\n", status, call_status);
180     }
181 
182     status = fuchsia_hardware_ethernet_DeviceStart(svc, &call_status);
183     if (status != ZX_OK || call_status != ZX_OK) {
184         fprintf(stderr, "ethtool: failed to start network interface\n");
185         return ZX_HANDLE_INVALID;
186     }
187 
188     return svc;
189 }
190 
main(int argc,const char ** argv)191 int main(int argc, const char** argv) {
192     ethtool_options_t options;
193     memset(&options, 0, sizeof(options));
194     if (parse_args(argc - 1, argv + 1, &options)) {
195         return -1;
196     }
197 
198     zx_handle_t svc = initialize_ethernet(&options);
199     if (svc == ZX_HANDLE_INVALID) {
200         return -1;
201     }
202     zx_status_t status, call_status;
203     if (options.setting_promisc) {
204         status = fuchsia_hardware_ethernet_DeviceSetPromiscuousMode(svc, options.promisc_on,
205                                                                     &call_status);
206         if (status != ZX_OK || call_status != ZX_OK) {
207             fprintf(stderr, "ethtool: failed to set promiscuous mode to %s: %d, %d\n",
208                     options.promisc_on ? "on" : "off", status, call_status);
209             return -1;
210         } else {
211             fprintf(stderr, "ethtool: set %s promiscuous mode to %s\n",
212                     options.device, options.promisc_on ? "on" : "off");
213         }
214     }
215     if (options.filter_macs) {
216         status = fuchsia_hardware_ethernet_DeviceConfigMulticastTestFilter(svc, &call_status);
217         if (status != ZX_OK || call_status != ZX_OK) {
218             fprintf(stderr, "ethtool: failed to config multicast test\n");
219             return -1;
220         }
221         for (int i = 0; i < options.n_filter_macs; i++) {
222             fuchsia_hardware_ethernet_MacAddress addr;
223             memcpy(&addr.octets, options.filter_macs + i * ETH_MAC_SIZE, ETH_MAC_SIZE);
224             printf("Sending addr %d %d %d %d %d %d\n", addr.octets[0], addr.octets[1],
225                    addr.octets[2], addr.octets[3], addr.octets[4], addr.octets[5]);
226             status =
227                 fuchsia_hardware_ethernet_DeviceConfigMulticastAddMac(svc, &addr, &call_status);
228             if (status != ZX_OK || call_status != ZX_OK) {
229                 fprintf(stderr, "ethtool: failed to add multicast addr\n");
230                 return -1;
231             }
232         }
233     }
234     if (options.dump_regs) {
235         status = fuchsia_hardware_ethernet_DeviceDumpRegisters(svc, &call_status);
236         if (status != ZX_OK || call_status != ZX_OK) {
237             fprintf(stderr, "ethtool: failed to request reg dump\n");
238             return -1;
239         }
240     }
241     zx_nanosleep(zx_deadline_after(ZX_SEC(options.pause_secs)));
242     return 0;
243 }
244