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
60 int
nfs_boot_init(struct nfs_diskless * nd,struct proc * procp)61 nfs_boot_init(struct nfs_diskless *nd, struct proc *procp)
62 {
63 panic("nfs_boot_init: NFSCLIENT not enabled in kernel");
64 }
65
66 int
nfs_boot_getfh(struct sockaddr_in * bpsin,char * key,struct nfs_dlmount * ndmntp,int retries)67 nfs_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 */
97 static int bp_whoami(struct sockaddr_in *bpsin,
98 struct in_addr *my_ip, struct in_addr *gw_ip);
99 static int bp_getfile(struct sockaddr_in *bpsin, char *key,
100 struct sockaddr_in *mdsin, char *servname, char *path, int retries);
101
102 /* mountd RPC */
103 static int md_mount(struct sockaddr_in *mdsin, char *path,
104 struct nfs_args *argp);
105
106 char *nfsbootdevname;
107
108 /*
109 * Called with an empty nfs_diskless struct to be filled in.
110 */
111 int
nfs_boot_init(struct nfs_diskless * nd,struct proc * procp)112 nfs_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 */
222 int
nfs_boot_getfh(struct sockaddr_in * bpsin,char * key,struct nfs_dlmount * ndmntp,int retries)223 nfs_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 */
317 static int
bp_whoami(struct sockaddr_in * bpsin,struct in_addr * my_ip,struct in_addr * gw_ip)318 bp_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
403 bad:
404 printf("nfs_boot: bootparam_whoami: bad reply\n");
405 error = EBADRPC;
406
407 out:
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 */
421 static int
bp_getfile(struct sockaddr_in * bpsin,char * key,struct sockaddr_in * md_sin,char * serv_name,char * pathname,int retries)422 bp_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
483 bad:
484 printf("nfs_boot: bootparam_getfile: bad reply\n");
485 error = EBADRPC;
486
487 out:
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 */
499 static int
md_mount(struct sockaddr_in * mdsin,char * path,struct nfs_args * argp)500 md_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
575 bad:
576 error = EBADRPC;
577
578 out:
579 m_freem(m);
580 return error;
581 }
582
583 #endif /* ifdef NFSCLIENT */
584