xref: /openbsd-src/sys/nfs/nfs_boot.c (revision 3a3fbb3f2e2521ab7c4a56b7ff7462ebd9095ec5)
1 /*	$OpenBSD: nfs_boot.c,v 1.12 2001/11/14 23:37:33 mickey 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/proc.h>
37 #include <sys/mount.h>
38 #include <sys/mbuf.h>
39 #include <sys/reboot.h>
40 #include <sys/socket.h>
41 #include <sys/socketvar.h>
42 
43 #include <net/if.h>
44 #include <net/route.h>
45 
46 #include <netinet/in.h>
47 #include <netinet/if_ether.h>
48 
49 #include <nfs/rpcv2.h>
50 #include <nfs/nfsproto.h>
51 #include <nfs/nfs.h>
52 #include <nfs/nfsdiskless.h>
53 #include <nfs/krpc.h>
54 #include <nfs/xdr_subs.h>
55 #include <nfs/nfs_var.h>
56 
57 #include "ether.h"
58 
59 #if !defined(NFSCLIENT) || (NETHER == 0 && NFDDI == 0)
60 
61 int
62 nfs_boot_init(nd, procp)
63 	struct nfs_diskless *nd;
64 	struct proc *procp;
65 {
66 	panic("nfs_boot_init: NFSCLIENT not enabled in kernel");
67 }
68 
69 int
70 nfs_boot_getfh(bpsin, key, ndmntp)
71 	struct sockaddr_in *bpsin;
72 	char *key;
73 	struct nfs_dlmount *ndmntp;
74 {
75 	/* can not get here */
76 	return (EOPNOTSUPP);
77 }
78 
79 #else
80 
81 /*
82  * Support for NFS diskless booting, specifically getting information
83  * about where to boot from, what pathnames, etc.
84  *
85  * This implememtation uses RARP and the bootparam RPC.
86  * We are forced to implement RPC anyway (to get file handles)
87  * so we might as well take advantage of it for bootparam too.
88  *
89  * The diskless boot sequence goes as follows:
90  * (1) Use RARP to get our interface address
91  * (2) Use RPC/bootparam/whoami to get our hostname,
92  *     our IP address, and the server's IP address.
93  * (3) Use RPC/bootparam/getfile to get the root path
94  * (4) Use RPC/mountd to get the root file handle
95  * (5) Use RPC/bootparam/getfile to get the swap path
96  * (6) Use RPC/mountd to get the swap file handle
97  *
98  * (This happens to be the way Sun does it too.)
99  */
100 
101 /* bootparam RPC */
102 static int bp_whoami __P((struct sockaddr_in *bpsin,
103 	struct in_addr *my_ip, struct in_addr *gw_ip));
104 static int bp_getfile __P((struct sockaddr_in *bpsin, char *key,
105 	struct sockaddr_in *mdsin, char *servname, char *path, int retries));
106 
107 /* mountd RPC */
108 static int md_mount __P((struct sockaddr_in *mdsin, char *path,
109 	u_char *fh));
110 
111 char	*nfsbootdevname;
112 
113 /*
114  * Called with an empty nfs_diskless struct to be filled in.
115  */
116 int
117 nfs_boot_init(nd, procp)
118 	struct nfs_diskless *nd;
119 	struct proc *procp;
120 {
121 	struct ifreq ireq;
122 	struct in_addr my_ip, gw_ip;
123 	struct sockaddr_in bp_sin;
124 	struct sockaddr_in *sin;
125 	struct ifnet *ifp;
126 	struct socket *so;
127 	int error;
128 
129 	/*
130 	 * Find an interface, rarp for its ip address, stuff it, the
131 	 * implied broadcast addr, and netmask into a nfs_diskless struct.
132 	 *
133 	 * This was moved here from nfs_vfsops.c because this procedure
134 	 * would be quite different if someone decides to write (i.e.) a
135 	 * BOOTP version of this file (might not use RARP, etc.)
136 	 */
137 
138 	/*
139 	 * Find a network interface.
140 	 */
141 	if (nfsbootdevname)
142 		ifp = ifunit(nfsbootdevname);
143 	else
144 		for (ifp = ifnet.tqh_first; ifp != 0; ifp = ifp->if_list.tqe_next)
145 			if ((ifp->if_flags &
146 			     (IFF_LOOPBACK|IFF_POINTOPOINT)) == 0)
147 				break;
148 	if (ifp == NULL)
149 		panic("nfs_boot: no suitable interface");
150 	bcopy(ifp->if_xname, ireq.ifr_name, IFNAMSIZ);
151 	printf("nfs_boot: using network interface '%s'\n",
152 	    ireq.ifr_name);
153 
154 	/*
155 	 * Bring up the interface.
156 	 *
157 	 * Get the old interface flags and or IFF_UP into them; if
158 	 * IFF_UP set blindly, interface selection can be clobbered.
159 	 */
160 	if ((error = socreate(AF_INET, &so, SOCK_DGRAM, 0)) != 0)
161 		panic("nfs_boot: socreate, error=%d", error);
162 	error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)&ireq, procp);
163 	if (error)
164 		panic("nfs_boot: GIFFLAGS, error=%d", error);
165 	ireq.ifr_flags |= IFF_UP;
166 	error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ireq, procp);
167 	if (error)
168 		panic("nfs_boot: SIFFLAGS, error=%d", error);
169 
170 	/*
171 	 * Do RARP for the interface address.
172 	 */
173 	if ((error = revarpwhoami(&my_ip, ifp)) != 0)
174 		panic("revarp failed, error=%d", error);
175 	printf("nfs_boot: client_addr=%s\n", inet_ntoa(my_ip));
176 
177 	/*
178 	 * Do enough of ifconfig(8) so that the chosen interface
179 	 * can talk to the servers.  (just set the address)
180 	 */
181 	sin = (struct sockaddr_in *)&ireq.ifr_addr;
182 	bzero((caddr_t)sin, sizeof(*sin));
183 	sin->sin_len = sizeof(*sin);
184 	sin->sin_family = AF_INET;
185 	sin->sin_addr.s_addr = my_ip.s_addr;
186 	error = ifioctl(so, SIOCSIFADDR, (caddr_t)&ireq, procp);
187 	if (error)
188 		panic("nfs_boot: set if addr, error=%d", error);
189 
190 	soclose(so);
191 
192 	/*
193 	 * Get client name and gateway address.
194 	 * RPC: bootparam/whoami
195 	 * Use the old broadcast address for the WHOAMI
196 	 * call because we do not yet know our netmask.
197 	 * The server address returned by the WHOAMI call
198 	 * is used for all subsequent booptaram RPCs.
199 	 */
200 	bzero((caddr_t)&bp_sin, 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 = INADDR_BROADCAST;
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 	printf("nfs_boot: server_addr=%s hostname=%s\n",
211 	    inet_ntoa(bp_sin.sin_addr), hostname);
212 
213 #ifdef	NFS_BOOT_GATEWAY
214 	/*
215 	 * XXX - This code is conditionally compiled only because
216 	 * many bootparam servers (in particular, SunOS 4.1.3)
217 	 * always set the gateway address to their own address.
218 	 * The bootparam server is not necessarily the gateway.
219 	 * We could just believe the server, and at worst you would
220 	 * need to delete the incorrect default route before adding
221 	 * the correct one, but for simplicity, ignore the gateway.
222 	 * If your server is OK, you can turn on this option.
223 	 *
224 	 * If the gateway address is set, add a default route.
225 	 * (The mountd RPCs may go across a gateway.)
226 	 */
227 	if (gw_ip.s_addr) {
228 		struct sockaddr dst, gw, mask;
229 		/* Destination: (default) */
230 		bzero((caddr_t)&dst, sizeof(dst));
231 		dst.sa_len = sizeof(dst);
232 		dst.sa_family = AF_INET;
233 		/* Gateway: */
234 		bzero((caddr_t)&gw, sizeof(gw));
235 		sin = (struct sockaddr_in *)&gw;
236 		sin->sin_len = sizeof(gw);
237 		sin->sin_family = AF_INET;
238 		sin->sin_addr.s_addr = gw_ip.s_addr;
239 		/* Mask: (zero length) */
240 		bzero(&mask, sizeof(mask));
241 
242 		printf("nfs_boot: gateway=%s\n", inet_ntoa(gw_ip));
243 		/* add, dest, gw, mask, flags, 0 */
244 		error = rtrequest(RTM_ADD, &dst, (struct sockaddr *)&gw,
245 		    &mask, (RTF_UP | RTF_GATEWAY | RTF_STATIC), NULL);
246 		if (error)
247 			printf("nfs_boot: add route, error=%d\n", error);
248 	}
249 #endif
250 
251 	bcopy(&bp_sin, &nd->nd_boot, sizeof(bp_sin));
252 
253 	return (0);
254 }
255 
256 int
257 nfs_boot_getfh(bpsin, key, ndmntp, retries)
258 	struct sockaddr_in *bpsin;	/* bootparam server */
259 	char *key;			/* root or swap */
260 	struct nfs_dlmount *ndmntp;	/* output */
261 	int retries;
262 {
263 	char pathname[MAXPATHLEN];
264 	char *sp, *dp, *endp;
265 	struct sockaddr_in *sin;
266 	int error;
267 
268 	sin = &ndmntp->ndm_saddr;
269 
270 	/*
271 	 * Get server:pathname for "key" (root or swap)
272 	 * using RPC to bootparam/getfile
273 	 */
274 	error = bp_getfile(bpsin, key, sin, ndmntp->ndm_host, pathname,
275 	    retries);
276 	if (error) {
277 		printf("nfs_boot: bootparam get %s: %d\n", key, error);
278 		return (error);
279 	}
280 
281 	/*
282 	 * Get file handle for "key" (root or swap)
283 	 * using RPC to mountd/mount
284 	 */
285 	error = md_mount(sin, pathname, ndmntp->ndm_fh);
286 	if (error) {
287 		printf("nfs_boot: mountd %s, error=%d\n", key, error);
288 		return (error);
289 	}
290 
291 	/* Set port number for NFS use. */
292 	/* XXX: NFS port is always 2049, right? */
293 	error = krpc_portmap(sin, NFS_PROG, NFS_VER2, &sin->sin_port);
294 	if (error) {
295 		printf("nfs_boot: portmap NFS/v2, error=%d\n", error);
296 		return (error);
297 	}
298 
299 	/* Construct remote path (for getmntinfo(3)) */
300 	dp = ndmntp->ndm_host;
301 	endp = dp + MNAMELEN - 1;
302 	dp += strlen(dp);
303 	*dp++ = ':';
304 	for (sp = pathname; *sp && dp < endp;)
305 		*dp++ = *sp++;
306 	*dp = '\0';
307 
308 	return (0);
309 }
310 
311 
312 /*
313  * RPC: bootparam/whoami
314  * Given client IP address, get:
315  *	client name	(hostname)
316  *	domain name (domainname)
317  *	gateway address
318  *
319  * The hostname and domainname are set here for convenience.
320  *
321  * Note - bpsin is initialized to the broadcast address,
322  * and will be replaced with the bootparam server address
323  * after this call is complete.  Have to use PMAP_PROC_CALL
324  * to make sure we get responses only from a servers that
325  * know about us (don't want to broadcast a getport call).
326  */
327 static int
328 bp_whoami(bpsin, my_ip, gw_ip)
329 	struct sockaddr_in *bpsin;
330 	struct in_addr *my_ip;
331 	struct in_addr *gw_ip;
332 {
333 	/* RPC structures for PMAPPROC_CALLIT */
334 	struct whoami_call {
335 		u_int32_t call_prog;
336 		u_int32_t call_vers;
337 		u_int32_t call_proc;
338 		u_int32_t call_arglen;
339 	} *call;
340 	struct callit_reply {
341 		u_int32_t port;
342 		u_int32_t encap_len;
343 		/* encapsulated data here */
344 	} *reply;
345 
346 	struct mbuf *m, *from;
347 	struct sockaddr_in *sin;
348 	int error, msg_len;
349 	int16_t port;
350 
351 	/*
352 	 * Build request message for PMAPPROC_CALLIT.
353 	 */
354 	m = m_get(M_WAIT, MT_DATA);
355 	call = mtod(m, struct whoami_call *);
356 	m->m_len = sizeof(*call);
357 	call->call_prog = txdr_unsigned(BOOTPARAM_PROG);
358 	call->call_vers = txdr_unsigned(BOOTPARAM_VERS);
359 	call->call_proc = txdr_unsigned(BOOTPARAM_WHOAMI);
360 
361 	/*
362 	 * append encapsulated data (client IP address)
363 	 */
364 	m->m_next = xdr_inaddr_encode(my_ip);
365 	call->call_arglen = txdr_unsigned(m->m_next->m_len);
366 
367 	/* RPC: portmap/callit */
368 	bpsin->sin_port = htons(PMAPPORT);
369 	from = NULL;
370 	error = krpc_call(bpsin, PMAPPROG, PMAPVERS,
371 			PMAPPROC_CALLIT, &m, &from, -1);
372 	if (error)
373 		return error;
374 
375 	/*
376 	 * Parse result message.
377 	 */
378 	if (m->m_len < sizeof(*reply)) {
379 		m = m_pullup(m, sizeof(*reply));
380 		if (m == NULL)
381 			goto bad;
382 	}
383 	reply = mtod(m, struct callit_reply *);
384 	port = fxdr_unsigned(u_int32_t, reply->port);
385 	msg_len = fxdr_unsigned(u_int32_t, reply->encap_len);
386 	m_adj(m, sizeof(*reply));
387 
388 	/*
389 	 * Save bootparam server address
390 	 */
391 	sin = mtod(from, struct sockaddr_in *);
392 	bpsin->sin_port = htons(port);
393 	bpsin->sin_addr.s_addr = sin->sin_addr.s_addr;
394 
395 	/* client name */
396 	hostnamelen = MAXHOSTNAMELEN-1;
397 	m = xdr_string_decode(m, hostname, &hostnamelen);
398 	if (m == NULL)
399 		goto bad;
400 
401 	/* domain name */
402 	domainnamelen = MAXHOSTNAMELEN-1;
403 	m = xdr_string_decode(m, domainname, &domainnamelen);
404 	if (m == NULL)
405 		goto bad;
406 
407 	/* gateway address */
408 	m = xdr_inaddr_decode(m, gw_ip);
409 	if (m == NULL)
410 		goto bad;
411 
412 	/* success */
413 	goto out;
414 
415 bad:
416 	printf("nfs_boot: bootparam_whoami: bad reply\n");
417 	error = EBADRPC;
418 
419 out:
420 	if (from)
421 		m_freem(from);
422 	if (m)
423 		m_freem(m);
424 	return(error);
425 }
426 
427 
428 /*
429  * RPC: bootparam/getfile
430  * Given client name and file "key", get:
431  *	server name
432  *	server IP address
433  *	server pathname
434  */
435 static int
436 bp_getfile(bpsin, key, md_sin, serv_name, pathname, retries)
437 	struct sockaddr_in *bpsin;
438 	char *key;
439 	struct sockaddr_in *md_sin;
440 	char *serv_name;
441 	char *pathname;
442 	int retries;
443 {
444 	struct mbuf *m;
445 	struct sockaddr_in *sin;
446 	struct in_addr inaddr;
447 	int error, sn_len, path_len;
448 
449 	/*
450 	 * Build request message.
451 	 */
452 
453 	/* client name (hostname) */
454 	m  = xdr_string_encode(hostname, hostnamelen);
455 	if (m == NULL)
456 		return (ENOMEM);
457 
458 	/* key name (root or swap) */
459 	m->m_next = xdr_string_encode(key, strlen(key));
460 	if (m->m_next == NULL)
461 		return (ENOMEM);
462 
463 	/* RPC: bootparam/getfile */
464 	error = krpc_call(bpsin, BOOTPARAM_PROG, BOOTPARAM_VERS,
465 			BOOTPARAM_GETFILE, &m, NULL, retries);
466 	if (error)
467 		return error;
468 
469 	/*
470 	 * Parse result message.
471 	 */
472 
473 	/* server name */
474 	sn_len = MNAMELEN-1;
475 	m = xdr_string_decode(m, serv_name, &sn_len);
476 	if (m == NULL)
477 		goto bad;
478 
479 	/* server IP address (mountd/NFS) */
480 	m = xdr_inaddr_decode(m, &inaddr);
481 	if (m == NULL)
482 		goto bad;
483 
484 	/* server pathname */
485 	path_len = MAXPATHLEN-1;
486 	m = xdr_string_decode(m, pathname, &path_len);
487 	if (m == NULL)
488 		goto bad;
489 
490 	/* setup server socket address */
491 	sin = md_sin;
492 	bzero((caddr_t)sin, sizeof(*sin));
493 	sin->sin_len = sizeof(*sin);
494 	sin->sin_family = AF_INET;
495 	sin->sin_addr = inaddr;
496 
497 	/* success */
498 	goto out;
499 
500 bad:
501 	printf("nfs_boot: bootparam_getfile: bad reply\n");
502 	error = EBADRPC;
503 
504 out:
505 	m_freem(m);
506 	return(0);
507 }
508 
509 
510 /*
511  * RPC: mountd/mount
512  * Given a server pathname, get an NFS file handle.
513  * Also, sets sin->sin_port to the NFS service port.
514  */
515 static int
516 md_mount(mdsin, path, fhp)
517 	struct sockaddr_in *mdsin;		/* mountd server address */
518 	char *path;
519 	u_char *fhp;
520 {
521 	/* The RPC structures */
522 	struct rdata {
523 		u_int32_t errno;
524 		u_int8_t  fh[NFSX_V2FH];
525 	} *rdata;
526 	struct mbuf *m;
527 	int error;
528 
529 	/* Get port number for MOUNTD. */
530 	error = krpc_portmap(mdsin, RPCPROG_MNT, RPCMNT_VER1,
531 						 &mdsin->sin_port);
532 	if (error) return error;
533 
534 	m = xdr_string_encode(path, strlen(path));
535 	if (m == NULL)
536 		return ENOMEM;
537 
538 	/* Do RPC to mountd. */
539 	error = krpc_call(mdsin, RPCPROG_MNT, RPCMNT_VER1,
540 			RPCMNT_MOUNT, &m, NULL, -1);
541 	if (error)
542 		return error;	/* message already freed */
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 (m->m_len < sizeof(*rdata)) {
555 		m = m_pullup(m, sizeof(*rdata));
556 		if (m == NULL)
557 			goto bad;
558 		rdata = mtod(m, struct rdata *);
559 	}
560 	bcopy(rdata->fh, fhp, NFSX_V2FH);
561 	goto out;
562 
563 bad:
564 	error = EBADRPC;
565 
566 out:
567 	m_freem(m);
568 	return error;
569 }
570 
571 #endif /* ifdef NFSCLIENT */
572