]>
Commit | Line | Data |
---|---|---|
5ba3f43e | 1 | /* |
f427ee49 | 2 | * Copyright (c) 2015-2020 Apple Inc. All rights reserved. |
5ba3f43e A |
3 | * |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
0a7de745 | 5 | * |
5ba3f43e A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
0a7de745 | 14 | * |
5ba3f43e A |
15 | * Please obtain a copy of the License at |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
0a7de745 | 17 | * |
5ba3f43e A |
18 | * The Original Code and all software distributed under the License are |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
0a7de745 | 25 | * |
5ba3f43e A |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
27 | */ | |
28 | ||
29 | /* | |
30 | * if_fake.c | |
31 | * - fake network interface used for testing | |
32 | * - "feth" (e.g. "feth0", "feth1") is a virtual ethernet interface that allows | |
33 | * two instances to have their output/input paths "crossed-over" so that | |
34 | * output on one is input on the other | |
35 | */ | |
36 | ||
37 | /* | |
38 | * Modification History: | |
39 | * | |
40 | * September 9, 2015 Dieter Siegmund (dieter@apple.com) | |
41 | * - created | |
42 | */ | |
43 | ||
44 | #include <sys/param.h> | |
45 | #include <sys/kernel.h> | |
46 | #include <sys/malloc.h> | |
47 | #include <sys/mbuf.h> | |
48 | #include <sys/queue.h> | |
49 | #include <sys/socket.h> | |
50 | #include <sys/sockio.h> | |
51 | #include <sys/sysctl.h> | |
52 | #include <sys/systm.h> | |
53 | #include <sys/kern_event.h> | |
54 | #include <sys/mcache.h> | |
55 | #include <sys/syslog.h> | |
56 | ||
57 | #include <net/bpf.h> | |
58 | #include <net/ethernet.h> | |
59 | #include <net/if.h> | |
60 | #include <net/if_vlan_var.h> | |
61 | #include <net/if_fake_var.h> | |
62 | #include <net/if_arp.h> | |
63 | #include <net/if_dl.h> | |
64 | #include <net/if_ether.h> | |
65 | #include <net/if_types.h> | |
66 | #include <libkern/OSAtomic.h> | |
67 | ||
68 | #include <net/dlil.h> | |
69 | ||
70 | #include <net/kpi_interface.h> | |
71 | #include <net/kpi_protocol.h> | |
72 | ||
73 | #include <kern/locks.h> | |
d9a64523 | 74 | #include <kern/zalloc.h> |
5ba3f43e A |
75 | |
76 | #ifdef INET | |
77 | #include <netinet/in.h> | |
78 | #include <netinet/if_ether.h> | |
79 | #endif | |
80 | ||
81 | #include <net/if_media.h> | |
82 | #include <net/ether_if_module.h> | |
83 | ||
cb323159 A |
84 | static boolean_t |
85 | is_power_of_two(unsigned int val) | |
86 | { | |
87 | return (val & (val - 1)) == 0; | |
88 | } | |
89 | ||
0a7de745 | 90 | #define FAKE_ETHER_NAME "feth" |
5ba3f43e A |
91 | |
92 | SYSCTL_DECL(_net_link); | |
0a7de745 A |
93 | SYSCTL_NODE(_net_link, OID_AUTO, fake, CTLFLAG_RW | CTLFLAG_LOCKED, 0, |
94 | "Fake interface"); | |
5ba3f43e A |
95 | |
96 | static int if_fake_txstart = 1; | |
97 | SYSCTL_INT(_net_link_fake, OID_AUTO, txstart, CTLFLAG_RW | CTLFLAG_LOCKED, | |
0a7de745 | 98 | &if_fake_txstart, 0, "Fake interface TXSTART mode"); |
5ba3f43e A |
99 | |
100 | static int if_fake_hwcsum = 0; | |
101 | SYSCTL_INT(_net_link_fake, OID_AUTO, hwcsum, CTLFLAG_RW | CTLFLAG_LOCKED, | |
0a7de745 | 102 | &if_fake_hwcsum, 0, "Fake interface simulate hardware checksum"); |
5ba3f43e A |
103 | |
104 | static int if_fake_nxattach = 0; | |
105 | SYSCTL_INT(_net_link_fake, OID_AUTO, nxattach, CTLFLAG_RW | CTLFLAG_LOCKED, | |
0a7de745 | 106 | &if_fake_nxattach, 0, "Fake interface auto-attach nexus"); |
5ba3f43e A |
107 | |
108 | static int if_fake_bsd_mode = 1; | |
109 | SYSCTL_INT(_net_link_fake, OID_AUTO, bsd_mode, CTLFLAG_RW | CTLFLAG_LOCKED, | |
0a7de745 | 110 | &if_fake_bsd_mode, 0, "Fake interface attach as BSD interface"); |
5ba3f43e A |
111 | |
112 | static int if_fake_debug = 0; | |
113 | SYSCTL_INT(_net_link_fake, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_LOCKED, | |
0a7de745 | 114 | &if_fake_debug, 0, "Fake interface debug logs"); |
5ba3f43e | 115 | |
d9a64523 A |
116 | static int if_fake_wmm_mode = 0; |
117 | SYSCTL_INT(_net_link_fake, OID_AUTO, wmm_mode, CTLFLAG_RW | CTLFLAG_LOCKED, | |
0a7de745 | 118 | &if_fake_wmm_mode, 0, "Fake interface in 802.11 WMM mode"); |
d9a64523 | 119 | |
cb323159 A |
120 | static int if_fake_multibuflet = 0; |
121 | SYSCTL_INT(_net_link_fake, OID_AUTO, multibuflet, CTLFLAG_RW | CTLFLAG_LOCKED, | |
122 | &if_fake_multibuflet, 0, "Fake interface using multi-buflet packets"); | |
123 | ||
f427ee49 A |
124 | typedef enum { |
125 | IFF_PP_MODE_GLOBAL = 0, /* share a global pool */ | |
126 | IFF_PP_MODE_PRIVATE = 1, /* creates its own rx/tx pool */ | |
127 | IFF_PP_MODE_PRIVATE_SPLIT = 2, /* creates its own split rx & tx pool */ | |
128 | } iff_pktpool_mode_t; | |
129 | static iff_pktpool_mode_t if_fake_pktpool_mode = 0; | |
130 | SYSCTL_INT(_net_link_fake, OID_AUTO, pktpool_mode, CTLFLAG_RW | CTLFLAG_LOCKED, | |
131 | &if_fake_pktpool_mode, 0, | |
132 | "Fake interface packet pool mode (0 global, 1 private, 2 private split"); | |
133 | ||
134 | #define FETH_LINK_LAYER_AGGRETATION_FACTOR_MAX 32 | |
135 | static int if_fake_link_layer_aggregation_factor = | |
136 | FETH_LINK_LAYER_AGGRETATION_FACTOR_MAX; | |
137 | static int | |
138 | feth_link_layer_aggregation_factor_sysctl SYSCTL_HANDLER_ARGS | |
139 | { | |
140 | #pragma unused(oidp, arg1, arg2) | |
141 | unsigned int new_value; | |
142 | int changed; | |
143 | int error; | |
cb323159 | 144 | |
f427ee49 A |
145 | error = sysctl_io_number(req, if_fake_link_layer_aggregation_factor, |
146 | sizeof(if_fake_link_layer_aggregation_factor), &new_value, | |
147 | &changed); | |
148 | if (error == 0 && changed != 0) { | |
149 | if (new_value <= 0 || | |
150 | new_value > FETH_LINK_LAYER_AGGRETATION_FACTOR_MAX) { | |
151 | return EINVAL; | |
152 | } | |
153 | if_fake_link_layer_aggregation_factor = new_value; | |
154 | } | |
155 | return error; | |
156 | } | |
cb323159 | 157 | |
f427ee49 A |
158 | SYSCTL_PROC(_net_link_fake, OID_AUTO, link_layer_aggregation_factor, |
159 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, | |
160 | 0, 0, feth_link_layer_aggregation_factor_sysctl, "IU", | |
161 | "Fake interface link layer aggregation factor"); | |
162 | ||
163 | #define FETH_TX_HEADROOM_MAX 32 | |
164 | static unsigned int if_fake_tx_headroom = FETH_TX_HEADROOM_MAX; | |
cb323159 A |
165 | static int |
166 | feth_tx_headroom_sysctl SYSCTL_HANDLER_ARGS | |
167 | { | |
168 | #pragma unused(oidp, arg1, arg2) | |
169 | unsigned int new_value; | |
170 | int changed; | |
171 | int error; | |
172 | ||
173 | error = sysctl_io_number(req, if_fake_tx_headroom, | |
174 | sizeof(if_fake_tx_headroom), &new_value, &changed); | |
175 | if (error == 0 && changed != 0) { | |
176 | if (new_value > FETH_TX_HEADROOM_MAX || | |
177 | (new_value % 8) != 0) { | |
178 | return EINVAL; | |
179 | } | |
180 | if_fake_tx_headroom = new_value; | |
181 | } | |
182 | return 0; | |
183 | } | |
184 | ||
185 | SYSCTL_PROC(_net_link_fake, OID_AUTO, tx_headroom, | |
186 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, | |
187 | 0, 0, feth_tx_headroom_sysctl, "IU", "Fake ethernet Tx headroom"); | |
188 | ||
f427ee49 A |
189 | static int if_fake_fcs = 0; |
190 | SYSCTL_INT(_net_link_fake, OID_AUTO, fcs, CTLFLAG_RW | CTLFLAG_LOCKED, | |
191 | &if_fake_fcs, 0, "Fake interface using frame check sequence"); | |
192 | ||
193 | #define FETH_TRAILER_LENGTH_MAX 28 | |
194 | char feth_trailer[FETH_TRAILER_LENGTH_MAX + 1] = "trailertrailertrailertrailer"; | |
195 | static unsigned int if_fake_trailer_length = 0; | |
196 | static int | |
197 | feth_trailer_length_sysctl SYSCTL_HANDLER_ARGS | |
198 | { | |
199 | #pragma unused(oidp, arg1, arg2) | |
200 | unsigned int new_value; | |
201 | int changed; | |
202 | int error; | |
203 | ||
204 | error = sysctl_io_number(req, if_fake_trailer_length, | |
205 | sizeof(if_fake_trailer_length), &new_value, &changed); | |
206 | if (error == 0 && changed != 0) { | |
207 | if (new_value > FETH_TRAILER_LENGTH_MAX) { | |
208 | return EINVAL; | |
209 | } | |
210 | if_fake_trailer_length = new_value; | |
211 | } | |
212 | return 0; | |
213 | } | |
214 | ||
215 | SYSCTL_PROC(_net_link_fake, OID_AUTO, trailer_length, | |
216 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, | |
217 | feth_trailer_length_sysctl, "IU", "Fake interface frame trailer length"); | |
cb323159 A |
218 | |
219 | /* sysctl net.link.fake.max_mtu */ | |
220 | #define FETH_MAX_MTU_DEFAULT 2048 | |
221 | #define FETH_MAX_MTU_MAX ((16 * 1024) - ETHER_HDR_LEN) | |
222 | ||
223 | static unsigned int if_fake_max_mtu = FETH_MAX_MTU_DEFAULT; | |
224 | ||
225 | /* sysctl net.link.fake.buflet_size */ | |
226 | #define FETH_BUFLET_SIZE_MIN 512 | |
227 | #define FETH_BUFLET_SIZE_MAX 2048 | |
228 | ||
229 | static unsigned int if_fake_buflet_size = FETH_BUFLET_SIZE_MIN; | |
230 | ||
231 | static int | |
232 | feth_max_mtu_sysctl SYSCTL_HANDLER_ARGS | |
233 | { | |
234 | #pragma unused(oidp, arg1, arg2) | |
235 | unsigned int new_value; | |
236 | int changed; | |
237 | int error; | |
238 | ||
239 | error = sysctl_io_number(req, if_fake_max_mtu, | |
240 | sizeof(if_fake_max_mtu), &new_value, &changed); | |
241 | if (error == 0 && changed != 0) { | |
242 | if (new_value > FETH_MAX_MTU_MAX || | |
243 | new_value < ETHERMTU || | |
244 | new_value <= if_fake_buflet_size) { | |
245 | return EINVAL; | |
246 | } | |
247 | if_fake_max_mtu = new_value; | |
248 | } | |
249 | return 0; | |
250 | } | |
251 | ||
252 | SYSCTL_PROC(_net_link_fake, OID_AUTO, max_mtu, | |
253 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, | |
254 | 0, 0, feth_max_mtu_sysctl, "IU", "Fake interface maximum MTU"); | |
255 | ||
256 | static int | |
257 | feth_buflet_size_sysctl SYSCTL_HANDLER_ARGS | |
258 | { | |
259 | #pragma unused(oidp, arg1, arg2) | |
260 | unsigned int new_value; | |
261 | int changed; | |
262 | int error; | |
263 | ||
264 | error = sysctl_io_number(req, if_fake_buflet_size, | |
265 | sizeof(if_fake_buflet_size), &new_value, &changed); | |
266 | if (error == 0 && changed != 0) { | |
267 | /* must be a power of 2 between min and max */ | |
268 | if (new_value > FETH_BUFLET_SIZE_MAX || | |
269 | new_value < FETH_BUFLET_SIZE_MIN || | |
270 | !is_power_of_two(new_value) || | |
271 | new_value >= if_fake_max_mtu) { | |
272 | return EINVAL; | |
273 | } | |
274 | if_fake_buflet_size = new_value; | |
275 | } | |
276 | return 0; | |
277 | } | |
278 | ||
279 | SYSCTL_PROC(_net_link_fake, OID_AUTO, buflet_size, | |
280 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, | |
281 | 0, 0, feth_buflet_size_sysctl, "IU", "Fake interface buflet size"); | |
282 | ||
283 | static unsigned int if_fake_user_access = 0; | |
284 | ||
285 | static int | |
286 | feth_user_access_sysctl SYSCTL_HANDLER_ARGS | |
287 | { | |
288 | #pragma unused(oidp, arg1, arg2) | |
289 | unsigned int new_value; | |
290 | int changed; | |
291 | int error; | |
292 | ||
293 | error = sysctl_io_number(req, if_fake_user_access, | |
294 | sizeof(if_fake_user_access), &new_value, &changed); | |
295 | if (error == 0 && changed != 0) { | |
296 | if (new_value != 0) { | |
297 | if (new_value != 1) { | |
298 | return EINVAL; | |
299 | } | |
cb323159 A |
300 | } |
301 | if_fake_user_access = new_value; | |
302 | } | |
303 | return 0; | |
304 | } | |
305 | ||
306 | SYSCTL_PROC(_net_link_fake, OID_AUTO, user_access, | |
307 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, | |
308 | 0, 0, feth_user_access_sysctl, "IU", "Fake interface user access"); | |
309 | ||
310 | /* sysctl net.link.fake.if_adv_intvl (unit: millisecond) */ | |
311 | #define FETH_IF_ADV_INTVL_MIN 10 | |
312 | #define FETH_IF_ADV_INTVL_MAX INT_MAX | |
313 | ||
314 | static int if_fake_if_adv_interval = 0; /* no interface advisory */ | |
315 | static int | |
316 | feth_if_adv_interval_sysctl SYSCTL_HANDLER_ARGS | |
317 | { | |
318 | #pragma unused(oidp, arg1, arg2) | |
319 | unsigned int new_value; | |
320 | int changed; | |
321 | int error; | |
322 | ||
323 | error = sysctl_io_number(req, if_fake_if_adv_interval, | |
324 | sizeof(if_fake_if_adv_interval), &new_value, &changed); | |
325 | if (error == 0 && changed != 0) { | |
326 | if ((new_value != 0) && (new_value > FETH_IF_ADV_INTVL_MAX || | |
327 | new_value < FETH_IF_ADV_INTVL_MIN)) { | |
328 | return EINVAL; | |
329 | } | |
330 | if_fake_if_adv_interval = new_value; | |
331 | } | |
332 | return 0; | |
333 | } | |
334 | ||
335 | SYSCTL_PROC(_net_link_fake, OID_AUTO, if_adv_intvl, | |
336 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, | |
337 | feth_if_adv_interval_sysctl, "IU", | |
338 | "Fake interface will generate interface advisories reports at the specified interval in ms"); | |
339 | ||
340 | /* sysctl net.link.fake.tx_drops */ | |
341 | /* | |
342 | * Fake ethernet will drop packet on the transmit path at the specified | |
343 | * rate, i.e drop one in every if_fake_tx_drops number of packets. | |
344 | */ | |
345 | #define FETH_TX_DROPS_MIN 0 | |
346 | #define FETH_TX_DROPS_MAX INT_MAX | |
347 | static int if_fake_tx_drops = 0; /* no packets are dropped */ | |
348 | static int | |
349 | feth_fake_tx_drops_sysctl SYSCTL_HANDLER_ARGS | |
350 | { | |
351 | #pragma unused(oidp, arg1, arg2) | |
352 | unsigned int new_value; | |
353 | int changed; | |
354 | int error; | |
355 | ||
356 | error = sysctl_io_number(req, if_fake_tx_drops, | |
357 | sizeof(if_fake_tx_drops), &new_value, &changed); | |
358 | if (error == 0 && changed != 0) { | |
359 | if (new_value > FETH_TX_DROPS_MAX || | |
360 | new_value < FETH_TX_DROPS_MIN) { | |
361 | return EINVAL; | |
362 | } | |
363 | if_fake_tx_drops = new_value; | |
364 | } | |
365 | return 0; | |
366 | } | |
367 | ||
368 | SYSCTL_PROC(_net_link_fake, OID_AUTO, tx_drops, | |
369 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, | |
370 | feth_fake_tx_drops_sysctl, "IU", | |
371 | "Fake interface will intermittently drop packets on Tx path"); | |
372 | ||
5ba3f43e | 373 | /** |
0a7de745 A |
374 | ** virtual ethernet structures, types |
375 | **/ | |
5ba3f43e | 376 | |
0a7de745 A |
377 | #define IFF_NUM_TX_RINGS_WMM_MODE 4 |
378 | #define IFF_NUM_RX_RINGS_WMM_MODE 1 | |
379 | #define IFF_MAX_TX_RINGS IFF_NUM_TX_RINGS_WMM_MODE | |
380 | #define IFF_MAX_RX_RINGS IFF_NUM_RX_RINGS_WMM_MODE | |
d9a64523 | 381 | |
f427ee49 A |
382 | #define IFF_MAX_BATCH_SIZE 32 |
383 | ||
0a7de745 A |
384 | typedef uint16_t iff_flags_t; |
385 | #define IFF_FLAGS_HWCSUM 0x0001 | |
386 | #define IFF_FLAGS_BSD_MODE 0x0002 | |
387 | #define IFF_FLAGS_DETACHING 0x0004 | |
388 | #define IFF_FLAGS_WMM_MODE 0x0008 | |
cb323159 | 389 | #define IFF_FLAGS_MULTIBUFLETS 0x0010 |
5ba3f43e A |
390 | |
391 | ||
392 | struct if_fake { | |
0a7de745 A |
393 | char iff_name[IFNAMSIZ]; /* our unique id */ |
394 | ifnet_t iff_ifp; | |
395 | iff_flags_t iff_flags; | |
396 | uint32_t iff_retain_count; | |
397 | ifnet_t iff_peer; /* the other end */ | |
398 | int iff_media_current; | |
399 | int iff_media_active; | |
400 | uint32_t iff_media_count; | |
401 | int iff_media_list[IF_FAKE_MEDIA_LIST_MAX]; | |
402 | struct mbuf * iff_pending_tx_packet; | |
403 | boolean_t iff_start_busy; | |
cb323159 | 404 | unsigned int iff_max_mtu; |
f427ee49 A |
405 | uint32_t iff_fcs; |
406 | uint32_t iff_trailer_length; | |
5ba3f43e A |
407 | }; |
408 | ||
409 | typedef struct if_fake * if_fake_ref; | |
410 | ||
411 | static if_fake_ref | |
412 | ifnet_get_if_fake(ifnet_t ifp); | |
413 | ||
0a7de745 | 414 | #define FETH_DPRINTF(fmt, ...) \ |
5ba3f43e A |
415 | { if (if_fake_debug != 0) printf("%s " fmt, __func__, ## __VA_ARGS__); } |
416 | ||
417 | static inline boolean_t | |
418 | feth_in_bsd_mode(if_fake_ref fakeif) | |
419 | { | |
0a7de745 | 420 | return (fakeif->iff_flags & IFF_FLAGS_BSD_MODE) != 0; |
5ba3f43e A |
421 | } |
422 | ||
423 | static inline void | |
424 | feth_set_detaching(if_fake_ref fakeif) | |
425 | { | |
426 | fakeif->iff_flags |= IFF_FLAGS_DETACHING; | |
427 | } | |
428 | ||
429 | static inline boolean_t | |
430 | feth_is_detaching(if_fake_ref fakeif) | |
431 | { | |
0a7de745 | 432 | return (fakeif->iff_flags & IFF_FLAGS_DETACHING) != 0; |
5ba3f43e A |
433 | } |
434 | ||
a39ff7e2 A |
435 | static int |
436 | feth_enable_dequeue_stall(ifnet_t ifp, uint32_t enable) | |
437 | { | |
438 | int error; | |
439 | ||
0a7de745 | 440 | if (enable != 0) { |
a39ff7e2 | 441 | error = ifnet_disable_output(ifp); |
0a7de745 | 442 | } else { |
a39ff7e2 | 443 | error = ifnet_enable_output(ifp); |
0a7de745 | 444 | } |
a39ff7e2 | 445 | |
0a7de745 | 446 | return error; |
a39ff7e2 | 447 | } |
5ba3f43e | 448 | |
d9a64523 | 449 | |
0a7de745 A |
450 | #define FETH_MAXUNIT IF_MAXUNIT |
451 | #define FETH_ZONE_MAX_ELEM MIN(IFNETS_MAX, FETH_MAXUNIT) | |
452 | #define M_FAKE M_DEVBUF | |
5ba3f43e | 453 | |
0a7de745 A |
454 | static int feth_clone_create(struct if_clone *, u_int32_t, void *); |
455 | static int feth_clone_destroy(ifnet_t); | |
456 | static int feth_output(ifnet_t ifp, struct mbuf *m); | |
457 | static void feth_start(ifnet_t ifp); | |
458 | static int feth_ioctl(ifnet_t ifp, u_long cmd, void * addr); | |
459 | static int feth_config(ifnet_t ifp, ifnet_t peer); | |
460 | static void feth_if_free(ifnet_t ifp); | |
461 | static void feth_ifnet_set_attrs(if_fake_ref fakeif, ifnet_t ifp); | |
462 | static void feth_free(if_fake_ref fakeif); | |
5ba3f43e A |
463 | |
464 | static struct if_clone | |
0a7de745 | 465 | feth_cloner = IF_CLONE_INITIALIZER(FAKE_ETHER_NAME, |
d9a64523 A |
466 | feth_clone_create, |
467 | feth_clone_destroy, | |
468 | 0, | |
469 | FETH_MAXUNIT, | |
470 | FETH_ZONE_MAX_ELEM, | |
471 | sizeof(struct if_fake)); | |
0a7de745 | 472 | static void interface_link_event(ifnet_t ifp, u_int32_t event_code); |
5ba3f43e A |
473 | |
474 | /* some media words to pretend to be ethernet */ | |
475 | static int default_media_words[] = { | |
476 | IFM_MAKEWORD(IFM_ETHER, 0, 0, 0), | |
477 | IFM_MAKEWORD(IFM_ETHER, IFM_10G_T, IFM_FDX, 0), | |
478 | IFM_MAKEWORD(IFM_ETHER, IFM_2500_T, IFM_FDX, 0), | |
479 | IFM_MAKEWORD(IFM_ETHER, IFM_5000_T, IFM_FDX, 0), | |
0a7de745 A |
480 | |
481 | IFM_MAKEWORD(IFM_ETHER, IFM_10G_KX4, IFM_FDX, 0), | |
482 | IFM_MAKEWORD(IFM_ETHER, IFM_20G_KR2, IFM_FDX, 0), | |
483 | IFM_MAKEWORD(IFM_ETHER, IFM_2500_SX, IFM_FDX, 0), | |
484 | IFM_MAKEWORD(IFM_ETHER, IFM_25G_KR, IFM_FDX, 0), | |
485 | IFM_MAKEWORD(IFM_ETHER, IFM_40G_SR4, IFM_FDX, 0), | |
486 | IFM_MAKEWORD(IFM_ETHER, IFM_50G_CR2, IFM_FDX, 0), | |
487 | IFM_MAKEWORD(IFM_ETHER, IFM_56G_R4, IFM_FDX, 0), | |
488 | IFM_MAKEWORD(IFM_ETHER, IFM_100G_CR4, IFM_FDX, 0), | |
489 | IFM_MAKEWORD(IFM_ETHER, IFM_400G_AUI8, IFM_FDX, 0), | |
5ba3f43e | 490 | }; |
0a7de745 A |
491 | #define default_media_words_count (sizeof(default_media_words) \ |
492 | / sizeof (default_media_words[0])) | |
5ba3f43e A |
493 | |
494 | /** | |
0a7de745 A |
495 | ** veth locks |
496 | **/ | |
5ba3f43e A |
497 | static inline lck_grp_t * |
498 | my_lck_grp_alloc_init(const char * grp_name) | |
499 | { | |
0a7de745 A |
500 | lck_grp_t * grp; |
501 | lck_grp_attr_t * grp_attrs; | |
502 | ||
5ba3f43e A |
503 | grp_attrs = lck_grp_attr_alloc_init(); |
504 | grp = lck_grp_alloc_init(grp_name, grp_attrs); | |
505 | lck_grp_attr_free(grp_attrs); | |
0a7de745 | 506 | return grp; |
5ba3f43e A |
507 | } |
508 | ||
509 | static inline lck_mtx_t * | |
510 | my_lck_mtx_alloc_init(lck_grp_t * lck_grp) | |
511 | { | |
0a7de745 A |
512 | lck_attr_t * lck_attrs; |
513 | lck_mtx_t * lck_mtx; | |
5ba3f43e A |
514 | |
515 | lck_attrs = lck_attr_alloc_init(); | |
516 | lck_mtx = lck_mtx_alloc_init(lck_grp, lck_attrs); | |
517 | lck_attr_free(lck_attrs); | |
0a7de745 | 518 | return lck_mtx; |
5ba3f43e A |
519 | } |
520 | ||
0a7de745 | 521 | static lck_mtx_t * feth_lck_mtx; |
5ba3f43e A |
522 | |
523 | static inline void | |
524 | feth_lock_init(void) | |
525 | { | |
0a7de745 | 526 | lck_grp_t * feth_lck_grp; |
5ba3f43e A |
527 | |
528 | feth_lck_grp = my_lck_grp_alloc_init("fake"); | |
529 | feth_lck_mtx = my_lck_mtx_alloc_init(feth_lck_grp); | |
530 | } | |
531 | ||
532 | #if 0 | |
f427ee49 A |
533 | static inline void |
534 | feth_assert_lock_held(void) | |
535 | { | |
536 | LCK_MTX_ASSERT(feth_lck_mtx, LCK_MTX_ASSERT_OWNED); | |
537 | return; | |
538 | } | |
539 | ||
5ba3f43e A |
540 | static inline void |
541 | feth_assert_lock_not_held(void) | |
542 | { | |
543 | LCK_MTX_ASSERT(feth_lck_mtx, LCK_MTX_ASSERT_NOTOWNED); | |
544 | return; | |
545 | } | |
546 | #endif | |
547 | ||
548 | static inline void | |
549 | feth_lock(void) | |
550 | { | |
551 | lck_mtx_lock(feth_lck_mtx); | |
552 | return; | |
553 | } | |
554 | ||
555 | static inline void | |
556 | feth_unlock(void) | |
557 | { | |
558 | lck_mtx_unlock(feth_lck_mtx); | |
559 | return; | |
560 | } | |
561 | ||
562 | static inline int | |
cb323159 | 563 | get_max_mtu(int bsd_mode, unsigned int max_mtu) |
5ba3f43e | 564 | { |
cb323159 A |
565 | unsigned int mtu; |
566 | ||
567 | if (bsd_mode != 0) { | |
568 | mtu = (njcl > 0) ? (M16KCLBYTES - ETHER_HDR_LEN) | |
569 | : MBIGCLBYTES - ETHER_HDR_LEN; | |
570 | if (mtu > max_mtu) { | |
571 | mtu = max_mtu; | |
572 | } | |
573 | } else { | |
574 | mtu = max_mtu; | |
5ba3f43e | 575 | } |
cb323159 A |
576 | return mtu; |
577 | } | |
578 | ||
579 | static inline unsigned int | |
580 | feth_max_mtu(ifnet_t ifp) | |
581 | { | |
582 | if_fake_ref fakeif; | |
583 | unsigned int max_mtu = ETHERMTU; | |
584 | ||
585 | feth_lock(); | |
586 | fakeif = ifnet_get_if_fake(ifp); | |
587 | if (fakeif != NULL) { | |
588 | max_mtu = fakeif->iff_max_mtu; | |
589 | } | |
590 | feth_unlock(); | |
591 | return max_mtu; | |
5ba3f43e A |
592 | } |
593 | ||
594 | static void | |
595 | feth_free(if_fake_ref fakeif) | |
596 | { | |
f427ee49 | 597 | VERIFY(fakeif->iff_retain_count == 0); |
5ba3f43e A |
598 | if (feth_in_bsd_mode(fakeif)) { |
599 | if (fakeif->iff_pending_tx_packet) { | |
600 | m_freem(fakeif->iff_pending_tx_packet); | |
601 | } | |
602 | } | |
603 | ||
604 | FETH_DPRINTF("%s\n", fakeif->iff_name); | |
d9a64523 | 605 | if_clone_softc_deallocate(&feth_cloner, fakeif); |
5ba3f43e A |
606 | } |
607 | ||
608 | static void | |
609 | feth_release(if_fake_ref fakeif) | |
610 | { | |
0a7de745 | 611 | u_int32_t old_retain_count; |
5ba3f43e A |
612 | |
613 | old_retain_count = OSDecrementAtomic(&fakeif->iff_retain_count); | |
614 | switch (old_retain_count) { | |
615 | case 0: | |
f427ee49 | 616 | VERIFY(old_retain_count != 0); |
5ba3f43e A |
617 | break; |
618 | case 1: | |
619 | feth_free(fakeif); | |
620 | break; | |
621 | default: | |
622 | break; | |
623 | } | |
624 | return; | |
625 | } | |
626 | ||
627 | ||
628 | /** | |
0a7de745 A |
629 | ** feth interface routines |
630 | **/ | |
5ba3f43e A |
631 | static void |
632 | feth_ifnet_set_attrs(if_fake_ref fakeif, ifnet_t ifp) | |
633 | { | |
634 | (void)ifnet_set_capabilities_enabled(ifp, 0, -1); | |
635 | ifnet_set_addrlen(ifp, ETHER_ADDR_LEN); | |
636 | ifnet_set_baudrate(ifp, 0); | |
637 | ifnet_set_mtu(ifp, ETHERMTU); | |
638 | ifnet_set_flags(ifp, | |
0a7de745 A |
639 | IFF_BROADCAST | IFF_MULTICAST | IFF_SIMPLEX, |
640 | 0xffff); | |
5ba3f43e A |
641 | ifnet_set_hdrlen(ifp, sizeof(struct ether_header)); |
642 | if ((fakeif->iff_flags & IFF_FLAGS_HWCSUM) != 0) { | |
643 | ifnet_set_offload(ifp, | |
644 | IFNET_CSUM_IP | IFNET_CSUM_TCP | IFNET_CSUM_UDP | | |
645 | IFNET_CSUM_TCPIPV6 | IFNET_CSUM_UDPIPV6); | |
646 | } else { | |
647 | ifnet_set_offload(ifp, 0); | |
648 | } | |
649 | } | |
650 | ||
651 | static void | |
652 | interface_link_event(ifnet_t ifp, u_int32_t event_code) | |
653 | { | |
f427ee49 A |
654 | struct event { |
655 | u_int32_t ifnet_family; | |
656 | u_int32_t unit; | |
657 | char if_name[IFNAMSIZ]; | |
658 | }; | |
659 | _Alignas(struct kern_event_msg) char message[sizeof(struct kern_event_msg) + sizeof(struct event)] = { 0 }; | |
660 | struct kern_event_msg *header = (struct kern_event_msg*)message; | |
661 | struct event *data = (struct event *)(header + 1); | |
662 | ||
663 | header->total_size = sizeof(message); | |
664 | header->vendor_code = KEV_VENDOR_APPLE; | |
665 | header->kev_class = KEV_NETWORK_CLASS; | |
666 | header->kev_subclass = KEV_DL_SUBCLASS; | |
667 | header->event_code = event_code; | |
668 | data->ifnet_family = ifnet_family(ifp); | |
669 | data->unit = (u_int32_t)ifnet_unit(ifp); | |
670 | strlcpy(data->if_name, ifnet_name(ifp), IFNAMSIZ); | |
671 | ifnet_event(ifp, header); | |
5ba3f43e A |
672 | } |
673 | ||
674 | static if_fake_ref | |
675 | ifnet_get_if_fake(ifnet_t ifp) | |
676 | { | |
0a7de745 | 677 | return (if_fake_ref)ifnet_softc(ifp); |
5ba3f43e A |
678 | } |
679 | ||
680 | static int | |
681 | feth_clone_create(struct if_clone *ifc, u_int32_t unit, __unused void *params) | |
682 | { | |
0a7de745 A |
683 | int error; |
684 | if_fake_ref fakeif; | |
685 | struct ifnet_init_eparams feth_init; | |
686 | ifnet_t ifp; | |
687 | uint8_t mac_address[ETHER_ADDR_LEN]; | |
5ba3f43e | 688 | |
d9a64523 | 689 | fakeif = if_clone_softc_allocate(&feth_cloner); |
5ba3f43e A |
690 | if (fakeif == NULL) { |
691 | return ENOBUFS; | |
692 | } | |
693 | fakeif->iff_retain_count = 1; | |
0a7de745 | 694 | #define FAKE_ETHER_NAME_LEN (sizeof(FAKE_ETHER_NAME) - 1) |
5ba3f43e A |
695 | _CASSERT(FAKE_ETHER_NAME_LEN == 4); |
696 | bcopy(FAKE_ETHER_NAME, mac_address, FAKE_ETHER_NAME_LEN); | |
697 | mac_address[ETHER_ADDR_LEN - 2] = (unit & 0xff00) >> 8; | |
698 | mac_address[ETHER_ADDR_LEN - 1] = unit & 0xff; | |
699 | if (if_fake_bsd_mode != 0) { | |
700 | fakeif->iff_flags |= IFF_FLAGS_BSD_MODE; | |
701 | } | |
702 | if (if_fake_hwcsum != 0) { | |
703 | fakeif->iff_flags |= IFF_FLAGS_HWCSUM; | |
704 | } | |
cb323159 | 705 | fakeif->iff_max_mtu = get_max_mtu(if_fake_bsd_mode, if_fake_max_mtu); |
f427ee49 A |
706 | fakeif->iff_fcs = if_fake_fcs; |
707 | fakeif->iff_trailer_length = if_fake_trailer_length; | |
5ba3f43e A |
708 | |
709 | /* use the interface name as the unique id for ifp recycle */ | |
710 | if ((unsigned int) | |
711 | snprintf(fakeif->iff_name, sizeof(fakeif->iff_name), "%s%d", | |
0a7de745 | 712 | ifc->ifc_name, unit) >= sizeof(fakeif->iff_name)) { |
5ba3f43e | 713 | feth_release(fakeif); |
0a7de745 | 714 | return EINVAL; |
5ba3f43e A |
715 | } |
716 | bzero(&feth_init, sizeof(feth_init)); | |
717 | feth_init.ver = IFNET_INIT_CURRENT_VERSION; | |
0a7de745 | 718 | feth_init.len = sizeof(feth_init); |
5ba3f43e A |
719 | if (feth_in_bsd_mode(fakeif)) { |
720 | if (if_fake_txstart != 0) { | |
721 | feth_init.start = feth_start; | |
722 | } else { | |
723 | feth_init.flags |= IFNET_INIT_LEGACY; | |
724 | feth_init.output = feth_output; | |
725 | } | |
726 | } | |
727 | if (if_fake_nxattach == 0) { | |
728 | feth_init.flags |= IFNET_INIT_NX_NOAUTO; | |
729 | } | |
730 | feth_init.uniqueid = fakeif->iff_name; | |
731 | feth_init.uniqueid_len = strlen(fakeif->iff_name); | |
732 | feth_init.name = ifc->ifc_name; | |
733 | feth_init.unit = unit; | |
734 | feth_init.family = IFNET_FAMILY_ETHERNET; | |
735 | feth_init.type = IFT_ETHER; | |
736 | feth_init.demux = ether_demux; | |
737 | feth_init.add_proto = ether_add_proto; | |
738 | feth_init.del_proto = ether_del_proto; | |
739 | feth_init.check_multi = ether_check_multi; | |
740 | feth_init.framer_extended = ether_frameout_extended; | |
741 | feth_init.softc = fakeif; | |
742 | feth_init.ioctl = feth_ioctl; | |
743 | feth_init.set_bpf_tap = NULL; | |
744 | feth_init.detach = feth_if_free; | |
745 | feth_init.broadcast_addr = etherbroadcastaddr; | |
746 | feth_init.broadcast_len = ETHER_ADDR_LEN; | |
747 | if (feth_in_bsd_mode(fakeif)) { | |
748 | error = ifnet_allocate_extended(&feth_init, &ifp); | |
749 | if (error) { | |
750 | feth_release(fakeif); | |
0a7de745 | 751 | return error; |
5ba3f43e A |
752 | } |
753 | feth_ifnet_set_attrs(fakeif, ifp); | |
754 | } | |
0a7de745 | 755 | fakeif->iff_media_count = MIN(default_media_words_count, IF_FAKE_MEDIA_LIST_MAX); |
5ba3f43e | 756 | bcopy(default_media_words, fakeif->iff_media_list, |
0a7de745 | 757 | fakeif->iff_media_count * sizeof(fakeif->iff_media_list[0])); |
5ba3f43e A |
758 | if (feth_in_bsd_mode(fakeif)) { |
759 | error = ifnet_attach(ifp, NULL); | |
760 | if (error) { | |
761 | ifnet_release(ifp); | |
762 | feth_release(fakeif); | |
0a7de745 | 763 | return error; |
5ba3f43e A |
764 | } |
765 | fakeif->iff_ifp = ifp; | |
766 | } | |
767 | ||
768 | ifnet_set_lladdr(ifp, mac_address, sizeof(mac_address)); | |
0a7de745 | 769 | |
5ba3f43e A |
770 | /* attach as ethernet */ |
771 | bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); | |
0a7de745 | 772 | return 0; |
5ba3f43e A |
773 | } |
774 | ||
775 | static int | |
776 | feth_clone_destroy(ifnet_t ifp) | |
777 | { | |
0a7de745 | 778 | if_fake_ref fakeif; |
5ba3f43e A |
779 | |
780 | feth_lock(); | |
781 | fakeif = ifnet_get_if_fake(ifp); | |
782 | if (fakeif == NULL || feth_is_detaching(fakeif)) { | |
783 | feth_unlock(); | |
0a7de745 | 784 | return 0; |
5ba3f43e A |
785 | } |
786 | feth_set_detaching(fakeif); | |
787 | feth_unlock(); | |
788 | ||
789 | feth_config(ifp, NULL); | |
790 | ifnet_detach(ifp); | |
791 | return 0; | |
792 | } | |
793 | ||
794 | static void | |
795 | feth_enqueue_input(ifnet_t ifp, struct mbuf * m) | |
796 | { | |
797 | struct ifnet_stat_increment_param stats = {}; | |
798 | ||
799 | stats.packets_in = 1; | |
800 | stats.bytes_in = (uint32_t)mbuf_pkthdr_len(m) + ETHER_HDR_LEN; | |
801 | ifnet_input(ifp, m, &stats); | |
802 | } | |
803 | ||
804 | static struct mbuf * | |
805 | copy_mbuf(struct mbuf *m) | |
806 | { | |
0a7de745 A |
807 | struct mbuf * copy_m; |
808 | uint32_t pkt_len; | |
809 | uint32_t offset; | |
5ba3f43e A |
810 | |
811 | if ((m->m_flags & M_PKTHDR) == 0) { | |
0a7de745 | 812 | return NULL; |
5ba3f43e A |
813 | } |
814 | pkt_len = m->m_pkthdr.len; | |
815 | MGETHDR(copy_m, M_DONTWAIT, MT_DATA); | |
816 | if (copy_m == NULL) { | |
817 | goto failed; | |
818 | } | |
819 | if (pkt_len > MHLEN) { | |
820 | if (pkt_len <= MCLBYTES) { | |
821 | MCLGET(copy_m, M_DONTWAIT); | |
822 | } else if (pkt_len <= MBIGCLBYTES) { | |
823 | copy_m = m_mbigget(copy_m, M_DONTWAIT); | |
824 | } else if (pkt_len <= M16KCLBYTES && njcl > 0) { | |
825 | copy_m = m_m16kget(copy_m, M_DONTWAIT); | |
826 | } else { | |
827 | printf("if_fake: copy_mbuf(): packet too large %d\n", | |
0a7de745 | 828 | pkt_len); |
5ba3f43e A |
829 | goto failed; |
830 | } | |
831 | if (copy_m == NULL || (copy_m->m_flags & M_EXT) == 0) { | |
832 | goto failed; | |
833 | } | |
834 | } | |
835 | mbuf_setlen(copy_m, pkt_len); | |
836 | copy_m->m_pkthdr.len = pkt_len; | |
9d749ea3 | 837 | copy_m->m_pkthdr.pkt_svc = m->m_pkthdr.pkt_svc; |
5ba3f43e A |
838 | offset = 0; |
839 | while (m != NULL && offset < pkt_len) { | |
0a7de745 | 840 | uint32_t frag_len; |
5ba3f43e A |
841 | |
842 | frag_len = m->m_len; | |
843 | if (frag_len > (pkt_len - offset)) { | |
844 | printf("if_fake_: Large mbuf fragment %d > %d\n", | |
0a7de745 | 845 | frag_len, (pkt_len - offset)); |
5ba3f43e A |
846 | goto failed; |
847 | } | |
848 | m_copydata(m, 0, frag_len, mtod(copy_m, void *) + offset); | |
849 | offset += frag_len; | |
850 | m = m->m_next; | |
851 | } | |
0a7de745 | 852 | return copy_m; |
5ba3f43e | 853 | |
0a7de745 | 854 | failed: |
5ba3f43e A |
855 | if (copy_m != NULL) { |
856 | m_freem(copy_m); | |
857 | } | |
0a7de745 | 858 | return NULL; |
5ba3f43e A |
859 | } |
860 | ||
f427ee49 A |
861 | static int |
862 | feth_add_mbuf_trailer(struct mbuf *m, void *trailer, size_t trailer_len) | |
863 | { | |
864 | int ret; | |
865 | ASSERT(trailer_len <= FETH_TRAILER_LENGTH_MAX); | |
866 | ||
867 | ret = m_append(m, trailer_len, (caddr_t)trailer); | |
868 | if (ret == 1) { | |
869 | FETH_DPRINTF("%s %zuB trailer added\n", __func__, trailer_len); | |
870 | return 0; | |
871 | } | |
872 | printf("%s m_append failed\n", __func__); | |
873 | return ENOTSUP; | |
874 | } | |
875 | ||
876 | static int | |
877 | feth_add_mbuf_fcs(struct mbuf *m) | |
878 | { | |
879 | uint32_t pkt_len, offset = 0; | |
880 | uint32_t crc = 0; | |
881 | int err = 0; | |
882 | ||
883 | ASSERT(sizeof(crc) == ETHER_CRC_LEN); | |
884 | ||
885 | pkt_len = m->m_pkthdr.len; | |
886 | struct mbuf *iter = m; | |
887 | while (iter != NULL && offset < pkt_len) { | |
888 | uint32_t frag_len = iter->m_len; | |
889 | ASSERT(frag_len <= (pkt_len - offset)); | |
890 | crc = crc32(crc, mtod(iter, void *), frag_len); | |
891 | offset += frag_len; | |
892 | iter = m->m_next; | |
893 | } | |
894 | ||
895 | err = feth_add_mbuf_trailer(m, &crc, ETHER_CRC_LEN); | |
896 | if (err != 0) { | |
897 | return err; | |
898 | } | |
899 | ||
900 | m->m_flags |= M_HASFCS; | |
901 | ||
902 | return 0; | |
903 | } | |
904 | ||
5ba3f43e A |
905 | static void |
906 | feth_output_common(ifnet_t ifp, struct mbuf * m, ifnet_t peer, | |
f427ee49 | 907 | iff_flags_t flags, bool fcs, void *trailer, size_t trailer_len) |
5ba3f43e | 908 | { |
0a7de745 | 909 | void * frame_header; |
5ba3f43e A |
910 | |
911 | frame_header = mbuf_data(m); | |
912 | if ((flags & IFF_FLAGS_HWCSUM) != 0) { | |
913 | m->m_pkthdr.csum_data = 0xffff; | |
914 | m->m_pkthdr.csum_flags = | |
0a7de745 A |
915 | CSUM_DATA_VALID | CSUM_PSEUDO_HDR | |
916 | CSUM_IP_CHECKED | CSUM_IP_VALID; | |
5ba3f43e A |
917 | } |
918 | ||
919 | (void)ifnet_stat_increment_out(ifp, 1, m->m_pkthdr.len, 0); | |
920 | bpf_tap_out(ifp, DLT_EN10MB, m, NULL, 0); | |
921 | ||
f427ee49 A |
922 | if (trailer != 0) { |
923 | feth_add_mbuf_trailer(m, trailer, trailer_len); | |
924 | } | |
925 | if (fcs) { | |
926 | feth_add_mbuf_fcs(m); | |
927 | } | |
928 | ||
5ba3f43e A |
929 | (void)mbuf_pkthdr_setrcvif(m, peer); |
930 | mbuf_pkthdr_setheader(m, frame_header); | |
0a7de745 | 931 | mbuf_pkthdr_adjustlen(m, -ETHER_HDR_LEN); |
5ba3f43e | 932 | (void)mbuf_setdata(m, (char *)mbuf_data(m) + ETHER_HDR_LEN, |
0a7de745 | 933 | mbuf_len(m) - ETHER_HDR_LEN); |
5ba3f43e | 934 | bpf_tap_in(peer, DLT_EN10MB, m, frame_header, |
0a7de745 | 935 | sizeof(struct ether_header)); |
5ba3f43e A |
936 | feth_enqueue_input(peer, m); |
937 | } | |
938 | ||
939 | static void | |
940 | feth_start(ifnet_t ifp) | |
941 | { | |
0a7de745 A |
942 | struct mbuf * copy_m = NULL; |
943 | if_fake_ref fakeif; | |
944 | iff_flags_t flags = 0; | |
f427ee49 A |
945 | bool fcs; |
946 | size_t trailer_len; | |
947 | ifnet_t peer = NULL; | |
0a7de745 A |
948 | struct mbuf * m; |
949 | struct mbuf * save_m; | |
5ba3f43e A |
950 | |
951 | feth_lock(); | |
952 | fakeif = ifnet_get_if_fake(ifp); | |
cb323159 A |
953 | if (fakeif == NULL) { |
954 | feth_unlock(); | |
955 | return; | |
956 | } | |
957 | ||
5ba3f43e A |
958 | if (fakeif->iff_start_busy) { |
959 | feth_unlock(); | |
960 | printf("if_fake: start is busy\n"); | |
961 | return; | |
962 | } | |
cb323159 A |
963 | |
964 | peer = fakeif->iff_peer; | |
965 | flags = fakeif->iff_flags; | |
f427ee49 A |
966 | fcs = fakeif->iff_fcs; |
967 | trailer_len = fakeif->iff_trailer_length; | |
5ba3f43e A |
968 | |
969 | /* check for pending TX */ | |
970 | m = fakeif->iff_pending_tx_packet; | |
971 | if (m != NULL) { | |
972 | if (peer != NULL) { | |
973 | copy_m = copy_mbuf(m); | |
974 | if (copy_m == NULL) { | |
975 | feth_unlock(); | |
976 | return; | |
977 | } | |
978 | } | |
979 | fakeif->iff_pending_tx_packet = NULL; | |
980 | m_freem(m); | |
981 | m = NULL; | |
982 | } | |
983 | fakeif->iff_start_busy = TRUE; | |
984 | feth_unlock(); | |
985 | save_m = NULL; | |
986 | for (;;) { | |
987 | if (copy_m != NULL) { | |
f427ee49 A |
988 | VERIFY(peer != NULL); |
989 | feth_output_common(ifp, copy_m, peer, flags, fcs, | |
990 | feth_trailer, trailer_len); | |
5ba3f43e A |
991 | copy_m = NULL; |
992 | } | |
993 | if (ifnet_dequeue(ifp, &m) != 0) { | |
994 | break; | |
995 | } | |
996 | if (peer == NULL) { | |
997 | m_freem(m); | |
998 | } else { | |
999 | copy_m = copy_mbuf(m); | |
1000 | if (copy_m == NULL) { | |
1001 | save_m = m; | |
1002 | break; | |
1003 | } | |
1004 | m_freem(m); | |
1005 | } | |
1006 | } | |
1007 | peer = NULL; | |
1008 | feth_lock(); | |
1009 | fakeif = ifnet_get_if_fake(ifp); | |
1010 | if (fakeif != NULL) { | |
1011 | fakeif->iff_start_busy = FALSE; | |
1012 | if (save_m != NULL && fakeif->iff_peer != NULL) { | |
1013 | /* save it for next time */ | |
1014 | fakeif->iff_pending_tx_packet = save_m; | |
1015 | save_m = NULL; | |
1016 | } | |
1017 | } | |
1018 | feth_unlock(); | |
1019 | if (save_m != NULL) { | |
1020 | /* didn't save packet, so free it */ | |
1021 | m_freem(save_m); | |
1022 | } | |
1023 | } | |
1024 | ||
1025 | static int | |
1026 | feth_output(ifnet_t ifp, struct mbuf * m) | |
1027 | { | |
0a7de745 A |
1028 | struct mbuf * copy_m; |
1029 | if_fake_ref fakeif; | |
1030 | iff_flags_t flags; | |
f427ee49 A |
1031 | bool fcs; |
1032 | size_t trailer_len; | |
1033 | ifnet_t peer = NULL; | |
5ba3f43e A |
1034 | |
1035 | if (m == NULL) { | |
0a7de745 | 1036 | return 0; |
5ba3f43e A |
1037 | } |
1038 | copy_m = copy_mbuf(m); | |
1039 | m_freem(m); | |
1040 | m = NULL; | |
1041 | if (copy_m == NULL) { | |
1042 | /* count this as an output error */ | |
1043 | ifnet_stat_increment_out(ifp, 0, 0, 1); | |
0a7de745 | 1044 | return 0; |
5ba3f43e A |
1045 | } |
1046 | feth_lock(); | |
1047 | fakeif = ifnet_get_if_fake(ifp); | |
1048 | if (fakeif != NULL) { | |
1049 | peer = fakeif->iff_peer; | |
1050 | flags = fakeif->iff_flags; | |
f427ee49 A |
1051 | fcs = fakeif->iff_fcs; |
1052 | trailer_len = fakeif->iff_trailer_length; | |
5ba3f43e A |
1053 | } |
1054 | feth_unlock(); | |
1055 | if (peer == NULL) { | |
1056 | m_freem(copy_m); | |
1057 | ifnet_stat_increment_out(ifp, 0, 0, 1); | |
0a7de745 | 1058 | return 0; |
5ba3f43e | 1059 | } |
f427ee49 A |
1060 | feth_output_common(ifp, copy_m, peer, flags, fcs, feth_trailer, |
1061 | trailer_len); | |
0a7de745 | 1062 | return 0; |
5ba3f43e A |
1063 | } |
1064 | ||
1065 | static int | |
1066 | feth_config(ifnet_t ifp, ifnet_t peer) | |
1067 | { | |
0a7de745 A |
1068 | int connected = FALSE; |
1069 | int disconnected = FALSE; | |
1070 | int error = 0; | |
1071 | if_fake_ref fakeif = NULL; | |
5ba3f43e A |
1072 | |
1073 | feth_lock(); | |
1074 | fakeif = ifnet_get_if_fake(ifp); | |
1075 | if (fakeif == NULL) { | |
1076 | error = EINVAL; | |
1077 | goto done; | |
1078 | } | |
1079 | if (peer != NULL) { | |
1080 | /* connect to peer */ | |
0a7de745 | 1081 | if_fake_ref peer_fakeif; |
5ba3f43e A |
1082 | |
1083 | peer_fakeif = ifnet_get_if_fake(peer); | |
1084 | if (peer_fakeif == NULL) { | |
1085 | error = EINVAL; | |
1086 | goto done; | |
1087 | } | |
1088 | if (feth_is_detaching(fakeif) || | |
1089 | feth_is_detaching(peer_fakeif) || | |
1090 | peer_fakeif->iff_peer != NULL || | |
1091 | fakeif->iff_peer != NULL) { | |
1092 | error = EBUSY; | |
1093 | goto done; | |
1094 | } | |
1095 | fakeif->iff_peer = peer; | |
1096 | peer_fakeif->iff_peer = ifp; | |
1097 | connected = TRUE; | |
0a7de745 | 1098 | } else if (fakeif->iff_peer != NULL) { |
5ba3f43e | 1099 | /* disconnect from peer */ |
0a7de745 | 1100 | if_fake_ref peer_fakeif; |
5ba3f43e A |
1101 | |
1102 | peer = fakeif->iff_peer; | |
1103 | peer_fakeif = ifnet_get_if_fake(peer); | |
1104 | if (peer_fakeif == NULL) { | |
1105 | /* should not happen */ | |
1106 | error = EINVAL; | |
1107 | goto done; | |
1108 | } | |
1109 | fakeif->iff_peer = NULL; | |
1110 | peer_fakeif->iff_peer = NULL; | |
1111 | disconnected = TRUE; | |
1112 | } | |
1113 | ||
0a7de745 | 1114 | done: |
5ba3f43e A |
1115 | feth_unlock(); |
1116 | ||
1117 | /* generate link status event if we connect or disconnect */ | |
1118 | if (connected) { | |
5ba3f43e A |
1119 | interface_link_event(ifp, KEV_DL_LINK_ON); |
1120 | interface_link_event(peer, KEV_DL_LINK_ON); | |
0a7de745 | 1121 | } else if (disconnected) { |
5ba3f43e A |
1122 | interface_link_event(ifp, KEV_DL_LINK_OFF); |
1123 | interface_link_event(peer, KEV_DL_LINK_OFF); | |
1124 | } | |
0a7de745 | 1125 | return error; |
5ba3f43e A |
1126 | } |
1127 | ||
1128 | static int | |
1129 | feth_set_media(ifnet_t ifp, struct if_fake_request * iffr) | |
1130 | { | |
0a7de745 A |
1131 | if_fake_ref fakeif; |
1132 | int error; | |
5ba3f43e A |
1133 | |
1134 | if (iffr->iffr_media.iffm_count > IF_FAKE_MEDIA_LIST_MAX) { | |
1135 | /* list is too long */ | |
0a7de745 | 1136 | return EINVAL; |
5ba3f43e A |
1137 | } |
1138 | feth_lock(); | |
1139 | fakeif = ifnet_get_if_fake(ifp); | |
1140 | if (fakeif == NULL) { | |
1141 | error = EINVAL; | |
1142 | goto done; | |
1143 | } | |
1144 | fakeif->iff_media_count = iffr->iffr_media.iffm_count; | |
1145 | bcopy(iffr->iffr_media.iffm_list, fakeif->iff_media_list, | |
0a7de745 | 1146 | iffr->iffr_media.iffm_count * sizeof(fakeif->iff_media_list[0])); |
5ba3f43e A |
1147 | #if 0 |
1148 | /* XXX: "auto-negotiate" active with peer? */ | |
1149 | /* generate link status event? */ | |
1150 | fakeif->iff_media_current = iffr->iffr_media.iffm_current; | |
1151 | #endif | |
1152 | error = 0; | |
0a7de745 | 1153 | done: |
5ba3f43e | 1154 | feth_unlock(); |
0a7de745 | 1155 | return error; |
5ba3f43e A |
1156 | } |
1157 | ||
1158 | static int | |
0a7de745 A |
1159 | if_fake_request_copyin(user_addr_t user_addr, |
1160 | struct if_fake_request *iffr, u_int32_t len) | |
5ba3f43e | 1161 | { |
0a7de745 | 1162 | int error; |
5ba3f43e A |
1163 | |
1164 | if (user_addr == USER_ADDR_NULL || len < sizeof(*iffr)) { | |
1165 | error = EINVAL; | |
1166 | goto done; | |
1167 | } | |
1168 | error = copyin(user_addr, iffr, sizeof(*iffr)); | |
1169 | if (error != 0) { | |
1170 | goto done; | |
1171 | } | |
1172 | if (iffr->iffr_reserved[0] != 0 || iffr->iffr_reserved[1] != 0 || | |
1173 | iffr->iffr_reserved[2] != 0 || iffr->iffr_reserved[3] != 0) { | |
1174 | error = EINVAL; | |
1175 | goto done; | |
1176 | } | |
0a7de745 A |
1177 | done: |
1178 | return error; | |
5ba3f43e A |
1179 | } |
1180 | ||
1181 | static int | |
1182 | feth_set_drvspec(ifnet_t ifp, uint32_t cmd, u_int32_t len, | |
0a7de745 | 1183 | user_addr_t user_addr) |
5ba3f43e | 1184 | { |
0a7de745 A |
1185 | int error; |
1186 | struct if_fake_request iffr; | |
1187 | ifnet_t peer; | |
5ba3f43e A |
1188 | |
1189 | switch (cmd) { | |
1190 | case IF_FAKE_S_CMD_SET_PEER: | |
1191 | error = if_fake_request_copyin(user_addr, &iffr, len); | |
1192 | if (error != 0) { | |
1193 | break; | |
1194 | } | |
1195 | if (iffr.iffr_peer_name[0] == '\0') { | |
1196 | error = feth_config(ifp, NULL); | |
1197 | break; | |
1198 | } | |
1199 | ||
1200 | /* ensure nul termination */ | |
1201 | iffr.iffr_peer_name[IFNAMSIZ - 1] = '\0'; | |
1202 | peer = ifunit(iffr.iffr_peer_name); | |
1203 | if (peer == NULL) { | |
1204 | error = ENXIO; | |
1205 | break; | |
1206 | } | |
1207 | if (ifnet_type(peer) != IFT_ETHER) { | |
1208 | error = EINVAL; | |
1209 | break; | |
1210 | } | |
1211 | if (strcmp(ifnet_name(peer), FAKE_ETHER_NAME) != 0) { | |
1212 | error = EINVAL; | |
1213 | break; | |
1214 | } | |
1215 | error = feth_config(ifp, peer); | |
1216 | break; | |
1217 | case IF_FAKE_S_CMD_SET_MEDIA: | |
1218 | error = if_fake_request_copyin(user_addr, &iffr, len); | |
1219 | if (error != 0) { | |
1220 | break; | |
1221 | } | |
1222 | error = feth_set_media(ifp, &iffr); | |
1223 | break; | |
a39ff7e2 A |
1224 | case IF_FAKE_S_CMD_SET_DEQUEUE_STALL: |
1225 | error = if_fake_request_copyin(user_addr, &iffr, len); | |
1226 | if (error != 0) { | |
1227 | break; | |
1228 | } | |
1229 | error = feth_enable_dequeue_stall(ifp, | |
1230 | iffr.iffr_dequeue_stall); | |
1231 | break; | |
5ba3f43e A |
1232 | default: |
1233 | error = EOPNOTSUPP; | |
1234 | break; | |
1235 | } | |
0a7de745 | 1236 | return error; |
5ba3f43e A |
1237 | } |
1238 | ||
1239 | static int | |
1240 | feth_get_drvspec(ifnet_t ifp, u_int32_t cmd, u_int32_t len, | |
0a7de745 | 1241 | user_addr_t user_addr) |
5ba3f43e | 1242 | { |
0a7de745 A |
1243 | int error = EOPNOTSUPP; |
1244 | if_fake_ref fakeif; | |
1245 | struct if_fake_request iffr; | |
1246 | ifnet_t peer; | |
5ba3f43e A |
1247 | |
1248 | switch (cmd) { | |
1249 | case IF_FAKE_G_CMD_GET_PEER: | |
1250 | if (len < sizeof(iffr)) { | |
1251 | error = EINVAL; | |
1252 | break; | |
1253 | } | |
1254 | feth_lock(); | |
cb323159 | 1255 | fakeif = ifnet_get_if_fake(ifp); |
5ba3f43e A |
1256 | if (fakeif == NULL) { |
1257 | feth_unlock(); | |
1258 | error = EOPNOTSUPP; | |
1259 | break; | |
1260 | } | |
1261 | peer = fakeif->iff_peer; | |
1262 | feth_unlock(); | |
1263 | bzero(&iffr, sizeof(iffr)); | |
1264 | if (peer != NULL) { | |
1265 | strlcpy(iffr.iffr_peer_name, | |
0a7de745 A |
1266 | if_name(peer), |
1267 | sizeof(iffr.iffr_peer_name)); | |
5ba3f43e A |
1268 | } |
1269 | error = copyout(&iffr, user_addr, sizeof(iffr)); | |
1270 | break; | |
1271 | default: | |
1272 | break; | |
1273 | } | |
0a7de745 | 1274 | return error; |
5ba3f43e A |
1275 | } |
1276 | ||
1277 | union ifdrvu { | |
0a7de745 A |
1278 | struct ifdrv32 *ifdrvu_32; |
1279 | struct ifdrv64 *ifdrvu_64; | |
1280 | void *ifdrvu_p; | |
5ba3f43e A |
1281 | }; |
1282 | ||
1283 | static int | |
1284 | feth_ioctl(ifnet_t ifp, u_long cmd, void * data) | |
1285 | { | |
0a7de745 A |
1286 | unsigned int count; |
1287 | struct ifdevmtu * devmtu_p; | |
1288 | union ifdrvu drv; | |
1289 | uint32_t drv_cmd; | |
1290 | uint32_t drv_len; | |
1291 | boolean_t drv_set_command = FALSE; | |
1292 | int error = 0; | |
1293 | struct ifmediareq * ifmr; | |
1294 | struct ifreq * ifr; | |
1295 | if_fake_ref fakeif; | |
1296 | int status; | |
1297 | user_addr_t user_addr; | |
5ba3f43e A |
1298 | |
1299 | ifr = (struct ifreq *)data; | |
1300 | switch (cmd) { | |
1301 | case SIOCSIFADDR: | |
1302 | ifnet_set_flags(ifp, IFF_UP, IFF_UP); | |
1303 | break; | |
1304 | ||
1305 | case SIOCGIFMEDIA32: | |
1306 | case SIOCGIFMEDIA64: | |
1307 | feth_lock(); | |
cb323159 | 1308 | fakeif = ifnet_get_if_fake(ifp); |
5ba3f43e A |
1309 | if (fakeif == NULL) { |
1310 | feth_unlock(); | |
0a7de745 | 1311 | return EOPNOTSUPP; |
5ba3f43e A |
1312 | } |
1313 | status = (fakeif->iff_peer != NULL) | |
1314 | ? (IFM_AVALID | IFM_ACTIVE) : IFM_AVALID; | |
1315 | ifmr = (struct ifmediareq *)data; | |
1316 | user_addr = (cmd == SIOCGIFMEDIA64) ? | |
0a7de745 A |
1317 | ((struct ifmediareq64 *)ifmr)->ifmu_ulist : |
1318 | CAST_USER_ADDR_T(((struct ifmediareq32 *)ifmr)->ifmu_ulist); | |
5ba3f43e A |
1319 | count = ifmr->ifm_count; |
1320 | ifmr->ifm_active = IFM_ETHER; | |
1321 | ifmr->ifm_current = IFM_ETHER; | |
1322 | ifmr->ifm_mask = 0; | |
1323 | ifmr->ifm_status = status; | |
1324 | if (user_addr == USER_ADDR_NULL) { | |
1325 | ifmr->ifm_count = fakeif->iff_media_count; | |
0a7de745 | 1326 | } else if (count > 0) { |
5ba3f43e A |
1327 | if (count > fakeif->iff_media_count) { |
1328 | count = fakeif->iff_media_count; | |
1329 | } | |
1330 | ifmr->ifm_count = count; | |
1331 | error = copyout(&fakeif->iff_media_list, user_addr, | |
0a7de745 | 1332 | count * sizeof(int)); |
5ba3f43e A |
1333 | } |
1334 | feth_unlock(); | |
1335 | break; | |
1336 | ||
1337 | case SIOCGIFDEVMTU: | |
1338 | devmtu_p = &ifr->ifr_devmtu; | |
1339 | devmtu_p->ifdm_current = ifnet_mtu(ifp); | |
cb323159 | 1340 | devmtu_p->ifdm_max = feth_max_mtu(ifp); |
5ba3f43e A |
1341 | devmtu_p->ifdm_min = IF_MINMTU; |
1342 | break; | |
1343 | ||
1344 | case SIOCSIFMTU: | |
cb323159 A |
1345 | if ((unsigned int)ifr->ifr_mtu > feth_max_mtu(ifp) || |
1346 | ifr->ifr_mtu < IF_MINMTU) { | |
5ba3f43e A |
1347 | error = EINVAL; |
1348 | } else { | |
1349 | error = ifnet_set_mtu(ifp, ifr->ifr_mtu); | |
1350 | } | |
1351 | break; | |
1352 | ||
1353 | case SIOCSDRVSPEC32: | |
1354 | case SIOCSDRVSPEC64: | |
1355 | error = proc_suser(current_proc()); | |
1356 | if (error != 0) { | |
1357 | break; | |
1358 | } | |
1359 | drv_set_command = TRUE; | |
f427ee49 | 1360 | OS_FALLTHROUGH; |
5ba3f43e A |
1361 | case SIOCGDRVSPEC32: |
1362 | case SIOCGDRVSPEC64: | |
1363 | drv.ifdrvu_p = data; | |
1364 | if (cmd == SIOCGDRVSPEC32 || cmd == SIOCSDRVSPEC32) { | |
1365 | drv_cmd = drv.ifdrvu_32->ifd_cmd; | |
1366 | drv_len = drv.ifdrvu_32->ifd_len; | |
1367 | user_addr = CAST_USER_ADDR_T(drv.ifdrvu_32->ifd_data); | |
5ba3f43e A |
1368 | } else { |
1369 | drv_cmd = drv.ifdrvu_64->ifd_cmd; | |
1370 | drv_len = drv.ifdrvu_64->ifd_len; | |
1371 | user_addr = drv.ifdrvu_64->ifd_data; | |
1372 | } | |
1373 | if (drv_set_command) { | |
1374 | error = feth_set_drvspec(ifp, drv_cmd, drv_len, | |
0a7de745 | 1375 | user_addr); |
5ba3f43e A |
1376 | } else { |
1377 | error = feth_get_drvspec(ifp, drv_cmd, drv_len, | |
0a7de745 | 1378 | user_addr); |
5ba3f43e A |
1379 | } |
1380 | break; | |
1381 | ||
1382 | case SIOCSIFLLADDR: | |
1383 | error = ifnet_set_lladdr(ifp, ifr->ifr_addr.sa_data, | |
1384 | ifr->ifr_addr.sa_len); | |
1385 | break; | |
1386 | ||
1387 | case SIOCSIFFLAGS: | |
a39ff7e2 A |
1388 | if ((ifp->if_flags & IFF_UP) != 0) { |
1389 | /* marked up, set running if not already set */ | |
1390 | if ((ifp->if_flags & IFF_RUNNING) == 0) { | |
1391 | /* set running */ | |
1392 | error = ifnet_set_flags(ifp, IFF_RUNNING, | |
1393 | IFF_RUNNING); | |
1394 | } | |
1395 | } else if ((ifp->if_flags & IFF_RUNNING) != 0) { | |
1396 | /* marked down, clear running */ | |
1397 | error = ifnet_set_flags(ifp, 0, IFF_RUNNING); | |
1398 | } | |
5ba3f43e A |
1399 | break; |
1400 | ||
1401 | case SIOCADDMULTI: | |
1402 | case SIOCDELMULTI: | |
1403 | error = 0; | |
1404 | break; | |
1405 | default: | |
1406 | error = EOPNOTSUPP; | |
1407 | break; | |
1408 | } | |
1409 | return error; | |
1410 | } | |
1411 | ||
0a7de745 | 1412 | static void |
5ba3f43e A |
1413 | feth_if_free(ifnet_t ifp) |
1414 | { | |
0a7de745 | 1415 | if_fake_ref fakeif; |
5ba3f43e A |
1416 | |
1417 | if (ifp == NULL) { | |
1418 | return; | |
1419 | } | |
1420 | feth_lock(); | |
1421 | fakeif = ifnet_get_if_fake(ifp); | |
1422 | if (fakeif == NULL) { | |
1423 | feth_unlock(); | |
1424 | return; | |
1425 | } | |
1426 | ifp->if_softc = NULL; | |
1427 | feth_unlock(); | |
1428 | feth_release(fakeif); | |
1429 | ifnet_release(ifp); | |
1430 | return; | |
1431 | } | |
1432 | ||
1433 | __private_extern__ void | |
1434 | if_fake_init(void) | |
1435 | { | |
1436 | int error; | |
1437 | ||
1438 | feth_lock_init(); | |
1439 | error = if_clone_attach(&feth_cloner); | |
1440 | if (error != 0) { | |
1441 | return; | |
1442 | } | |
1443 | return; | |
1444 | } |