nfs_boot.c revision 1.13
1/*	$OpenBSD: nfs_boot.c,v 1.13 2002/01/16 21:51:16 ericj 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
61int
62nfs_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
69int
70nfs_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 */
102static int bp_whoami __P((struct sockaddr_in *bpsin,
103	struct in_addr *my_ip, struct in_addr *gw_ip));
104static 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 */
108static int md_mount __P((struct sockaddr_in *mdsin, char *path,
109	u_char *fh));
110
111char	*nfsbootdevname;
112
113/*
114 * Called with an empty nfs_diskless struct to be filled in.
115 */
116int
117nfs_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 = TAILQ_FIRST(&ifnet); ifp != NULL;
145		    ifp = TAILQ_NEXT(ifp, if_list)) {
146			if ((ifp->if_flags &
147			     (IFF_LOOPBACK|IFF_POINTOPOINT)) == 0)
148				break;
149		}
150	}
151	if (ifp == NULL)
152		panic("nfs_boot: no suitable interface");
153	bcopy(ifp->if_xname, ireq.ifr_name, IFNAMSIZ);
154	printf("nfs_boot: using network interface '%s'\n", ireq.ifr_name);
155
156	/*
157	 * Bring up the interface.
158	 *
159	 * Get the old interface flags and or IFF_UP into them; if
160	 * IFF_UP set blindly, interface selection can be clobbered.
161	 */
162	if ((error = socreate(AF_INET, &so, SOCK_DGRAM, 0)) != 0)
163		panic("nfs_boot: socreate, error=%d", error);
164	error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)&ireq, procp);
165	if (error)
166		panic("nfs_boot: GIFFLAGS, error=%d", error);
167	ireq.ifr_flags |= IFF_UP;
168	error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ireq, procp);
169	if (error)
170		panic("nfs_boot: SIFFLAGS, error=%d", error);
171
172	/*
173	 * Do RARP for the interface address.
174	 */
175	if ((error = revarpwhoami(&my_ip, ifp)) != 0)
176		panic("revarp failed, error=%d", error);
177	printf("nfs_boot: client_addr=%s\n", inet_ntoa(my_ip));
178
179	/*
180	 * Do enough of ifconfig(8) so that the chosen interface
181	 * can talk to the servers.  (just set the address)
182	 */
183	sin = (struct sockaddr_in *)&ireq.ifr_addr;
184	bzero((caddr_t)sin, sizeof(*sin));
185	sin->sin_len = sizeof(*sin);
186	sin->sin_family = AF_INET;
187	sin->sin_addr.s_addr = my_ip.s_addr;
188	error = ifioctl(so, SIOCSIFADDR, (caddr_t)&ireq, procp);
189	if (error)
190		panic("nfs_boot: set if addr, error=%d", error);
191
192	soclose(so);
193
194	/*
195	 * Get client name and gateway address.
196	 * RPC: bootparam/whoami
197	 * Use the old broadcast address for the WHOAMI
198	 * call because we do not yet know our netmask.
199	 * The server address returned by the WHOAMI call
200	 * is used for all subsequent booptaram RPCs.
201	 */
202	bzero((caddr_t)&bp_sin, sizeof(bp_sin));
203	bp_sin.sin_len = sizeof(bp_sin);
204	bp_sin.sin_family = AF_INET;
205	bp_sin.sin_addr.s_addr = INADDR_BROADCAST;
206	hostnamelen = MAXHOSTNAMELEN;
207
208	/* this returns gateway IP address */
209	error = bp_whoami(&bp_sin, &my_ip, &gw_ip);
210	if (error)
211		panic("nfs_boot: bootparam whoami, error=%d", error);
212	printf("nfs_boot: server_addr=%s hostname=%s\n",
213	    inet_ntoa(bp_sin.sin_addr), hostname);
214
215#ifdef	NFS_BOOT_GATEWAY
216	/*
217	 * XXX - This code is conditionally compiled only because
218	 * many bootparam servers (in particular, SunOS 4.1.3)
219	 * always set the gateway address to their own address.
220	 * The bootparam server is not necessarily the gateway.
221	 * We could just believe the server, and at worst you would
222	 * need to delete the incorrect default route before adding
223	 * the correct one, but for simplicity, ignore the gateway.
224	 * If your server is OK, you can turn on this option.
225	 *
226	 * If the gateway address is set, add a default route.
227	 * (The mountd RPCs may go across a gateway.)
228	 */
229	if (gw_ip.s_addr) {
230		struct sockaddr dst, gw, mask;
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
244		printf("nfs_boot: gateway=%s\n", inet_ntoa(gw_ip));
245		/* add, dest, gw, mask, flags, 0 */
246		error = rtrequest(RTM_ADD, &dst, (struct sockaddr *)&gw,
247		    &mask, (RTF_UP | RTF_GATEWAY | RTF_STATIC), NULL);
248		if (error)
249			printf("nfs_boot: add route, error=%d\n", error);
250	}
251#endif
252
253	bcopy(&bp_sin, &nd->nd_boot, sizeof(bp_sin));
254
255	return (0);
256}
257
258int
259nfs_boot_getfh(bpsin, key, ndmntp, retries)
260	struct sockaddr_in *bpsin;	/* bootparam server */
261	char *key;			/* root or swap */
262	struct nfs_dlmount *ndmntp;	/* output */
263	int retries;
264{
265	char pathname[MAXPATHLEN];
266	char *sp, *dp, *endp;
267	struct sockaddr_in *sin;
268	int error;
269
270	sin = &ndmntp->ndm_saddr;
271
272	/*
273	 * Get server:pathname for "key" (root or swap)
274	 * using RPC to bootparam/getfile
275	 */
276	error = bp_getfile(bpsin, key, sin, ndmntp->ndm_host, pathname,
277	    retries);
278	if (error) {
279		printf("nfs_boot: bootparam get %s: %d\n", key, error);
280		return (error);
281	}
282
283	/*
284	 * Get file handle for "key" (root or swap)
285	 * using RPC to mountd/mount
286	 */
287	error = md_mount(sin, pathname, ndmntp->ndm_fh);
288	if (error) {
289		printf("nfs_boot: mountd %s, error=%d\n", key, error);
290		return (error);
291	}
292
293	/* Set port number for NFS use. */
294	/* XXX: NFS port is always 2049, right? */
295	error = krpc_portmap(sin, NFS_PROG, NFS_VER2, &sin->sin_port);
296	if (error) {
297		printf("nfs_boot: portmap NFS/v2, error=%d\n", error);
298		return (error);
299	}
300
301	/* Construct remote path (for getmntinfo(3)) */
302	dp = ndmntp->ndm_host;
303	endp = dp + MNAMELEN - 1;
304	dp += strlen(dp);
305	*dp++ = ':';
306	for (sp = pathname; *sp && dp < endp;)
307		*dp++ = *sp++;
308	*dp = '\0';
309
310	return (0);
311}
312
313
314/*
315 * RPC: bootparam/whoami
316 * Given client IP address, get:
317 *	client name	(hostname)
318 *	domain name (domainname)
319 *	gateway address
320 *
321 * The hostname and domainname are set here for convenience.
322 *
323 * Note - bpsin is initialized to the broadcast address,
324 * and will be replaced with the bootparam server address
325 * after this call is complete.  Have to use PMAP_PROC_CALL
326 * to make sure we get responses only from a servers that
327 * know about us (don't want to broadcast a getport call).
328 */
329static int
330bp_whoami(bpsin, my_ip, gw_ip)
331	struct sockaddr_in *bpsin;
332	struct in_addr *my_ip;
333	struct in_addr *gw_ip;
334{
335	/* RPC structures for PMAPPROC_CALLIT */
336	struct whoami_call {
337		u_int32_t call_prog;
338		u_int32_t call_vers;
339		u_int32_t call_proc;
340		u_int32_t call_arglen;
341	} *call;
342	struct callit_reply {
343		u_int32_t port;
344		u_int32_t encap_len;
345		/* encapsulated data here */
346	} *reply;
347
348	struct mbuf *m, *from;
349	struct sockaddr_in *sin;
350	int error, msg_len;
351	int16_t port;
352
353	/*
354	 * Build request message for PMAPPROC_CALLIT.
355	 */
356	m = m_get(M_WAIT, MT_DATA);
357	call = mtod(m, struct whoami_call *);
358	m->m_len = sizeof(*call);
359	call->call_prog = txdr_unsigned(BOOTPARAM_PROG);
360	call->call_vers = txdr_unsigned(BOOTPARAM_VERS);
361	call->call_proc = txdr_unsigned(BOOTPARAM_WHOAMI);
362
363	/*
364	 * append encapsulated data (client IP address)
365	 */
366	m->m_next = xdr_inaddr_encode(my_ip);
367	call->call_arglen = txdr_unsigned(m->m_next->m_len);
368
369	/* RPC: portmap/callit */
370	bpsin->sin_port = htons(PMAPPORT);
371	from = NULL;
372	error = krpc_call(bpsin, PMAPPROG, PMAPVERS,
373			PMAPPROC_CALLIT, &m, &from, -1);
374	if (error)
375		return error;
376
377	/*
378	 * Parse result message.
379	 */
380	if (m->m_len < sizeof(*reply)) {
381		m = m_pullup(m, sizeof(*reply));
382		if (m == NULL)
383			goto bad;
384	}
385	reply = mtod(m, struct callit_reply *);
386	port = fxdr_unsigned(u_int32_t, reply->port);
387	msg_len = fxdr_unsigned(u_int32_t, reply->encap_len);
388	m_adj(m, sizeof(*reply));
389
390	/*
391	 * Save bootparam server address
392	 */
393	sin = mtod(from, struct sockaddr_in *);
394	bpsin->sin_port = htons(port);
395	bpsin->sin_addr.s_addr = sin->sin_addr.s_addr;
396
397	/* client name */
398	hostnamelen = MAXHOSTNAMELEN-1;
399	m = xdr_string_decode(m, hostname, &hostnamelen);
400	if (m == NULL)
401		goto bad;
402
403	/* domain name */
404	domainnamelen = MAXHOSTNAMELEN-1;
405	m = xdr_string_decode(m, domainname, &domainnamelen);
406	if (m == NULL)
407		goto bad;
408
409	/* gateway address */
410	m = xdr_inaddr_decode(m, gw_ip);
411	if (m == NULL)
412		goto bad;
413
414	/* success */
415	goto out;
416
417bad:
418	printf("nfs_boot: bootparam_whoami: bad reply\n");
419	error = EBADRPC;
420
421out:
422	if (from)
423		m_freem(from);
424	if (m)
425		m_freem(m);
426	return(error);
427}
428
429
430/*
431 * RPC: bootparam/getfile
432 * Given client name and file "key", get:
433 *	server name
434 *	server IP address
435 *	server pathname
436 */
437static int
438bp_getfile(bpsin, key, md_sin, serv_name, pathname, retries)
439	struct sockaddr_in *bpsin;
440	char *key;
441	struct sockaddr_in *md_sin;
442	char *serv_name;
443	char *pathname;
444	int retries;
445{
446	struct mbuf *m;
447	struct sockaddr_in *sin;
448	struct in_addr inaddr;
449	int error, sn_len, path_len;
450
451	/*
452	 * Build request message.
453	 */
454
455	/* client name (hostname) */
456	m  = xdr_string_encode(hostname, hostnamelen);
457	if (m == NULL)
458		return (ENOMEM);
459
460	/* key name (root or swap) */
461	m->m_next = xdr_string_encode(key, strlen(key));
462	if (m->m_next == NULL)
463		return (ENOMEM);
464
465	/* RPC: bootparam/getfile */
466	error = krpc_call(bpsin, BOOTPARAM_PROG, BOOTPARAM_VERS,
467			BOOTPARAM_GETFILE, &m, NULL, retries);
468	if (error)
469		return error;
470
471	/*
472	 * Parse result message.
473	 */
474
475	/* server name */
476	sn_len = MNAMELEN-1;
477	m = xdr_string_decode(m, serv_name, &sn_len);
478	if (m == NULL)
479		goto bad;
480
481	/* server IP address (mountd/NFS) */
482	m = xdr_inaddr_decode(m, &inaddr);
483	if (m == NULL)
484		goto bad;
485
486	/* server pathname */
487	path_len = MAXPATHLEN-1;
488	m = xdr_string_decode(m, pathname, &path_len);
489	if (m == NULL)
490		goto bad;
491
492	/* setup server socket address */
493	sin = md_sin;
494	bzero((caddr_t)sin, sizeof(*sin));
495	sin->sin_len = sizeof(*sin);
496	sin->sin_family = AF_INET;
497	sin->sin_addr = inaddr;
498
499	/* success */
500	goto out;
501
502bad:
503	printf("nfs_boot: bootparam_getfile: bad reply\n");
504	error = EBADRPC;
505
506out:
507	m_freem(m);
508	return(0);
509}
510
511
512/*
513 * RPC: mountd/mount
514 * Given a server pathname, get an NFS file handle.
515 * Also, sets sin->sin_port to the NFS service port.
516 */
517static int
518md_mount(mdsin, path, fhp)
519	struct sockaddr_in *mdsin;		/* mountd server address */
520	char *path;
521	u_char *fhp;
522{
523	/* The RPC structures */
524	struct rdata {
525		u_int32_t errno;
526		u_int8_t  fh[NFSX_V2FH];
527	} *rdata;
528	struct mbuf *m;
529	int error;
530
531	/* Get port number for MOUNTD. */
532	error = krpc_portmap(mdsin, RPCPROG_MNT, RPCMNT_VER1,
533						 &mdsin->sin_port);
534	if (error) return error;
535
536	m = xdr_string_encode(path, strlen(path));
537	if (m == NULL)
538		return ENOMEM;
539
540	/* Do RPC to mountd. */
541	error = krpc_call(mdsin, RPCPROG_MNT, RPCMNT_VER1,
542			RPCMNT_MOUNT, &m, NULL, -1);
543	if (error)
544		return error;	/* message already freed */
545
546	/* The reply might have only the errno. */
547	if (m->m_len < 4)
548		goto bad;
549	/* Have at least errno, so check that. */
550	rdata = mtod(m, struct rdata *);
551	error = fxdr_unsigned(u_int32_t, rdata->errno);
552	if (error)
553		goto out;
554
555	 /* Have errno==0, so the fh must be there. */
556	if (m->m_len < sizeof(*rdata)) {
557		m = m_pullup(m, sizeof(*rdata));
558		if (m == NULL)
559			goto bad;
560		rdata = mtod(m, struct rdata *);
561	}
562	bcopy(rdata->fh, fhp, NFSX_V2FH);
563	goto out;
564
565bad:
566	error = EBADRPC;
567
568out:
569	m_freem(m);
570	return error;
571}
572
573#endif /* ifdef NFSCLIENT */
574