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