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