2 * Copyright (c) 2001-2019 Apple Inc. All rights reserved.
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
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.
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
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.
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
31 * 14 December, 2001 Dieter Siegmund (dieter@apple.com)
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/kernel.h>
38 #include <sys/ioctl.h>
39 #include <sys/proc_internal.h>
40 #include <sys/mount_internal.h>
42 #include <sys/filedesc.h>
43 #include <sys/vnode_internal.h>
44 #include <sys/malloc.h>
45 #include <sys/socket.h>
46 #include <sys/socketvar.h>
47 #include <sys/reboot.h>
48 #include <sys/kauth.h>
50 #include <net/if_dl.h>
51 #include <net/if_types.h>
52 #include <net/route.h>
53 #include <netinet/in.h>
54 #include <netinet/if_ether.h>
55 #include <netinet/dhcp_options.h>
57 #include <kern/kern_types.h>
58 #include <kern/kalloc.h>
59 #include <sys/netboot.h>
60 #include <sys/imageboot.h>
61 #include <pexpert/pexpert.h>
63 extern int nfs_mountroot(void); /* nfs_vfsops.c */
64 extern int (*mountroot
)(void);
66 extern unsigned char rootdevice
[];
68 static int S_netboot
= 0;
69 static struct netboot_info
* S_netboot_info_p
;
72 IOBSDRegistryEntryForDeviceTree(const char * path
);
75 IOBSDRegistryEntryRelease(void * entry
);
78 IOBSDRegistryEntryGetData(void * entry
, const char * property_name
,
81 #define BOOTP_RESPONSE "bootp-response"
82 #define BSDP_RESPONSE "bsdp-response"
83 #define DHCP_RESPONSE "dhcp-response"
85 #define IP_FORMAT "%d.%d.%d.%d"
86 #define IP_CH(ip) ((u_char *)ip)
87 #define IP_LIST(ip) IP_CH(ip)[0],IP_CH(ip)[1],IP_CH(ip)[2],IP_CH(ip)[3]
89 #define kNetBootRootPathPrefixNFS "nfs:"
90 #define kNetBootRootPathPrefixHTTP "http:"
93 kNetBootImageTypeUnknown
= 0,
94 kNetBootImageTypeNFS
= 1,
95 kNetBootImageTypeHTTP
= 2,
99 struct in_addr client_ip
;
100 struct in_addr server_ip
;
102 size_t server_name_length
;
104 size_t mount_point_length
;
106 size_t image_path_length
;
107 NetBootImageType image_type
;
108 char * second_image_path
;
109 size_t second_image_path_length
;
113 * Function: parse_booter_path
115 * Parse a string of the form:
116 * "<IP>:<host>:<mount>[:<image_path>]"
117 * into the given ip address, host, mount point, and optionally, image_path.
120 * The passed in string is modified i.e. ':' is replaced by '\0'.
122 * "17.202.16.17:seaport:/release/.images/Image9/CurrentHera"
124 static __inline__ boolean_t
125 parse_booter_path(char * path
, struct in_addr
* iaddr_p
, char const * * host
,
126 char * * mount_dir
, char * * image_path
)
133 colon
= strchr(start
, ':');
138 if (inet_aton(start
, iaddr_p
) != 1) {
144 colon
= strchr(start
, ':');
153 colon
= strchr(start
, ':');
167 * Function: find_colon
169 * Find the next unescaped instance of the colon character.
170 * If a colon is escaped (preceded by a backslash '\' character),
171 * shift the string over by one character to overwrite the backslash.
173 static __inline__
char *
174 find_colon(char * str
)
179 while ((colon
= strchr(start
, ':')) != NULL
) {
183 if (colon
== start
) {
186 if (colon
[-1] != '\\') {
189 for (dst
= colon
- 1, src
= colon
; *dst
!= '\0'; dst
++, src
++) {
198 * Function: parse_netboot_path
200 * Parse a string of the form:
201 * "nfs:<IP>:<mount>[:<image_path>]"
202 * into the given ip address, host, mount point, and optionally, image_path.
204 * - the passed in string is modified i.e. ':' is replaced by '\0'
205 * - literal colons must be escaped with a backslash
208 * nfs:17.202.42.112:/Library/NetBoot/NetBootSP0:Jaguar/Jaguar.dmg
209 * nfs:17.202.42.112:/Volumes/Foo\:/Library/NetBoot/NetBootSP0:Jaguar/Jaguar.dmg
211 static __inline__ boolean_t
212 parse_netboot_path(char * path
, struct in_addr
* iaddr_p
, char const * * host
,
213 char * * mount_dir
, char * * image_path
)
215 static char tmp
[MAX_IPv4_STR_LEN
]; /* Danger - not thread safe */
219 if (strncmp(path
, kNetBootRootPathPrefixNFS
,
220 strlen(kNetBootRootPathPrefixNFS
)) != 0) {
225 start
= path
+ strlen(kNetBootRootPathPrefixNFS
);
226 colon
= strchr(start
, ':');
231 if (inet_aton(start
, iaddr_p
) != 1) {
237 colon
= find_colon(start
);
245 (void)find_colon(start
);
248 *host
= inet_ntop(AF_INET
, iaddr_p
, tmp
, sizeof(tmp
));
253 parse_image_path(char * path
, struct in_addr
* iaddr_p
, char const * * host
,
254 char * * mount_dir
, char * * image_path
)
256 if (path
[0] >= '0' && path
[0] <= '9') {
257 return parse_booter_path(path
, iaddr_p
, host
, mount_dir
,
260 return parse_netboot_path(path
, iaddr_p
, host
, mount_dir
,
265 get_root_path(char * root_path
)
268 boolean_t found
= FALSE
;
272 entry
= IOBSDRegistryEntryForDeviceTree("/chosen");
276 pkt
= IOBSDRegistryEntryGetData(entry
, BSDP_RESPONSE
, &pkt_len
);
277 if (pkt
!= NULL
&& pkt_len
>= (int)sizeof(struct dhcp
)) {
278 printf("netboot: retrieving root path from BSDP response\n");
280 pkt
= IOBSDRegistryEntryGetData(entry
, BOOTP_RESPONSE
,
282 if (pkt
!= NULL
&& pkt_len
>= (int)sizeof(struct dhcp
)) {
283 printf("netboot: retrieving root path from BOOTP response\n");
290 const struct dhcp
* reply
;
292 reply
= (const struct dhcp
*)pkt
;
293 (void)dhcpol_parse_packet(&options
, reply
, pkt_len
);
295 path
= (const char *)dhcpol_find(&options
,
296 dhcptag_root_path_e
, &len
, NULL
);
298 memcpy(root_path
, path
, len
);
299 root_path
[len
] = '\0';
303 IOBSDRegistryEntryRelease(entry
);
308 save_path(char * * str_p
, size_t * length_p
, char * path
)
310 *length_p
= strlen(path
) + 1;
311 *str_p
= (char *)kheap_alloc(KHEAP_DATA_BUFFERS
, *length_p
, Z_WAITOK
);
312 strlcpy(*str_p
, path
, *length_p
);
316 static struct netboot_info
*
317 netboot_info_init(struct in_addr iaddr
)
319 boolean_t have_root_path
= FALSE
;
320 struct netboot_info
* info
= NULL
;
321 char * root_path
= NULL
;
323 info
= (struct netboot_info
*)kalloc(sizeof(*info
));
324 bzero(info
, sizeof(*info
));
325 info
->client_ip
= iaddr
;
326 info
->image_type
= kNetBootImageTypeUnknown
;
328 /* check for a booter-specified path then a NetBoot path */
329 root_path
= zalloc(ZV_NAMEI
);
331 if (PE_parse_boot_argn("rp0", root_path
, MAXPATHLEN
) == TRUE
332 || PE_parse_boot_argn("rp", root_path
, MAXPATHLEN
) == TRUE
333 || PE_parse_boot_argn("rootpath", root_path
, MAXPATHLEN
) == TRUE
) {
334 if (imageboot_format_is_valid(root_path
)) {
335 printf("netboot_info_init: rp0='%s' isn't a network path,"
336 " ignoring\n", root_path
);
338 have_root_path
= TRUE
;
341 if (have_root_path
== FALSE
) {
342 have_root_path
= get_root_path(root_path
);
344 if (have_root_path
) {
345 const char * server_name
= NULL
;
346 char * mount_point
= NULL
;
347 char * image_path
= NULL
;
348 struct in_addr server_ip
;
350 if (parse_image_path(root_path
, &server_ip
, &server_name
,
351 &mount_point
, &image_path
)) {
352 info
->image_type
= kNetBootImageTypeNFS
;
353 info
->server_ip
= server_ip
;
354 info
->server_name_length
= strlen(server_name
) + 1;
355 info
->server_name
= kheap_alloc(KHEAP_DATA_BUFFERS
,
356 info
->server_name_length
, Z_WAITOK
);
357 info
->mount_point_length
= strlen(mount_point
) + 1;
358 info
->mount_point
= kheap_alloc(KHEAP_DATA_BUFFERS
,
359 info
->mount_point_length
, Z_WAITOK
);
360 strlcpy(info
->server_name
, server_name
, info
->server_name_length
);
361 strlcpy(info
->mount_point
, mount_point
, info
->mount_point_length
);
363 printf("netboot: NFS Server %s Mount %s",
364 server_name
, info
->mount_point
);
365 if (image_path
!= NULL
) {
366 boolean_t needs_slash
= FALSE
;
368 info
->image_path_length
= strlen(image_path
) + 1;
369 if (image_path
[0] != '/') {
371 info
->image_path_length
++;
373 info
->image_path
= kheap_alloc(KHEAP_DATA_BUFFERS
,
374 info
->image_path_length
, Z_WAITOK
);
376 info
->image_path
[0] = '/';
377 strlcpy(info
->image_path
+ 1, image_path
,
378 info
->image_path_length
- 1);
380 strlcpy(info
->image_path
, image_path
,
381 info
->image_path_length
);
383 printf(" Image %s", info
->image_path
);
386 } else if (strncmp(root_path
, kNetBootRootPathPrefixHTTP
,
387 strlen(kNetBootRootPathPrefixHTTP
)) == 0) {
388 info
->image_type
= kNetBootImageTypeHTTP
;
389 save_path(&info
->image_path
, &info
->image_path_length
,
391 printf("netboot: HTTP URL %s\n", info
->image_path
);
393 printf("netboot: root path uses unrecognized format\n");
396 /* check for image-within-image */
397 if (info
->image_path
!= NULL
) {
398 if (PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG
, root_path
, MAXPATHLEN
)
399 || PE_parse_boot_argn("rp1", root_path
, MAXPATHLEN
)) {
400 /* rp1/root-dmg is the second-level image */
401 save_path(&info
->second_image_path
, &info
->second_image_path_length
,
405 if (info
->second_image_path
!= NULL
) {
406 printf("netboot: nested image %s\n", info
->second_image_path
);
409 zfree(ZV_NAMEI
, root_path
);
414 netboot_info_free(struct netboot_info
* * info_p
)
416 struct netboot_info
* info
= *info_p
;
419 if (info
->mount_point
) {
420 kheap_free(KHEAP_DATA_BUFFERS
, info
->mount_point
,
421 info
->mount_point_length
);
423 if (info
->server_name
) {
424 kheap_free(KHEAP_DATA_BUFFERS
, info
->server_name
,
425 info
->server_name_length
);
427 if (info
->image_path
) {
428 kheap_free(KHEAP_DATA_BUFFERS
, info
->image_path
,
429 info
->image_path_length
);
431 if (info
->second_image_path
) {
432 kheap_free(KHEAP_DATA_BUFFERS
, info
->second_image_path
,
433 info
->second_image_path_length
);
435 kfree(info
, sizeof(*info
));
441 netboot_iaddr(struct in_addr
* iaddr_p
)
443 if (S_netboot_info_p
== NULL
) {
447 *iaddr_p
= S_netboot_info_p
->client_ip
;
452 netboot_rootpath(struct in_addr
* server_ip
,
453 char * name
, size_t name_len
,
454 char * path
, size_t path_len
)
456 if (S_netboot_info_p
== NULL
) {
463 if (S_netboot_info_p
->mount_point_length
== 0) {
466 if (path_len
< S_netboot_info_p
->mount_point_length
) {
467 printf("netboot: path too small %zu < %zu\n",
468 path_len
, S_netboot_info_p
->mount_point_length
);
471 strlcpy(path
, S_netboot_info_p
->mount_point
, path_len
);
472 strlcpy(name
, S_netboot_info_p
->server_name
, name_len
);
473 *server_ip
= S_netboot_info_p
->server_ip
;
479 get_ip_parameters(struct in_addr
* iaddr_p
, struct in_addr
* netmask_p
,
480 struct in_addr
* router_p
)
487 entry
= IOBSDRegistryEntryForDeviceTree("/chosen");
491 pkt
= IOBSDRegistryEntryGetData(entry
, DHCP_RESPONSE
, &pkt_len
);
492 if (pkt
!= NULL
&& pkt_len
>= (int)sizeof(struct dhcp
)) {
493 printf("netboot: retrieving IP information from DHCP response\n");
495 pkt
= IOBSDRegistryEntryGetData(entry
, BOOTP_RESPONSE
, &pkt_len
);
496 if (pkt
!= NULL
&& pkt_len
>= (int)sizeof(struct dhcp
)) {
497 printf("netboot: retrieving IP information from BOOTP response\n");
501 const struct in_addr
* ip
;
504 const struct dhcp
* reply
;
506 reply
= (const struct dhcp
*)pkt
;
507 (void)dhcpol_parse_packet(&options
, reply
, pkt_len
);
508 *iaddr_p
= reply
->dp_yiaddr
;
509 ip
= (const struct in_addr
*)
510 dhcpol_find(&options
,
511 dhcptag_subnet_mask_e
, &len
, NULL
);
515 ip
= (const struct in_addr
*)
516 dhcpol_find(&options
, dhcptag_router_e
, &len
, NULL
);
521 IOBSDRegistryEntryRelease(entry
);
526 route_cmd(int cmd
, struct in_addr d
, struct in_addr g
,
527 struct in_addr m
, uint32_t more_flags
, unsigned int ifscope
)
529 struct sockaddr_in dst
;
531 uint32_t flags
= RTF_UP
| RTF_STATIC
;
532 struct sockaddr_in gw
;
533 struct sockaddr_in mask
;
538 bzero((caddr_t
)&dst
, sizeof(dst
));
539 dst
.sin_len
= sizeof(dst
);
540 dst
.sin_family
= AF_INET
;
544 bzero((caddr_t
)&gw
, sizeof(gw
));
545 gw
.sin_len
= sizeof(gw
);
546 gw
.sin_family
= AF_INET
;
550 bzero(&mask
, sizeof(mask
));
551 mask
.sin_len
= sizeof(mask
);
552 mask
.sin_family
= AF_INET
;
555 error
= rtrequest_scoped(cmd
, (struct sockaddr
*)&dst
,
556 (struct sockaddr
*)&gw
, (struct sockaddr
*)&mask
, flags
, NULL
, ifscope
);
562 default_route_add(struct in_addr router
, boolean_t proxy_arp
)
565 struct in_addr zeroes
= { .s_addr
= 0 };
567 if (proxy_arp
== FALSE
) {
568 flags
|= RTF_GATEWAY
;
570 return route_cmd(RTM_ADD
, zeroes
, router
, zeroes
, flags
, IFSCOPE_NONE
);
574 host_route_delete(struct in_addr host
, unsigned int ifscope
)
576 struct in_addr zeroes
= { .s_addr
= 0 };
578 return route_cmd(RTM_DELETE
, host
, zeroes
, zeroes
, RTF_HOST
, ifscope
);
581 static struct ifnet
*
584 struct ifnet
* ifp
= NULL
;
588 ifp
= ifunit((char *)rootdevice
);
591 ifnet_head_lock_shared();
592 TAILQ_FOREACH(ifp
, &ifnet_head
, if_link
)
593 if ((ifp
->if_flags
& (IFF_LOOPBACK
| IFF_POINTOPOINT
)) == 0) {
602 static const struct sockaddr_in blank_sin
= {
603 .sin_len
= sizeof(struct sockaddr_in
),
604 .sin_family
= AF_INET
,
606 .sin_addr
= { .s_addr
= 0 },
607 .sin_zero
= { 0, 0, 0, 0, 0, 0, 0, 0 }
611 inet_aifaddr(struct socket
* so
, const char * name
,
612 const struct in_addr
* addr
,
613 const struct in_addr
* mask
,
614 const struct in_addr
* broadcast
)
616 struct ifaliasreq ifra
;
618 bzero(&ifra
, sizeof(ifra
));
619 strlcpy(ifra
.ifra_name
, name
, sizeof(ifra
.ifra_name
));
621 *((struct sockaddr_in
*)(void *)&ifra
.ifra_addr
) = blank_sin
;
622 ((struct sockaddr_in
*)(void *)&ifra
.ifra_addr
)->sin_addr
= *addr
;
625 *((struct sockaddr_in
*)(void *)&ifra
.ifra_mask
) = blank_sin
;
626 ((struct sockaddr_in
*)(void *)&ifra
.ifra_mask
)->sin_addr
= *mask
;
629 *((struct sockaddr_in
*)(void *)&ifra
.ifra_broadaddr
) = blank_sin
;
630 ((struct sockaddr_in
*)(void *)&ifra
.ifra_broadaddr
)->sin_addr
= *broadcast
;
632 return ifioctl(so
, SIOCAIFADDR
, (caddr_t
)&ifra
, current_proc());
637 netboot_mountroot(void)
640 struct in_addr iaddr
= { .s_addr
= 0 };
643 struct in_addr netmask
= { .s_addr
= 0 };
644 proc_t procp
= current_proc();
645 struct in_addr router
= { .s_addr
= 0 };
646 struct socket
* so
= NULL
;
649 bzero(&ifr
, sizeof(ifr
));
651 /* find the interface */
652 ifp
= find_interface();
654 printf("netboot: no suitable interface\n");
658 snprintf(ifr
.ifr_name
, sizeof(ifr
.ifr_name
), "%s", if_name(ifp
));
659 printf("netboot: using network interface '%s'\n", ifr
.ifr_name
);
662 if ((error
= socreate(AF_INET
, &so
, SOCK_DGRAM
, 0)) != 0) {
663 printf("netboot: socreate, error=%d\n", error
);
666 ifr
.ifr_flags
= ifp
->if_flags
| IFF_UP
;
667 error
= ifioctl(so
, SIOCSIFFLAGS
, (caddr_t
)&ifr
, procp
);
669 printf("netboot: SIFFLAGS, error=%d\n", error
);
673 /* grab information from the registry */
674 if (get_ip_parameters(&iaddr
, &netmask
, &router
) == FALSE
) {
675 printf("netboot: can't retrieve IP parameters\n");
678 printf("netboot: IP address " IP_FORMAT
, IP_LIST(&iaddr
));
679 if (netmask
.s_addr
) {
680 printf(" netmask " IP_FORMAT
, IP_LIST(&netmask
));
683 printf(" router " IP_FORMAT
, IP_LIST(&router
));
686 error
= inet_aifaddr(so
, ifr
.ifr_name
, &iaddr
, &netmask
, NULL
);
688 printf("netboot: inet_aifaddr failed, %d\n", error
);
691 if (router
.s_addr
== 0) {
692 /* enable proxy arp if we don't have a router */
693 router
.s_addr
= iaddr
.s_addr
;
695 printf("netboot: adding default route " IP_FORMAT
"\n",
697 error
= default_route_add(router
, router
.s_addr
== iaddr
.s_addr
);
699 printf("netboot: default_route_add failed %d\n", error
);
704 S_netboot_info_p
= netboot_info_init(iaddr
);
705 switch (S_netboot_info_p
->image_type
) {
707 case kNetBootImageTypeNFS
:
708 for (try = 1; TRUE
; try++) {
709 error
= nfs_mountroot();
713 printf("netboot: nfs_mountroot() attempt %u failed; "
714 "clearing ARP entry and trying again\n", try);
716 * error is either EHOSTDOWN or EHOSTUNREACH, which likely means
717 * that the port we're plugged into has spanning tree enabled,
718 * and either the router or the server can't answer our ARP
719 * requests. Clear the incomplete ARP entry by removing the
720 * appropriate route, depending on the error code:
721 * EHOSTDOWN NFS server's route
722 * EHOSTUNREACH router's route
728 /* remove the server's arp entry */
729 error
= host_route_delete(S_netboot_info_p
->server_ip
,
732 printf("netboot: host_route_delete(" IP_FORMAT
734 IP_LIST(&S_netboot_info_p
->server_ip
), error
);
738 error
= host_route_delete(router
, ifp
->if_index
);
740 printf("netboot: host_route_delete(" IP_FORMAT
741 ") failed %d\n", IP_LIST(&router
), error
);
747 case kNetBootImageTypeHTTP
:
748 error
= netboot_setup();
769 if (S_netboot_info_p
== NULL
770 || S_netboot_info_p
->image_path
== NULL
) {
773 printf("netboot_setup: calling imageboot_mount_image\n");
774 error
= imageboot_mount_image(S_netboot_info_p
->image_path
, -1, IMAGEBOOT_DMG
);
776 printf("netboot: failed to mount root image, %d\n", error
);
777 } else if (S_netboot_info_p
->second_image_path
!= NULL
) {
778 error
= imageboot_mount_image(S_netboot_info_p
->second_image_path
, 0, IMAGEBOOT_DMG
);
780 printf("netboot: failed to mount second root image, %d\n", error
);
785 netboot_info_free(&S_netboot_info_p
);