1 | /* $NetBSD: nfs_bootparam.c,v 1.38 2013/09/12 18:00:18 drochner Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 1995, 1997 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Adam Glass and Gordon W. Ross. |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * 1. Redistributions of source code must retain the above copyright |
14 | * notice, this list of conditions and the following disclaimer. |
15 | * 2. Redistributions in binary form must reproduce the above copyright |
16 | * notice, this list of conditions and the following disclaimer in the |
17 | * documentation and/or other materials provided with the distribution. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 | * POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | |
32 | /* |
33 | * Support for NFS diskless booting, Sun-style (RPC/bootparams) |
34 | */ |
35 | |
36 | #include <sys/cdefs.h> |
37 | __KERNEL_RCSID(0, "$NetBSD: nfs_bootparam.c,v 1.38 2013/09/12 18:00:18 drochner Exp $" ); |
38 | |
39 | #ifdef _KERNEL_OPT |
40 | #include "opt_nfs_boot.h" |
41 | #include "arp.h" |
42 | #endif |
43 | |
44 | #include <sys/param.h> |
45 | #include <sys/systm.h> |
46 | #include <sys/kernel.h> |
47 | #include <sys/ioctl.h> |
48 | #include <sys/proc.h> |
49 | #include <sys/mount.h> |
50 | #include <sys/mbuf.h> |
51 | #include <sys/reboot.h> |
52 | #include <sys/socket.h> |
53 | #include <sys/socketvar.h> |
54 | #include <sys/vnode.h> |
55 | |
56 | #include <net/if.h> |
57 | #include <net/if_types.h> |
58 | #include <net/route.h> |
59 | #include <net/if_ether.h> |
60 | |
61 | #include <netinet/in.h> |
62 | #include <netinet/if_inarp.h> |
63 | |
64 | #include <nfs/rpcv2.h> |
65 | #include <nfs/krpc.h> |
66 | #include <nfs/xdr_subs.h> |
67 | |
68 | #include <nfs/nfsproto.h> |
69 | #include <nfs/nfs.h> |
70 | #include <nfs/nfsmount.h> |
71 | #include <nfs/nfsdiskless.h> |
72 | #include <nfs/nfs_var.h> |
73 | |
74 | /* |
75 | * There are two implementations of NFS diskless boot. |
76 | * This implementation uses Sun RPC/bootparams, and the |
77 | * the other uses BOOTP (RFC951 - see nfs_bootdhcp.c). |
78 | * |
79 | * The Sun-style boot sequence goes as follows: |
80 | * (1) Use RARP to get our interface address |
81 | * (2) Use RPC/bootparam/whoami to get our hostname, |
82 | * our IP address, and the server's IP address. |
83 | * (3) Use RPC/bootparam/getfile to get the root path |
84 | * (4) Use RPC/mountd to get the root file handle |
85 | * (5) Use RPC/bootparam/getfile to get the swap path |
86 | * (6) Use RPC/mountd to get the swap file handle |
87 | */ |
88 | |
89 | /* bootparam RPC */ |
90 | static int bp_whoami (struct sockaddr_in *bpsin, |
91 | struct in_addr *my_ip, struct in_addr *gw_ip, struct lwp *l); |
92 | static int bp_getfile (struct sockaddr_in *bpsin, const char *key, |
93 | struct nfs_dlmount *ndm, struct lwp *l); |
94 | |
95 | |
96 | /* |
97 | * Get client name, gateway address, then |
98 | * get root and swap server:pathname info. |
99 | * RPCs: bootparam/whoami, bootparam/getfile |
100 | * |
101 | * Use the old broadcast address for the WHOAMI |
102 | * call because we do not yet know our netmask. |
103 | * The server address returned by the WHOAMI call |
104 | * is used for all subsequent bootparam RPCs. |
105 | */ |
106 | int |
107 | nfs_bootparam(struct nfs_diskless *nd, struct lwp *lwp, int *flags) |
108 | { |
109 | struct ifnet *ifp = nd->nd_ifp; |
110 | struct in_addr my_ip, arps_ip, gw_ip; |
111 | struct sockaddr_in bp_sin; |
112 | struct sockaddr_in *sin; |
113 | #ifndef NFS_BOOTPARAM_NOGATEWAY |
114 | struct nfs_dlmount *gw_ndm = 0; |
115 | char *p; |
116 | u_int32_t mask; |
117 | #endif |
118 | int error; |
119 | |
120 | /* |
121 | * Bring up the interface. (just set the "up" flag) |
122 | */ |
123 | error = nfs_boot_ifupdown(ifp, lwp, 1); |
124 | if (error) { |
125 | printf("nfs_boot: SIFFLAGS, error=%d\n" , error); |
126 | return (error); |
127 | } |
128 | |
129 | error = EADDRNOTAVAIL; |
130 | #if NARP > 0 |
131 | if (ifp->if_type == IFT_ETHER || ifp->if_type == IFT_FDDI) { |
132 | /* |
133 | * Do RARP for the interface address. |
134 | */ |
135 | error = revarpwhoarewe(ifp, &arps_ip, &my_ip); |
136 | } |
137 | #endif |
138 | if (error) { |
139 | printf("revarp failed, error=%d\n" , error); |
140 | goto out; |
141 | } |
142 | |
143 | if (!(*flags & NFS_BOOT_HAS_MYIP)) { |
144 | nd->nd_myip.s_addr = my_ip.s_addr; |
145 | printf("nfs_boot: client_addr=%s" , inet_ntoa(my_ip)); |
146 | printf(" (RARP from %s)\n" , inet_ntoa(arps_ip)); |
147 | *flags |= NFS_BOOT_HAS_MYIP; |
148 | } |
149 | |
150 | /* |
151 | * Do enough of ifconfig(8) so that the chosen interface |
152 | * can talk to the servers. (just set the address) |
153 | */ |
154 | error = nfs_boot_setaddress(ifp, lwp, my_ip.s_addr, |
155 | INADDR_ANY, INADDR_ANY); |
156 | if (error) { |
157 | printf("nfs_boot: set ifaddr, error=%d\n" , error); |
158 | goto out; |
159 | } |
160 | |
161 | /* |
162 | * Get client name and gateway address. |
163 | * RPC: bootparam/whoami |
164 | * Use the old broadcast address for the WHOAMI |
165 | * call because we do not yet know our netmask. |
166 | * The server address returned by the WHOAMI call |
167 | * is used for all subsequent booptaram RPCs. |
168 | */ |
169 | sin = &bp_sin; |
170 | memset((void *)sin, 0, sizeof(*sin)); |
171 | sin->sin_len = sizeof(*sin); |
172 | sin->sin_family = AF_INET; |
173 | sin->sin_addr.s_addr = INADDR_BROADCAST; |
174 | |
175 | /* Do the RPC/bootparam/whoami. */ |
176 | error = bp_whoami(sin, &my_ip, &gw_ip, lwp); |
177 | if (error) { |
178 | printf("nfs_boot: bootparam whoami, error=%d\n" , error); |
179 | goto delout; |
180 | } |
181 | *flags |= NFS_BOOT_HAS_SERVADDR | NFS_BOOT_HAS_SERVER; |
182 | printf("nfs_boot: server_addr=%s\n" , inet_ntoa(sin->sin_addr)); |
183 | printf("nfs_boot: hostname=%s\n" , hostname); |
184 | |
185 | /* |
186 | * Now fetch the server:pathname strings and server IP |
187 | * for root and swap. Missing swap is not fatal. |
188 | */ |
189 | error = bp_getfile(sin, "root" , &nd->nd_root, lwp); |
190 | if (error) { |
191 | printf("nfs_boot: bootparam get root: %d\n" , error); |
192 | goto delout; |
193 | } |
194 | *flags |= NFS_BOOT_HAS_ROOTPATH; |
195 | |
196 | #ifndef NFS_BOOTPARAM_NOGATEWAY |
197 | gw_ndm = kmem_alloc(sizeof(*gw_ndm), KM_SLEEP); |
198 | memset((void *)gw_ndm, 0, sizeof(*gw_ndm)); |
199 | error = bp_getfile(sin, "gateway" , gw_ndm, lwp); |
200 | if (error) { |
201 | /* No gateway supplied. No error, but try fallback. */ |
202 | error = 0; |
203 | goto nogwrepl; |
204 | } |
205 | sin = (struct sockaddr_in *) &gw_ndm->ndm_saddr; |
206 | if (sin->sin_addr.s_addr == 0) |
207 | goto out; /* no gateway */ |
208 | |
209 | /* OK, we have a gateway! */ |
210 | printf("nfs_boot: gateway=%s\n" , inet_ntoa(sin->sin_addr)); |
211 | /* Just save it. Caller adds the route. */ |
212 | nd->nd_gwip = sin->sin_addr; |
213 | *flags |= NFS_BOOT_HAS_GWIP; |
214 | |
215 | /* Look for a mask string after the colon. */ |
216 | p = strchr(gw_ndm->ndm_host, ':'); |
217 | if (p == 0) |
218 | goto out; /* no netmask */ |
219 | /* have pathname */ |
220 | p++; /* skip ':' */ |
221 | mask = inet_addr(p); /* libkern */ |
222 | if (mask == 0) |
223 | goto out; /* no netmask */ |
224 | |
225 | /* Have a netmask too! Save it; update the I/F. */ |
226 | nd->nd_mask.s_addr = mask; |
227 | *flags |= NFS_BOOT_HAS_MASK; |
228 | printf("nfs_boot: my_mask=%s\n" , inet_ntoa(nd->nd_mask)); |
229 | (void) nfs_boot_deladdress(ifp, lwp, my_ip.s_addr); |
230 | error = nfs_boot_setaddress(ifp, lwp, my_ip.s_addr, |
231 | mask, INADDR_ANY); |
232 | if (error) { |
233 | printf("nfs_boot: set ifmask, error=%d\n" , error); |
234 | goto out; |
235 | } |
236 | goto gwok; |
237 | nogwrepl: |
238 | #endif |
239 | #ifdef NFS_BOOT_GATEWAY |
240 | /* |
241 | * Note: we normally ignore the gateway address returned |
242 | * by the "bootparam/whoami" RPC above, because many old |
243 | * bootparam servers supply a bogus gateway value. |
244 | * |
245 | * These deficiencies in the bootparam RPC interface are |
246 | * circumvented by using the bootparam/getfile RPC. The |
247 | * parameter "gateway" is requested, and if its returned, |
248 | * we use the "server" part of the reply as the gateway, |
249 | * and use the "pathname" part of the reply as the mask. |
250 | * (The mask comes to us as a string.) |
251 | */ |
252 | if (gw_ip.s_addr) { |
253 | /* Our caller will add the route. */ |
254 | nd->nd_gwip = gw_ip; |
255 | *flags |= NFS_BOOT_HAS_GWIP; |
256 | } |
257 | #endif |
258 | |
259 | delout: |
260 | if (error) |
261 | (void) nfs_boot_deladdress(ifp, lwp, my_ip.s_addr); |
262 | out: |
263 | if (error) { |
264 | (void) nfs_boot_ifupdown(ifp, lwp, 0); |
265 | nfs_boot_flushrt(ifp); |
266 | } |
267 | #ifndef NFS_BOOTPARAM_NOGATEWAY |
268 | gwok: |
269 | if (gw_ndm) |
270 | kmem_free(gw_ndm, sizeof(*gw_ndm)); |
271 | #endif |
272 | if ((*flags & NFS_BOOT_ALLINFO) != NFS_BOOT_ALLINFO) |
273 | return error ? error : EADDRNOTAVAIL; |
274 | |
275 | return (error); |
276 | } |
277 | |
278 | |
279 | /* |
280 | * RPC: bootparam/whoami |
281 | * Given client IP address, get: |
282 | * client name (hostname) |
283 | * domain name (domainname) |
284 | * gateway address |
285 | * |
286 | * The hostname and domainname are set here for convenience. |
287 | * |
288 | * Note - bpsin is initialized to the broadcast address, |
289 | * and will be replaced with the bootparam server address |
290 | * after this call is complete. Have to use PMAP_PROC_CALL |
291 | * to make sure we get responses only from a servers that |
292 | * know about us (don't want to broadcast a getport call). |
293 | */ |
294 | static int |
295 | bp_whoami(struct sockaddr_in *bpsin, struct in_addr *my_ip, |
296 | struct in_addr *gw_ip, struct lwp *l) |
297 | { |
298 | /* RPC structures for PMAPPROC_CALLIT */ |
299 | struct whoami_call { |
300 | u_int32_t call_prog; |
301 | u_int32_t call_vers; |
302 | u_int32_t call_proc; |
303 | u_int32_t call_arglen; |
304 | } *call; |
305 | struct callit_reply { |
306 | u_int32_t port; |
307 | u_int32_t encap_len; |
308 | /* encapsulated data here */ |
309 | } *reply; |
310 | |
311 | struct mbuf *m, *from; |
312 | struct sockaddr_in *sin; |
313 | int error; |
314 | int16_t port; |
315 | |
316 | /* |
317 | * Build request message for PMAPPROC_CALLIT. |
318 | */ |
319 | m = m_get(M_WAIT, MT_DATA); |
320 | call = mtod(m, struct whoami_call *); |
321 | m->m_len = sizeof(*call); |
322 | call->call_prog = txdr_unsigned(BOOTPARAM_PROG); |
323 | call->call_vers = txdr_unsigned(BOOTPARAM_VERS); |
324 | call->call_proc = txdr_unsigned(BOOTPARAM_WHOAMI); |
325 | |
326 | /* |
327 | * append encapsulated data (client IP address) |
328 | */ |
329 | m->m_next = xdr_inaddr_encode(my_ip); |
330 | call->call_arglen = txdr_unsigned(m->m_next->m_len); |
331 | |
332 | /* RPC: portmap/callit */ |
333 | bpsin->sin_port = htons(PMAPPORT); |
334 | error = krpc_call(bpsin, PMAPPROG, PMAPVERS, |
335 | PMAPPROC_CALLIT, &m, &from, l); |
336 | if (error) { |
337 | m_freem(m); |
338 | return error; |
339 | } |
340 | |
341 | /* |
342 | * Parse result message. |
343 | */ |
344 | if (m->m_len < sizeof(*reply)) { |
345 | m = m_pullup(m, sizeof(*reply)); |
346 | if (m == NULL) |
347 | goto bad; |
348 | } |
349 | reply = mtod(m, struct callit_reply *); |
350 | port = fxdr_unsigned(u_int32_t, reply->port); |
351 | m_adj(m, sizeof(*reply)); |
352 | |
353 | /* |
354 | * Save bootparam server address |
355 | */ |
356 | sin = mtod(from, struct sockaddr_in *); |
357 | bpsin->sin_port = htons(port); |
358 | bpsin->sin_addr.s_addr = sin->sin_addr.s_addr; |
359 | |
360 | /* client name */ |
361 | hostnamelen = MAXHOSTNAMELEN-1; |
362 | m = xdr_string_decode(m, hostname, &hostnamelen); |
363 | if (m == NULL) |
364 | goto bad; |
365 | |
366 | /* domain name */ |
367 | domainnamelen = MAXHOSTNAMELEN-1; |
368 | m = xdr_string_decode(m, domainname, &domainnamelen); |
369 | if (m == NULL) |
370 | goto bad; |
371 | |
372 | /* gateway address */ |
373 | m = xdr_inaddr_decode(m, gw_ip); |
374 | if (m == NULL) |
375 | goto bad; |
376 | |
377 | /* success */ |
378 | goto out; |
379 | |
380 | bad: |
381 | printf("nfs_boot: bootparam_whoami: bad reply\n" ); |
382 | error = EBADRPC; |
383 | |
384 | out: |
385 | m_freem(from); |
386 | if (m) |
387 | m_freem(m); |
388 | return(error); |
389 | } |
390 | |
391 | |
392 | /* |
393 | * RPC: bootparam/getfile |
394 | * Given client name and file "key", get: |
395 | * server name |
396 | * server IP address |
397 | * server pathname |
398 | */ |
399 | static int |
400 | bp_getfile(struct sockaddr_in *bpsin, const char *key, |
401 | struct nfs_dlmount *ndm, struct lwp *l) |
402 | { |
403 | char pathname[MNAMELEN]; |
404 | struct in_addr inaddr; |
405 | struct sockaddr_in *sin; |
406 | struct mbuf *m; |
407 | char *serv_name; |
408 | int error, sn_len, path_len; |
409 | |
410 | /* |
411 | * Build request message. |
412 | */ |
413 | |
414 | /* client name (hostname) */ |
415 | m = xdr_string_encode(hostname, hostnamelen); |
416 | if (m == NULL) |
417 | return (ENOMEM); |
418 | |
419 | /* key name (root or swap) */ |
420 | /*XXXUNCONST*/ |
421 | m->m_next = xdr_string_encode(__UNCONST(key), strlen(key)); |
422 | if (m->m_next == NULL) |
423 | return (ENOMEM); |
424 | |
425 | /* RPC: bootparam/getfile */ |
426 | error = krpc_call(bpsin, BOOTPARAM_PROG, BOOTPARAM_VERS, |
427 | BOOTPARAM_GETFILE, &m, NULL, l); |
428 | if (error) |
429 | return error; |
430 | |
431 | /* |
432 | * Parse result message. |
433 | */ |
434 | |
435 | /* server name */ |
436 | serv_name = &ndm->ndm_host[0]; |
437 | sn_len = sizeof(ndm->ndm_host) - 1; |
438 | m = xdr_string_decode(m, serv_name, &sn_len); |
439 | if (m == NULL) |
440 | goto bad; |
441 | |
442 | /* server IP address (mountd/NFS) */ |
443 | m = xdr_inaddr_decode(m, &inaddr); |
444 | if (m == NULL) |
445 | goto bad; |
446 | |
447 | /* server pathname */ |
448 | path_len = sizeof(pathname) - 1; |
449 | m = xdr_string_decode(m, pathname, &path_len); |
450 | if (m == NULL) |
451 | goto bad; |
452 | |
453 | /* |
454 | * Store the results in the nfs_dlmount. |
455 | * The strings become "server:pathname" |
456 | */ |
457 | sin = (struct sockaddr_in *) &ndm->ndm_saddr; |
458 | memset((void *)sin, 0, sizeof(*sin)); |
459 | sin->sin_len = sizeof(*sin); |
460 | sin->sin_family = AF_INET; |
461 | sin->sin_addr = inaddr; |
462 | if ((sn_len + 1 + path_len + 1) > sizeof(ndm->ndm_host)) { |
463 | printf("nfs_boot: getfile name too long\n" ); |
464 | error = EIO; |
465 | goto out; |
466 | } |
467 | ndm->ndm_host[sn_len] = ':'; |
468 | memcpy(ndm->ndm_host + sn_len + 1, pathname, path_len + 1); |
469 | |
470 | /* success */ |
471 | goto out; |
472 | |
473 | bad: |
474 | printf("nfs_boot: bootparam_getfile: bad reply\n" ); |
475 | error = EBADRPC; |
476 | |
477 | out: |
478 | m_freem(m); |
479 | return(0); |
480 | } |
481 | |