/* $NetBSD: bootp_subr.c,v 1.3 2016/12/13 22:52:46 pgoyette Exp $ */
/*-
* Copyright (c) 1995 Gordon Ross, Adam Glass
* Copyright (c) 1992 Regents of the University of California.
* All rights reserved.
*
* This software was developed by the Computer Systems Engineering group
* at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
* contributed to Berkeley.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Lawrence Berkeley Laboratory and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* based on:
* nfs/krpc_subr.c
* NetBSD: krpc_subr.c,v 1.10 1995/08/08 20:43:43 gwr Exp
*/
/*
* Wait 10 seconds for interface appearance
* USB ethernet adapters might require some time to pop up
*/
#ifndef BOOTP_IFACE_WAIT_TIMEOUT
#define BOOTP_IFACE_WAIT_TIMEOUT 10
#endif
/*
* What is the longest we will wait before re-sending a request?
* Note this is also the frequency of "RPC timeout" messages.
* The re-send loop count sup linearly to this maximum, so the
* first complaint will happen after (1+2+3+4+5)=15 seconds.
*/
#define MAX_RESEND_DELAY 5 /* seconds */
static int bootpc_received(struct bootpc_globalcontext *gctx,
struct bootpc_ifcontext *ifctx);
static __inline int bootpc_ifctx_isresolved(struct bootpc_ifcontext *ifctx);
static __inline int bootpc_ifctx_isunresolved(struct bootpc_ifcontext *ifctx);
static __inline int bootpc_ifctx_isfailed(struct bootpc_ifcontext *ifctx);
/*
* In order to have multiple active interfaces with address 0.0.0.0
* and be able to send data to a selected interface, we first set
* mask to /8 on all interfaces, and temporarily set it to /0 when
* doing sosend().
*/
/*
* Bind the local endpoint to a bootp client port.
*/
sin = &dst;
clear_sinaddr(sin);
sin->sin_port = htons(IPPORT_BOOTPC);
error = sobind(bootp_so, (struct sockaddr *)sin, td);
if (error != 0) {
printf("bind failed\n");
goto out;
}
/*
* Setup socket address for the server.
*/
sin = &dst;
clear_sinaddr(sin);
sin->sin_addr.s_addr = INADDR_BROADCAST;
sin->sin_port = htons(IPPORT_BOOTPS);
/*
* Send it, repeatedly, until a reply is received,
* but delay each re-send by an increasing amount.
* If the delay hits the maximum, start complaining.
*/
timo = 0;
rtimo = 0;
for (;;) {
/* Determine new timeout. */
if (timo < MAX_RESEND_DELAY)
timo++;
else {
printf("DHCP/BOOTP timeout for server ");
print_sin_addr(&dst);
printf("\n");
}
/*
* Wait for up to timo seconds for a reply.
* The socket receive timeout was set to 1 second.
*/
atimo = timo + time_second;
while (time_second < atimo) {
aio.iov_base = (void *) &gctx->reply;
aio.iov_len = sizeof(gctx->reply);
if (ifctx != NULL) {
s = bootpc_tag(&gctx->tmptag,
&gctx->reply,
gctx->replylen,
TAG_DHCP_MSGTYPE);
if (s != NULL) {
switch (*s) {
case DHCP_OFFER:
s = "DHCP Offer";
break;
case DHCP_ACK:
s = "DHCP Ack";
break;
default:
s = "DHCP (unexpected)";
break;
}
} else
s = "BOOTP Reply";
printf("Received %s packet"
" on %s from ",
s,
ifctx->ireq.ifr_name);
print_in_addr(gctx->reply.siaddr);
if (gctx->reply.giaddr.s_addr !=
htonl(INADDR_ANY)) {
printf(" via ");
print_in_addr(gctx->reply.giaddr);
}
if (bootpc_received(gctx, ifctx) != 0) {
printf(" (accepted)");
if (ifctx->outstanding) {
ifctx->outstanding = 0;
outstanding--;
}
/* Network settle delay */
if (outstanding == 0)
atimo = time_second +
BOOTP_SETTLE_DELAY;
} else
printf(" (ignored)");
if (ifctx->gotrootpath ||
gctx->any_root_overrides) {
gotrootpath = 1;
rtimo = time_second +
BOOTP_SETTLE_DELAY;
if (ifctx->gotrootpath)
printf(" (got root path)");
}
printf("\n");
}
} /* while secs */
#ifdef BOOTP_TIMEOUT
if (gctx->secs > BOOTP_TIMEOUT && BOOTP_TIMEOUT > 0)
break;
#endif
/* Force a retry if halfway in DHCP negotiation */
retry = 0;
STAILQ_FOREACH(ifctx, &gctx->interfaces, next)
if (ifctx->state == IF_DHCP_OFFERED) {
if (ifctx->dhcpquerytype == DHCP_DISCOVER)
retry = 1;
else
ifctx->state = IF_DHCP_UNRESOLVED;
}
/*
* Bring up the interface.
*
* Get the old interface flags and or IFF_UP into them; if
* IFF_UP set blindly, interface selection can be clobbered.
*/
error = ifioctl(bootp_so, SIOCGIFFLAGS, (void *)ifr, td);
if (error != 0)
panic("%s: SIOCGIFFLAGS, error=%d", __func__, error);
ifr->ifr_flags |= IFF_UP;
error = ifioctl(bootp_so, SIOCSIFFLAGS, (void *)ifr, td);
if (error != 0)
panic("%s: SIOCSIFFLAGS, error=%d", __func__, error);
/*
* Do enough of ifconfig(8) so that the chosen interface
* can talk to the servers. Set address to 0.0.0.0/8 and
* broadcast address to local broadcast.
*/
sin = (struct sockaddr_in *)&ifra->ifra_addr;
clear_sinaddr(sin);
sin = (struct sockaddr_in *)&ifra->ifra_mask;
clear_sinaddr(sin);
sin->sin_addr.s_addr = htonl(IN_CLASSA_NET);
sin = (struct sockaddr_in *)&ifra->ifra_broadaddr;
clear_sinaddr(sin);
sin->sin_addr.s_addr = htonl(INADDR_BROADCAST);
error = ifioctl(bootp_so, SIOCAIFADDR, (void *)ifra, td);
if (error != 0)
panic("%s: SIOCAIFADDR, error=%d", __func__, error);
}
/*
* Do enough of ifconfig(8) so that the chosen interface
* can talk to the servers. (just set the address)
*/
sin = (struct sockaddr_in *) &ifr->ifr_addr;
clear_sinaddr(sin);
error = ifioctl(bootp_so, SIOCDIFADDR, (void *) ifr, td);
if (error != 0)
panic("%s: SIOCDIFADDR, error=%d", __func__, error);
printf("%s at ", ifctx->ireq.ifr_name);
print_sin_addr(&ifctx->myaddr);
printf(" server ");
print_in_addr(ifctx->reply.siaddr);
ifctx->gw.sin_addr = ifctx->reply.giaddr;
if (ifctx->reply.giaddr.s_addr != htonl(INADDR_ANY)) {
printf(" via gateway ");
print_in_addr(ifctx->reply.giaddr);
}
/* This call used for the side effect (overload flag) */
(void) bootpc_tag(&gctx->tmptag,
&ifctx->reply, ifctx->replylen, TAG_END);
if ((gctx->tmptag.overload & OVERLOAD_SNAME) == 0)
if (ifctx->reply.sname[0] != '\0')
printf(" server name %s", ifctx->reply.sname);
if ((gctx->tmptag.overload & OVERLOAD_FILE) == 0)
if (ifctx->reply.file[0] != '\0')
printf(" boot file %s", ifctx->reply.file);
printf("\n");
p = bootpc_tag(&gctx->tag, &ifctx->reply, ifctx->replylen,
TAG_SUBNETMASK);
if (p != NULL) {
if (gctx->tag.taglen != 4)
panic("bootpc: subnet mask len is %d",
gctx->tag.taglen);
bcopy(p, &ifctx->netmask.sin_addr, 4);
ifctx->gotnetmask = 1;
printf("subnet mask ");
print_sin_addr(&ifctx->netmask);
printf(" ");
}
p = bootpc_tag(&gctx->tag, &ifctx->reply, ifctx->replylen,
TAG_ROUTERS);
if (p != NULL) {
/* Routers */
if (gctx->tag.taglen % 4)
panic("bootpc: Router Len is %d", gctx->tag.taglen);
if (gctx->tag.taglen > 0) {
bcopy(p, &ifctx->gw.sin_addr, 4);
printf("router ");
print_sin_addr(&ifctx->gw);
printf(" ");
ifctx->gotgw = 1;
gctx->gotgw = 1;
}
}
/*
* Choose a root filesystem. If a value is forced in the environment
* and it contains "nfs:", use it unconditionally. Otherwise, if the
* kernel is compiled with the ROOTDEVNAME option, then use it if:
* - The server doesn't provide a pathname.
* - The boothowto flags include RB_DFLTROOT (user said to override
* the server value).
*/
p = NULL;
if ((s = kern_getenv("vfs.root.mountfrom")) != NULL) {
if ((p = strstr(s, "nfs:")) != NULL)
p = strdup(p + 4, M_TEMP);
freeenv(s);
}
if (p == NULL) {
p = bootpc_tag(&gctx->tag, &ifctx->reply, ifctx->replylen,
TAG_ROOT);
if (p != NULL)
ifctx->gotrootpath = 1;
}
#ifdef ROOTDEVNAME
if ((p == NULL || (boothowto & RB_DFLTROOT) != 0) &&
(p = strstr(ROOTDEVNAME, "nfs:")) != NULL) {
p += 4;
}
#endif
if (p != NULL) {
if (gctx->setrootfs != NULL) {
printf("rootfs %s (ignored) ", p);
} else if (setmyfs(&nd->root_saddr,
nd->root_hostnam, p, &ifctx->reply.siaddr)) {
if (*p == '/') {
printf("root_server ");
print_sin_addr(&nd->root_saddr);
printf(" ");
}
printf("rootfs %s ", p);
gctx->gotrootpath = 1;
gctx->setrootfs = ifctx;
p = bootpc_tag(&gctx->tag, &ifctx->reply,
ifctx->replylen,
TAG_ROOTOPTS);
if (p != NULL) {
mountopts(&nd->root_args, p);
printf("rootopts %s ", p);
}
} else
panic("Failed to set rootfs to %s", p);
}
p = bootpc_tag(&gctx->tag, &ifctx->reply, ifctx->replylen,
TAG_HOSTNAME);
if (p != NULL) {
if (gctx->tag.taglen >= MAXHOSTNAMELEN)
panic("bootpc: hostname >= %d bytes",
MAXHOSTNAMELEN);
if (gctx->sethostname != NULL) {
printf("hostname %s (ignored) ", p);
} else {
strcpy(nd->my_hostnam, p);
mutex_enter(&prison0.pr_mtx);
strcpy(prison0.pr_hostname, p);
mutex_exit(&prison0.pr_mtx);
printf("hostname %s ", p);
gctx->sethostname = ifctx;
}
}
p = bootpc_tag(&gctx->tag, &ifctx->reply, ifctx->replylen,
TAG_COOKIE);
if (p != NULL) { /* store in a sysctl variable */
int i, l = sizeof(bootp_cookie) - 1;
for (i = 0; i < l && p[i] != '\0'; i++)
bootp_cookie[i] = p[i];
p[i] = '\0';
}
p = bootpc_tag(&gctx->tag, &ifctx->reply, ifctx->replylen,
TAG_INTF_MTU);
if (p != NULL) {
ifctx->mtu = be16dec(p);
}
printf("\n");
if (ifctx->gotnetmask == 0) {
if (IN_CLASSA(ntohl(ifctx->myaddr.sin_addr.s_addr)))
ifctx->netmask.sin_addr.s_addr = htonl(IN_CLASSA_NET);
else if (IN_CLASSB(ntohl(ifctx->myaddr.sin_addr.s_addr)))
ifctx->netmask.sin_addr.s_addr = htonl(IN_CLASSB_NET);
else
ifctx->netmask.sin_addr.s_addr = htonl(IN_CLASSC_NET);
}
}
void
bootpc_init(void)
{
struct bootpc_ifcontext *ifctx; /* Interface BOOTP contexts */
struct bootpc_globalcontext *gctx; /* Global BOOTP context */
struct ifnet *ifp;
struct sockaddr_dl *sdl;
struct ifaddr *ifa;
int error;
#ifndef BOOTP_WIRED_TO
int ifcnt;
#endif
struct nfsv3_diskless *nd;
struct lwp *td;
int timeout;
int delay;
/*
* If ROOTDEVNAME is defined or vfs.root.mountfrom is set then we have
* root-path overrides that can potentially let us boot even if we don't
* get a root path from the server, so we can treat that as a non-error.
*/
#ifdef ROOTDEVNAME
gctx->any_root_overrides = 1;
#else
gctx->any_root_overrides = testenv("vfs.root.mountfrom");
#endif
/*
* Find a network interface.
*/
CURVNET_SET(TD_TO_VNET(td));
#ifdef BOOTP_WIRED_TO
printf("%s: wired to interface '%s'\n", __func__,
__XSTRING(BOOTP_WIRED_TO));
allocifctx(gctx);
#else
/*
* Preallocate interface context storage, if another interface
* attaches and wins the race, it won't be eligible for bootp.
*/
ifcnt = 0;
IFNET_RLOCK();
TAILQ_FOREACH(ifp, &V_ifnet, if_list) {
if ((ifp->if_flags &
(IFF_LOOPBACK | IFF_POINTOPOINT | IFF_BROADCAST)) !=
IFF_BROADCAST)
continue;
switch (ifp->if_type) {
case IFT_ETHER:
case IFT_FDDI:
case IFT_ISO88025:
break;
default:
continue;
}
ifcnt++;
}
IFNET_RUNLOCK();
if (ifcnt == 0)
panic("%s: no eligible interfaces", __func__);
for (; ifcnt > 0; ifcnt--)
allocifctx(gctx);
#endif
retry:
ifctx = STAILQ_FIRST(&gctx->interfaces);
IFNET_RLOCK();
TAILQ_FOREACH(ifp, &V_ifnet, if_list) {
if (ifctx == NULL)
break;
#ifdef BOOTP_WIRED_TO
if (strcmp(ifp->if_xname, __XSTRING(BOOTP_WIRED_TO)) != 0)
continue;
#else
if ((ifp->if_flags &
(IFF_LOOPBACK | IFF_POINTOPOINT | IFF_BROADCAST)) !=
IFF_BROADCAST)
continue;
switch (ifp->if_type) {
case IFT_ETHER:
case IFT_FDDI:
case IFT_ISO88025:
break;
default:
continue;
}
#endif
strlcpy(ifctx->ireq.ifr_name, ifp->if_xname,
sizeof(ifctx->ireq.ifr_name));
ifctx->ifp = ifp;
/* Get HW address */
sdl = NULL;
TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list)
if (ifa->ifa_addr->sa_family == AF_LINK) {
sdl = (struct sockaddr_dl *)ifa->ifa_addr;
if (sdl->sdl_type == IFT_ETHER)
break;
}
if (sdl == NULL)
panic("bootpc: Unable to find HW address for %s",
ifctx->ireq.ifr_name);
ifctx->sdl = sdl;
/*
* RPC: mountd/mount
* Given a server pathname, get an NFS file handle.
* Also, sets sin->sin_port to the NFS service port.
*/
static int
md_mount(struct sockaddr_in *mdsin, char *path, u_char *fhp, int *fhsizep,
struct nfs_args *args, struct lwp *td)
{
struct mbuf *m;
int error;
int authunixok;
int authcount;
int authver;
/* XXX honor v2/v3 flags in args->flags? */
#ifdef BOOTP_NFSV3
/* First try NFS v3 */
/* Get port number for MOUNTD. */
error = krpc_portmap(mdsin, RPCPROG_MNT, RPCMNT_VER3,
&mdsin->sin_port, td);
if (error == 0) {
m = xdr_string_encode(path, strlen(path));
/* Do RPC to mountd. */
error = krpc_call(mdsin, RPCPROG_MNT, RPCMNT_VER3,
RPCMNT_MOUNT, &m, NULL, td);
}
if (error == 0) {
args->flags |= NFSMNT_NFSV3;
} else {
#endif
/* Fallback to NFS v2 */
/* Get port number for MOUNTD. */
error = krpc_portmap(mdsin, RPCPROG_MNT, RPCMNT_VER1,
&mdsin->sin_port, td);
if (error != 0)
return error;
m = xdr_string_encode(path, strlen(path));
/* Do RPC to mountd. */
error = krpc_call(mdsin, RPCPROG_MNT, RPCMNT_VER1,
RPCMNT_MOUNT, &m, NULL, td);
if (error != 0)
return error; /* message already freed */