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