nfs_boot.c revision 1.49
1/*	$OpenBSD: nfs_boot.c,v 1.49 2024/05/01 13:15:59 jsg 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/ioctl.h>
35#include <sys/mount.h>
36#include <sys/mbuf.h>
37#include <sys/socket.h>
38#include <sys/socketvar.h>
39#include <sys/queue.h>
40
41#include <net/if.h>
42#include <net/if_var.h>
43
44#include <netinet/in.h>
45#include <netinet/in_var.h>
46#include <netinet/if_ether.h>
47
48#include <nfs/rpcv2.h>
49#include <nfs/nfsproto.h>
50#include <nfs/nfs.h>
51#include <nfs/nfsdiskless.h>
52#include <nfs/krpc.h>
53#include <nfs/xdr_subs.h>
54#include <nfs/nfs_var.h>
55
56#include "ether.h"
57
58#if !defined(NFSCLIENT) || (NETHER == 0)
59
60int
61nfs_boot_init(struct nfs_diskless *nd, struct proc *procp)
62{
63	panic("nfs_boot_init: NFSCLIENT not enabled in kernel");
64}
65
66int
67nfs_boot_getfh(struct sockaddr_in *bpsin, char *key,
68    struct nfs_dlmount *ndmntp, int retries)
69{
70	/* can not get here */
71	return (EOPNOTSUPP);
72}
73
74#else
75
76/*
77 * Support for NFS diskless booting, specifically getting information
78 * about where to boot from, what pathnames, etc.
79 *
80 * This implementation uses RARP and the bootparam RPC.
81 * We are forced to implement RPC anyway (to get file handles)
82 * so we might as well take advantage of it for bootparam too.
83 *
84 * The diskless boot sequence goes as follows:
85 * (1) Use RARP to get our interface address
86 * (2) Use RPC/bootparam/whoami to get our hostname,
87 *     our IP address, and the server's IP address.
88 * (3) Use RPC/bootparam/getfile to get the root path
89 * (4) Use RPC/mountd to get the root file handle
90 * (5) Use RPC/bootparam/getfile to get the swap path
91 * (6) Use RPC/mountd to get the swap file handle
92 *
93 * (This happens to be the way Sun does it too.)
94 */
95
96/* bootparam RPC */
97static int bp_whoami(struct sockaddr_in *bpsin,
98	struct in_addr *my_ip, struct in_addr *gw_ip);
99static int bp_getfile(struct sockaddr_in *bpsin, char *key,
100	struct sockaddr_in *mdsin, char *servname, char *path, int retries);
101
102/* mountd RPC */
103static int md_mount(struct sockaddr_in *mdsin, char *path,
104	struct nfs_args *argp);
105
106char	*nfsbootdevname;
107
108/*
109 * Called with an empty nfs_diskless struct to be filled in.
110 */
111int
112nfs_boot_init(struct nfs_diskless *nd, struct proc *procp)
113{
114	struct ifreq ireq;
115	struct in_aliasreq ifra;
116	struct in_addr my_ip, gw_ip;
117	struct sockaddr_in bp_sin;
118	struct sockaddr_in *sin;
119	struct ifnet *ifp;
120	struct socket *so;
121	struct ifaddr *ifa;
122	char addr[INET_ADDRSTRLEN];
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 == NULL || (ifp = if_unit(nfsbootdevname)) == NULL)
138		panic("nfs_boot: no suitable interface");
139
140	bcopy(ifp->if_xname, ireq.ifr_name, IFNAMSIZ);
141	printf("nfs_boot: using interface %s, with revarp & bootparams\n",
142	    ireq.ifr_name);
143
144	/*
145	 * Bring up the interface.
146	 *
147	 * Get the old interface flags and or IFF_UP into them; if
148	 * IFF_UP set blindly, interface selection can be clobbered.
149	 */
150	if ((error = socreate(AF_INET, &so, SOCK_DGRAM, 0)) != 0)
151		panic("nfs_boot: socreate, error=%d", error);
152	error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)&ireq, procp);
153	if (error)
154		panic("nfs_boot: GIFFLAGS, error=%d", error);
155	ireq.ifr_flags |= IFF_UP;
156	error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ireq, procp);
157	if (error)
158		panic("nfs_boot: SIFFLAGS, error=%d", error);
159
160	/*
161	 * Do RARP for the interface address.
162	 */
163	if ((error = revarpwhoami(&my_ip, ifp)) != 0)
164		panic("reverse arp not answered by rarpd(8) or dhcpd(8)");
165	inet_ntop(AF_INET, &my_ip, addr, sizeof(addr));
166	printf("nfs_boot: client_addr=%s\n", addr);
167
168	/*
169	 * Do enough of ifconfig(8) so that the chosen interface
170	 * can talk to the servers.  (just set the address)
171	 */
172	memset(&ifra, 0, sizeof(ifra));
173	bcopy(ifp->if_xname, ifra.ifra_name, sizeof(ifra.ifra_name));
174
175	sin = &ifra.ifra_addr;
176	sin->sin_len = sizeof(*sin);
177	sin->sin_family = AF_INET;
178	sin->sin_addr.s_addr = my_ip.s_addr;
179	error = ifioctl(so, SIOCAIFADDR, (caddr_t)&ifra, procp);
180	if (error)
181		panic("nfs_boot: set if addr, error=%d", error);
182
183	soclose(so, 0);
184
185	TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
186		if (ifa->ifa_addr->sa_family == AF_INET)
187			break;
188	}
189	if (ifa == NULL)
190		panic("nfs_boot: address not configured on %s", ifp->if_xname);
191	if_put(ifp);
192
193	/*
194	 * Get client name and gateway address.
195	 * RPC: bootparam/whoami
196	 * The server address returned by the WHOAMI call
197	 * is used for all subsequent bootparam RPCs.
198	 */
199	memset(&bp_sin, 0, 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 = ifatoia(ifa)->ia_broadaddr.sin_addr.s_addr;
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	inet_ntop(AF_INET, &bp_sin.sin_addr, addr, sizeof(addr));
210	printf("nfs_boot: server_addr=%s hostname=%s\n", addr, hostname);
211
212	bcopy(&bp_sin, &nd->nd_boot, sizeof(bp_sin));
213
214	return (0);
215}
216
217/*
218 * bpsin:	bootparam server
219 * key:		root or swap
220 * ndmntp:	output
221 */
222int
223nfs_boot_getfh(struct sockaddr_in *bpsin, char *key,
224    struct nfs_dlmount *ndmntp, int retries)
225{
226	struct nfs_args *args;
227	char pathname[MAXPATHLEN];
228	char *sp, *dp, *endp;
229	struct sockaddr_in *sin;
230	int error;
231
232	args = &ndmntp->ndm_args;
233
234	/* Initialize mount args. */
235	memset(args, 0, sizeof(*args));
236	args->addr     = sintosa(&ndmntp->ndm_saddr);
237	args->addrlen  = args->addr->sa_len;
238	args->sotype   = SOCK_DGRAM;
239	args->fh       = ndmntp->ndm_fh;
240	args->hostname = ndmntp->ndm_host;
241	args->flags    = NFSMNT_NFSV3;
242#ifdef	NFS_BOOT_OPTIONS
243	args->flags    |= NFS_BOOT_OPTIONS;
244#endif
245#ifdef	NFS_BOOT_RWSIZE
246	/*
247	 * Reduce rsize,wsize for interfaces that consistently
248	 * drop fragments of long UDP messages.	 (i.e. wd8003).
249	 * You can always change these later via remount.
250	 */
251	args->flags   |= NFSMNT_WSIZE | NFSMNT_RSIZE;
252	args->wsize    = NFS_BOOT_RWSIZE;
253	args->rsize    = NFS_BOOT_RWSIZE;
254#endif
255
256	sin = &ndmntp->ndm_saddr;
257
258	/*
259	 * Get server:pathname for "key" (root or swap)
260	 * using RPC to bootparam/getfile
261	 */
262	error = bp_getfile(bpsin, key, sin, ndmntp->ndm_host, pathname,
263	    retries);
264	if (error) {
265		printf("nfs_boot: bootparam get %s: %d\n", key, error);
266		return (error);
267	}
268
269	/*
270	 * Get file handle for "key" (root or swap)
271	 * using RPC to mountd/mount
272	 */
273	error = md_mount(sin, pathname, args);
274	if (error) {
275		printf("nfs_boot: mountd %s, error=%d\n", key, error);
276		return (error);
277	}
278
279	/* Set port number for NFS use. */
280	/* XXX: NFS port is always 2049, right? */
281	error = krpc_portmap(sin, NFS_PROG,
282	    (args->flags & NFSMNT_NFSV3) ? NFS_VER3 : NFS_VER2,
283	    &sin->sin_port);
284	if (error) {
285		printf("nfs_boot: portmap NFS, error=%d\n", error);
286		return (error);
287	}
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	return (0);
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(struct sockaddr_in *bpsin, 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, -1);
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	m_freem(from);
409	m_freem(m);
410	return(error);
411}
412
413
414/*
415 * RPC: bootparam/getfile
416 * Given client name and file "key", get:
417 *	server name
418 *	server IP address
419 *	server pathname
420 */
421static int
422bp_getfile(struct sockaddr_in *bpsin, char *key, struct sockaddr_in *md_sin,
423    char *serv_name, char *pathname, int retries)
424{
425	struct mbuf *m;
426	struct sockaddr_in *sin;
427	struct in_addr inaddr;
428	int error, sn_len, path_len;
429
430	/*
431	 * Build request message.
432	 */
433
434	/* client name (hostname) */
435	m  = xdr_string_encode(hostname, hostnamelen);
436	if (m == NULL)
437		return (ENOMEM);
438
439	/* key name (root or swap) */
440	m->m_next = xdr_string_encode(key, strlen(key));
441	if (m->m_next == NULL) {
442		m_freem(m);
443		return (ENOMEM);
444	}
445
446	/* RPC: bootparam/getfile */
447	error = krpc_call(bpsin, BOOTPARAM_PROG, BOOTPARAM_VERS,
448			BOOTPARAM_GETFILE, &m, NULL, retries);
449	if (error)
450		return error;
451
452	/*
453	 * Parse result message.
454	 */
455
456	/* server name */
457	sn_len = MNAMELEN-1;
458	m = xdr_string_decode(m, serv_name, &sn_len);
459	if (m == NULL)
460		goto bad;
461
462	/* server IP address (mountd/NFS) */
463	m = xdr_inaddr_decode(m, &inaddr);
464	if (m == NULL)
465		goto bad;
466
467	/* server pathname */
468	path_len = MAXPATHLEN-1;
469	m = xdr_string_decode(m, pathname, &path_len);
470	if (m == NULL)
471		goto bad;
472
473	/* setup server socket address */
474	sin = md_sin;
475	memset(sin, 0, sizeof(*sin));
476	sin->sin_len = sizeof(*sin);
477	sin->sin_family = AF_INET;
478	sin->sin_addr = inaddr;
479
480	/* success */
481	goto out;
482
483bad:
484	printf("nfs_boot: bootparam_getfile: bad reply\n");
485	error = EBADRPC;
486
487out:
488	m_freem(m);
489	return(error);
490}
491
492
493/*
494 * RPC: mountd/mount
495 * Given a server pathname, get an NFS file handle.
496 * Also, sets sin->sin_port to the NFS service port.
497 * mdsin:	mountd server address
498 */
499static int
500md_mount(struct sockaddr_in *mdsin, char *path, struct nfs_args *argp)
501{
502	/* The RPC structures */
503	struct rdata {
504		u_int32_t errno;
505		union {
506			u_int8_t v2fh[NFSX_V2FH];
507			struct {
508				u_int32_t fhlen;
509				u_int8_t fh[1];
510			} v3fh;
511		} fh;
512	} *rdata;
513	struct mbuf *m;
514	u_int8_t *fh;
515	int minlen, error;
516	int mntver;
517
518	mntver = (argp->flags & NFSMNT_NFSV3) ? 3 : 2;
519	do {
520		error = krpc_portmap(mdsin, RPCPROG_MNT, mntver,
521		    &mdsin->sin_port);
522		if (error)
523			continue;
524
525		m = xdr_string_encode(path, strlen(path));
526		if (m == NULL)
527			return ENOMEM;
528
529		/* Do RPC to mountd. */
530		error = krpc_call(mdsin, RPCPROG_MNT, mntver,
531		    RPCMNT_MOUNT, &m, NULL, -1);
532
533		if (error != EPROGMISMATCH)
534			break;
535		/* Try lower version of mountd. */
536	} while (--mntver >= 1);
537	if (error)
538		return error;	/* message already freed */
539
540	if (mntver != 3)
541		argp->flags &= ~NFSMNT_NFSV3;
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 (mntver == 3) {
554		argp->fhsize = fxdr_unsigned(u_int32_t, rdata->fh.v3fh.fhlen);
555		if (argp->fhsize > NFSX_V3FHMAX)
556			goto bad;
557		minlen = 2 * sizeof(u_int32_t) + argp->fhsize;
558	} else {
559		argp->fhsize = NFSX_V2FH;
560		minlen = sizeof(u_int32_t) + argp->fhsize;
561	}
562
563	if (m->m_len < minlen) {
564		m = m_pullup(m, minlen);
565		if (m == NULL)
566			return (EBADRPC);
567		rdata = mtod(m, struct rdata *);
568	}
569
570	fh = (mntver == 3) ? rdata->fh.v3fh.fh : rdata->fh.v2fh;
571	bcopy(fh, argp->fh, argp->fhsize);
572
573	goto out;
574
575bad:
576	error = EBADRPC;
577
578out:
579	m_freem(m);
580	return error;
581}
582
583#endif /* ifdef NFSCLIENT */
584