#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/ioctl.h>
#include <sys/proc_internal.h>
#include <sys/mount_internal.h>
#include <sys/mbuf.h>
#include <sys/filedesc.h>
#include <sys/vnode_internal.h>
#include <sys/malloc.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/reboot.h>
#include <sys/kauth.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/dhcp_options.h>
#include <netinet/in_dhcp.h>
#include <kern/kern_types.h>
#include <kern/kalloc.h>
#include <pexpert/pexpert.h>
extern struct filedesc filedesc0;
extern int nfs_mountroot(void);
extern int (*mountroot)(void);
extern unsigned char rootdevice[];
static int S_netboot = 0;
static struct netboot_info * S_netboot_info_p;
void *
IOBSDRegistryEntryForDeviceTree(const char * path);
void
IOBSDRegistryEntryRelease(void * entry);
const void *
IOBSDRegistryEntryGetData(void * entry, const char * property_name,
int * packet_length);
extern int vndevice_root_image(const char * path, char devname[],
dev_t * dev_p);
extern int di_root_image(const char *path, char devname[], dev_t *dev_p);
#define BOOTP_RESPONSE "bootp-response"
#define BSDP_RESPONSE "bsdp-response"
#define DHCP_RESPONSE "dhcp-response"
int inet_aton(char * cp, struct in_addr * pin);
boolean_t netboot_iaddr(struct in_addr * iaddr_p);
boolean_t netboot_rootpath(struct in_addr * server_ip,
char * name, int name_len,
char * path, int path_len);
int netboot_setup(void);
int netboot_mountroot(void);
int netboot_root(void);
#define IP_FORMAT "%d.%d.%d.%d"
#define IP_CH(ip) ((u_char *)ip)
#define IP_LIST(ip) IP_CH(ip)[0],IP_CH(ip)[1],IP_CH(ip)[2],IP_CH(ip)[3]
#define kNetBootRootPathPrefixNFS "nfs:"
#define kNetBootRootPathPrefixHTTP "http:"
typedef enum {
kNetBootImageTypeUnknown = 0,
kNetBootImageTypeNFS = 1,
kNetBootImageTypeHTTP = 2,
} NetBootImageType;
struct netboot_info {
struct in_addr client_ip;
struct in_addr server_ip;
char * server_name;
int server_name_length;
char * mount_point;
int mount_point_length;
char * image_path;
int image_path_length;
NetBootImageType image_type;
boolean_t use_hdix;
};
int
inet_aton(char * cp, struct in_addr * pin)
{
u_char * b = (u_char *)pin;
int i;
char * p;
for (p = cp, i = 0; i < 4; i++) {
u_long l = strtoul(p, 0, 0);
if (l > 255)
return (FALSE);
b[i] = l;
p = strchr(p, '.');
if (i < 3 && p == NULL)
return (FALSE);
p++;
}
return (TRUE);
}
static __inline__ boolean_t
parse_booter_path(char * path, struct in_addr * iaddr_p, char const * * host,
char * * mount_dir, char * * image_path)
{
char * start;
char * colon;
start = path;
colon = strchr(start, ':');
if (colon == NULL) {
return (FALSE);
}
*colon = '\0';
if (inet_aton(start, iaddr_p) != 1) {
return (FALSE);
}
start = colon + 1;
colon = strchr(start, ':');
if (colon == NULL) {
return (FALSE);
}
*colon = '\0';
*host = start;
start = colon + 1;
colon = strchr(start, ':');
*mount_dir = start;
if (colon == NULL) {
*image_path = NULL;
}
else {
*colon = '\0';
start = colon + 1;
*image_path = start;
}
return (TRUE);
}
static __inline__ char *
find_colon(char * str)
{
char * start = str;
char * colon;
while ((colon = strchr(start, ':')) != NULL) {
char * dst;
char * src;
if (colon == start) {
break;
}
if (colon[-1] != '\\')
break;
for (dst = colon - 1, src = colon; *dst != '\0'; dst++, src++) {
*dst = *src;
}
start = colon;
}
return (colon);
}
static __inline__ boolean_t
parse_netboot_path(char * path, struct in_addr * iaddr_p, char const * * host,
char * * mount_dir, char * * image_path)
{
static char tmp[MAX_IPv4_STR_LEN];
char * start;
char * colon;
if (strncmp(path, kNetBootRootPathPrefixNFS,
strlen(kNetBootRootPathPrefixNFS)) != 0) {
return (FALSE);
}
start = path + strlen(kNetBootRootPathPrefixNFS);
colon = strchr(start, ':');
if (colon == NULL) {
return (FALSE);
}
*colon = '\0';
if (inet_aton(start, iaddr_p) != 1) {
return (FALSE);
}
start = colon + 1;
colon = find_colon(start);
*mount_dir = start;
if (colon == NULL) {
*image_path = NULL;
}
else {
*colon = '\0';
start = colon + 1;
(void)find_colon(start);
*image_path = start;
}
*host = inet_ntop(AF_INET, iaddr_p, tmp, sizeof(tmp));
return (TRUE);
}
static boolean_t
parse_image_path(char * path, struct in_addr * iaddr_p, char const * * host,
char * * mount_dir, char * * image_path)
{
if (path[0] >= '0' && path[0] <= '9') {
return (parse_booter_path(path, iaddr_p, host, mount_dir,
image_path));
}
return (parse_netboot_path(path, iaddr_p, host, mount_dir,
image_path));
}
static boolean_t
get_root_path(char * root_path)
{
void * entry;
boolean_t found = FALSE;
const void * pkt;
int pkt_len;
entry = IOBSDRegistryEntryForDeviceTree("/chosen");
if (entry == NULL) {
return (FALSE);
}
pkt = IOBSDRegistryEntryGetData(entry, BSDP_RESPONSE, &pkt_len);
if (pkt != NULL && pkt_len >= (int)sizeof(struct dhcp)) {
printf("netboot: retrieving root path from BSDP response\n");
}
else {
pkt = IOBSDRegistryEntryGetData(entry, BOOTP_RESPONSE,
&pkt_len);
if (pkt != NULL && pkt_len >= (int)sizeof(struct dhcp)) {
printf("netboot: retrieving root path from BOOTP response\n");
}
}
if (pkt != NULL) {
int len;
dhcpol_t options;
const char * path;
const struct dhcp * reply;
reply = (const struct dhcp *)pkt;
(void)dhcpol_parse_packet(&options, reply, pkt_len);
path = (const char *)dhcpol_find(&options,
dhcptag_root_path_e, &len, NULL);
if (path) {
memcpy(root_path, path, len);
root_path[len] = '\0';
found = TRUE;
}
}
IOBSDRegistryEntryRelease(entry);
return (found);
}
static struct netboot_info *
netboot_info_init(struct in_addr iaddr)
{
struct netboot_info * info;
char * root_path = NULL;
boolean_t use_hdix = TRUE;
char * vndevice = NULL;
MALLOC_ZONE(vndevice, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
if (vndevice == NULL)
panic("netboot_info_init: M_NAMEI zone exhausted");
if (PE_parse_boot_argn("vndevice", vndevice, MAXPATHLEN) == TRUE) {
use_hdix = FALSE;
}
FREE_ZONE(vndevice, MAXPATHLEN, M_NAMEI);
info = (struct netboot_info *)kalloc(sizeof(*info));
bzero(info, sizeof(*info));
info->client_ip = iaddr;
info->image_type = kNetBootImageTypeUnknown;
info->use_hdix = use_hdix;
MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
if (root_path == NULL)
panic("netboot_info_init: M_NAMEI zone exhausted");
if (PE_parse_boot_argn("rp", root_path, MAXPATHLEN) == TRUE
|| PE_parse_boot_argn("rootpath", root_path, MAXPATHLEN) == TRUE
|| get_root_path(root_path) == TRUE) {
const char * server_name = NULL;
char * mount_point = NULL;
char * image_path = NULL;
struct in_addr server_ip;
if (parse_image_path(root_path, &server_ip, &server_name,
&mount_point, &image_path)) {
info->image_type = kNetBootImageTypeNFS;
info->server_ip = server_ip;
info->server_name_length = strlen(server_name) + 1;
info->server_name = (char *)kalloc(info->server_name_length);
info->mount_point_length = strlen(mount_point) + 1;
info->mount_point = (char *)kalloc(info->mount_point_length);
strlcpy(info->server_name, server_name, info->server_name_length);
strlcpy(info->mount_point, mount_point, info->mount_point_length);
printf("Server %s Mount %s",
server_name, info->mount_point);
if (image_path != NULL) {
boolean_t needs_slash = FALSE;
info->image_path_length = strlen(image_path) + 1;
if (image_path[0] != '/') {
needs_slash = TRUE;
info->image_path_length++;
}
info->image_path = (char *)kalloc(info->image_path_length);
if (needs_slash) {
info->image_path[0] = '/';
strlcpy(info->image_path + 1, image_path,
info->image_path_length - 1);
} else {
strlcpy(info->image_path, image_path,
info->image_path_length);
}
printf(" Image %s", info->image_path);
}
printf("\n");
}
else if (strncmp(root_path, kNetBootRootPathPrefixHTTP,
strlen(kNetBootRootPathPrefixHTTP)) == 0) {
info->image_type = kNetBootImageTypeHTTP;
info->use_hdix = TRUE;
info->image_path_length = strlen(root_path) + 1;
info->image_path = (char *)kalloc(info->image_path_length);
strlcpy(info->image_path, root_path, info->image_path_length);
}
else {
printf("netboot: root path uses unrecognized format\n");
}
}
FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI);
return (info);
}
static void
netboot_info_free(struct netboot_info * * info_p)
{
struct netboot_info * info = *info_p;
if (info) {
if (info->mount_point) {
kfree(info->mount_point, info->mount_point_length);
}
if (info->server_name) {
kfree(info->server_name, info->server_name_length);
}
if (info->image_path) {
kfree(info->image_path, info->image_path_length);
}
kfree(info, sizeof(*info));
}
*info_p = NULL;
return;
}
boolean_t
netboot_iaddr(struct in_addr * iaddr_p)
{
if (S_netboot_info_p == NULL)
return (FALSE);
*iaddr_p = S_netboot_info_p->client_ip;
return (TRUE);
}
boolean_t
netboot_rootpath(struct in_addr * server_ip,
char * name, int name_len,
char * path, int path_len)
{
if (S_netboot_info_p == NULL)
return (FALSE);
name[0] = '\0';
path[0] = '\0';
if (S_netboot_info_p->mount_point_length == 0) {
return (FALSE);
}
if (path_len < S_netboot_info_p->mount_point_length) {
printf("netboot: path too small %d < %d\n",
path_len, S_netboot_info_p->mount_point_length);
return (FALSE);
}
strlcpy(path, S_netboot_info_p->mount_point, path_len);
strlcpy(name, S_netboot_info_p->server_name, name_len);
*server_ip = S_netboot_info_p->server_ip;
return (TRUE);
}
static boolean_t
get_ip_parameters(struct in_addr * iaddr_p, struct in_addr * netmask_p,
struct in_addr * router_p)
{
void * entry;
const void * pkt;
int pkt_len;
entry = IOBSDRegistryEntryForDeviceTree("/chosen");
if (entry == NULL) {
return (FALSE);
}
pkt = IOBSDRegistryEntryGetData(entry, DHCP_RESPONSE, &pkt_len);
if (pkt != NULL && pkt_len >= (int)sizeof(struct dhcp)) {
printf("netboot: retrieving IP information from DHCP response\n");
}
else {
pkt = IOBSDRegistryEntryGetData(entry, BOOTP_RESPONSE, &pkt_len);
if (pkt != NULL && pkt_len >= (int)sizeof(struct dhcp)) {
printf("netboot: retrieving IP information from BOOTP response\n");
}
}
if (pkt != NULL) {
const struct in_addr * ip;
int len;
dhcpol_t options;
const struct dhcp * reply;
reply = (const struct dhcp *)pkt;
(void)dhcpol_parse_packet(&options, reply, pkt_len);
*iaddr_p = reply->dp_yiaddr;
ip = (const struct in_addr *)
dhcpol_find(&options,
dhcptag_subnet_mask_e, &len, NULL);
if (ip) {
*netmask_p = *ip;
}
ip = (const struct in_addr *)
dhcpol_find(&options, dhcptag_router_e, &len, NULL);
if (ip) {
*router_p = *ip;
}
}
IOBSDRegistryEntryRelease(entry);
return (pkt != NULL);
}
static int
route_cmd(int cmd, struct in_addr d, struct in_addr g,
struct in_addr m, u_long more_flags)
{
struct sockaddr_in dst;
u_long flags = RTF_UP | RTF_STATIC;
struct sockaddr_in gw;
struct sockaddr_in mask;
flags |= more_flags;
bzero((caddr_t)&dst, sizeof(dst));
dst.sin_len = sizeof(dst);
dst.sin_family = AF_INET;
dst.sin_addr = d;
bzero((caddr_t)&gw, sizeof(gw));
gw.sin_len = sizeof(gw);
gw.sin_family = AF_INET;
gw.sin_addr = g;
bzero(&mask, sizeof(mask));
mask.sin_len = sizeof(mask);
mask.sin_family = AF_INET;
mask.sin_addr = m;
return (rtrequest(cmd, (struct sockaddr *)&dst, (struct sockaddr *)&gw,
(struct sockaddr *)&mask, flags, NULL));
}
static int
default_route_add(struct in_addr router, boolean_t proxy_arp)
{
u_long flags = 0;
struct in_addr zeroes = { 0 };
if (proxy_arp == FALSE) {
flags |= RTF_GATEWAY;
}
return (route_cmd(RTM_ADD, zeroes, router, zeroes, flags));
}
static int
host_route_delete(struct in_addr host)
{
struct in_addr zeroes = { 0 };
return (route_cmd(RTM_DELETE, host, zeroes, zeroes, RTF_HOST));
}
static struct ifnet *
find_interface(void)
{
struct ifnet * ifp = NULL;
if (rootdevice[0]) {
ifp = ifunit((char *)rootdevice);
}
if (ifp == NULL) {
ifnet_head_lock_shared();
TAILQ_FOREACH(ifp, &ifnet_head, if_link)
if ((ifp->if_flags & (IFF_LOOPBACK|IFF_POINTOPOINT)) == 0)
break;
ifnet_head_done();
}
return (ifp);
}
int
netboot_mountroot(void)
{
int error = 0;
struct in_addr iaddr = { 0 };
struct ifreq ifr;
struct ifnet * ifp;
struct in_addr netmask = { 0 };
proc_t procp = current_proc();
struct in_addr router = { 0 };
struct socket * so = NULL;
unsigned int try;
bzero(&ifr, sizeof(ifr));
ifp = find_interface();
if (ifp == NULL) {
printf("netboot: no suitable interface\n");
error = ENXIO;
goto failed;
}
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d", ifp->if_name,
ifp->if_unit);
printf("netboot: using network interface '%s'\n", ifr.ifr_name);
if ((error = socreate(AF_INET, &so, SOCK_DGRAM, 0)) != 0) {
printf("netboot: socreate, error=%d\n", error);
goto failed;
}
ifr.ifr_flags = ifp->if_flags | IFF_UP;
error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ifr, procp);
if (error) {
printf("netboot: SIFFLAGS, error=%d\n", error);
goto failed;
}
if (get_ip_parameters(&iaddr, &netmask, &router) == FALSE) {
error = dhcp(ifp, &iaddr, 64, &netmask, &router, procp);
if (error) {
printf("netboot: DHCP failed %d\n", error);
goto failed;
}
}
printf("netboot: IP address " IP_FORMAT, IP_LIST(&iaddr));
if (netmask.s_addr) {
printf(" netmask " IP_FORMAT, IP_LIST(&netmask));
}
if (router.s_addr) {
printf(" router " IP_FORMAT, IP_LIST(&router));
}
printf("\n");
error = inet_aifaddr(so, ifr.ifr_name, &iaddr, &netmask, NULL);
if (error) {
printf("netboot: inet_aifaddr failed, %d\n", error);
goto failed;
}
if (router.s_addr == 0) {
router.s_addr = iaddr.s_addr;
}
printf("netboot: adding default route " IP_FORMAT "\n",
IP_LIST(&router));
error = default_route_add(router, router.s_addr == iaddr.s_addr);
if (error) {
printf("netboot: default_route_add failed %d\n", error);
}
soclose(so);
S_netboot_info_p = netboot_info_init(iaddr);
switch (S_netboot_info_p->image_type) {
default:
case kNetBootImageTypeNFS:
for (try = 1; TRUE; try++) {
error = nfs_mountroot();
if (error == 0) {
break;
}
printf("netboot: nfs_mountroot() attempt %u failed; "
"clearing ARP entry and trying again\n", try);
switch (error) {
default:
case EHOSTDOWN:
error = host_route_delete(S_netboot_info_p->server_ip);
if (error) {
printf("netboot: host_route_delete(" IP_FORMAT
") failed %d\n",
IP_LIST(&S_netboot_info_p->server_ip), error);
}
break;
case EHOSTUNREACH:
error = host_route_delete(router);
if (error) {
printf("netboot: host_route_delete(" IP_FORMAT
") failed %d\n", IP_LIST(&router), error);
}
break;
}
}
break;
case kNetBootImageTypeHTTP:
error = netboot_setup();
break;
}
if (error == 0) {
S_netboot = 1;
}
else {
S_netboot = 0;
}
return (error);
failed:
if (so != NULL) {
soclose(so);
}
return (error);
}
int
netboot_setup()
{
dev_t dev;
int error = 0;
if (S_netboot_info_p == NULL
|| S_netboot_info_p->image_path == NULL) {
goto done;
}
if (S_netboot_info_p->use_hdix) {
printf("netboot_setup: calling di_root_image\n");
error = di_root_image(S_netboot_info_p->image_path,
(char *)rootdevice, &dev);
if (error) {
printf("netboot_setup: di_root_image: failed %d\n", error);
goto done;
}
}
else {
printf("netboot_setup: calling vndevice_root_image\n");
error = vndevice_root_image(S_netboot_info_p->image_path,
(char *)rootdevice, &dev);
if (error) {
printf("netboot_setup: vndevice_root_image: failed %d\n", error);
goto done;
}
}
rootdev = dev;
mountroot = NULL;
printf("netboot: root device 0x%x\n", rootdev);
error = vfs_mountroot();
if (error == 0 && rootvnode != NULL) {
struct vnode *tvp;
struct vnode *newdp;
if (VFS_ROOT(TAILQ_LAST(&mountlist,mntlist), &newdp, vfs_context_kernel()))
panic("netboot_setup: cannot find root vnode");
vnode_ref(newdp);
vnode_put(newdp);
tvp = rootvnode;
vnode_rele(tvp);
filedesc0.fd_cdir = newdp;
rootvnode = newdp;
mount_list_lock();
TAILQ_REMOVE(&mountlist, TAILQ_FIRST(&mountlist), mnt_list);
mount_list_unlock();
mountlist.tqh_first->mnt_flag |= MNT_ROOTFS;
}
done:
netboot_info_free(&S_netboot_info_p);
return (error);
}
int
netboot_root(void)
{
return (S_netboot);
}