nfs_boot.c revision 1.40
1/* $OpenBSD: nfs_boot.c,v 1.40 2016/11/22 12:11:38 mpi 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/mount.h> 37#include <sys/mbuf.h> 38#include <sys/reboot.h> 39#include <sys/socket.h> 40#include <sys/socketvar.h> 41#include <sys/queue.h> 42 43#include <net/if.h> 44#include <net/if_var.h> 45 46#include <netinet/in.h> 47#include <netinet/in_var.h> 48#include <netinet/if_ether.h> 49 50#include <nfs/rpcv2.h> 51#include <nfs/nfsproto.h> 52#include <nfs/nfs.h> 53#include <nfs/nfsdiskless.h> 54#include <nfs/krpc.h> 55#include <nfs/xdr_subs.h> 56#include <nfs/nfs_var.h> 57 58#include "ether.h" 59 60#if !defined(NFSCLIENT) || (NETHER == 0 && NFDDI == 0) 61 62int 63nfs_boot_init(struct nfs_diskless *nd, struct proc *procp) 64{ 65 panic("nfs_boot_init: NFSCLIENT not enabled in kernel"); 66} 67 68int 69nfs_boot_getfh(struct sockaddr_in *bpsin, char *key, 70 struct nfs_dlmount *ndmntp, int retries) 71{ 72 /* can not get here */ 73 return (EOPNOTSUPP); 74} 75 76#else 77 78/* 79 * Support for NFS diskless booting, specifically getting information 80 * about where to boot from, what pathnames, etc. 81 * 82 * This implementation 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(struct sockaddr_in *bpsin, 100 struct in_addr *my_ip, struct in_addr *gw_ip); 101static int bp_getfile(struct sockaddr_in *bpsin, char *key, 102 struct sockaddr_in *mdsin, char *servname, char *path, int retries); 103 104/* mountd RPC */ 105static int md_mount(struct sockaddr_in *mdsin, char *path, 106 struct nfs_args *argp); 107 108char *nfsbootdevname; 109 110/* 111 * Called with an empty nfs_diskless struct to be filled in. 112 */ 113int 114nfs_boot_init(struct nfs_diskless *nd, struct proc *procp) 115{ 116 struct ifreq ireq; 117 struct in_aliasreq ifra; 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 struct ifaddr *ifa; 124 char addr[INET_ADDRSTRLEN]; 125 int s, error; 126 127 /* 128 * Find an interface, rarp for its ip address, stuff it, the 129 * implied broadcast addr, and netmask into a nfs_diskless struct. 130 * 131 * This was moved here from nfs_vfsops.c because this procedure 132 * would be quite different if someone decides to write (i.e.) a 133 * BOOTP version of this file (might not use RARP, etc.) 134 */ 135 136 /* 137 * Find a network interface. 138 */ 139 if (nfsbootdevname) 140 ifp = ifunit(nfsbootdevname); 141 else { 142 TAILQ_FOREACH(ifp, &ifnet, if_list) { 143 if ((ifp->if_flags & 144 (IFF_LOOPBACK|IFF_POINTOPOINT)) == 0) 145 break; 146 } 147 } 148 if (ifp == NULL) 149 panic("nfs_boot: no suitable interface"); 150 bcopy(ifp->if_xname, ireq.ifr_name, IFNAMSIZ); 151 printf("nfs_boot: using interface %s, with revarp & bootparams\n", 152 ireq.ifr_name); 153 154 /* 155 * Bring up the interface. 156 * 157 * Get the old interface flags and or IFF_UP into them; if 158 * IFF_UP set blindly, interface selection can be clobbered. 159 */ 160 if ((error = socreate(AF_INET, &so, SOCK_DGRAM, 0)) != 0) 161 panic("nfs_boot: socreate, error=%d", error); 162 s = splsoftnet(); 163 error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)&ireq, procp); 164 splx(s); 165 if (error) 166 panic("nfs_boot: GIFFLAGS, error=%d", error); 167 ireq.ifr_flags |= IFF_UP; 168 s = splsoftnet(); 169 error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ireq, procp); 170 splx(s); 171 if (error) 172 panic("nfs_boot: SIFFLAGS, error=%d", error); 173 174 /* 175 * Do RARP for the interface address. 176 */ 177 if ((error = revarpwhoami(&my_ip, ifp)) != 0) 178 panic("reverse arp not answered by rarpd(8) or dhcpd(8)"); 179 inet_ntop(AF_INET, &my_ip, addr, sizeof(addr)); 180 printf("nfs_boot: client_addr=%s\n", addr); 181 182 /* 183 * Do enough of ifconfig(8) so that the chosen interface 184 * can talk to the servers. (just set the address) 185 */ 186 memset(&ifra, 0, sizeof(ifra)); 187 bcopy(ifp->if_xname, ifra.ifra_name, sizeof(ifra.ifra_name)); 188 189 sin = &ifra.ifra_addr; 190 sin->sin_len = sizeof(*sin); 191 sin->sin_family = AF_INET; 192 sin->sin_addr.s_addr = my_ip.s_addr; 193 s = splsoftnet(); 194 error = ifioctl(so, SIOCAIFADDR, (caddr_t)&ifra, procp); 195 splx(s); 196 if (error) 197 panic("nfs_boot: set if addr, error=%d", error); 198 199 soclose(so); 200 201 TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { 202 if (ifa->ifa_addr->sa_family == AF_INET) 203 break; 204 } 205 if (ifa == NULL) 206 panic("nfs_boot: address not configured on %s", ifp->if_xname); 207 208 /* 209 * Get client name and gateway address. 210 * RPC: bootparam/whoami 211 * The server address returned by the WHOAMI call 212 * is used for all subsequent bootparam RPCs. 213 */ 214 memset(&bp_sin, 0, sizeof(bp_sin)); 215 bp_sin.sin_len = sizeof(bp_sin); 216 bp_sin.sin_family = AF_INET; 217 bp_sin.sin_addr.s_addr = ifatoia(ifa)->ia_broadaddr.sin_addr.s_addr; 218 hostnamelen = MAXHOSTNAMELEN; 219 220 /* this returns gateway IP address */ 221 error = bp_whoami(&bp_sin, &my_ip, &gw_ip); 222 if (error) 223 panic("nfs_boot: bootparam whoami, error=%d", error); 224 inet_ntop(AF_INET, &bp_sin.sin_addr, addr, sizeof(addr)); 225 printf("nfs_boot: server_addr=%s hostname=%s\n", addr, hostname); 226 227 bcopy(&bp_sin, &nd->nd_boot, sizeof(bp_sin)); 228 229 return (0); 230} 231 232/* 233 * bpsin: bootparam server 234 * key: root or swap 235 * ndmntp: output 236 */ 237int 238nfs_boot_getfh(struct sockaddr_in *bpsin, char *key, 239 struct nfs_dlmount *ndmntp, int retries) 240{ 241 struct nfs_args *args; 242 char pathname[MAXPATHLEN]; 243 char *sp, *dp, *endp; 244 struct sockaddr_in *sin; 245 int error; 246 247 args = &ndmntp->ndm_args; 248 249 /* Initialize mount args. */ 250 memset(args, 0, sizeof(*args)); 251 args->addr = sintosa(&ndmntp->ndm_saddr); 252 args->addrlen = args->addr->sa_len; 253 args->sotype = SOCK_DGRAM; 254 args->fh = ndmntp->ndm_fh; 255 args->hostname = ndmntp->ndm_host; 256 args->flags = NFSMNT_NFSV3; 257#ifdef NFS_BOOT_OPTIONS 258 args->flags |= NFS_BOOT_OPTIONS; 259#endif 260#ifdef NFS_BOOT_RWSIZE 261 /* 262 * Reduce rsize,wsize for interfaces that consistently 263 * drop fragments of long UDP messages. (i.e. wd8003). 264 * You can always change these later via remount. 265 */ 266 args->flags |= NFSMNT_WSIZE | NFSMNT_RSIZE; 267 args->wsize = NFS_BOOT_RWSIZE; 268 args->rsize = NFS_BOOT_RWSIZE; 269#endif 270 271 sin = &ndmntp->ndm_saddr; 272 273 /* 274 * Get server:pathname for "key" (root or swap) 275 * using RPC to bootparam/getfile 276 */ 277 error = bp_getfile(bpsin, key, sin, ndmntp->ndm_host, pathname, 278 retries); 279 if (error) { 280 printf("nfs_boot: bootparam get %s: %d\n", key, error); 281 return (error); 282 } 283 284 /* 285 * Get file handle for "key" (root or swap) 286 * using RPC to mountd/mount 287 */ 288 error = md_mount(sin, pathname, args); 289 if (error) { 290 printf("nfs_boot: mountd %s, error=%d\n", key, error); 291 return (error); 292 } 293 294 /* Set port number for NFS use. */ 295 /* XXX: NFS port is always 2049, right? */ 296 error = krpc_portmap(sin, NFS_PROG, 297 (args->flags & NFSMNT_NFSV3) ? NFS_VER3 : NFS_VER2, 298 &sin->sin_port); 299 if (error) { 300 printf("nfs_boot: portmap NFS, error=%d\n", error); 301 return (error); 302 } 303 304 /* Construct remote path (for getmntinfo(3)) */ 305 dp = ndmntp->ndm_host; 306 endp = dp + MNAMELEN - 1; 307 dp += strlen(dp); 308 *dp++ = ':'; 309 for (sp = pathname; *sp && dp < endp;) 310 *dp++ = *sp++; 311 *dp = '\0'; 312 313 return (0); 314} 315 316 317/* 318 * RPC: bootparam/whoami 319 * Given client IP address, get: 320 * client name (hostname) 321 * domain name (domainname) 322 * gateway address 323 * 324 * The hostname and domainname are set here for convenience. 325 * 326 * Note - bpsin is initialized to the broadcast address, 327 * and will be replaced with the bootparam server address 328 * after this call is complete. Have to use PMAP_PROC_CALL 329 * to make sure we get responses only from a servers that 330 * know about us (don't want to broadcast a getport call). 331 */ 332static int 333bp_whoami(struct sockaddr_in *bpsin, struct in_addr *my_ip, 334 struct in_addr *gw_ip) 335{ 336 /* RPC structures for PMAPPROC_CALLIT */ 337 struct whoami_call { 338 u_int32_t call_prog; 339 u_int32_t call_vers; 340 u_int32_t call_proc; 341 u_int32_t call_arglen; 342 } *call; 343 struct callit_reply { 344 u_int32_t port; 345 u_int32_t encap_len; 346 /* encapsulated data here */ 347 } *reply; 348 349 struct mbuf *m, *from; 350 struct sockaddr_in *sin; 351 int error, msg_len; 352 int16_t port; 353 354 /* 355 * Build request message for PMAPPROC_CALLIT. 356 */ 357 m = m_get(M_WAIT, MT_DATA); 358 call = mtod(m, struct whoami_call *); 359 m->m_len = sizeof(*call); 360 call->call_prog = txdr_unsigned(BOOTPARAM_PROG); 361 call->call_vers = txdr_unsigned(BOOTPARAM_VERS); 362 call->call_proc = txdr_unsigned(BOOTPARAM_WHOAMI); 363 364 /* 365 * append encapsulated data (client IP address) 366 */ 367 m->m_next = xdr_inaddr_encode(my_ip); 368 call->call_arglen = txdr_unsigned(m->m_next->m_len); 369 370 /* RPC: portmap/callit */ 371 bpsin->sin_port = htons(PMAPPORT); 372 from = NULL; 373 error = krpc_call(bpsin, PMAPPROG, PMAPVERS, 374 PMAPPROC_CALLIT, &m, &from, -1); 375 if (error) 376 return error; 377 378 /* 379 * Parse result message. 380 */ 381 if (m->m_len < sizeof(*reply)) { 382 m = m_pullup(m, sizeof(*reply)); 383 if (m == NULL) 384 goto bad; 385 } 386 reply = mtod(m, struct callit_reply *); 387 port = fxdr_unsigned(u_int32_t, reply->port); 388 msg_len = fxdr_unsigned(u_int32_t, reply->encap_len); 389 m_adj(m, sizeof(*reply)); 390 391 /* 392 * Save bootparam server address 393 */ 394 sin = mtod(from, struct sockaddr_in *); 395 bpsin->sin_port = htons(port); 396 bpsin->sin_addr.s_addr = sin->sin_addr.s_addr; 397 398 /* client name */ 399 hostnamelen = MAXHOSTNAMELEN-1; 400 m = xdr_string_decode(m, hostname, &hostnamelen); 401 if (m == NULL) 402 goto bad; 403 404 /* domain name */ 405 domainnamelen = MAXHOSTNAMELEN-1; 406 m = xdr_string_decode(m, domainname, &domainnamelen); 407 if (m == NULL) 408 goto bad; 409 410 /* gateway address */ 411 m = xdr_inaddr_decode(m, gw_ip); 412 if (m == NULL) 413 goto bad; 414 415 /* success */ 416 goto out; 417 418bad: 419 printf("nfs_boot: bootparam_whoami: bad reply\n"); 420 error = EBADRPC; 421 422out: 423 m_freem(from); 424 m_freem(m); 425 return(error); 426} 427 428 429/* 430 * RPC: bootparam/getfile 431 * Given client name and file "key", get: 432 * server name 433 * server IP address 434 * server pathname 435 */ 436static int 437bp_getfile(struct sockaddr_in *bpsin, char *key, struct sockaddr_in *md_sin, 438 char *serv_name, char *pathname, int retries) 439{ 440 struct mbuf *m; 441 struct sockaddr_in *sin; 442 struct in_addr inaddr; 443 int error, sn_len, path_len; 444 445 /* 446 * Build request message. 447 */ 448 449 /* client name (hostname) */ 450 m = xdr_string_encode(hostname, hostnamelen); 451 if (m == NULL) 452 return (ENOMEM); 453 454 /* key name (root or swap) */ 455 m->m_next = xdr_string_encode(key, strlen(key)); 456 if (m->m_next == NULL) 457 return (ENOMEM); 458 459 /* RPC: bootparam/getfile */ 460 error = krpc_call(bpsin, BOOTPARAM_PROG, BOOTPARAM_VERS, 461 BOOTPARAM_GETFILE, &m, NULL, retries); 462 if (error) 463 return error; 464 465 /* 466 * Parse result message. 467 */ 468 469 /* server name */ 470 sn_len = MNAMELEN-1; 471 m = xdr_string_decode(m, serv_name, &sn_len); 472 if (m == NULL) 473 goto bad; 474 475 /* server IP address (mountd/NFS) */ 476 m = xdr_inaddr_decode(m, &inaddr); 477 if (m == NULL) 478 goto bad; 479 480 /* server pathname */ 481 path_len = MAXPATHLEN-1; 482 m = xdr_string_decode(m, pathname, &path_len); 483 if (m == NULL) 484 goto bad; 485 486 /* setup server socket address */ 487 sin = md_sin; 488 memset(sin, 0, sizeof(*sin)); 489 sin->sin_len = sizeof(*sin); 490 sin->sin_family = AF_INET; 491 sin->sin_addr = inaddr; 492 493 /* success */ 494 goto out; 495 496bad: 497 printf("nfs_boot: bootparam_getfile: bad reply\n"); 498 error = EBADRPC; 499 500out: 501 m_freem(m); 502 return(error); 503} 504 505 506/* 507 * RPC: mountd/mount 508 * Given a server pathname, get an NFS file handle. 509 * Also, sets sin->sin_port to the NFS service port. 510 * mdsin: mountd server address 511 */ 512static int 513md_mount(struct sockaddr_in *mdsin, char *path, struct nfs_args *argp) 514{ 515 /* The RPC structures */ 516 struct rdata { 517 u_int32_t errno; 518 union { 519 u_int8_t v2fh[NFSX_V2FH]; 520 struct { 521 u_int32_t fhlen; 522 u_int8_t fh[1]; 523 } v3fh; 524 } fh; 525 } *rdata; 526 struct mbuf *m; 527 u_int8_t *fh; 528 int minlen, error; 529 int mntver; 530 531 mntver = (argp->flags & NFSMNT_NFSV3) ? 3 : 2; 532 do { 533 error = krpc_portmap(mdsin, RPCPROG_MNT, mntver, 534 &mdsin->sin_port); 535 if (error) 536 continue; 537 538 m = xdr_string_encode(path, strlen(path)); 539 if (m == NULL) 540 return ENOMEM; 541 542 /* Do RPC to mountd. */ 543 error = krpc_call(mdsin, RPCPROG_MNT, mntver, 544 RPCMNT_MOUNT, &m, NULL, -1); 545 546 if (error != EPROGMISMATCH) 547 break; 548 /* Try lower version of mountd. */ 549 } while (--mntver >= 1); 550 if (error) 551 return error; /* message already freed */ 552 553 if (mntver != 3) 554 argp->flags &= ~NFSMNT_NFSV3; 555 556 /* The reply might have only the errno. */ 557 if (m->m_len < 4) 558 goto bad; 559 /* Have at least errno, so check that. */ 560 rdata = mtod(m, struct rdata *); 561 error = fxdr_unsigned(u_int32_t, rdata->errno); 562 if (error) 563 goto out; 564 565 /* Have errno==0, so the fh must be there. */ 566 if (mntver == 3) { 567 argp->fhsize = fxdr_unsigned(u_int32_t, rdata->fh.v3fh.fhlen); 568 if (argp->fhsize > NFSX_V3FHMAX) 569 goto bad; 570 minlen = 2 * sizeof(u_int32_t) + argp->fhsize; 571 } else { 572 argp->fhsize = NFSX_V2FH; 573 minlen = sizeof(u_int32_t) + argp->fhsize; 574 } 575 576 if (m->m_len < minlen) { 577 m = m_pullup(m, minlen); 578 if (m == NULL) 579 return (EBADRPC); 580 rdata = mtod(m, struct rdata *); 581 } 582 583 fh = (mntver == 3) ? rdata->fh.v3fh.fh : rdata->fh.v2fh; 584 bcopy(fh, argp->fh, argp->fhsize); 585 586 goto out; 587 588bad: 589 error = EBADRPC; 590 591out: 592 m_freem(m); 593 return error; 594} 595 596#endif /* ifdef NFSCLIENT */ 597