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