]> git.saurik.com Git - apple/xnu.git/blame - bsd/net/if_utun.c
xnu-1504.3.12.tar.gz
[apple/xnu.git] / bsd / net / if_utun.c
CommitLineData
b0d623f7
A
1/*
2 * Copyright (c) 2008 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
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.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
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.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29
30
31/* ----------------------------------------------------------------------------------
32Application of kernel control for interface creation
33
34Theory of operation:
35utun (user tunnel) acts as glue between kernel control sockets and network interfaces.
36This kernel control will register an interface for every client that connects.
37---------------------------------------------------------------------------------- */
38
39#include <sys/systm.h>
40#include <sys/kern_control.h>
41#include <net/kpi_protocol.h>
42#include <net/kpi_interface.h>
43#include <sys/socket.h>
44#include <net/if.h>
45#include <net/if_types.h>
46#include <net/bpf.h>
47#include <net/if_utun.h>
48#include <libkern/OSMalloc.h>
49#include <libkern/OSAtomic.h>
50#include <sys/mbuf.h>
51#include <sys/sockio.h>
52#include <netinet/in.h>
53#include <netinet6/in6_var.h>
54#include <netinet6/in6_var.h>
55#include <sys/kauth.h>
56
57
58/* Kernel Control functions */
59static errno_t utun_ctl_connect(kern_ctl_ref kctlref, struct sockaddr_ctl *sac,
60 void **unitinfo);
61static errno_t utun_ctl_disconnect(kern_ctl_ref kctlref, u_int32_t unit,
62 void *unitinfo);
63static errno_t utun_ctl_send(kern_ctl_ref kctlref, u_int32_t unit,
64 void *unitinfo, mbuf_t m, int flags);
65static errno_t utun_ctl_getopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo,
66 int opt, void *data, size_t *len);
67static errno_t utun_ctl_setopt(kern_ctl_ref kctlref, u_int32_t unit, void *unitinfo,
68 int opt, void *data, size_t len);
69
70/* Network Interface functions */
71static errno_t utun_output(ifnet_t interface, mbuf_t data);
72static errno_t utun_demux(ifnet_t interface, mbuf_t data, char *frame_header,
73 protocol_family_t *protocol);
74static errno_t utun_framer(ifnet_t interface, mbuf_t *packet,
75 const struct sockaddr *dest, const char *desk_linkaddr,
76 const char *frame_type);
77static errno_t utun_add_proto(ifnet_t interface, protocol_family_t protocol,
78 const struct ifnet_demux_desc *demux_array,
79 u_int32_t demux_count);
80static errno_t utun_del_proto(ifnet_t interface, protocol_family_t protocol);
81static errno_t utun_ioctl(ifnet_t interface, u_long cmd, void *data);
82static void utun_detached(ifnet_t interface);
83
84/* Protocol handlers */
85static errno_t utun_attach_proto(ifnet_t interface, protocol_family_t proto);
86static errno_t utun_proto_input(ifnet_t interface, protocol_family_t protocol,
87 mbuf_t m, char *frame_header);
88static errno_t utun_proto_pre_output(ifnet_t interface, protocol_family_t protocol,
89 mbuf_t *packet, const struct sockaddr *dest, void *route,
90 char *frame_type, char *link_layer_dest);
91
92/* Control block allocated for each kernel control connection */
93struct utun_pcb {
94 kern_ctl_ref ctlref;
95 u_int32_t unit;
96 ifnet_t ifp;
97 u_int32_t flags;
98};
99
100static kern_ctl_ref utun_kctlref;
101static u_int32_t utun_family;
102static OSMallocTag utun_malloc_tag;
103static SInt32 utun_ifcount = 0;
104
105/* Prepend length */
106static void*
107utun_alloc(size_t size)
108{
109 size_t *mem = OSMalloc(size + sizeof(size_t), utun_malloc_tag);
110
111 if (mem) {
112 *mem = size + sizeof(size_t);
113 mem++;
114 }
115
116 return (void*)mem;
117}
118
119static void
120utun_free(void *ptr)
121{
122 size_t *size = ptr;
123 size--;
124 OSFree(size, *size, utun_malloc_tag);
125}
126
127errno_t
128utun_register_control(void)
129{
130 struct kern_ctl_reg kern_ctl;
131 errno_t result = 0;
132
133 /* Create a tag to allocate memory */
134 utun_malloc_tag = OSMalloc_Tagalloc(UTUN_CONTROL_NAME, OSMT_DEFAULT);
135
136 /* Find a unique value for our interface family */
137 result = mbuf_tag_id_find(UTUN_CONTROL_NAME, &utun_family);
138 if (result != 0) {
139 printf("utun_register_control - mbuf_tag_id_find_internal failed: %d\n", result);
140 return result;
141 }
142
143 bzero(&kern_ctl, sizeof(kern_ctl));
144 strncpy(kern_ctl.ctl_name, UTUN_CONTROL_NAME, sizeof(kern_ctl.ctl_name));
145 kern_ctl.ctl_name[sizeof(kern_ctl.ctl_name) - 1] = 0;
146 kern_ctl.ctl_flags = CTL_FLAG_PRIVILEGED; /* Require root */
147 kern_ctl.ctl_connect = utun_ctl_connect;
148 kern_ctl.ctl_disconnect = utun_ctl_disconnect;
149 kern_ctl.ctl_send = utun_ctl_send;
150 kern_ctl.ctl_setopt = utun_ctl_setopt;
151 kern_ctl.ctl_getopt = utun_ctl_getopt;
152
153 result = ctl_register(&kern_ctl, &utun_kctlref);
154 if (result != 0) {
155 printf("utun_register_control - ctl_register failed: %d\n", result);
156 return result;
157 }
158
159 /* Register the protocol plumbers */
160 if ((result = proto_register_plumber(PF_INET, utun_family,
161 utun_attach_proto, NULL)) != 0) {
162 printf("utun_register_control - proto_register_plumber(PF_INET, %d) failed: %d\n",
163 utun_family, result);
164 ctl_deregister(utun_kctlref);
165 return result;
166 }
167
168 /* Register the protocol plumbers */
169 if ((result = proto_register_plumber(PF_INET6, utun_family,
170 utun_attach_proto, NULL)) != 0) {
171 proto_unregister_plumber(PF_INET, utun_family);
172 ctl_deregister(utun_kctlref);
173 printf("utun_register_control - proto_register_plumber(PF_INET6, %d) failed: %d\n",
174 utun_family, result);
175 return result;
176 }
177
178 return 0;
179}
180
181/* Kernel control functions */
182
183static errno_t
184utun_ctl_connect(
185 kern_ctl_ref kctlref,
186 struct sockaddr_ctl *sac,
187 void **unitinfo)
188{
189 struct ifnet_init_params utun_init;
190 struct utun_pcb *pcb;
191 errno_t result;
192
193 /* kernel control allocates, interface frees */
194 pcb = utun_alloc(sizeof(*pcb));
195 if (pcb == NULL)
196 return ENOMEM;
197
198 /* Setup the protocol control block */
199 bzero(pcb, sizeof(*pcb));
200 *unitinfo = pcb;
201 pcb->ctlref = kctlref;
202 pcb->unit = sac->sc_unit;
203
204 printf("utun_ctl_connect: creating interface utun%d\n", pcb->unit - 1);
205
206 /* Create the interface */
207 bzero(&utun_init, sizeof(utun_init));
208 utun_init.name = "utun";
209 utun_init.unit = pcb->unit - 1;
210 utun_init.family = utun_family;
211 utun_init.type = IFT_OTHER;
212 utun_init.output = utun_output;
213 utun_init.demux = utun_demux;
214 utun_init.framer = utun_framer;
215 utun_init.add_proto = utun_add_proto;
216 utun_init.del_proto = utun_del_proto;
217 utun_init.softc = pcb;
218 utun_init.ioctl = utun_ioctl;
219 utun_init.detach = utun_detached;
220
221 result = ifnet_allocate(&utun_init, &pcb->ifp);
222 if (result != 0) {
223 printf("utun_ctl_connect - ifnet_allocate failed: %d\n", result);
224 utun_free(pcb);
225 return result;
226 }
227 OSIncrementAtomic(&utun_ifcount);
228
229 /* Set flags and additional information. */
230 ifnet_set_mtu(pcb->ifp, 1500);
231 ifnet_set_flags(pcb->ifp, IFF_UP | IFF_MULTICAST | IFF_POINTOPOINT, 0xffff);
232
233 /* The interface must generate its own IPv6 LinkLocal address,
234 * if possible following the recommendation of RFC2472 to the 64bit interface ID
235 */
236 ifnet_set_eflags(pcb->ifp, IFEF_NOAUTOIPV6LL, IFEF_NOAUTOIPV6LL);
237
238 /* Attach the interface */
239 result = ifnet_attach(pcb->ifp, NULL);
240 if (result != 0) {
241 printf("utun_ctl_connect - ifnet_allocate failed: %d\n", result);
242 ifnet_release(pcb->ifp);
243 utun_free(pcb);
244 }
245
246 /* Attach to bpf */
247 if (result == 0)
248 bpfattach(pcb->ifp, DLT_NULL, 4);
249
250 return result;
251}
252
253static errno_t
254utun_detach_ip(
255 ifnet_t interface,
256 protocol_family_t protocol,
257 socket_t pf_socket)
258{
259 errno_t result = EPROTONOSUPPORT;
260
261 /* Attempt a detach */
262 if (protocol == PF_INET) {
263 struct ifreq ifr;
264
265 bzero(&ifr, sizeof(ifr));
266 snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d",
267 ifnet_name(interface), ifnet_unit(interface));
268
269 result = sock_ioctl(pf_socket, SIOCPROTODETACH, &ifr);
270 }
271 else if (protocol == PF_INET6) {
272 struct in6_ifreq ifr6;
273
274 bzero(&ifr6, sizeof(ifr6));
275 snprintf(ifr6.ifr_name, sizeof(ifr6.ifr_name), "%s%d",
276 ifnet_name(interface), ifnet_unit(interface));
277
278 result = sock_ioctl(pf_socket, SIOCPROTODETACH_IN6, &ifr6);
279 }
280
281 return result;
282}
283
284static void
285utun_remove_address(
286 ifnet_t interface,
287 protocol_family_t protocol,
288 ifaddr_t address,
289 socket_t pf_socket)
290{
291 errno_t result = 0;
292
293 /* Attempt a detach */
294 if (protocol == PF_INET) {
295 struct ifreq ifr;
296
297 bzero(&ifr, sizeof(ifr));
298 snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d",
299 ifnet_name(interface), ifnet_unit(interface));
300 result = ifaddr_address(address, &ifr.ifr_addr, sizeof(ifr.ifr_addr));
301 if (result != 0) {
302 printf("utun_remove_address - ifaddr_address failed: %d", result);
303 }
304 else {
305 result = sock_ioctl(pf_socket, SIOCDIFADDR, &ifr);
306 if (result != 0) {
307 printf("utun_remove_address - SIOCDIFADDR failed: %d", result);
308 }
309 }
310 }
311 else if (protocol == PF_INET6) {
312 struct in6_ifreq ifr6;
313
314 bzero(&ifr6, sizeof(ifr6));
315 snprintf(ifr6.ifr_name, sizeof(ifr6.ifr_name), "%s%d",
316 ifnet_name(interface), ifnet_unit(interface));
317 result = ifaddr_address(address, (struct sockaddr*)&ifr6.ifr_addr,
318 sizeof(ifr6.ifr_addr));
319 if (result != 0) {
320 printf("utun_remove_address - ifaddr_address failed (v6): %d",
321 result);
322 }
323 else {
324 result = sock_ioctl(pf_socket, SIOCDIFADDR_IN6, &ifr6);
325 if (result != 0) {
326 printf("utun_remove_address - SIOCDIFADDR_IN6 failed: %d",
327 result);
328 }
329 }
330 }
331}
332
333static void
334utun_cleanup_family(
335 ifnet_t interface,
336 protocol_family_t protocol)
337{
338 errno_t result = 0;
339 socket_t pf_socket = NULL;
340 ifaddr_t *addresses = NULL;
341 int i;
342
343 if (protocol != PF_INET && protocol != PF_INET6) {
344 printf("utun_cleanup_family - invalid protocol family %d\n", protocol);
345 return;
346 }
347
348 /* Create a socket for removing addresses and detaching the protocol */
349 result = sock_socket(protocol, SOCK_DGRAM, 0, NULL, NULL, &pf_socket);
350 if (result != 0) {
351 if (result != EAFNOSUPPORT)
352 printf("utun_cleanup_family - failed to create %s socket: %d\n",
353 protocol == PF_INET ? "IP" : "IPv6", result);
354 goto cleanup;
355 }
356
357 result = utun_detach_ip(interface, protocol, pf_socket);
358 if (result == 0 || result == ENXIO) {
359 /* We are done! We either detached or weren't attached. */
360 goto cleanup;
361 }
362 else if (result != EBUSY) {
363 /* Uh, not really sure what happened here... */
364 printf("utun_cleanup_family - utun_detach_ip failed: %d\n", result);
365 goto cleanup;
366 }
367
368 /*
369 * At this point, we received an EBUSY error. This means there are
370 * addresses attached. We should detach them and then try again.
371 */
372 result = ifnet_get_address_list_family(interface, &addresses, protocol);
373 if (result != 0) {
374 printf("fnet_get_address_list_family(%s%d, 0xblah, %s) - failed: %d\n",
375 ifnet_name(interface), ifnet_unit(interface),
376 protocol == PF_INET ? "PF_INET" : "PF_INET6", result);
377 goto cleanup;
378 }
379
380 for (i = 0; addresses[i] != 0; i++) {
381 utun_remove_address(interface, protocol, addresses[i], pf_socket);
382 }
383 ifnet_free_address_list(addresses);
384 addresses = NULL;
385
386 /*
387 * The addresses should be gone, we should try the remove again.
388 */
389 result = utun_detach_ip(interface, protocol, pf_socket);
390 if (result != 0 && result != ENXIO) {
391 printf("utun_cleanup_family - utun_detach_ip failed: %d\n", result);
392 }
393
394cleanup:
395 if (pf_socket != NULL)
396 sock_close(pf_socket);
397
398 if (addresses != NULL)
399 ifnet_free_address_list(addresses);
400}
401
402static errno_t
403utun_ctl_disconnect(
404 __unused kern_ctl_ref kctlref,
405 __unused u_int32_t unit,
406 void *unitinfo)
407{
408 struct utun_pcb *pcb = unitinfo;
409 ifnet_t ifp = pcb->ifp;
410 errno_t result = 0;
411
412 pcb->ctlref = NULL;
413 pcb->unit = 0;
414
415 /*
416 * We want to do everything in our power to ensure that the interface
417 * really goes away when the socket is closed. We must remove IP/IPv6
418 * addresses and detach the protocols. Finally, we can remove and
419 * release the interface.
420 */
421 utun_cleanup_family(ifp, AF_INET);
422 utun_cleanup_family(ifp, AF_INET6);
423
424 if ((result = ifnet_detach(ifp)) != 0) {
425 printf("utun_ctl_disconnect - ifnet_detach failed: %d\n", result);
426 }
427
428 if ((result = ifnet_release(ifp)) != 0) {
429 printf("utun_ctl_disconnect - ifnet_release failed: %d\n", result);
430 }
431
432 return 0;
433}
434
435static errno_t
436utun_ctl_send(
437 __unused kern_ctl_ref kctlref,
438 __unused u_int32_t unit,
439 void *unitinfo,
440 mbuf_t m,
441 __unused int flags)
442{
443 struct utun_pcb *pcb = unitinfo;
444 struct ifnet_stat_increment_param incs;
445 errno_t result;
446
447 mbuf_pkthdr_setrcvif(m, pcb->ifp);
448
449 bpf_tap_in(pcb->ifp, DLT_NULL, m, 0, 0);
450
451 if (pcb->flags & UTUN_FLAGS_NO_INPUT) {
452 /* flush data */
453 mbuf_freem(m);
454 return 0;
455 }
456
457 bzero(&incs, sizeof(incs));
458 incs.packets_in = 1;
459 incs.bytes_in = mbuf_pkthdr_len(m);
460 result = ifnet_input(pcb->ifp, m, &incs);
461 if (result != 0) {
462 ifnet_stat_increment_in(pcb->ifp, 0, 0, 1);
463 printf("utun_ctl_send - ifnet_input failed: %d\n", result);
464 mbuf_freem(m);
465 }
466
467 return 0;
468}
469
470static errno_t
471utun_ctl_setopt(
472 __unused kern_ctl_ref kctlref,
473 __unused u_int32_t unit,
474 void *unitinfo,
475 int opt,
476 void *data,
477 size_t len)
478{
479 struct utun_pcb *pcb = unitinfo;
480 errno_t result = 0;
481
482 /* check for privileges for privileged options */
483 switch (opt) {
484 case UTUN_OPT_FLAGS:
485 if (kauth_cred_issuser(kauth_cred_get()) == 0) {
486 return EPERM;
487 }
488 break;
489 }
490
491 switch (opt) {
492 case UTUN_OPT_FLAGS:
493 if (len != sizeof(u_int32_t))
494 result = EMSGSIZE;
495 else
496 pcb->flags = *(u_int32_t *)data;
497 break;
498 default:
499 result = ENOPROTOOPT;
500 break;
501 }
502
503 return result;
504}
505
506static errno_t
507utun_ctl_getopt(
508 __unused kern_ctl_ref kctlref,
509 __unused u_int32_t unit,
510 void *unitinfo,
511 int opt,
512 void *data,
513 size_t *len)
514{
515 struct utun_pcb *pcb = unitinfo;
516 errno_t result = 0;
517
518 switch (opt) {
519 case UTUN_OPT_FLAGS:
520 if (*len != sizeof(u_int32_t))
521 result = EMSGSIZE;
522 else
523 *(u_int32_t *)data = pcb->flags;
524 break;
525 case UTUN_OPT_IFNAME:
526 *len = snprintf(data, *len, "%s%d", ifnet_name(pcb->ifp), ifnet_unit(pcb->ifp)) + 1;
527 break;
528 default:
529 result = ENOPROTOOPT;
530 break;
531 }
532
533 return result;
534}
535
536/* Network Interface functions */
537static errno_t
538utun_output(
539 ifnet_t interface,
540 mbuf_t data)
541{
542 struct utun_pcb *pcb = ifnet_softc(interface);
543 errno_t result;
544
545 bpf_tap_out(pcb->ifp, DLT_NULL, data, 0, 0);
546
547 if (pcb->flags & UTUN_FLAGS_NO_OUTPUT) {
548 /* flush data */
549 mbuf_freem(data);
550 return 0;
551 }
552
553 if (pcb->ctlref) {
554 int length = mbuf_pkthdr_len(data);
555 result = ctl_enqueuembuf(pcb->ctlref, pcb->unit, data, CTL_DATA_EOR);
556 if (result != 0) {
557 mbuf_freem(data);
558 printf("utun_output - ctl_enqueuembuf failed: %d\n", result);
559 ifnet_stat_increment_out(interface, 0, 0, 1);
560 }
561 else {
562 ifnet_stat_increment_out(interface, 1, length, 0);
563 }
564 }
565 else
566 mbuf_freem(data);
567
568 return 0;
569}
570
571/* Network Interface functions */
572static errno_t
573utun_demux(
574 __unused ifnet_t interface,
575 mbuf_t data,
576 __unused char *frame_header,
577 protocol_family_t *protocol)
578{
579
580 while (data != NULL && mbuf_len(data) < 1) {
581 data = mbuf_next(data);
582 }
583
584 if (data == NULL)
585 return ENOENT;
586
587 *protocol = ntohl(*(u_int32_t *)mbuf_data(data));
588 return 0;
589}
590
591static errno_t
592utun_framer(
593 __unused ifnet_t interface,
594 mbuf_t *packet,
595 __unused const struct sockaddr *dest,
596 __unused const char *desk_linkaddr,
597 const char *frame_type)
598{
599
600 if (mbuf_prepend(packet, sizeof(protocol_family_t), MBUF_DONTWAIT) != 0) {
601 printf("utun_framer - ifnet_output prepend failed\n");
602 ifnet_stat_increment_out(interface, 0, 0, 1);
603 // just return, because the buffer was freed in mbuf_prepend
604 return EJUSTRETURN;
605 }
606
607 // place protocol number at the beginning of the mbuf
608 *(protocol_family_t *)mbuf_data(*packet) = htonl(*(protocol_family_t *)(uintptr_t)(size_t)frame_type);
609
610 return 0;
611}
612
613static errno_t
614utun_add_proto(
615 __unused ifnet_t interface,
616 protocol_family_t protocol,
617 __unused const struct ifnet_demux_desc *demux_array,
618 __unused u_int32_t demux_count)
619{
620 switch(protocol) {
621 case PF_INET:
622 return 0;
623 case PF_INET6:
624 return 0;
625 default:
626 break;
627 }
628
629 return ENOPROTOOPT;
630}
631
632static errno_t
633utun_del_proto(
634 __unused ifnet_t interface,
635 __unused protocol_family_t protocol)
636{
637 return 0;
638}
639
640static errno_t
641utun_ioctl(
642 __unused ifnet_t interface,
643 __unused u_long command,
644 void *data)
645{
646 errno_t result = 0;
647 struct ifaddr *ifa = (struct ifaddr *)data;
648
649 switch(command) {
650 case SIOCSIFMTU:
651 ifnet_set_mtu(interface, ((struct ifreq*)data)->ifr_mtu);
652 break;
653
654 case SIOCSIFADDR:
655 case SIOCAIFADDR:
656 /* This will be called for called for IPv6 Address additions */
657 if (ifa->ifa_addr->sa_family == AF_INET6)
658 break;
659 /* Fall though for other families like IPv4 */
660
661 default:
662 result = EOPNOTSUPP;
663 }
664
665 return result;
666}
667
668static void
669utun_detached(
670 ifnet_t interface)
671{
672 struct utun_pcb *pcb = ifnet_softc(interface);
673
674 utun_free(pcb);
675
676 OSDecrementAtomic(&utun_ifcount);
677}
678
679/* Protocol Handlers */
680
681static errno_t
682utun_proto_input(
683 __unused ifnet_t interface,
684 protocol_family_t protocol,
685 mbuf_t m,
686 __unused char *frame_header)
687{
688
689 // remove protocol family first
690 mbuf_adj(m, sizeof(u_int32_t));
691
692 proto_input(protocol, m);
693
694 return 0;
695}
696
697static errno_t
698utun_proto_pre_output(
699 __unused ifnet_t interface,
700 protocol_family_t protocol,
701 __unused mbuf_t *packet,
702 __unused const struct sockaddr *dest,
703 __unused void *route,
704 __unused char *frame_type,
705 __unused char *link_layer_dest)
706{
707
708 *(protocol_family_t *)(void *)frame_type = protocol;
709 return 0;
710}
711
712static errno_t
713utun_attach_proto(
714 ifnet_t interface,
715 protocol_family_t protocol)
716{
717 struct ifnet_attach_proto_param proto;
718 errno_t result;
719
720 bzero(&proto, sizeof(proto));
721 proto.input = utun_proto_input;
722 proto.pre_output = utun_proto_pre_output;
723
724 result = ifnet_attach_protocol(interface, protocol, &proto);
725 if (result != 0 && result != EEXIST) {
726 printf("utun_attach_inet - ifnet_attach_protocol %d failed: %d\n",
727 protocol, result);
728 }
729
730 return result;
731}
732