nfs_boot.c revision 1.42
1/* $OpenBSD: nfs_boot.c,v 1.42 2017/07/19 12:32:13 claudio 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 NET_LOCK(s); 163 error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)&ireq, procp); 164 NET_UNLOCK(s); 165 if (error) 166 panic("nfs_boot: GIFFLAGS, error=%d", error); 167 ireq.ifr_flags |= IFF_UP; 168 NET_LOCK(s); 169 error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ireq, procp); 170 NET_UNLOCK(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 NET_LOCK(s); 194 error = ifioctl(so, SIOCAIFADDR, (caddr_t)&ifra, procp); 195 NET_UNLOCK(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 m_freem(m); 458 return (ENOMEM); 459 } 460 461 /* RPC: bootparam/getfile */ 462 error = krpc_call(bpsin, BOOTPARAM_PROG, BOOTPARAM_VERS, 463 BOOTPARAM_GETFILE, &m, NULL, retries); 464 if (error) 465 return error; 466 467 /* 468 * Parse result message. 469 */ 470 471 /* server name */ 472 sn_len = MNAMELEN-1; 473 m = xdr_string_decode(m, serv_name, &sn_len); 474 if (m == NULL) 475 goto bad; 476 477 /* server IP address (mountd/NFS) */ 478 m = xdr_inaddr_decode(m, &inaddr); 479 if (m == NULL) 480 goto bad; 481 482 /* server pathname */ 483 path_len = MAXPATHLEN-1; 484 m = xdr_string_decode(m, pathname, &path_len); 485 if (m == NULL) 486 goto bad; 487 488 /* setup server socket address */ 489 sin = md_sin; 490 memset(sin, 0, sizeof(*sin)); 491 sin->sin_len = sizeof(*sin); 492 sin->sin_family = AF_INET; 493 sin->sin_addr = inaddr; 494 495 /* success */ 496 goto out; 497 498bad: 499 printf("nfs_boot: bootparam_getfile: bad reply\n"); 500 error = EBADRPC; 501 502out: 503 m_freem(m); 504 return(error); 505} 506 507 508/* 509 * RPC: mountd/mount 510 * Given a server pathname, get an NFS file handle. 511 * Also, sets sin->sin_port to the NFS service port. 512 * mdsin: mountd server address 513 */ 514static int 515md_mount(struct sockaddr_in *mdsin, char *path, struct nfs_args *argp) 516{ 517 /* The RPC structures */ 518 struct rdata { 519 u_int32_t errno; 520 union { 521 u_int8_t v2fh[NFSX_V2FH]; 522 struct { 523 u_int32_t fhlen; 524 u_int8_t fh[1]; 525 } v3fh; 526 } fh; 527 } *rdata; 528 struct mbuf *m; 529 u_int8_t *fh; 530 int minlen, error; 531 int mntver; 532 533 mntver = (argp->flags & NFSMNT_NFSV3) ? 3 : 2; 534 do { 535 error = krpc_portmap(mdsin, RPCPROG_MNT, mntver, 536 &mdsin->sin_port); 537 if (error) 538 continue; 539 540 m = xdr_string_encode(path, strlen(path)); 541 if (m == NULL) 542 return ENOMEM; 543 544 /* Do RPC to mountd. */ 545 error = krpc_call(mdsin, RPCPROG_MNT, mntver, 546 RPCMNT_MOUNT, &m, NULL, -1); 547 548 if (error != EPROGMISMATCH) 549 break; 550 /* Try lower version of mountd. */ 551 } while (--mntver >= 1); 552 if (error) 553 return error; /* message already freed */ 554 555 if (mntver != 3) 556 argp->flags &= ~NFSMNT_NFSV3; 557 558 /* The reply might have only the errno. */ 559 if (m->m_len < 4) 560 goto bad; 561 /* Have at least errno, so check that. */ 562 rdata = mtod(m, struct rdata *); 563 error = fxdr_unsigned(u_int32_t, rdata->errno); 564 if (error) 565 goto out; 566 567 /* Have errno==0, so the fh must be there. */ 568 if (mntver == 3) { 569 argp->fhsize = fxdr_unsigned(u_int32_t, rdata->fh.v3fh.fhlen); 570 if (argp->fhsize > NFSX_V3FHMAX) 571 goto bad; 572 minlen = 2 * sizeof(u_int32_t) + argp->fhsize; 573 } else { 574 argp->fhsize = NFSX_V2FH; 575 minlen = sizeof(u_int32_t) + argp->fhsize; 576 } 577 578 if (m->m_len < minlen) { 579 m = m_pullup(m, minlen); 580 if (m == NULL) 581 return (EBADRPC); 582 rdata = mtod(m, struct rdata *); 583 } 584 585 fh = (mntver == 3) ? rdata->fh.v3fh.fh : rdata->fh.v2fh; 586 bcopy(fh, argp->fh, argp->fhsize); 587 588 goto out; 589 590bad: 591 error = EBADRPC; 592 593out: 594 m_freem(m); 595 return error; 596} 597 598#endif /* ifdef NFSCLIENT */ 599