xref: /openbsd-src/sys/nfs/nfs_boot.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /*	$OpenBSD: nfs_boot.c,v 1.46 2020/08/24 10:02:07 mvs Exp $ */
2 /*	$NetBSD: nfs_boot.c,v 1.26 1996/05/07 02:51:25 thorpej Exp $	*/
3 
4 /*
5  * Copyright (c) 1995 Adam Glass, Gordon Ross
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the authors may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/kernel.h>
34 #include <sys/conf.h>
35 #include <sys/ioctl.h>
36 #include <sys/mount.h>
37 #include <sys/mbuf.h>
38 #include <sys/reboot.h>
39 #include <sys/socket.h>
40 #include <sys/socketvar.h>
41 #include <sys/queue.h>
42 
43 #include <net/if.h>
44 #include <net/if_var.h>
45 
46 #include <netinet/in.h>
47 #include <netinet/in_var.h>
48 #include <netinet/if_ether.h>
49 
50 #include <nfs/rpcv2.h>
51 #include <nfs/nfsproto.h>
52 #include <nfs/nfs.h>
53 #include <nfs/nfsdiskless.h>
54 #include <nfs/krpc.h>
55 #include <nfs/xdr_subs.h>
56 #include <nfs/nfs_var.h>
57 
58 #include "ether.h"
59 
60 #if !defined(NFSCLIENT) || (NETHER == 0 && NFDDI == 0)
61 
62 int
63 nfs_boot_init(struct nfs_diskless *nd, struct proc *procp)
64 {
65 	panic("nfs_boot_init: NFSCLIENT not enabled in kernel");
66 }
67 
68 int
69 nfs_boot_getfh(struct sockaddr_in *bpsin, char *key,
70     struct nfs_dlmount *ndmntp, int retries)
71 {
72 	/* can not get here */
73 	return (EOPNOTSUPP);
74 }
75 
76 #else
77 
78 /*
79  * Support for NFS diskless booting, specifically getting information
80  * about where to boot from, what pathnames, etc.
81  *
82  * This implementation uses RARP and the bootparam RPC.
83  * We are forced to implement RPC anyway (to get file handles)
84  * so we might as well take advantage of it for bootparam too.
85  *
86  * The diskless boot sequence goes as follows:
87  * (1) Use RARP to get our interface address
88  * (2) Use RPC/bootparam/whoami to get our hostname,
89  *     our IP address, and the server's IP address.
90  * (3) Use RPC/bootparam/getfile to get the root path
91  * (4) Use RPC/mountd to get the root file handle
92  * (5) Use RPC/bootparam/getfile to get the swap path
93  * (6) Use RPC/mountd to get the swap file handle
94  *
95  * (This happens to be the way Sun does it too.)
96  */
97 
98 /* bootparam RPC */
99 static int bp_whoami(struct sockaddr_in *bpsin,
100 	struct in_addr *my_ip, struct in_addr *gw_ip);
101 static int bp_getfile(struct sockaddr_in *bpsin, char *key,
102 	struct sockaddr_in *mdsin, char *servname, char *path, int retries);
103 
104 /* mountd RPC */
105 static int md_mount(struct sockaddr_in *mdsin, char *path,
106 	struct nfs_args *argp);
107 
108 char	*nfsbootdevname;
109 
110 /*
111  * Called with an empty nfs_diskless struct to be filled in.
112  */
113 int
114 nfs_boot_init(struct nfs_diskless *nd, struct proc *procp)
115 {
116 	struct ifreq ireq;
117 	struct in_aliasreq ifra;
118 	struct in_addr my_ip, gw_ip;
119 	struct sockaddr_in bp_sin;
120 	struct sockaddr_in *sin;
121 	struct ifnet *ifp;
122 	struct socket *so;
123 	struct ifaddr *ifa;
124 	char addr[INET_ADDRSTRLEN];
125 	int error;
126 
127 	/*
128 	 * Find an interface, rarp for its ip address, stuff it, the
129 	 * implied broadcast addr, and netmask into a nfs_diskless struct.
130 	 *
131 	 * This was moved here from nfs_vfsops.c because this procedure
132 	 * would be quite different if someone decides to write (i.e.) a
133 	 * BOOTP version of this file (might not use RARP, etc.)
134 	 */
135 
136 	/*
137 	 * Find a network interface.
138 	 */
139 	if (nfsbootdevname == NULL || (ifp = ifunit(nfsbootdevname)) == NULL)
140 		panic("nfs_boot: no suitable interface");
141 
142 	bcopy(ifp->if_xname, ireq.ifr_name, IFNAMSIZ);
143 	printf("nfs_boot: using interface %s, with revarp & bootparams\n",
144 	    ireq.ifr_name);
145 
146 	/*
147 	 * Bring up the interface.
148 	 *
149 	 * Get the old interface flags and or IFF_UP into them; if
150 	 * IFF_UP set blindly, interface selection can be clobbered.
151 	 */
152 	if ((error = socreate(AF_INET, &so, SOCK_DGRAM, 0)) != 0)
153 		panic("nfs_boot: socreate, error=%d", error);
154 	error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)&ireq, procp);
155 	if (error)
156 		panic("nfs_boot: GIFFLAGS, error=%d", error);
157 	ireq.ifr_flags |= IFF_UP;
158 	error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ireq, procp);
159 	if (error)
160 		panic("nfs_boot: SIFFLAGS, error=%d", error);
161 
162 	/*
163 	 * Do RARP for the interface address.
164 	 */
165 	if ((error = revarpwhoami(&my_ip, ifp)) != 0)
166 		panic("reverse arp not answered by rarpd(8) or dhcpd(8)");
167 	inet_ntop(AF_INET, &my_ip, addr, sizeof(addr));
168 	printf("nfs_boot: client_addr=%s\n", addr);
169 
170 	/*
171 	 * Do enough of ifconfig(8) so that the chosen interface
172 	 * can talk to the servers.  (just set the address)
173 	 */
174 	memset(&ifra, 0, sizeof(ifra));
175 	bcopy(ifp->if_xname, ifra.ifra_name, sizeof(ifra.ifra_name));
176 
177 	sin = &ifra.ifra_addr;
178 	sin->sin_len = sizeof(*sin);
179 	sin->sin_family = AF_INET;
180 	sin->sin_addr.s_addr = my_ip.s_addr;
181 	error = ifioctl(so, SIOCAIFADDR, (caddr_t)&ifra, procp);
182 	if (error)
183 		panic("nfs_boot: set if addr, error=%d", error);
184 
185 	soclose(so, 0);
186 
187 	TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
188 		if (ifa->ifa_addr->sa_family == AF_INET)
189 			break;
190 	}
191 	if (ifa == NULL)
192 		panic("nfs_boot: address not configured on %s", ifp->if_xname);
193 
194 	/*
195 	 * Get client name and gateway address.
196 	 * RPC: bootparam/whoami
197 	 * The server address returned by the WHOAMI call
198 	 * is used for all subsequent bootparam RPCs.
199 	 */
200 	memset(&bp_sin, 0, sizeof(bp_sin));
201 	bp_sin.sin_len = sizeof(bp_sin);
202 	bp_sin.sin_family = AF_INET;
203 	bp_sin.sin_addr.s_addr = ifatoia(ifa)->ia_broadaddr.sin_addr.s_addr;
204 	hostnamelen = MAXHOSTNAMELEN;
205 
206 	/* this returns gateway IP address */
207 	error = bp_whoami(&bp_sin, &my_ip, &gw_ip);
208 	if (error)
209 		panic("nfs_boot: bootparam whoami, error=%d", error);
210 	inet_ntop(AF_INET, &bp_sin.sin_addr, addr, sizeof(addr));
211 	printf("nfs_boot: server_addr=%s hostname=%s\n", addr, hostname);
212 
213 	bcopy(&bp_sin, &nd->nd_boot, sizeof(bp_sin));
214 
215 	return (0);
216 }
217 
218 /*
219  * bpsin:	bootparam server
220  * key:		root or swap
221  * ndmntp:	output
222  */
223 int
224 nfs_boot_getfh(struct sockaddr_in *bpsin, char *key,
225     struct nfs_dlmount *ndmntp, int retries)
226 {
227 	struct nfs_args *args;
228 	char pathname[MAXPATHLEN];
229 	char *sp, *dp, *endp;
230 	struct sockaddr_in *sin;
231 	int error;
232 
233 	args = &ndmntp->ndm_args;
234 
235 	/* Initialize mount args. */
236 	memset(args, 0, sizeof(*args));
237 	args->addr     = sintosa(&ndmntp->ndm_saddr);
238 	args->addrlen  = args->addr->sa_len;
239 	args->sotype   = SOCK_DGRAM;
240 	args->fh       = ndmntp->ndm_fh;
241 	args->hostname = ndmntp->ndm_host;
242 	args->flags    = NFSMNT_NFSV3;
243 #ifdef	NFS_BOOT_OPTIONS
244 	args->flags    |= NFS_BOOT_OPTIONS;
245 #endif
246 #ifdef	NFS_BOOT_RWSIZE
247 	/*
248 	 * Reduce rsize,wsize for interfaces that consistently
249 	 * drop fragments of long UDP messages.	 (i.e. wd8003).
250 	 * You can always change these later via remount.
251 	 */
252 	args->flags   |= NFSMNT_WSIZE | NFSMNT_RSIZE;
253 	args->wsize    = NFS_BOOT_RWSIZE;
254 	args->rsize    = NFS_BOOT_RWSIZE;
255 #endif
256 
257 	sin = &ndmntp->ndm_saddr;
258 
259 	/*
260 	 * Get server:pathname for "key" (root or swap)
261 	 * using RPC to bootparam/getfile
262 	 */
263 	error = bp_getfile(bpsin, key, sin, ndmntp->ndm_host, pathname,
264 	    retries);
265 	if (error) {
266 		printf("nfs_boot: bootparam get %s: %d\n", key, error);
267 		return (error);
268 	}
269 
270 	/*
271 	 * Get file handle for "key" (root or swap)
272 	 * using RPC to mountd/mount
273 	 */
274 	error = md_mount(sin, pathname, args);
275 	if (error) {
276 		printf("nfs_boot: mountd %s, error=%d\n", key, error);
277 		return (error);
278 	}
279 
280 	/* Set port number for NFS use. */
281 	/* XXX: NFS port is always 2049, right? */
282 	error = krpc_portmap(sin, NFS_PROG,
283 	    (args->flags & NFSMNT_NFSV3) ? NFS_VER3 : NFS_VER2,
284 	    &sin->sin_port);
285 	if (error) {
286 		printf("nfs_boot: portmap NFS, error=%d\n", error);
287 		return (error);
288 	}
289 
290 	/* Construct remote path (for getmntinfo(3)) */
291 	dp = ndmntp->ndm_host;
292 	endp = dp + MNAMELEN - 1;
293 	dp += strlen(dp);
294 	*dp++ = ':';
295 	for (sp = pathname; *sp && dp < endp;)
296 		*dp++ = *sp++;
297 	*dp = '\0';
298 
299 	return (0);
300 }
301 
302 
303 /*
304  * RPC: bootparam/whoami
305  * Given client IP address, get:
306  *	client name	(hostname)
307  *	domain name (domainname)
308  *	gateway address
309  *
310  * The hostname and domainname are set here for convenience.
311  *
312  * Note - bpsin is initialized to the broadcast address,
313  * and will be replaced with the bootparam server address
314  * after this call is complete.  Have to use PMAP_PROC_CALL
315  * to make sure we get responses only from a servers that
316  * know about us (don't want to broadcast a getport call).
317  */
318 static int
319 bp_whoami(struct sockaddr_in *bpsin, struct in_addr *my_ip,
320     struct in_addr *gw_ip)
321 {
322 	/* RPC structures for PMAPPROC_CALLIT */
323 	struct whoami_call {
324 		u_int32_t call_prog;
325 		u_int32_t call_vers;
326 		u_int32_t call_proc;
327 		u_int32_t call_arglen;
328 	} *call;
329 	struct callit_reply {
330 		u_int32_t port;
331 		u_int32_t encap_len;
332 		/* encapsulated data here */
333 	} *reply;
334 
335 	struct mbuf *m, *from;
336 	struct sockaddr_in *sin;
337 	int error, msg_len;
338 	int16_t port;
339 
340 	/*
341 	 * Build request message for PMAPPROC_CALLIT.
342 	 */
343 	m = m_get(M_WAIT, MT_DATA);
344 	call = mtod(m, struct whoami_call *);
345 	m->m_len = sizeof(*call);
346 	call->call_prog = txdr_unsigned(BOOTPARAM_PROG);
347 	call->call_vers = txdr_unsigned(BOOTPARAM_VERS);
348 	call->call_proc = txdr_unsigned(BOOTPARAM_WHOAMI);
349 
350 	/*
351 	 * append encapsulated data (client IP address)
352 	 */
353 	m->m_next = xdr_inaddr_encode(my_ip);
354 	call->call_arglen = txdr_unsigned(m->m_next->m_len);
355 
356 	/* RPC: portmap/callit */
357 	bpsin->sin_port = htons(PMAPPORT);
358 	from = NULL;
359 	error = krpc_call(bpsin, PMAPPROG, PMAPVERS,
360 			PMAPPROC_CALLIT, &m, &from, -1);
361 	if (error)
362 		return error;
363 
364 	/*
365 	 * Parse result message.
366 	 */
367 	if (m->m_len < sizeof(*reply)) {
368 		m = m_pullup(m, sizeof(*reply));
369 		if (m == NULL)
370 			goto bad;
371 	}
372 	reply = mtod(m, struct callit_reply *);
373 	port = fxdr_unsigned(u_int32_t, reply->port);
374 	msg_len = fxdr_unsigned(u_int32_t, reply->encap_len);
375 	m_adj(m, sizeof(*reply));
376 
377 	/*
378 	 * Save bootparam server address
379 	 */
380 	sin = mtod(from, struct sockaddr_in *);
381 	bpsin->sin_port = htons(port);
382 	bpsin->sin_addr.s_addr = sin->sin_addr.s_addr;
383 
384 	/* client name */
385 	hostnamelen = MAXHOSTNAMELEN-1;
386 	m = xdr_string_decode(m, hostname, &hostnamelen);
387 	if (m == NULL)
388 		goto bad;
389 
390 	/* domain name */
391 	domainnamelen = MAXHOSTNAMELEN-1;
392 	m = xdr_string_decode(m, domainname, &domainnamelen);
393 	if (m == NULL)
394 		goto bad;
395 
396 	/* gateway address */
397 	m = xdr_inaddr_decode(m, gw_ip);
398 	if (m == NULL)
399 		goto bad;
400 
401 	/* success */
402 	goto out;
403 
404 bad:
405 	printf("nfs_boot: bootparam_whoami: bad reply\n");
406 	error = EBADRPC;
407 
408 out:
409 	m_freem(from);
410 	m_freem(m);
411 	return(error);
412 }
413 
414 
415 /*
416  * RPC: bootparam/getfile
417  * Given client name and file "key", get:
418  *	server name
419  *	server IP address
420  *	server pathname
421  */
422 static int
423 bp_getfile(struct sockaddr_in *bpsin, char *key, struct sockaddr_in *md_sin,
424     char *serv_name, char *pathname, int retries)
425 {
426 	struct mbuf *m;
427 	struct sockaddr_in *sin;
428 	struct in_addr inaddr;
429 	int error, sn_len, path_len;
430 
431 	/*
432 	 * Build request message.
433 	 */
434 
435 	/* client name (hostname) */
436 	m  = xdr_string_encode(hostname, hostnamelen);
437 	if (m == NULL)
438 		return (ENOMEM);
439 
440 	/* key name (root or swap) */
441 	m->m_next = xdr_string_encode(key, strlen(key));
442 	if (m->m_next == NULL) {
443 		m_freem(m);
444 		return (ENOMEM);
445 	}
446 
447 	/* RPC: bootparam/getfile */
448 	error = krpc_call(bpsin, BOOTPARAM_PROG, BOOTPARAM_VERS,
449 			BOOTPARAM_GETFILE, &m, NULL, retries);
450 	if (error)
451 		return error;
452 
453 	/*
454 	 * Parse result message.
455 	 */
456 
457 	/* server name */
458 	sn_len = MNAMELEN-1;
459 	m = xdr_string_decode(m, serv_name, &sn_len);
460 	if (m == NULL)
461 		goto bad;
462 
463 	/* server IP address (mountd/NFS) */
464 	m = xdr_inaddr_decode(m, &inaddr);
465 	if (m == NULL)
466 		goto bad;
467 
468 	/* server pathname */
469 	path_len = MAXPATHLEN-1;
470 	m = xdr_string_decode(m, pathname, &path_len);
471 	if (m == NULL)
472 		goto bad;
473 
474 	/* setup server socket address */
475 	sin = md_sin;
476 	memset(sin, 0, sizeof(*sin));
477 	sin->sin_len = sizeof(*sin);
478 	sin->sin_family = AF_INET;
479 	sin->sin_addr = inaddr;
480 
481 	/* success */
482 	goto out;
483 
484 bad:
485 	printf("nfs_boot: bootparam_getfile: bad reply\n");
486 	error = EBADRPC;
487 
488 out:
489 	m_freem(m);
490 	return(error);
491 }
492 
493 
494 /*
495  * RPC: mountd/mount
496  * Given a server pathname, get an NFS file handle.
497  * Also, sets sin->sin_port to the NFS service port.
498  * mdsin:	mountd server address
499  */
500 static int
501 md_mount(struct sockaddr_in *mdsin, char *path, struct nfs_args *argp)
502 {
503 	/* The RPC structures */
504 	struct rdata {
505 		u_int32_t errno;
506 		union {
507 			u_int8_t v2fh[NFSX_V2FH];
508 			struct {
509 				u_int32_t fhlen;
510 				u_int8_t fh[1];
511 			} v3fh;
512 		} fh;
513 	} *rdata;
514 	struct mbuf *m;
515 	u_int8_t *fh;
516 	int minlen, error;
517 	int mntver;
518 
519 	mntver = (argp->flags & NFSMNT_NFSV3) ? 3 : 2;
520 	do {
521 		error = krpc_portmap(mdsin, RPCPROG_MNT, mntver,
522 		    &mdsin->sin_port);
523 		if (error)
524 			continue;
525 
526 		m = xdr_string_encode(path, strlen(path));
527 		if (m == NULL)
528 			return ENOMEM;
529 
530 		/* Do RPC to mountd. */
531 		error = krpc_call(mdsin, RPCPROG_MNT, mntver,
532 		    RPCMNT_MOUNT, &m, NULL, -1);
533 
534 		if (error != EPROGMISMATCH)
535 			break;
536 		/* Try lower version of mountd. */
537 	} while (--mntver >= 1);
538 	if (error)
539 		return error;	/* message already freed */
540 
541 	if (mntver != 3)
542 		argp->flags &= ~NFSMNT_NFSV3;
543 
544 	/* The reply might have only the errno. */
545 	if (m->m_len < 4)
546 		goto bad;
547 	/* Have at least errno, so check that. */
548 	rdata = mtod(m, struct rdata *);
549 	error = fxdr_unsigned(u_int32_t, rdata->errno);
550 	if (error)
551 		goto out;
552 
553 	 /* Have errno==0, so the fh must be there. */
554 	if (mntver == 3) {
555 		argp->fhsize = fxdr_unsigned(u_int32_t, rdata->fh.v3fh.fhlen);
556 		if (argp->fhsize > NFSX_V3FHMAX)
557 			goto bad;
558 		minlen = 2 * sizeof(u_int32_t) + argp->fhsize;
559 	} else {
560 		argp->fhsize = NFSX_V2FH;
561 		minlen = sizeof(u_int32_t) + argp->fhsize;
562 	}
563 
564 	if (m->m_len < minlen) {
565 		m = m_pullup(m, minlen);
566 		if (m == NULL)
567 			return (EBADRPC);
568 		rdata = mtod(m, struct rdata *);
569 	}
570 
571 	fh = (mntver == 3) ? rdata->fh.v3fh.fh : rdata->fh.v2fh;
572 	bcopy(fh, argp->fh, argp->fhsize);
573 
574 	goto out;
575 
576 bad:
577 	error = EBADRPC;
578 
579 out:
580 	m_freem(m);
581 	return error;
582 }
583 
584 #endif /* ifdef NFSCLIENT */
585