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