nfs_boot.c revision 1.30
1/* $OpenBSD: nfs_boot.c,v 1.30 2013/11/11 09:15:35 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/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#include <sys/queue.h> 43 44#include <net/if.h> 45#include <net/route.h> 46 47#include <netinet/in.h> 48#include <netinet/in_var.h> 49#include <netinet/if_ether.h> 50 51#include <nfs/rpcv2.h> 52#include <nfs/nfsproto.h> 53#include <nfs/nfs.h> 54#include <nfs/nfsdiskless.h> 55#include <nfs/krpc.h> 56#include <nfs/xdr_subs.h> 57#include <nfs/nfs_var.h> 58 59#include "ether.h" 60 61#if !defined(NFSCLIENT) || (NETHER == 0 && NFDDI == 0) 62 63int 64nfs_boot_init(struct nfs_diskless *nd, struct proc *procp) 65{ 66 panic("nfs_boot_init: NFSCLIENT not enabled in kernel"); 67} 68 69int 70nfs_boot_getfh(struct sockaddr_in *bpsin, char *key, 71 struct nfs_dlmount *ndmntp, int retries) 72{ 73 /* can not get here */ 74 return (EOPNOTSUPP); 75} 76 77#else 78 79/* 80 * Support for NFS diskless booting, specifically getting information 81 * about where to boot from, what pathnames, etc. 82 * 83 * This implementation uses RARP and the bootparam RPC. 84 * We are forced to implement RPC anyway (to get file handles) 85 * so we might as well take advantage of it for bootparam too. 86 * 87 * The diskless boot sequence goes as follows: 88 * (1) Use RARP to get our interface address 89 * (2) Use RPC/bootparam/whoami to get our hostname, 90 * our IP address, and the server's IP address. 91 * (3) Use RPC/bootparam/getfile to get the root path 92 * (4) Use RPC/mountd to get the root file handle 93 * (5) Use RPC/bootparam/getfile to get the swap path 94 * (6) Use RPC/mountd to get the swap file handle 95 * 96 * (This happens to be the way Sun does it too.) 97 */ 98 99/* bootparam RPC */ 100static int bp_whoami(struct sockaddr_in *bpsin, 101 struct in_addr *my_ip, struct in_addr *gw_ip); 102static int bp_getfile(struct sockaddr_in *bpsin, char *key, 103 struct sockaddr_in *mdsin, char *servname, char *path, int retries); 104 105/* mountd RPC */ 106static int md_mount(struct sockaddr_in *mdsin, char *path, 107 struct nfs_args *argp); 108 109char *nfsbootdevname; 110 111/* 112 * Called with an empty nfs_diskless struct to be filled in. 113 */ 114int 115nfs_boot_init(struct nfs_diskless *nd, struct proc *procp) 116{ 117 struct ifreq ireq; 118 struct in_aliasreq ifra; 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 char addr[INET_ADDRSTRLEN]; 125 int 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 error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)&ireq, procp); 163 if (error) 164 panic("nfs_boot: GIFFLAGS, error=%d", error); 165 ireq.ifr_flags |= IFF_UP; 166 error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ireq, procp); 167 if (error) 168 panic("nfs_boot: SIFFLAGS, error=%d", error); 169 170 /* 171 * Do RARP for the interface address. 172 */ 173 if ((error = revarpwhoami(&my_ip, ifp)) != 0) 174 panic("reverse arp not answered by rarpd(8) or dhcpd(8)"); 175 inet_ntop(AF_INET, &my_ip, addr, sizeof(addr)); 176 printf("nfs_boot: client_addr=%s\n", addr); 177 178 /* 179 * Do enough of ifconfig(8) so that the chosen interface 180 * can talk to the servers. (just set the address) 181 */ 182 bzero(&ifra, sizeof(ifra)); 183 bcopy(ifp->if_xname, ifra.ifra_name, sizeof(ifra.ifra_name)); 184 185 sin = (struct sockaddr_in *)&ifra.ifra_addr; 186 sin->sin_len = sizeof(*sin); 187 sin->sin_family = AF_INET; 188 sin->sin_addr.s_addr = my_ip.s_addr; 189 error = ifioctl(so, SIOCAIFADDR, (caddr_t)&ifra, procp); 190 if (error) 191 panic("nfs_boot: set if addr, error=%d", error); 192 193 soclose(so); 194 195 /* 196 * Get client name and gateway address. 197 * RPC: bootparam/whoami 198 * Use the old broadcast address for the WHOAMI 199 * call because we do not yet know our netmask. 200 * The server address returned by the WHOAMI call 201 * is used for all subsequent bootparam RPCs. 202 */ 203 bzero((caddr_t)&bp_sin, sizeof(bp_sin)); 204 bp_sin.sin_len = sizeof(bp_sin); 205 bp_sin.sin_family = AF_INET; 206 bp_sin.sin_addr.s_addr = INADDR_BROADCAST; 207 hostnamelen = MAXHOSTNAMELEN; 208 209 /* this returns gateway IP address */ 210 error = bp_whoami(&bp_sin, &my_ip, &gw_ip); 211 if (error) 212 panic("nfs_boot: bootparam whoami, error=%d", error); 213 inet_ntop(AF_INET, &bp_sin.sin_addr, addr, sizeof(addr)); 214 printf("nfs_boot: server_addr=%s hostname=%s\n", addr, hostname); 215 216#ifdef NFS_BOOT_GATEWAY 217 /* 218 * XXX - This code is conditionally compiled only because 219 * many bootparam servers (in particular, SunOS 4.1.3) 220 * always set the gateway address to their own address. 221 * The bootparam server is not necessarily the gateway. 222 * We could just believe the server, and at worst you would 223 * need to delete the incorrect default route before adding 224 * the correct one, but for simplicity, ignore the gateway. 225 * If your server is OK, you can turn on this option. 226 * 227 * If the gateway address is set, add a default route. 228 * (The mountd RPCs may go across a gateway.) 229 */ 230 if (gw_ip.s_addr) { 231 struct sockaddr dst, gw, mask; 232 struct rt_addrinfo info; 233 /* Destination: (default) */ 234 bzero((caddr_t)&dst, sizeof(dst)); 235 dst.sa_len = sizeof(dst); 236 dst.sa_family = AF_INET; 237 /* Gateway: */ 238 bzero((caddr_t)&gw, sizeof(gw)); 239 sin = (struct sockaddr_in *)&gw; 240 sin->sin_len = sizeof(gw); 241 sin->sin_family = AF_INET; 242 sin->sin_addr.s_addr = gw_ip.s_addr; 243 /* Mask: (zero length) */ 244 bzero(&mask, sizeof(mask)); 245 bzero(&info, sizeof(info)); 246 info.rti_info[RTAX_DST] = &dst; 247 info.rti_info[RTAX_GATEWAY] = &gw; 248 info.rti_info[RTAX_NETMASK] = &mask; 249 info.rti_flags = (RTF_UP | RTF_GATEWAY | RTF_STATIC); 250 251 inet_ntop(AF_INET, gw_ip, addr, sizeof(addr)); 252 printf("nfs_boot: gateway=%s\n", addr); 253 /* add, dest, gw, mask, flags, 0 */ 254 error = rtrequest1(RTM_ADD, &info, RTP_STATIC, NULL, 0); 255 if (error) 256 printf("nfs_boot: add route, error=%d\n", error); 257 } 258#endif 259 260 bcopy(&bp_sin, &nd->nd_boot, sizeof(bp_sin)); 261 262 return (0); 263} 264 265/* 266 * bpsin: bootparam server 267 * key: root or swap 268 * ndmntp: output 269 */ 270int 271nfs_boot_getfh(struct sockaddr_in *bpsin, char *key, 272 struct nfs_dlmount *ndmntp, int retries) 273{ 274 struct nfs_args *args; 275 char pathname[MAXPATHLEN]; 276 char *sp, *dp, *endp; 277 struct sockaddr_in *sin; 278 int error; 279 280 args = &ndmntp->ndm_args; 281 282 /* Initialize mount args. */ 283 bzero((caddr_t) args, sizeof(*args)); 284 args->addr = (struct sockaddr *)&ndmntp->ndm_saddr; 285 args->addrlen = args->addr->sa_len; 286 args->sotype = SOCK_DGRAM; 287 args->fh = ndmntp->ndm_fh; 288 args->hostname = ndmntp->ndm_host; 289 args->flags = NFSMNT_NFSV3; 290#ifdef NFS_BOOT_OPTIONS 291 args->flags |= NFS_BOOT_OPTIONS; 292#endif 293#ifdef NFS_BOOT_RWSIZE 294 /* 295 * Reduce rsize,wsize for interfaces that consistently 296 * drop fragments of long UDP messages. (i.e. wd8003). 297 * You can always change these later via remount. 298 */ 299 args->flags |= NFSMNT_WSIZE | NFSMNT_RSIZE; 300 args->wsize = NFS_BOOT_RWSIZE; 301 args->rsize = NFS_BOOT_RWSIZE; 302#endif 303 304 sin = &ndmntp->ndm_saddr; 305 306 /* 307 * Get server:pathname for "key" (root or swap) 308 * using RPC to bootparam/getfile 309 */ 310 error = bp_getfile(bpsin, key, sin, ndmntp->ndm_host, pathname, 311 retries); 312 if (error) { 313 printf("nfs_boot: bootparam get %s: %d\n", key, error); 314 return (error); 315 } 316 317 /* 318 * Get file handle for "key" (root or swap) 319 * using RPC to mountd/mount 320 */ 321 error = md_mount(sin, pathname, args); 322 if (error) { 323 printf("nfs_boot: mountd %s, error=%d\n", key, error); 324 return (error); 325 } 326 327 /* Set port number for NFS use. */ 328 /* XXX: NFS port is always 2049, right? */ 329 error = krpc_portmap(sin, NFS_PROG, 330 (args->flags & NFSMNT_NFSV3) ? NFS_VER3 : NFS_VER2, 331 &sin->sin_port); 332 if (error) { 333 printf("nfs_boot: portmap NFS, error=%d\n", error); 334 return (error); 335 } 336 337 /* Construct remote path (for getmntinfo(3)) */ 338 dp = ndmntp->ndm_host; 339 endp = dp + MNAMELEN - 1; 340 dp += strlen(dp); 341 *dp++ = ':'; 342 for (sp = pathname; *sp && dp < endp;) 343 *dp++ = *sp++; 344 *dp = '\0'; 345 346 return (0); 347} 348 349 350/* 351 * RPC: bootparam/whoami 352 * Given client IP address, get: 353 * client name (hostname) 354 * domain name (domainname) 355 * gateway address 356 * 357 * The hostname and domainname are set here for convenience. 358 * 359 * Note - bpsin is initialized to the broadcast address, 360 * and will be replaced with the bootparam server address 361 * after this call is complete. Have to use PMAP_PROC_CALL 362 * to make sure we get responses only from a servers that 363 * know about us (don't want to broadcast a getport call). 364 */ 365static int 366bp_whoami(struct sockaddr_in *bpsin, struct in_addr *my_ip, 367 struct in_addr *gw_ip) 368{ 369 /* RPC structures for PMAPPROC_CALLIT */ 370 struct whoami_call { 371 u_int32_t call_prog; 372 u_int32_t call_vers; 373 u_int32_t call_proc; 374 u_int32_t call_arglen; 375 } *call; 376 struct callit_reply { 377 u_int32_t port; 378 u_int32_t encap_len; 379 /* encapsulated data here */ 380 } *reply; 381 382 struct mbuf *m, *from; 383 struct sockaddr_in *sin; 384 int error, msg_len; 385 int16_t port; 386 387 /* 388 * Build request message for PMAPPROC_CALLIT. 389 */ 390 m = m_get(M_WAIT, MT_DATA); 391 call = mtod(m, struct whoami_call *); 392 m->m_len = sizeof(*call); 393 call->call_prog = txdr_unsigned(BOOTPARAM_PROG); 394 call->call_vers = txdr_unsigned(BOOTPARAM_VERS); 395 call->call_proc = txdr_unsigned(BOOTPARAM_WHOAMI); 396 397 /* 398 * append encapsulated data (client IP address) 399 */ 400 m->m_next = xdr_inaddr_encode(my_ip); 401 call->call_arglen = txdr_unsigned(m->m_next->m_len); 402 403 /* RPC: portmap/callit */ 404 bpsin->sin_port = htons(PMAPPORT); 405 from = NULL; 406 error = krpc_call(bpsin, PMAPPROG, PMAPVERS, 407 PMAPPROC_CALLIT, &m, &from, -1); 408 if (error) 409 return error; 410 411 /* 412 * Parse result message. 413 */ 414 if (m->m_len < sizeof(*reply)) { 415 m = m_pullup(m, sizeof(*reply)); 416 if (m == NULL) 417 goto bad; 418 } 419 reply = mtod(m, struct callit_reply *); 420 port = fxdr_unsigned(u_int32_t, reply->port); 421 msg_len = fxdr_unsigned(u_int32_t, reply->encap_len); 422 m_adj(m, sizeof(*reply)); 423 424 /* 425 * Save bootparam server address 426 */ 427 sin = mtod(from, struct sockaddr_in *); 428 bpsin->sin_port = htons(port); 429 bpsin->sin_addr.s_addr = sin->sin_addr.s_addr; 430 431 /* client name */ 432 hostnamelen = MAXHOSTNAMELEN-1; 433 m = xdr_string_decode(m, hostname, &hostnamelen); 434 if (m == NULL) 435 goto bad; 436 437 /* domain name */ 438 domainnamelen = MAXHOSTNAMELEN-1; 439 m = xdr_string_decode(m, domainname, &domainnamelen); 440 if (m == NULL) 441 goto bad; 442 443 /* gateway address */ 444 m = xdr_inaddr_decode(m, gw_ip); 445 if (m == NULL) 446 goto bad; 447 448 /* success */ 449 goto out; 450 451bad: 452 printf("nfs_boot: bootparam_whoami: bad reply\n"); 453 error = EBADRPC; 454 455out: 456 if (from) 457 m_freem(from); 458 if (m) 459 m_freem(m); 460 return(error); 461} 462 463 464/* 465 * RPC: bootparam/getfile 466 * Given client name and file "key", get: 467 * server name 468 * server IP address 469 * server pathname 470 */ 471static int 472bp_getfile(struct sockaddr_in *bpsin, char *key, struct sockaddr_in *md_sin, 473 char *serv_name, char *pathname, int retries) 474{ 475 struct mbuf *m; 476 struct sockaddr_in *sin; 477 struct in_addr inaddr; 478 int error, sn_len, path_len; 479 480 /* 481 * Build request message. 482 */ 483 484 /* client name (hostname) */ 485 m = xdr_string_encode(hostname, hostnamelen); 486 if (m == NULL) 487 return (ENOMEM); 488 489 /* key name (root or swap) */ 490 m->m_next = xdr_string_encode(key, strlen(key)); 491 if (m->m_next == NULL) 492 return (ENOMEM); 493 494 /* RPC: bootparam/getfile */ 495 error = krpc_call(bpsin, BOOTPARAM_PROG, BOOTPARAM_VERS, 496 BOOTPARAM_GETFILE, &m, NULL, retries); 497 if (error) 498 return error; 499 500 /* 501 * Parse result message. 502 */ 503 504 /* server name */ 505 sn_len = MNAMELEN-1; 506 m = xdr_string_decode(m, serv_name, &sn_len); 507 if (m == NULL) 508 goto bad; 509 510 /* server IP address (mountd/NFS) */ 511 m = xdr_inaddr_decode(m, &inaddr); 512 if (m == NULL) 513 goto bad; 514 515 /* server pathname */ 516 path_len = MAXPATHLEN-1; 517 m = xdr_string_decode(m, pathname, &path_len); 518 if (m == NULL) 519 goto bad; 520 521 /* setup server socket address */ 522 sin = md_sin; 523 bzero((caddr_t)sin, sizeof(*sin)); 524 sin->sin_len = sizeof(*sin); 525 sin->sin_family = AF_INET; 526 sin->sin_addr = inaddr; 527 528 /* success */ 529 goto out; 530 531bad: 532 printf("nfs_boot: bootparam_getfile: bad reply\n"); 533 error = EBADRPC; 534 535out: 536 m_freem(m); 537 return(error); 538} 539 540 541/* 542 * RPC: mountd/mount 543 * Given a server pathname, get an NFS file handle. 544 * Also, sets sin->sin_port to the NFS service port. 545 * mdsin: mountd server address 546 */ 547static int 548md_mount(struct sockaddr_in *mdsin, char *path, struct nfs_args *argp) 549{ 550 /* The RPC structures */ 551 struct rdata { 552 u_int32_t errno; 553 union { 554 u_int8_t v2fh[NFSX_V2FH]; 555 struct { 556 u_int32_t fhlen; 557 u_int8_t fh[1]; 558 } v3fh; 559 } fh; 560 } *rdata; 561 struct mbuf *m; 562 u_int8_t *fh; 563 int minlen, error; 564 int mntver; 565 566 mntver = (argp->flags & NFSMNT_NFSV3) ? 3 : 2; 567 do { 568 error = krpc_portmap(mdsin, RPCPROG_MNT, mntver, 569 &mdsin->sin_port); 570 if (error) 571 continue; 572 573 m = xdr_string_encode(path, strlen(path)); 574 if (m == NULL) 575 return ENOMEM; 576 577 /* Do RPC to mountd. */ 578 error = krpc_call(mdsin, RPCPROG_MNT, mntver, 579 RPCMNT_MOUNT, &m, NULL, -1); 580 581 if (error != EPROGMISMATCH) 582 break; 583 /* Try lower version of mountd. */ 584 } while (--mntver >= 1); 585 if (error) 586 return error; /* message already freed */ 587 588 if (mntver != 3) 589 argp->flags &= ~NFSMNT_NFSV3; 590 591 /* The reply might have only the errno. */ 592 if (m->m_len < 4) 593 goto bad; 594 /* Have at least errno, so check that. */ 595 rdata = mtod(m, struct rdata *); 596 error = fxdr_unsigned(u_int32_t, rdata->errno); 597 if (error) 598 goto out; 599 600 /* Have errno==0, so the fh must be there. */ 601 if (mntver == 3) { 602 argp->fhsize = fxdr_unsigned(u_int32_t, rdata->fh.v3fh.fhlen); 603 if (argp->fhsize > NFSX_V3FHMAX) 604 goto bad; 605 minlen = 2 * sizeof(u_int32_t) + argp->fhsize; 606 } else { 607 argp->fhsize = NFSX_V2FH; 608 minlen = sizeof(u_int32_t) + argp->fhsize; 609 } 610 611 if (m->m_len < minlen) { 612 m = m_pullup(m, minlen); 613 if (m == NULL) 614 return (EBADRPC); 615 rdata = mtod(m, struct rdata *); 616 } 617 618 fh = (mntver == 3) ? rdata->fh.v3fh.fh : rdata->fh.v2fh; 619 bcopy(fh, argp->fh, argp->fhsize); 620 621 goto out; 622 623bad: 624 error = EBADRPC; 625 626out: 627 m_freem(m); 628 return error; 629} 630 631#endif /* ifdef NFSCLIENT */ 632