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