]>
Commit | Line | Data |
---|---|---|
5ba3f43e | 1 | /* |
0a7de745 | 2 | * Copyright (c) 2015-2019 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 | ||
124 | static int if_fake_copypkt_mode = 0; | |
125 | SYSCTL_INT(_net_link_fake, OID_AUTO, copypkt_mode, CTLFLAG_RW | CTLFLAG_LOCKED, | |
126 | &if_fake_copypkt_mode, 0, "Fake interface copying packet to peer"); | |
127 | ||
128 | /* sysctl net.link.fake.tx_headroom */ | |
129 | #define FETH_TX_HEADROOM_MAX 32 | |
130 | static unsigned int if_fake_tx_headroom = 0; | |
131 | ||
132 | static int | |
133 | feth_tx_headroom_sysctl SYSCTL_HANDLER_ARGS | |
134 | { | |
135 | #pragma unused(oidp, arg1, arg2) | |
136 | unsigned int new_value; | |
137 | int changed; | |
138 | int error; | |
139 | ||
140 | error = sysctl_io_number(req, if_fake_tx_headroom, | |
141 | sizeof(if_fake_tx_headroom), &new_value, &changed); | |
142 | if (error == 0 && changed != 0) { | |
143 | if (new_value > FETH_TX_HEADROOM_MAX || | |
144 | (new_value % 8) != 0) { | |
145 | return EINVAL; | |
146 | } | |
147 | if_fake_tx_headroom = new_value; | |
148 | } | |
149 | return 0; | |
150 | } | |
151 | ||
152 | SYSCTL_PROC(_net_link_fake, OID_AUTO, tx_headroom, | |
153 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, | |
154 | 0, 0, feth_tx_headroom_sysctl, "IU", "Fake ethernet Tx headroom"); | |
155 | ||
156 | ||
157 | /* sysctl net.link.fake.max_mtu */ | |
158 | #define FETH_MAX_MTU_DEFAULT 2048 | |
159 | #define FETH_MAX_MTU_MAX ((16 * 1024) - ETHER_HDR_LEN) | |
160 | ||
161 | static unsigned int if_fake_max_mtu = FETH_MAX_MTU_DEFAULT; | |
162 | ||
163 | /* sysctl net.link.fake.buflet_size */ | |
164 | #define FETH_BUFLET_SIZE_MIN 512 | |
165 | #define FETH_BUFLET_SIZE_MAX 2048 | |
166 | ||
167 | static unsigned int if_fake_buflet_size = FETH_BUFLET_SIZE_MIN; | |
168 | ||
169 | static int | |
170 | feth_max_mtu_sysctl SYSCTL_HANDLER_ARGS | |
171 | { | |
172 | #pragma unused(oidp, arg1, arg2) | |
173 | unsigned int new_value; | |
174 | int changed; | |
175 | int error; | |
176 | ||
177 | error = sysctl_io_number(req, if_fake_max_mtu, | |
178 | sizeof(if_fake_max_mtu), &new_value, &changed); | |
179 | if (error == 0 && changed != 0) { | |
180 | if (new_value > FETH_MAX_MTU_MAX || | |
181 | new_value < ETHERMTU || | |
182 | new_value <= if_fake_buflet_size) { | |
183 | return EINVAL; | |
184 | } | |
185 | if_fake_max_mtu = new_value; | |
186 | } | |
187 | return 0; | |
188 | } | |
189 | ||
190 | SYSCTL_PROC(_net_link_fake, OID_AUTO, max_mtu, | |
191 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, | |
192 | 0, 0, feth_max_mtu_sysctl, "IU", "Fake interface maximum MTU"); | |
193 | ||
194 | static int | |
195 | feth_buflet_size_sysctl SYSCTL_HANDLER_ARGS | |
196 | { | |
197 | #pragma unused(oidp, arg1, arg2) | |
198 | unsigned int new_value; | |
199 | int changed; | |
200 | int error; | |
201 | ||
202 | error = sysctl_io_number(req, if_fake_buflet_size, | |
203 | sizeof(if_fake_buflet_size), &new_value, &changed); | |
204 | if (error == 0 && changed != 0) { | |
205 | /* must be a power of 2 between min and max */ | |
206 | if (new_value > FETH_BUFLET_SIZE_MAX || | |
207 | new_value < FETH_BUFLET_SIZE_MIN || | |
208 | !is_power_of_two(new_value) || | |
209 | new_value >= if_fake_max_mtu) { | |
210 | return EINVAL; | |
211 | } | |
212 | if_fake_buflet_size = new_value; | |
213 | } | |
214 | return 0; | |
215 | } | |
216 | ||
217 | SYSCTL_PROC(_net_link_fake, OID_AUTO, buflet_size, | |
218 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, | |
219 | 0, 0, feth_buflet_size_sysctl, "IU", "Fake interface buflet size"); | |
220 | ||
221 | static unsigned int if_fake_user_access = 0; | |
222 | ||
223 | static int | |
224 | feth_user_access_sysctl SYSCTL_HANDLER_ARGS | |
225 | { | |
226 | #pragma unused(oidp, arg1, arg2) | |
227 | unsigned int new_value; | |
228 | int changed; | |
229 | int error; | |
230 | ||
231 | error = sysctl_io_number(req, if_fake_user_access, | |
232 | sizeof(if_fake_user_access), &new_value, &changed); | |
233 | if (error == 0 && changed != 0) { | |
234 | if (new_value != 0) { | |
235 | if (new_value != 1) { | |
236 | return EINVAL; | |
237 | } | |
238 | /* | |
239 | * copypkt mode requires a kernel only buffer pool so | |
240 | * it is incompatible with user access mode. | |
241 | */ | |
242 | if (if_fake_copypkt_mode != 0) { | |
243 | return ENOTSUP; | |
244 | } | |
245 | } | |
246 | if_fake_user_access = new_value; | |
247 | } | |
248 | return 0; | |
249 | } | |
250 | ||
251 | SYSCTL_PROC(_net_link_fake, OID_AUTO, user_access, | |
252 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, | |
253 | 0, 0, feth_user_access_sysctl, "IU", "Fake interface user access"); | |
254 | ||
255 | /* sysctl net.link.fake.if_adv_intvl (unit: millisecond) */ | |
256 | #define FETH_IF_ADV_INTVL_MIN 10 | |
257 | #define FETH_IF_ADV_INTVL_MAX INT_MAX | |
258 | ||
259 | static int if_fake_if_adv_interval = 0; /* no interface advisory */ | |
260 | static int | |
261 | feth_if_adv_interval_sysctl SYSCTL_HANDLER_ARGS | |
262 | { | |
263 | #pragma unused(oidp, arg1, arg2) | |
264 | unsigned int new_value; | |
265 | int changed; | |
266 | int error; | |
267 | ||
268 | error = sysctl_io_number(req, if_fake_if_adv_interval, | |
269 | sizeof(if_fake_if_adv_interval), &new_value, &changed); | |
270 | if (error == 0 && changed != 0) { | |
271 | if ((new_value != 0) && (new_value > FETH_IF_ADV_INTVL_MAX || | |
272 | new_value < FETH_IF_ADV_INTVL_MIN)) { | |
273 | return EINVAL; | |
274 | } | |
275 | if_fake_if_adv_interval = new_value; | |
276 | } | |
277 | return 0; | |
278 | } | |
279 | ||
280 | SYSCTL_PROC(_net_link_fake, OID_AUTO, if_adv_intvl, | |
281 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, | |
282 | feth_if_adv_interval_sysctl, "IU", | |
283 | "Fake interface will generate interface advisories reports at the specified interval in ms"); | |
284 | ||
285 | /* sysctl net.link.fake.tx_drops */ | |
286 | /* | |
287 | * Fake ethernet will drop packet on the transmit path at the specified | |
288 | * rate, i.e drop one in every if_fake_tx_drops number of packets. | |
289 | */ | |
290 | #define FETH_TX_DROPS_MIN 0 | |
291 | #define FETH_TX_DROPS_MAX INT_MAX | |
292 | static int if_fake_tx_drops = 0; /* no packets are dropped */ | |
293 | static int | |
294 | feth_fake_tx_drops_sysctl SYSCTL_HANDLER_ARGS | |
295 | { | |
296 | #pragma unused(oidp, arg1, arg2) | |
297 | unsigned int new_value; | |
298 | int changed; | |
299 | int error; | |
300 | ||
301 | error = sysctl_io_number(req, if_fake_tx_drops, | |
302 | sizeof(if_fake_tx_drops), &new_value, &changed); | |
303 | if (error == 0 && changed != 0) { | |
304 | if (new_value > FETH_TX_DROPS_MAX || | |
305 | new_value < FETH_TX_DROPS_MIN) { | |
306 | return EINVAL; | |
307 | } | |
308 | if_fake_tx_drops = new_value; | |
309 | } | |
310 | return 0; | |
311 | } | |
312 | ||
313 | SYSCTL_PROC(_net_link_fake, OID_AUTO, tx_drops, | |
314 | CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, | |
315 | feth_fake_tx_drops_sysctl, "IU", | |
316 | "Fake interface will intermittently drop packets on Tx path"); | |
317 | ||
5ba3f43e | 318 | /** |
0a7de745 A |
319 | ** virtual ethernet structures, types |
320 | **/ | |
5ba3f43e | 321 | |
0a7de745 A |
322 | #define IFF_NUM_TX_RINGS_WMM_MODE 4 |
323 | #define IFF_NUM_RX_RINGS_WMM_MODE 1 | |
324 | #define IFF_MAX_TX_RINGS IFF_NUM_TX_RINGS_WMM_MODE | |
325 | #define IFF_MAX_RX_RINGS IFF_NUM_RX_RINGS_WMM_MODE | |
d9a64523 | 326 | |
0a7de745 A |
327 | typedef uint16_t iff_flags_t; |
328 | #define IFF_FLAGS_HWCSUM 0x0001 | |
329 | #define IFF_FLAGS_BSD_MODE 0x0002 | |
330 | #define IFF_FLAGS_DETACHING 0x0004 | |
331 | #define IFF_FLAGS_WMM_MODE 0x0008 | |
cb323159 A |
332 | #define IFF_FLAGS_MULTIBUFLETS 0x0010 |
333 | #define IFF_FLAGS_COPYPKT_MODE 0x0020 | |
5ba3f43e A |
334 | |
335 | ||
336 | struct if_fake { | |
0a7de745 A |
337 | char iff_name[IFNAMSIZ]; /* our unique id */ |
338 | ifnet_t iff_ifp; | |
339 | iff_flags_t iff_flags; | |
340 | uint32_t iff_retain_count; | |
341 | ifnet_t iff_peer; /* the other end */ | |
342 | int iff_media_current; | |
343 | int iff_media_active; | |
344 | uint32_t iff_media_count; | |
345 | int iff_media_list[IF_FAKE_MEDIA_LIST_MAX]; | |
346 | struct mbuf * iff_pending_tx_packet; | |
347 | boolean_t iff_start_busy; | |
cb323159 | 348 | unsigned int iff_max_mtu; |
5ba3f43e A |
349 | }; |
350 | ||
351 | typedef struct if_fake * if_fake_ref; | |
352 | ||
353 | static if_fake_ref | |
354 | ifnet_get_if_fake(ifnet_t ifp); | |
355 | ||
0a7de745 | 356 | #define FETH_DPRINTF(fmt, ...) \ |
5ba3f43e A |
357 | { if (if_fake_debug != 0) printf("%s " fmt, __func__, ## __VA_ARGS__); } |
358 | ||
359 | static inline boolean_t | |
360 | feth_in_bsd_mode(if_fake_ref fakeif) | |
361 | { | |
0a7de745 | 362 | return (fakeif->iff_flags & IFF_FLAGS_BSD_MODE) != 0; |
5ba3f43e A |
363 | } |
364 | ||
365 | static inline void | |
366 | feth_set_detaching(if_fake_ref fakeif) | |
367 | { | |
368 | fakeif->iff_flags |= IFF_FLAGS_DETACHING; | |
369 | } | |
370 | ||
371 | static inline boolean_t | |
372 | feth_is_detaching(if_fake_ref fakeif) | |
373 | { | |
0a7de745 | 374 | return (fakeif->iff_flags & IFF_FLAGS_DETACHING) != 0; |
5ba3f43e A |
375 | } |
376 | ||
a39ff7e2 A |
377 | static int |
378 | feth_enable_dequeue_stall(ifnet_t ifp, uint32_t enable) | |
379 | { | |
380 | int error; | |
381 | ||
0a7de745 | 382 | if (enable != 0) { |
a39ff7e2 | 383 | error = ifnet_disable_output(ifp); |
0a7de745 | 384 | } else { |
a39ff7e2 | 385 | error = ifnet_enable_output(ifp); |
0a7de745 | 386 | } |
a39ff7e2 | 387 | |
0a7de745 | 388 | return error; |
a39ff7e2 | 389 | } |
5ba3f43e | 390 | |
d9a64523 | 391 | |
0a7de745 A |
392 | #define FETH_MAXUNIT IF_MAXUNIT |
393 | #define FETH_ZONE_MAX_ELEM MIN(IFNETS_MAX, FETH_MAXUNIT) | |
394 | #define M_FAKE M_DEVBUF | |
5ba3f43e | 395 | |
0a7de745 A |
396 | static int feth_clone_create(struct if_clone *, u_int32_t, void *); |
397 | static int feth_clone_destroy(ifnet_t); | |
398 | static int feth_output(ifnet_t ifp, struct mbuf *m); | |
399 | static void feth_start(ifnet_t ifp); | |
400 | static int feth_ioctl(ifnet_t ifp, u_long cmd, void * addr); | |
401 | static int feth_config(ifnet_t ifp, ifnet_t peer); | |
402 | static void feth_if_free(ifnet_t ifp); | |
403 | static void feth_ifnet_set_attrs(if_fake_ref fakeif, ifnet_t ifp); | |
404 | static void feth_free(if_fake_ref fakeif); | |
5ba3f43e A |
405 | |
406 | static struct if_clone | |
0a7de745 | 407 | feth_cloner = IF_CLONE_INITIALIZER(FAKE_ETHER_NAME, |
d9a64523 A |
408 | feth_clone_create, |
409 | feth_clone_destroy, | |
410 | 0, | |
411 | FETH_MAXUNIT, | |
412 | FETH_ZONE_MAX_ELEM, | |
413 | sizeof(struct if_fake)); | |
0a7de745 | 414 | static void interface_link_event(ifnet_t ifp, u_int32_t event_code); |
5ba3f43e A |
415 | |
416 | /* some media words to pretend to be ethernet */ | |
417 | static int default_media_words[] = { | |
418 | IFM_MAKEWORD(IFM_ETHER, 0, 0, 0), | |
419 | IFM_MAKEWORD(IFM_ETHER, IFM_10G_T, IFM_FDX, 0), | |
420 | IFM_MAKEWORD(IFM_ETHER, IFM_2500_T, IFM_FDX, 0), | |
421 | IFM_MAKEWORD(IFM_ETHER, IFM_5000_T, IFM_FDX, 0), | |
0a7de745 A |
422 | |
423 | IFM_MAKEWORD(IFM_ETHER, IFM_10G_KX4, IFM_FDX, 0), | |
424 | IFM_MAKEWORD(IFM_ETHER, IFM_20G_KR2, IFM_FDX, 0), | |
425 | IFM_MAKEWORD(IFM_ETHER, IFM_2500_SX, IFM_FDX, 0), | |
426 | IFM_MAKEWORD(IFM_ETHER, IFM_25G_KR, IFM_FDX, 0), | |
427 | IFM_MAKEWORD(IFM_ETHER, IFM_40G_SR4, IFM_FDX, 0), | |
428 | IFM_MAKEWORD(IFM_ETHER, IFM_50G_CR2, IFM_FDX, 0), | |
429 | IFM_MAKEWORD(IFM_ETHER, IFM_56G_R4, IFM_FDX, 0), | |
430 | IFM_MAKEWORD(IFM_ETHER, IFM_100G_CR4, IFM_FDX, 0), | |
431 | IFM_MAKEWORD(IFM_ETHER, IFM_400G_AUI8, IFM_FDX, 0), | |
5ba3f43e | 432 | }; |
0a7de745 A |
433 | #define default_media_words_count (sizeof(default_media_words) \ |
434 | / sizeof (default_media_words[0])) | |
5ba3f43e A |
435 | |
436 | /** | |
0a7de745 A |
437 | ** veth locks |
438 | **/ | |
5ba3f43e A |
439 | static inline lck_grp_t * |
440 | my_lck_grp_alloc_init(const char * grp_name) | |
441 | { | |
0a7de745 A |
442 | lck_grp_t * grp; |
443 | lck_grp_attr_t * grp_attrs; | |
444 | ||
5ba3f43e A |
445 | grp_attrs = lck_grp_attr_alloc_init(); |
446 | grp = lck_grp_alloc_init(grp_name, grp_attrs); | |
447 | lck_grp_attr_free(grp_attrs); | |
0a7de745 | 448 | return grp; |
5ba3f43e A |
449 | } |
450 | ||
451 | static inline lck_mtx_t * | |
452 | my_lck_mtx_alloc_init(lck_grp_t * lck_grp) | |
453 | { | |
0a7de745 A |
454 | lck_attr_t * lck_attrs; |
455 | lck_mtx_t * lck_mtx; | |
5ba3f43e A |
456 | |
457 | lck_attrs = lck_attr_alloc_init(); | |
458 | lck_mtx = lck_mtx_alloc_init(lck_grp, lck_attrs); | |
459 | lck_attr_free(lck_attrs); | |
0a7de745 | 460 | return lck_mtx; |
5ba3f43e A |
461 | } |
462 | ||
0a7de745 | 463 | static lck_mtx_t * feth_lck_mtx; |
5ba3f43e A |
464 | |
465 | static inline void | |
466 | feth_lock_init(void) | |
467 | { | |
0a7de745 | 468 | lck_grp_t * feth_lck_grp; |
5ba3f43e A |
469 | |
470 | feth_lck_grp = my_lck_grp_alloc_init("fake"); | |
471 | feth_lck_mtx = my_lck_mtx_alloc_init(feth_lck_grp); | |
472 | } | |
473 | ||
474 | #if 0 | |
475 | static inline void | |
476 | feth_assert_lock_not_held(void) | |
477 | { | |
478 | LCK_MTX_ASSERT(feth_lck_mtx, LCK_MTX_ASSERT_NOTOWNED); | |
479 | return; | |
480 | } | |
481 | #endif | |
482 | ||
483 | static inline void | |
484 | feth_lock(void) | |
485 | { | |
486 | lck_mtx_lock(feth_lck_mtx); | |
487 | return; | |
488 | } | |
489 | ||
490 | static inline void | |
491 | feth_unlock(void) | |
492 | { | |
493 | lck_mtx_unlock(feth_lck_mtx); | |
494 | return; | |
495 | } | |
496 | ||
497 | static inline int | |
cb323159 | 498 | get_max_mtu(int bsd_mode, unsigned int max_mtu) |
5ba3f43e | 499 | { |
cb323159 A |
500 | unsigned int mtu; |
501 | ||
502 | if (bsd_mode != 0) { | |
503 | mtu = (njcl > 0) ? (M16KCLBYTES - ETHER_HDR_LEN) | |
504 | : MBIGCLBYTES - ETHER_HDR_LEN; | |
505 | if (mtu > max_mtu) { | |
506 | mtu = max_mtu; | |
507 | } | |
508 | } else { | |
509 | mtu = max_mtu; | |
5ba3f43e | 510 | } |
cb323159 A |
511 | return mtu; |
512 | } | |
513 | ||
514 | static inline unsigned int | |
515 | feth_max_mtu(ifnet_t ifp) | |
516 | { | |
517 | if_fake_ref fakeif; | |
518 | unsigned int max_mtu = ETHERMTU; | |
519 | ||
520 | feth_lock(); | |
521 | fakeif = ifnet_get_if_fake(ifp); | |
522 | if (fakeif != NULL) { | |
523 | max_mtu = fakeif->iff_max_mtu; | |
524 | } | |
525 | feth_unlock(); | |
526 | return max_mtu; | |
5ba3f43e A |
527 | } |
528 | ||
529 | static void | |
530 | feth_free(if_fake_ref fakeif) | |
531 | { | |
532 | assert(fakeif->iff_retain_count == 0); | |
533 | if (feth_in_bsd_mode(fakeif)) { | |
534 | if (fakeif->iff_pending_tx_packet) { | |
535 | m_freem(fakeif->iff_pending_tx_packet); | |
536 | } | |
537 | } | |
538 | ||
539 | FETH_DPRINTF("%s\n", fakeif->iff_name); | |
d9a64523 | 540 | if_clone_softc_deallocate(&feth_cloner, fakeif); |
5ba3f43e A |
541 | } |
542 | ||
543 | static void | |
544 | feth_release(if_fake_ref fakeif) | |
545 | { | |
0a7de745 | 546 | u_int32_t old_retain_count; |
5ba3f43e A |
547 | |
548 | old_retain_count = OSDecrementAtomic(&fakeif->iff_retain_count); | |
549 | switch (old_retain_count) { | |
550 | case 0: | |
551 | assert(old_retain_count != 0); | |
552 | break; | |
553 | case 1: | |
554 | feth_free(fakeif); | |
555 | break; | |
556 | default: | |
557 | break; | |
558 | } | |
559 | return; | |
560 | } | |
561 | ||
562 | ||
563 | /** | |
0a7de745 A |
564 | ** feth interface routines |
565 | **/ | |
5ba3f43e A |
566 | static void |
567 | feth_ifnet_set_attrs(if_fake_ref fakeif, ifnet_t ifp) | |
568 | { | |
569 | (void)ifnet_set_capabilities_enabled(ifp, 0, -1); | |
570 | ifnet_set_addrlen(ifp, ETHER_ADDR_LEN); | |
571 | ifnet_set_baudrate(ifp, 0); | |
572 | ifnet_set_mtu(ifp, ETHERMTU); | |
573 | ifnet_set_flags(ifp, | |
0a7de745 A |
574 | IFF_BROADCAST | IFF_MULTICAST | IFF_SIMPLEX, |
575 | 0xffff); | |
5ba3f43e A |
576 | ifnet_set_hdrlen(ifp, sizeof(struct ether_header)); |
577 | if ((fakeif->iff_flags & IFF_FLAGS_HWCSUM) != 0) { | |
578 | ifnet_set_offload(ifp, | |
579 | IFNET_CSUM_IP | IFNET_CSUM_TCP | IFNET_CSUM_UDP | | |
580 | IFNET_CSUM_TCPIPV6 | IFNET_CSUM_UDPIPV6); | |
581 | } else { | |
582 | ifnet_set_offload(ifp, 0); | |
583 | } | |
584 | } | |
585 | ||
586 | static void | |
587 | interface_link_event(ifnet_t ifp, u_int32_t event_code) | |
588 | { | |
589 | struct { | |
0a7de745 A |
590 | struct kern_event_msg header; |
591 | u_int32_t unit; | |
592 | char if_name[IFNAMSIZ]; | |
5ba3f43e A |
593 | } event; |
594 | ||
595 | bzero(&event, sizeof(event)); | |
596 | event.header.total_size = sizeof(event); | |
597 | event.header.vendor_code = KEV_VENDOR_APPLE; | |
598 | event.header.kev_class = KEV_NETWORK_CLASS; | |
599 | event.header.kev_subclass = KEV_DL_SUBCLASS; | |
600 | event.header.event_code = event_code; | |
601 | event.header.event_data[0] = ifnet_family(ifp); | |
602 | event.unit = (u_int32_t) ifnet_unit(ifp); | |
603 | strlcpy(event.if_name, ifnet_name(ifp), IFNAMSIZ); | |
604 | ifnet_event(ifp, &event.header); | |
605 | return; | |
606 | } | |
607 | ||
608 | static if_fake_ref | |
609 | ifnet_get_if_fake(ifnet_t ifp) | |
610 | { | |
0a7de745 | 611 | return (if_fake_ref)ifnet_softc(ifp); |
5ba3f43e A |
612 | } |
613 | ||
614 | static int | |
615 | feth_clone_create(struct if_clone *ifc, u_int32_t unit, __unused void *params) | |
616 | { | |
0a7de745 A |
617 | int error; |
618 | if_fake_ref fakeif; | |
619 | struct ifnet_init_eparams feth_init; | |
620 | ifnet_t ifp; | |
621 | uint8_t mac_address[ETHER_ADDR_LEN]; | |
5ba3f43e | 622 | |
d9a64523 | 623 | fakeif = if_clone_softc_allocate(&feth_cloner); |
5ba3f43e A |
624 | if (fakeif == NULL) { |
625 | return ENOBUFS; | |
626 | } | |
627 | fakeif->iff_retain_count = 1; | |
0a7de745 | 628 | #define FAKE_ETHER_NAME_LEN (sizeof(FAKE_ETHER_NAME) - 1) |
5ba3f43e A |
629 | _CASSERT(FAKE_ETHER_NAME_LEN == 4); |
630 | bcopy(FAKE_ETHER_NAME, mac_address, FAKE_ETHER_NAME_LEN); | |
631 | mac_address[ETHER_ADDR_LEN - 2] = (unit & 0xff00) >> 8; | |
632 | mac_address[ETHER_ADDR_LEN - 1] = unit & 0xff; | |
633 | if (if_fake_bsd_mode != 0) { | |
634 | fakeif->iff_flags |= IFF_FLAGS_BSD_MODE; | |
635 | } | |
636 | if (if_fake_hwcsum != 0) { | |
637 | fakeif->iff_flags |= IFF_FLAGS_HWCSUM; | |
638 | } | |
cb323159 | 639 | fakeif->iff_max_mtu = get_max_mtu(if_fake_bsd_mode, if_fake_max_mtu); |
5ba3f43e A |
640 | |
641 | /* use the interface name as the unique id for ifp recycle */ | |
642 | if ((unsigned int) | |
643 | snprintf(fakeif->iff_name, sizeof(fakeif->iff_name), "%s%d", | |
0a7de745 | 644 | ifc->ifc_name, unit) >= sizeof(fakeif->iff_name)) { |
5ba3f43e | 645 | feth_release(fakeif); |
0a7de745 | 646 | return EINVAL; |
5ba3f43e A |
647 | } |
648 | bzero(&feth_init, sizeof(feth_init)); | |
649 | feth_init.ver = IFNET_INIT_CURRENT_VERSION; | |
0a7de745 | 650 | feth_init.len = sizeof(feth_init); |
5ba3f43e A |
651 | if (feth_in_bsd_mode(fakeif)) { |
652 | if (if_fake_txstart != 0) { | |
653 | feth_init.start = feth_start; | |
654 | } else { | |
655 | feth_init.flags |= IFNET_INIT_LEGACY; | |
656 | feth_init.output = feth_output; | |
657 | } | |
658 | } | |
659 | if (if_fake_nxattach == 0) { | |
660 | feth_init.flags |= IFNET_INIT_NX_NOAUTO; | |
661 | } | |
662 | feth_init.uniqueid = fakeif->iff_name; | |
663 | feth_init.uniqueid_len = strlen(fakeif->iff_name); | |
664 | feth_init.name = ifc->ifc_name; | |
665 | feth_init.unit = unit; | |
666 | feth_init.family = IFNET_FAMILY_ETHERNET; | |
667 | feth_init.type = IFT_ETHER; | |
668 | feth_init.demux = ether_demux; | |
669 | feth_init.add_proto = ether_add_proto; | |
670 | feth_init.del_proto = ether_del_proto; | |
671 | feth_init.check_multi = ether_check_multi; | |
672 | feth_init.framer_extended = ether_frameout_extended; | |
673 | feth_init.softc = fakeif; | |
674 | feth_init.ioctl = feth_ioctl; | |
675 | feth_init.set_bpf_tap = NULL; | |
676 | feth_init.detach = feth_if_free; | |
677 | feth_init.broadcast_addr = etherbroadcastaddr; | |
678 | feth_init.broadcast_len = ETHER_ADDR_LEN; | |
679 | if (feth_in_bsd_mode(fakeif)) { | |
680 | error = ifnet_allocate_extended(&feth_init, &ifp); | |
681 | if (error) { | |
682 | feth_release(fakeif); | |
0a7de745 | 683 | return error; |
5ba3f43e A |
684 | } |
685 | feth_ifnet_set_attrs(fakeif, ifp); | |
686 | } | |
0a7de745 | 687 | fakeif->iff_media_count = MIN(default_media_words_count, IF_FAKE_MEDIA_LIST_MAX); |
5ba3f43e | 688 | bcopy(default_media_words, fakeif->iff_media_list, |
0a7de745 | 689 | fakeif->iff_media_count * sizeof(fakeif->iff_media_list[0])); |
5ba3f43e A |
690 | if (feth_in_bsd_mode(fakeif)) { |
691 | error = ifnet_attach(ifp, NULL); | |
692 | if (error) { | |
693 | ifnet_release(ifp); | |
694 | feth_release(fakeif); | |
0a7de745 | 695 | return error; |
5ba3f43e A |
696 | } |
697 | fakeif->iff_ifp = ifp; | |
698 | } | |
699 | ||
700 | ifnet_set_lladdr(ifp, mac_address, sizeof(mac_address)); | |
0a7de745 | 701 | |
5ba3f43e A |
702 | /* attach as ethernet */ |
703 | bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); | |
0a7de745 | 704 | return 0; |
5ba3f43e A |
705 | } |
706 | ||
707 | static int | |
708 | feth_clone_destroy(ifnet_t ifp) | |
709 | { | |
0a7de745 | 710 | if_fake_ref fakeif; |
5ba3f43e A |
711 | |
712 | feth_lock(); | |
713 | fakeif = ifnet_get_if_fake(ifp); | |
714 | if (fakeif == NULL || feth_is_detaching(fakeif)) { | |
715 | feth_unlock(); | |
0a7de745 | 716 | return 0; |
5ba3f43e A |
717 | } |
718 | feth_set_detaching(fakeif); | |
719 | feth_unlock(); | |
720 | ||
721 | feth_config(ifp, NULL); | |
722 | ifnet_detach(ifp); | |
723 | return 0; | |
724 | } | |
725 | ||
726 | static void | |
727 | feth_enqueue_input(ifnet_t ifp, struct mbuf * m) | |
728 | { | |
729 | struct ifnet_stat_increment_param stats = {}; | |
730 | ||
731 | stats.packets_in = 1; | |
732 | stats.bytes_in = (uint32_t)mbuf_pkthdr_len(m) + ETHER_HDR_LEN; | |
733 | ifnet_input(ifp, m, &stats); | |
734 | } | |
735 | ||
736 | static struct mbuf * | |
737 | copy_mbuf(struct mbuf *m) | |
738 | { | |
0a7de745 A |
739 | struct mbuf * copy_m; |
740 | uint32_t pkt_len; | |
741 | uint32_t offset; | |
5ba3f43e A |
742 | |
743 | if ((m->m_flags & M_PKTHDR) == 0) { | |
0a7de745 | 744 | return NULL; |
5ba3f43e A |
745 | } |
746 | pkt_len = m->m_pkthdr.len; | |
747 | MGETHDR(copy_m, M_DONTWAIT, MT_DATA); | |
748 | if (copy_m == NULL) { | |
749 | goto failed; | |
750 | } | |
751 | if (pkt_len > MHLEN) { | |
752 | if (pkt_len <= MCLBYTES) { | |
753 | MCLGET(copy_m, M_DONTWAIT); | |
754 | } else if (pkt_len <= MBIGCLBYTES) { | |
755 | copy_m = m_mbigget(copy_m, M_DONTWAIT); | |
756 | } else if (pkt_len <= M16KCLBYTES && njcl > 0) { | |
757 | copy_m = m_m16kget(copy_m, M_DONTWAIT); | |
758 | } else { | |
759 | printf("if_fake: copy_mbuf(): packet too large %d\n", | |
0a7de745 | 760 | pkt_len); |
5ba3f43e A |
761 | goto failed; |
762 | } | |
763 | if (copy_m == NULL || (copy_m->m_flags & M_EXT) == 0) { | |
764 | goto failed; | |
765 | } | |
766 | } | |
767 | mbuf_setlen(copy_m, pkt_len); | |
768 | copy_m->m_pkthdr.len = pkt_len; | |
9d749ea3 | 769 | copy_m->m_pkthdr.pkt_svc = m->m_pkthdr.pkt_svc; |
5ba3f43e A |
770 | offset = 0; |
771 | while (m != NULL && offset < pkt_len) { | |
0a7de745 | 772 | uint32_t frag_len; |
5ba3f43e A |
773 | |
774 | frag_len = m->m_len; | |
775 | if (frag_len > (pkt_len - offset)) { | |
776 | printf("if_fake_: Large mbuf fragment %d > %d\n", | |
0a7de745 | 777 | frag_len, (pkt_len - offset)); |
5ba3f43e A |
778 | goto failed; |
779 | } | |
780 | m_copydata(m, 0, frag_len, mtod(copy_m, void *) + offset); | |
781 | offset += frag_len; | |
782 | m = m->m_next; | |
783 | } | |
0a7de745 | 784 | return copy_m; |
5ba3f43e | 785 | |
0a7de745 | 786 | failed: |
5ba3f43e A |
787 | if (copy_m != NULL) { |
788 | m_freem(copy_m); | |
789 | } | |
0a7de745 | 790 | return NULL; |
5ba3f43e A |
791 | } |
792 | ||
793 | static void | |
794 | feth_output_common(ifnet_t ifp, struct mbuf * m, ifnet_t peer, | |
0a7de745 | 795 | iff_flags_t flags) |
5ba3f43e | 796 | { |
0a7de745 | 797 | void * frame_header; |
5ba3f43e A |
798 | |
799 | frame_header = mbuf_data(m); | |
800 | if ((flags & IFF_FLAGS_HWCSUM) != 0) { | |
801 | m->m_pkthdr.csum_data = 0xffff; | |
802 | m->m_pkthdr.csum_flags = | |
0a7de745 A |
803 | CSUM_DATA_VALID | CSUM_PSEUDO_HDR | |
804 | CSUM_IP_CHECKED | CSUM_IP_VALID; | |
5ba3f43e A |
805 | } |
806 | ||
807 | (void)ifnet_stat_increment_out(ifp, 1, m->m_pkthdr.len, 0); | |
808 | bpf_tap_out(ifp, DLT_EN10MB, m, NULL, 0); | |
809 | ||
810 | (void)mbuf_pkthdr_setrcvif(m, peer); | |
811 | mbuf_pkthdr_setheader(m, frame_header); | |
0a7de745 | 812 | mbuf_pkthdr_adjustlen(m, -ETHER_HDR_LEN); |
5ba3f43e | 813 | (void)mbuf_setdata(m, (char *)mbuf_data(m) + ETHER_HDR_LEN, |
0a7de745 | 814 | mbuf_len(m) - ETHER_HDR_LEN); |
5ba3f43e | 815 | bpf_tap_in(peer, DLT_EN10MB, m, frame_header, |
0a7de745 | 816 | sizeof(struct ether_header)); |
5ba3f43e A |
817 | feth_enqueue_input(peer, m); |
818 | } | |
819 | ||
820 | static void | |
821 | feth_start(ifnet_t ifp) | |
822 | { | |
0a7de745 A |
823 | struct mbuf * copy_m = NULL; |
824 | if_fake_ref fakeif; | |
825 | iff_flags_t flags = 0; | |
826 | ifnet_t peer = NULL; | |
827 | struct mbuf * m; | |
828 | struct mbuf * save_m; | |
5ba3f43e A |
829 | |
830 | feth_lock(); | |
831 | fakeif = ifnet_get_if_fake(ifp); | |
cb323159 A |
832 | if (fakeif == NULL) { |
833 | feth_unlock(); | |
834 | return; | |
835 | } | |
836 | ||
5ba3f43e A |
837 | if (fakeif->iff_start_busy) { |
838 | feth_unlock(); | |
839 | printf("if_fake: start is busy\n"); | |
840 | return; | |
841 | } | |
cb323159 A |
842 | |
843 | peer = fakeif->iff_peer; | |
844 | flags = fakeif->iff_flags; | |
5ba3f43e A |
845 | |
846 | /* check for pending TX */ | |
847 | m = fakeif->iff_pending_tx_packet; | |
848 | if (m != NULL) { | |
849 | if (peer != NULL) { | |
850 | copy_m = copy_mbuf(m); | |
851 | if (copy_m == NULL) { | |
852 | feth_unlock(); | |
853 | return; | |
854 | } | |
855 | } | |
856 | fakeif->iff_pending_tx_packet = NULL; | |
857 | m_freem(m); | |
858 | m = NULL; | |
859 | } | |
860 | fakeif->iff_start_busy = TRUE; | |
861 | feth_unlock(); | |
862 | save_m = NULL; | |
863 | for (;;) { | |
864 | if (copy_m != NULL) { | |
865 | assert(peer != NULL); | |
866 | feth_output_common(ifp, copy_m, peer, flags); | |
867 | copy_m = NULL; | |
868 | } | |
869 | if (ifnet_dequeue(ifp, &m) != 0) { | |
870 | break; | |
871 | } | |
872 | if (peer == NULL) { | |
873 | m_freem(m); | |
874 | } else { | |
875 | copy_m = copy_mbuf(m); | |
876 | if (copy_m == NULL) { | |
877 | save_m = m; | |
878 | break; | |
879 | } | |
880 | m_freem(m); | |
881 | } | |
882 | } | |
883 | peer = NULL; | |
884 | feth_lock(); | |
885 | fakeif = ifnet_get_if_fake(ifp); | |
886 | if (fakeif != NULL) { | |
887 | fakeif->iff_start_busy = FALSE; | |
888 | if (save_m != NULL && fakeif->iff_peer != NULL) { | |
889 | /* save it for next time */ | |
890 | fakeif->iff_pending_tx_packet = save_m; | |
891 | save_m = NULL; | |
892 | } | |
893 | } | |
894 | feth_unlock(); | |
895 | if (save_m != NULL) { | |
896 | /* didn't save packet, so free it */ | |
897 | m_freem(save_m); | |
898 | } | |
899 | } | |
900 | ||
901 | static int | |
902 | feth_output(ifnet_t ifp, struct mbuf * m) | |
903 | { | |
0a7de745 A |
904 | struct mbuf * copy_m; |
905 | if_fake_ref fakeif; | |
906 | iff_flags_t flags; | |
907 | ifnet_t peer = NULL; | |
5ba3f43e A |
908 | |
909 | if (m == NULL) { | |
0a7de745 | 910 | return 0; |
5ba3f43e A |
911 | } |
912 | copy_m = copy_mbuf(m); | |
913 | m_freem(m); | |
914 | m = NULL; | |
915 | if (copy_m == NULL) { | |
916 | /* count this as an output error */ | |
917 | ifnet_stat_increment_out(ifp, 0, 0, 1); | |
0a7de745 | 918 | return 0; |
5ba3f43e A |
919 | } |
920 | feth_lock(); | |
921 | fakeif = ifnet_get_if_fake(ifp); | |
922 | if (fakeif != NULL) { | |
923 | peer = fakeif->iff_peer; | |
924 | flags = fakeif->iff_flags; | |
925 | } | |
926 | feth_unlock(); | |
927 | if (peer == NULL) { | |
928 | m_freem(copy_m); | |
929 | ifnet_stat_increment_out(ifp, 0, 0, 1); | |
0a7de745 | 930 | return 0; |
5ba3f43e A |
931 | } |
932 | feth_output_common(ifp, copy_m, peer, flags); | |
0a7de745 | 933 | return 0; |
5ba3f43e A |
934 | } |
935 | ||
936 | static int | |
937 | feth_config(ifnet_t ifp, ifnet_t peer) | |
938 | { | |
0a7de745 A |
939 | int connected = FALSE; |
940 | int disconnected = FALSE; | |
941 | int error = 0; | |
942 | if_fake_ref fakeif = NULL; | |
5ba3f43e A |
943 | |
944 | feth_lock(); | |
945 | fakeif = ifnet_get_if_fake(ifp); | |
946 | if (fakeif == NULL) { | |
947 | error = EINVAL; | |
948 | goto done; | |
949 | } | |
950 | if (peer != NULL) { | |
951 | /* connect to peer */ | |
0a7de745 | 952 | if_fake_ref peer_fakeif; |
5ba3f43e A |
953 | |
954 | peer_fakeif = ifnet_get_if_fake(peer); | |
955 | if (peer_fakeif == NULL) { | |
956 | error = EINVAL; | |
957 | goto done; | |
958 | } | |
959 | if (feth_is_detaching(fakeif) || | |
960 | feth_is_detaching(peer_fakeif) || | |
961 | peer_fakeif->iff_peer != NULL || | |
962 | fakeif->iff_peer != NULL) { | |
963 | error = EBUSY; | |
964 | goto done; | |
965 | } | |
966 | fakeif->iff_peer = peer; | |
967 | peer_fakeif->iff_peer = ifp; | |
968 | connected = TRUE; | |
0a7de745 | 969 | } else if (fakeif->iff_peer != NULL) { |
5ba3f43e | 970 | /* disconnect from peer */ |
0a7de745 | 971 | if_fake_ref peer_fakeif; |
5ba3f43e A |
972 | |
973 | peer = fakeif->iff_peer; | |
974 | peer_fakeif = ifnet_get_if_fake(peer); | |
975 | if (peer_fakeif == NULL) { | |
976 | /* should not happen */ | |
977 | error = EINVAL; | |
978 | goto done; | |
979 | } | |
980 | fakeif->iff_peer = NULL; | |
981 | peer_fakeif->iff_peer = NULL; | |
982 | disconnected = TRUE; | |
983 | } | |
984 | ||
0a7de745 | 985 | done: |
5ba3f43e A |
986 | feth_unlock(); |
987 | ||
988 | /* generate link status event if we connect or disconnect */ | |
989 | if (connected) { | |
5ba3f43e A |
990 | interface_link_event(ifp, KEV_DL_LINK_ON); |
991 | interface_link_event(peer, KEV_DL_LINK_ON); | |
0a7de745 | 992 | } else if (disconnected) { |
5ba3f43e A |
993 | interface_link_event(ifp, KEV_DL_LINK_OFF); |
994 | interface_link_event(peer, KEV_DL_LINK_OFF); | |
995 | } | |
0a7de745 | 996 | return error; |
5ba3f43e A |
997 | } |
998 | ||
999 | static int | |
1000 | feth_set_media(ifnet_t ifp, struct if_fake_request * iffr) | |
1001 | { | |
0a7de745 A |
1002 | if_fake_ref fakeif; |
1003 | int error; | |
5ba3f43e A |
1004 | |
1005 | if (iffr->iffr_media.iffm_count > IF_FAKE_MEDIA_LIST_MAX) { | |
1006 | /* list is too long */ | |
0a7de745 | 1007 | return EINVAL; |
5ba3f43e A |
1008 | } |
1009 | feth_lock(); | |
1010 | fakeif = ifnet_get_if_fake(ifp); | |
1011 | if (fakeif == NULL) { | |
1012 | error = EINVAL; | |
1013 | goto done; | |
1014 | } | |
1015 | fakeif->iff_media_count = iffr->iffr_media.iffm_count; | |
1016 | bcopy(iffr->iffr_media.iffm_list, fakeif->iff_media_list, | |
0a7de745 | 1017 | iffr->iffr_media.iffm_count * sizeof(fakeif->iff_media_list[0])); |
5ba3f43e A |
1018 | #if 0 |
1019 | /* XXX: "auto-negotiate" active with peer? */ | |
1020 | /* generate link status event? */ | |
1021 | fakeif->iff_media_current = iffr->iffr_media.iffm_current; | |
1022 | #endif | |
1023 | error = 0; | |
0a7de745 | 1024 | done: |
5ba3f43e | 1025 | feth_unlock(); |
0a7de745 | 1026 | return error; |
5ba3f43e A |
1027 | } |
1028 | ||
1029 | static int | |
0a7de745 A |
1030 | if_fake_request_copyin(user_addr_t user_addr, |
1031 | struct if_fake_request *iffr, u_int32_t len) | |
5ba3f43e | 1032 | { |
0a7de745 | 1033 | int error; |
5ba3f43e A |
1034 | |
1035 | if (user_addr == USER_ADDR_NULL || len < sizeof(*iffr)) { | |
1036 | error = EINVAL; | |
1037 | goto done; | |
1038 | } | |
1039 | error = copyin(user_addr, iffr, sizeof(*iffr)); | |
1040 | if (error != 0) { | |
1041 | goto done; | |
1042 | } | |
1043 | if (iffr->iffr_reserved[0] != 0 || iffr->iffr_reserved[1] != 0 || | |
1044 | iffr->iffr_reserved[2] != 0 || iffr->iffr_reserved[3] != 0) { | |
1045 | error = EINVAL; | |
1046 | goto done; | |
1047 | } | |
0a7de745 A |
1048 | done: |
1049 | return error; | |
5ba3f43e A |
1050 | } |
1051 | ||
1052 | static int | |
1053 | feth_set_drvspec(ifnet_t ifp, uint32_t cmd, u_int32_t len, | |
0a7de745 | 1054 | user_addr_t user_addr) |
5ba3f43e | 1055 | { |
0a7de745 A |
1056 | int error; |
1057 | struct if_fake_request iffr; | |
1058 | ifnet_t peer; | |
5ba3f43e A |
1059 | |
1060 | switch (cmd) { | |
1061 | case IF_FAKE_S_CMD_SET_PEER: | |
1062 | error = if_fake_request_copyin(user_addr, &iffr, len); | |
1063 | if (error != 0) { | |
1064 | break; | |
1065 | } | |
1066 | if (iffr.iffr_peer_name[0] == '\0') { | |
1067 | error = feth_config(ifp, NULL); | |
1068 | break; | |
1069 | } | |
1070 | ||
1071 | /* ensure nul termination */ | |
1072 | iffr.iffr_peer_name[IFNAMSIZ - 1] = '\0'; | |
1073 | peer = ifunit(iffr.iffr_peer_name); | |
1074 | if (peer == NULL) { | |
1075 | error = ENXIO; | |
1076 | break; | |
1077 | } | |
1078 | if (ifnet_type(peer) != IFT_ETHER) { | |
1079 | error = EINVAL; | |
1080 | break; | |
1081 | } | |
1082 | if (strcmp(ifnet_name(peer), FAKE_ETHER_NAME) != 0) { | |
1083 | error = EINVAL; | |
1084 | break; | |
1085 | } | |
1086 | error = feth_config(ifp, peer); | |
1087 | break; | |
1088 | case IF_FAKE_S_CMD_SET_MEDIA: | |
1089 | error = if_fake_request_copyin(user_addr, &iffr, len); | |
1090 | if (error != 0) { | |
1091 | break; | |
1092 | } | |
1093 | error = feth_set_media(ifp, &iffr); | |
1094 | break; | |
a39ff7e2 A |
1095 | case IF_FAKE_S_CMD_SET_DEQUEUE_STALL: |
1096 | error = if_fake_request_copyin(user_addr, &iffr, len); | |
1097 | if (error != 0) { | |
1098 | break; | |
1099 | } | |
1100 | error = feth_enable_dequeue_stall(ifp, | |
1101 | iffr.iffr_dequeue_stall); | |
1102 | break; | |
5ba3f43e A |
1103 | default: |
1104 | error = EOPNOTSUPP; | |
1105 | break; | |
1106 | } | |
0a7de745 | 1107 | return error; |
5ba3f43e A |
1108 | } |
1109 | ||
1110 | static int | |
1111 | feth_get_drvspec(ifnet_t ifp, u_int32_t cmd, u_int32_t len, | |
0a7de745 | 1112 | user_addr_t user_addr) |
5ba3f43e | 1113 | { |
0a7de745 A |
1114 | int error = EOPNOTSUPP; |
1115 | if_fake_ref fakeif; | |
1116 | struct if_fake_request iffr; | |
1117 | ifnet_t peer; | |
5ba3f43e A |
1118 | |
1119 | switch (cmd) { | |
1120 | case IF_FAKE_G_CMD_GET_PEER: | |
1121 | if (len < sizeof(iffr)) { | |
1122 | error = EINVAL; | |
1123 | break; | |
1124 | } | |
1125 | feth_lock(); | |
cb323159 | 1126 | fakeif = ifnet_get_if_fake(ifp); |
5ba3f43e A |
1127 | if (fakeif == NULL) { |
1128 | feth_unlock(); | |
1129 | error = EOPNOTSUPP; | |
1130 | break; | |
1131 | } | |
1132 | peer = fakeif->iff_peer; | |
1133 | feth_unlock(); | |
1134 | bzero(&iffr, sizeof(iffr)); | |
1135 | if (peer != NULL) { | |
1136 | strlcpy(iffr.iffr_peer_name, | |
0a7de745 A |
1137 | if_name(peer), |
1138 | sizeof(iffr.iffr_peer_name)); | |
5ba3f43e A |
1139 | } |
1140 | error = copyout(&iffr, user_addr, sizeof(iffr)); | |
1141 | break; | |
1142 | default: | |
1143 | break; | |
1144 | } | |
0a7de745 | 1145 | return error; |
5ba3f43e A |
1146 | } |
1147 | ||
1148 | union ifdrvu { | |
0a7de745 A |
1149 | struct ifdrv32 *ifdrvu_32; |
1150 | struct ifdrv64 *ifdrvu_64; | |
1151 | void *ifdrvu_p; | |
5ba3f43e A |
1152 | }; |
1153 | ||
1154 | static int | |
1155 | feth_ioctl(ifnet_t ifp, u_long cmd, void * data) | |
1156 | { | |
0a7de745 A |
1157 | unsigned int count; |
1158 | struct ifdevmtu * devmtu_p; | |
1159 | union ifdrvu drv; | |
1160 | uint32_t drv_cmd; | |
1161 | uint32_t drv_len; | |
1162 | boolean_t drv_set_command = FALSE; | |
1163 | int error = 0; | |
1164 | struct ifmediareq * ifmr; | |
1165 | struct ifreq * ifr; | |
1166 | if_fake_ref fakeif; | |
1167 | int status; | |
1168 | user_addr_t user_addr; | |
5ba3f43e A |
1169 | |
1170 | ifr = (struct ifreq *)data; | |
1171 | switch (cmd) { | |
1172 | case SIOCSIFADDR: | |
1173 | ifnet_set_flags(ifp, IFF_UP, IFF_UP); | |
1174 | break; | |
1175 | ||
1176 | case SIOCGIFMEDIA32: | |
1177 | case SIOCGIFMEDIA64: | |
1178 | feth_lock(); | |
cb323159 | 1179 | fakeif = ifnet_get_if_fake(ifp); |
5ba3f43e A |
1180 | if (fakeif == NULL) { |
1181 | feth_unlock(); | |
0a7de745 | 1182 | return EOPNOTSUPP; |
5ba3f43e A |
1183 | } |
1184 | status = (fakeif->iff_peer != NULL) | |
1185 | ? (IFM_AVALID | IFM_ACTIVE) : IFM_AVALID; | |
1186 | ifmr = (struct ifmediareq *)data; | |
1187 | user_addr = (cmd == SIOCGIFMEDIA64) ? | |
0a7de745 A |
1188 | ((struct ifmediareq64 *)ifmr)->ifmu_ulist : |
1189 | CAST_USER_ADDR_T(((struct ifmediareq32 *)ifmr)->ifmu_ulist); | |
5ba3f43e A |
1190 | count = ifmr->ifm_count; |
1191 | ifmr->ifm_active = IFM_ETHER; | |
1192 | ifmr->ifm_current = IFM_ETHER; | |
1193 | ifmr->ifm_mask = 0; | |
1194 | ifmr->ifm_status = status; | |
1195 | if (user_addr == USER_ADDR_NULL) { | |
1196 | ifmr->ifm_count = fakeif->iff_media_count; | |
0a7de745 | 1197 | } else if (count > 0) { |
5ba3f43e A |
1198 | if (count > fakeif->iff_media_count) { |
1199 | count = fakeif->iff_media_count; | |
1200 | } | |
1201 | ifmr->ifm_count = count; | |
1202 | error = copyout(&fakeif->iff_media_list, user_addr, | |
0a7de745 | 1203 | count * sizeof(int)); |
5ba3f43e A |
1204 | } |
1205 | feth_unlock(); | |
1206 | break; | |
1207 | ||
1208 | case SIOCGIFDEVMTU: | |
1209 | devmtu_p = &ifr->ifr_devmtu; | |
1210 | devmtu_p->ifdm_current = ifnet_mtu(ifp); | |
cb323159 | 1211 | devmtu_p->ifdm_max = feth_max_mtu(ifp); |
5ba3f43e A |
1212 | devmtu_p->ifdm_min = IF_MINMTU; |
1213 | break; | |
1214 | ||
1215 | case SIOCSIFMTU: | |
cb323159 A |
1216 | if ((unsigned int)ifr->ifr_mtu > feth_max_mtu(ifp) || |
1217 | ifr->ifr_mtu < IF_MINMTU) { | |
5ba3f43e A |
1218 | error = EINVAL; |
1219 | } else { | |
1220 | error = ifnet_set_mtu(ifp, ifr->ifr_mtu); | |
1221 | } | |
1222 | break; | |
1223 | ||
1224 | case SIOCSDRVSPEC32: | |
1225 | case SIOCSDRVSPEC64: | |
1226 | error = proc_suser(current_proc()); | |
1227 | if (error != 0) { | |
1228 | break; | |
1229 | } | |
1230 | drv_set_command = TRUE; | |
0a7de745 | 1231 | /* FALL THROUGH */ |
5ba3f43e A |
1232 | case SIOCGDRVSPEC32: |
1233 | case SIOCGDRVSPEC64: | |
1234 | drv.ifdrvu_p = data; | |
1235 | if (cmd == SIOCGDRVSPEC32 || cmd == SIOCSDRVSPEC32) { | |
1236 | drv_cmd = drv.ifdrvu_32->ifd_cmd; | |
1237 | drv_len = drv.ifdrvu_32->ifd_len; | |
1238 | user_addr = CAST_USER_ADDR_T(drv.ifdrvu_32->ifd_data); | |
5ba3f43e A |
1239 | } else { |
1240 | drv_cmd = drv.ifdrvu_64->ifd_cmd; | |
1241 | drv_len = drv.ifdrvu_64->ifd_len; | |
1242 | user_addr = drv.ifdrvu_64->ifd_data; | |
1243 | } | |
1244 | if (drv_set_command) { | |
1245 | error = feth_set_drvspec(ifp, drv_cmd, drv_len, | |
0a7de745 | 1246 | user_addr); |
5ba3f43e A |
1247 | } else { |
1248 | error = feth_get_drvspec(ifp, drv_cmd, drv_len, | |
0a7de745 | 1249 | user_addr); |
5ba3f43e A |
1250 | } |
1251 | break; | |
1252 | ||
1253 | case SIOCSIFLLADDR: | |
1254 | error = ifnet_set_lladdr(ifp, ifr->ifr_addr.sa_data, | |
1255 | ifr->ifr_addr.sa_len); | |
1256 | break; | |
1257 | ||
1258 | case SIOCSIFFLAGS: | |
a39ff7e2 A |
1259 | if ((ifp->if_flags & IFF_UP) != 0) { |
1260 | /* marked up, set running if not already set */ | |
1261 | if ((ifp->if_flags & IFF_RUNNING) == 0) { | |
1262 | /* set running */ | |
1263 | error = ifnet_set_flags(ifp, IFF_RUNNING, | |
1264 | IFF_RUNNING); | |
1265 | } | |
1266 | } else if ((ifp->if_flags & IFF_RUNNING) != 0) { | |
1267 | /* marked down, clear running */ | |
1268 | error = ifnet_set_flags(ifp, 0, IFF_RUNNING); | |
1269 | } | |
5ba3f43e A |
1270 | break; |
1271 | ||
1272 | case SIOCADDMULTI: | |
1273 | case SIOCDELMULTI: | |
1274 | error = 0; | |
1275 | break; | |
1276 | default: | |
1277 | error = EOPNOTSUPP; | |
1278 | break; | |
1279 | } | |
1280 | return error; | |
1281 | } | |
1282 | ||
0a7de745 | 1283 | static void |
5ba3f43e A |
1284 | feth_if_free(ifnet_t ifp) |
1285 | { | |
0a7de745 | 1286 | if_fake_ref fakeif; |
5ba3f43e A |
1287 | |
1288 | if (ifp == NULL) { | |
1289 | return; | |
1290 | } | |
1291 | feth_lock(); | |
1292 | fakeif = ifnet_get_if_fake(ifp); | |
1293 | if (fakeif == NULL) { | |
1294 | feth_unlock(); | |
1295 | return; | |
1296 | } | |
1297 | ifp->if_softc = NULL; | |
1298 | feth_unlock(); | |
1299 | feth_release(fakeif); | |
1300 | ifnet_release(ifp); | |
1301 | return; | |
1302 | } | |
1303 | ||
1304 | __private_extern__ void | |
1305 | if_fake_init(void) | |
1306 | { | |
1307 | int error; | |
1308 | ||
1309 | feth_lock_init(); | |
1310 | error = if_clone_attach(&feth_cloner); | |
1311 | if (error != 0) { | |
1312 | return; | |
1313 | } | |
1314 | return; | |
1315 | } |