1 | /* $NetBSD: nfs_bootdhcp.c,v 1.56 2016/06/10 13:27:16 ozaki-r 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 with BOOTP (RFC951, RFC1048) |
34 | * |
35 | * History: |
36 | * |
37 | * Tor Egge developed the initial version of this code based on |
38 | * the Sun RPC/bootparam sources nfs_boot.c and krpc_subr.c and |
39 | * submitted that work to NetBSD as bugreport "kern/2351" on |
40 | * 29 Apr 1996. |
41 | * |
42 | * Gordon Ross reorganized Tor's version into this form and |
43 | * integrated it into the NetBSD sources during Aug 1997. |
44 | */ |
45 | |
46 | #include <sys/cdefs.h> |
47 | __KERNEL_RCSID(0, "$NetBSD: nfs_bootdhcp.c,v 1.56 2016/06/10 13:27:16 ozaki-r Exp $" ); |
48 | |
49 | #ifdef _KERNEL_OPT |
50 | #include "opt_nfs_boot.h" |
51 | #include "opt_tftproot.h" |
52 | #endif |
53 | |
54 | #include <sys/param.h> |
55 | #include <sys/systm.h> |
56 | #include <sys/kernel.h> |
57 | #include <sys/device.h> |
58 | #include <sys/ioctl.h> |
59 | #include <sys/proc.h> |
60 | #include <sys/mount.h> |
61 | #include <sys/mbuf.h> |
62 | #include <sys/reboot.h> |
63 | #include <sys/socket.h> |
64 | #include <sys/socketvar.h> |
65 | |
66 | #include <net/if.h> |
67 | #include <net/if_types.h> |
68 | #include <net/if_arp.h> /* ARPHRD_ETHER, etc. */ |
69 | #include <net/if_dl.h> |
70 | #include <net/if_ether.h> |
71 | #include <net/route.h> |
72 | |
73 | #include <netinet/in.h> |
74 | #include <netinet/if_inarp.h> |
75 | |
76 | #include <nfs/rpcv2.h> |
77 | |
78 | #include <nfs/nfsproto.h> |
79 | #include <nfs/nfs.h> |
80 | #include <nfs/nfsmount.h> |
81 | #include <nfs/nfsdiskless.h> |
82 | |
83 | /* |
84 | * There are two implementations of NFS diskless boot. |
85 | * This implementation uses BOOTP (RFC951, RFC1048), and |
86 | * the other uses Sun RPC/bootparams (nfs_bootparam.c). |
87 | * |
88 | * This method gets everything it needs with one BOOTP |
89 | * request and reply. Note that this actually uses only |
90 | * the old BOOTP functionality subset of DHCP. It is not |
91 | * clear that DHCP provides any advantage over BOOTP for |
92 | * diskless boot. DHCP allows the server to assign an IP |
93 | * address without any a-priori knowledge of the client, |
94 | * but we require that the server has a-priori knowledge |
95 | * of the client so it can export our (unique) NFS root. |
96 | * Given that the server needs a-priori knowledge about |
97 | * the client anyway, it might as well assign a fixed IP |
98 | * address for the client and support BOOTP. |
99 | * |
100 | * On the other hand, disk-FULL clients may use DHCP, but |
101 | * in that case the DHCP client should be user-mode code, |
102 | * and has no bearing on the code below. -gwr |
103 | */ |
104 | |
105 | /* Begin stuff from bootp.h */ |
106 | /* Definitions from RFC951 */ |
107 | #define BP_CHADDR_LEN 16 |
108 | #define BP_SNAME_LEN 64 |
109 | #define BP_FILE_LEN 128 |
110 | #define BP_VEND_LEN 64 |
111 | struct bootp { |
112 | u_int8_t bp_op; /* packet opcode type */ |
113 | u_int8_t bp_htype; /* hardware addr type */ |
114 | u_int8_t bp_hlen; /* hardware addr length */ |
115 | u_int8_t bp_hops; /* gateway hops */ |
116 | u_int32_t bp_xid; /* transaction ID */ |
117 | u_int16_t bp_secs; /* seconds since boot began */ |
118 | u_int16_t bp_flags; /* RFC1532 broadcast, etc. */ |
119 | struct in_addr bp_ciaddr; /* client IP address */ |
120 | struct in_addr bp_yiaddr; /* 'your' IP address */ |
121 | struct in_addr bp_siaddr; /* server IP address */ |
122 | struct in_addr bp_giaddr; /* gateway IP address */ |
123 | u_int8_t bp_chaddr[BP_CHADDR_LEN]; /* client hardware address */ |
124 | char bp_sname[BP_SNAME_LEN]; /* server host name */ |
125 | char bp_file[BP_FILE_LEN]; /* boot file name */ |
126 | u_int8_t bp_vend[BP_VEND_LEN]; /* RFC1048 options */ |
127 | /* |
128 | * Note that BOOTP packets are allowed to be longer |
129 | * (see RFC 1532 sect. 2.1) and common practice is to |
130 | * allow the option data in bp_vend to extend into the |
131 | * additional space provided in longer packets. |
132 | */ |
133 | }; |
134 | |
135 | #define IPPORT_BOOTPS 67 |
136 | #define IPPORT_BOOTPC 68 |
137 | |
138 | #define BOOTREQUEST 1 |
139 | #define BOOTREPLY 2 |
140 | |
141 | /* |
142 | * Is this available from the sockaddr_dl somehow? |
143 | * Perhaps (struct arphdr)->ar_hrd = ARPHRD_ETHER? |
144 | * The interface has ->if_type but not the ARP fmt. |
145 | */ |
146 | #define HTYPE_ETHERNET 1 |
147 | #define HTYPE_IEEE802 6 |
148 | |
149 | /* |
150 | * Vendor magic cookie (v_magic) for RFC1048 |
151 | */ |
152 | static const u_int8_t vm_rfc1048[4] = { 99, 130, 83, 99 }; |
153 | |
154 | /* |
155 | * Tag values used to specify what information is being supplied in |
156 | * the vendor (options) data area of the packet. |
157 | */ |
158 | /* RFC 1048 */ |
159 | #define TAG_END ((unsigned char) 255) |
160 | #define TAG_PAD ((unsigned char) 0) |
161 | #define TAG_SUBNET_MASK ((unsigned char) 1) |
162 | #define TAG_TIME_OFFSET ((unsigned char) 2) |
163 | #define TAG_GATEWAY ((unsigned char) 3) |
164 | #define TAG_TIME_SERVER ((unsigned char) 4) |
165 | #define TAG_NAME_SERVER ((unsigned char) 5) |
166 | #define TAG_DOMAIN_SERVER ((unsigned char) 6) |
167 | #define TAG_LOG_SERVER ((unsigned char) 7) |
168 | #define TAG_COOKIE_SERVER ((unsigned char) 8) |
169 | #define TAG_LPR_SERVER ((unsigned char) 9) |
170 | #define TAG_IMPRESS_SERVER ((unsigned char) 10) |
171 | #define TAG_RLP_SERVER ((unsigned char) 11) |
172 | #define TAG_HOST_NAME ((unsigned char) 12) |
173 | #define TAG_BOOT_SIZE ((unsigned char) 13) |
174 | /* RFC 1395 */ |
175 | #define TAG_DUMP_FILE ((unsigned char) 14) |
176 | #define TAG_DOMAIN_NAME ((unsigned char) 15) |
177 | #define TAG_SWAP_SERVER ((unsigned char) 16) |
178 | #define TAG_ROOT_PATH ((unsigned char) 17) |
179 | /* RFC 2132 */ |
180 | #define TAG_INTERFACE_MTU ((unsigned char) 26) |
181 | /* End of stuff from bootp.h */ |
182 | |
183 | #ifdef NFS_BOOT_DHCP |
184 | #define TAG_REQ_ADDR ((unsigned char) 50) |
185 | #define TAG_LEASETIME ((unsigned char) 51) |
186 | #define TAG_OVERLOAD ((unsigned char) 52) |
187 | #define TAG_DHCP_MSGTYPE ((unsigned char) 53) |
188 | #define TAG_SERVERID ((unsigned char) 54) |
189 | #define TAG_PARAM_REQ ((unsigned char) 55) |
190 | #define TAG_MSG ((unsigned char) 56) |
191 | #define TAG_MAXSIZE ((unsigned char) 57) |
192 | #define TAG_T1 ((unsigned char) 58) |
193 | #define TAG_T2 ((unsigned char) 59) |
194 | #define TAG_CLASSID ((unsigned char) 60) |
195 | #define TAG_CLIENTID ((unsigned char) 61) |
196 | #endif |
197 | |
198 | #ifdef NFS_BOOT_DHCP |
199 | #define DHCPDISCOVER 1 |
200 | #define DHCPOFFER 2 |
201 | #define DHCPREQUEST 3 |
202 | #define DHCPDECLINE 4 |
203 | #define DHCPACK 5 |
204 | #define DHCPNAK 6 |
205 | #define DHCPRELEASE 7 |
206 | #endif |
207 | |
208 | #define IP_MIN_MTU 576 |
209 | |
210 | #ifdef NFS_BOOT_DHCP |
211 | #define BOOTP_SIZE_MAX (sizeof(struct bootp)+312-64) |
212 | #else |
213 | /* |
214 | * The "extended" size is somewhat arbitrary, but is |
215 | * constrained by the maximum message size specified |
216 | * by RFC1533 (567 total). This value increases the |
217 | * space for options from 64 bytes to 256 bytes. |
218 | */ |
219 | #define BOOTP_SIZE_MAX (sizeof(struct bootp)+256-64) |
220 | #endif |
221 | #define BOOTP_SIZE_MIN (sizeof(struct bootp)) |
222 | |
223 | /* Convenience macro */ |
224 | #define INTOHL(ina) ((u_int32_t)ntohl((ina).s_addr)) |
225 | |
226 | static int bootpc_call (struct nfs_diskless *, struct lwp *, int *); |
227 | static void bootp_extract (struct bootp *, int, struct nfs_diskless *, int *); |
228 | |
229 | #ifdef DEBUG_NFS_BOOT_DHCP |
230 | #define DPRINTF(s) printf s |
231 | #else |
232 | #define DPRINTF(s) |
233 | #endif |
234 | |
235 | |
236 | /* |
237 | * Get our boot parameters using BOOTP. |
238 | */ |
239 | int |
240 | nfs_bootdhcp(struct nfs_diskless *nd, struct lwp *lwp, int *flags) |
241 | { |
242 | struct ifnet *ifp = nd->nd_ifp; |
243 | int error; |
244 | |
245 | /* |
246 | * Do enough of ifconfig(8) so that the chosen interface |
247 | * can talk to the servers. Use address zero for now. |
248 | */ |
249 | error = nfs_boot_setaddress(ifp, lwp, |
250 | *flags & NFS_BOOT_HAS_MYIP ? nd->nd_myip.s_addr : INADDR_ANY, |
251 | *flags & NFS_BOOT_HAS_MASK ? nd->nd_mask.s_addr : INADDR_ANY, |
252 | INADDR_BROADCAST); |
253 | if (error) { |
254 | printf("nfs_boot: set ifaddr zero, error=%d\n" , error); |
255 | return (error); |
256 | } |
257 | |
258 | /* This function call does the real send/recv work. */ |
259 | error = bootpc_call(nd, lwp, flags); |
260 | |
261 | /* Get rid of the temporary (zero) IP address. */ |
262 | (void) nfs_boot_deladdress(ifp, lwp, INADDR_ANY); |
263 | |
264 | /* NOW we can test the error from bootpc_call. */ |
265 | if (error) |
266 | goto out; |
267 | |
268 | /* |
269 | * Do ifconfig with our real IP address and mask. |
270 | */ |
271 | error = nfs_boot_setaddress(ifp, lwp, nd->nd_myip.s_addr, |
272 | nd->nd_mask.s_addr, INADDR_ANY); |
273 | if (error) { |
274 | printf("nfs_boot: set ifaddr real, error=%d\n" , error); |
275 | goto out; |
276 | } |
277 | |
278 | if ((*flags & NFS_BOOT_ALLINFO) != NFS_BOOT_ALLINFO) { |
279 | printf("nfs_boot: missing options (need IP, netmask, " |
280 | "gateway, next-server, root-path)\n" ); |
281 | return EADDRNOTAVAIL; |
282 | } |
283 | |
284 | out: |
285 | if (error) { |
286 | (void) nfs_boot_ifupdown(ifp, lwp, 0); |
287 | nfs_boot_flushrt(ifp); |
288 | } |
289 | return (error); |
290 | } |
291 | |
292 | struct bootpcontext { |
293 | int xid; |
294 | const u_char *haddr; |
295 | u_char halen; |
296 | struct bootp *replybuf; |
297 | int replylen; |
298 | #ifdef NFS_BOOT_DHCP |
299 | char expected_dhcpmsgtype, dhcp_ok; |
300 | struct in_addr dhcp_serverip; |
301 | #endif |
302 | }; |
303 | |
304 | static int bootpset (struct mbuf*, void*, int); |
305 | static int bootpcheck (struct mbuf**, void*); |
306 | |
307 | static int |
308 | bootpset(struct mbuf *m, void *context, int waited) |
309 | { |
310 | struct bootp *bootp; |
311 | |
312 | /* we know it's contigous (in 1 mbuf cluster) */ |
313 | bootp = mtod(m, struct bootp*); |
314 | |
315 | bootp->bp_secs = htons(waited); |
316 | |
317 | return (0); |
318 | } |
319 | |
320 | static int |
321 | bootpcheck(struct mbuf **mp, void *context) |
322 | { |
323 | struct bootp *bootp; |
324 | struct bootpcontext *bpc = context; |
325 | struct mbuf *m = *mp; |
326 | u_int tag, len; |
327 | u_char *p, *limit; |
328 | |
329 | /* |
330 | * Is this a valid reply? |
331 | */ |
332 | if (m->m_pkthdr.len < BOOTP_SIZE_MIN) { |
333 | DPRINTF(("bootpcheck: short packet %d < %zu\n" , |
334 | m->m_pkthdr.len, BOOTP_SIZE_MIN)); |
335 | return (-1); |
336 | } |
337 | if (m->m_pkthdr.len > BOOTP_SIZE_MAX) { |
338 | DPRINTF(("Bootpcheck: long packet %d > %zu\n" , |
339 | m->m_pkthdr.len, BOOTP_SIZE_MAX)); |
340 | return (-1); |
341 | } |
342 | |
343 | /* |
344 | * don't make first checks more expensive than necessary |
345 | */ |
346 | if (m->m_len < offsetof(struct bootp, bp_sname)) { |
347 | m = *mp = m_pullup(m, offsetof(struct bootp, bp_sname)); |
348 | if (m == NULL) { |
349 | DPRINTF(("bootpcheck: m_pullup failed\n" )); |
350 | return (-1); |
351 | } |
352 | } |
353 | bootp = mtod(m, struct bootp*); |
354 | |
355 | if (bootp->bp_op != BOOTREPLY) { |
356 | DPRINTF(("bootpcheck: op %d is not reply\n" , bootp->bp_op)); |
357 | return (-1); |
358 | } |
359 | if (bootp->bp_hlen != bpc->halen) { |
360 | DPRINTF(("bootpcheck: hlen %d != %d\n" , bootp->bp_hlen, |
361 | bpc->halen)); |
362 | return (-1); |
363 | } |
364 | if (memcmp(bootp->bp_chaddr, bpc->haddr, bpc->halen)) { |
365 | #ifdef DEBUG_NFS_BOOT_DHCP |
366 | char *bp_chaddr, *haddr; |
367 | |
368 | bp_chaddr = malloc(3 * bpc->halen, M_TEMP, M_WAITOK); |
369 | haddr = malloc(3 * bpc->halen, M_TEMP, M_WAITOK); |
370 | |
371 | DPRINTF(("bootpcheck: incorrect hwaddr %s != %s\n" , |
372 | ether_snprintf(bp_chaddr, 3 * bpc->halen, |
373 | bootp->bp_chaddr), |
374 | ether_snprintf(haddr, 3 * bpc->halen, bpc->haddr))); |
375 | |
376 | free(bp_chaddr, M_TEMP); |
377 | free(haddr, M_TEMP); |
378 | #endif |
379 | return (-1); |
380 | } |
381 | if (bootp->bp_xid != bpc->xid) { |
382 | DPRINTF(("bootpcheck: xid %d != %d\n" , bootp->bp_xid, |
383 | bpc->xid)); |
384 | return (-1); |
385 | } |
386 | |
387 | /* |
388 | * OK, it's worth to look deeper. |
389 | * We copy the mbuf into a flat buffer here because |
390 | * m_pullup() is a bit limited for this purpose |
391 | * (doesn't allocate a cluster if necessary). |
392 | */ |
393 | bpc->replylen = m->m_pkthdr.len; |
394 | m_copydata(m, 0, bpc->replylen, (void *)bpc->replybuf); |
395 | bootp = bpc->replybuf; |
396 | |
397 | /* |
398 | * Check if the IP address we get looks correct. |
399 | * (DHCP servers can send junk to unknown clients.) |
400 | * XXX more checks might be needed |
401 | */ |
402 | if (bootp->bp_yiaddr.s_addr == INADDR_ANY || |
403 | bootp->bp_yiaddr.s_addr == INADDR_BROADCAST) { |
404 | printf("nfs_boot: wrong IP addr %s" , |
405 | inet_ntoa(bootp->bp_yiaddr)); |
406 | goto warn; |
407 | } |
408 | |
409 | /* |
410 | * Check the vendor data. |
411 | */ |
412 | if (memcmp(bootp->bp_vend, vm_rfc1048, 4)) { |
413 | printf("nfs_boot: reply missing options" ); |
414 | goto warn; |
415 | } |
416 | p = &bootp->bp_vend[4]; |
417 | limit = ((u_char*)bootp) + bpc->replylen; |
418 | while (p < limit) { |
419 | tag = *p++; |
420 | if (tag == TAG_END) |
421 | break; |
422 | if (tag == TAG_PAD) |
423 | continue; |
424 | len = *p++; |
425 | if ((p + len) > limit) { |
426 | printf("nfs_boot: option %d too long" , tag); |
427 | goto warn; |
428 | } |
429 | switch (tag) { |
430 | #ifdef NFS_BOOT_DHCP |
431 | case TAG_DHCP_MSGTYPE: |
432 | if (*p != bpc->expected_dhcpmsgtype) |
433 | return (-1); |
434 | bpc->dhcp_ok = 1; |
435 | break; |
436 | case TAG_SERVERID: |
437 | memcpy(&bpc->dhcp_serverip.s_addr, p, |
438 | sizeof(bpc->dhcp_serverip.s_addr)); |
439 | break; |
440 | #endif |
441 | default: |
442 | break; |
443 | } |
444 | p += len; |
445 | } |
446 | return (0); |
447 | |
448 | warn: |
449 | printf(" (bad reply from %s)\n" , inet_ntoa(bootp->bp_siaddr)); |
450 | return (-1); |
451 | } |
452 | |
453 | static void |
454 | bootp_addvend(u_char *area) |
455 | { |
456 | #ifdef NFS_BOOT_DHCP |
457 | char vci[64]; |
458 | int vcilen; |
459 | |
460 | *area++ = TAG_PARAM_REQ; |
461 | *area++ = 7; |
462 | *area++ = TAG_SUBNET_MASK; |
463 | *area++ = TAG_GATEWAY; |
464 | *area++ = TAG_HOST_NAME; |
465 | *area++ = TAG_DOMAIN_NAME; |
466 | *area++ = TAG_ROOT_PATH; |
467 | *area++ = TAG_SWAP_SERVER; |
468 | *area++ = TAG_INTERFACE_MTU; |
469 | |
470 | /* Insert a NetBSD Vendor Class Identifier option. */ |
471 | snprintf(vci, sizeof(vci), "%s:%s:kernel:%s" , ostype, MACHINE, |
472 | osrelease); |
473 | vcilen = strlen(vci); |
474 | *area++ = TAG_CLASSID; |
475 | *area++ = vcilen; |
476 | (void)memcpy(area, vci, vcilen); |
477 | area += vcilen; |
478 | #endif |
479 | *area = TAG_END; |
480 | } |
481 | |
482 | static int |
483 | bootpc_call(struct nfs_diskless *nd, struct lwp *lwp, int *flags) |
484 | { |
485 | struct socket *so; |
486 | struct ifnet *ifp = nd->nd_ifp; |
487 | static u_int32_t xid = ~0xFF; |
488 | struct bootp *bootp; /* request */ |
489 | struct mbuf *m; |
490 | struct sockaddr_in sin; |
491 | int error; |
492 | const u_char *haddr; |
493 | u_char hafmt, halen; |
494 | struct bootpcontext bpc; |
495 | unsigned int index; |
496 | |
497 | error = socreate(AF_INET, &so, SOCK_DGRAM, 0, lwp, NULL); |
498 | if (error) { |
499 | printf("bootp: socreate, error=%d\n" , error); |
500 | return (error); |
501 | } |
502 | |
503 | /* |
504 | * Initialize to NULL anything that will hold an allocation, |
505 | * and free each at the end if not null. |
506 | */ |
507 | bpc.replybuf = NULL; |
508 | m = NULL; |
509 | |
510 | /* Record our H/W (Ethernet) address. */ |
511 | { const struct sockaddr_dl *sdl = ifp->if_sadl; |
512 | switch (sdl->sdl_type) { |
513 | case IFT_ISO88025: |
514 | hafmt = HTYPE_IEEE802; |
515 | break; |
516 | case IFT_ETHER: |
517 | case IFT_FDDI: |
518 | hafmt = HTYPE_ETHERNET; |
519 | break; |
520 | default: |
521 | printf("bootp: unsupported interface type %d\n" , |
522 | sdl->sdl_type); |
523 | error = EINVAL; |
524 | goto out; |
525 | } |
526 | halen = sdl->sdl_alen; |
527 | haddr = (const unsigned char *)CLLADDR(sdl); |
528 | } |
529 | |
530 | /* |
531 | * Skip the route table when sending on this socket. |
532 | * If this is not done, ip_output finds the loopback |
533 | * interface (why?) and then fails because broadcast |
534 | * is not supported on that interface... |
535 | */ |
536 | { int32_t opt; |
537 | |
538 | opt = 1; |
539 | error = so_setsockopt(NULL, so, SOL_SOCKET, SO_DONTROUTE, &opt, |
540 | sizeof(opt)); |
541 | } |
542 | if (error) { |
543 | DPRINTF(("bootpc_call: SO_DONTROUTE failed %d\n" , error)); |
544 | goto out; |
545 | } |
546 | |
547 | /* Enable broadcast. */ |
548 | if ((error = nfs_boot_enbroadcast(so))) { |
549 | DPRINTF(("bootpc_call: SO_BROADCAST failed %d\n" , error)); |
550 | goto out; |
551 | } |
552 | |
553 | /* |
554 | * Set some TTL so we can boot through routers. |
555 | * Real BOOTP forwarding agents don't need this; they obey "bp_hops" |
556 | * and set "bp_giaddr", thus rewrite the packet anyway. |
557 | * The "helper-address" feature of some popular router vendor seems |
558 | * to do simple IP forwarding and drops packets with (ip_ttl == 1). |
559 | */ |
560 | { u_char opt; |
561 | |
562 | opt = 7; |
563 | error = so_setsockopt(NULL, so, IPPROTO_IP, IP_MULTICAST_TTL, |
564 | &opt, sizeof(opt)); |
565 | } |
566 | if (error) { |
567 | DPRINTF(("bootpc_call: IP_MULTICAST_TTL failed %d\n" , error)); |
568 | goto out; |
569 | } |
570 | |
571 | /* Set the receive timeout for the socket. */ |
572 | if ((error = nfs_boot_setrecvtimo(so))) { |
573 | DPRINTF(("bootpc_call: SO_RCVTIMEO failed %d\n" , error)); |
574 | goto out; |
575 | } |
576 | |
577 | /* |
578 | * Bind the local endpoint to a bootp client port. |
579 | */ |
580 | if ((error = nfs_boot_sobind_ipport(so, IPPORT_BOOTPC, lwp))) { |
581 | DPRINTF(("bootpc_call: bind failed %d\n" , error)); |
582 | goto out; |
583 | } |
584 | |
585 | /* |
586 | * Setup socket address for the server. |
587 | */ |
588 | sin.sin_len = sizeof(sin); |
589 | sin.sin_family = AF_INET; |
590 | sin.sin_addr.s_addr = INADDR_BROADCAST; |
591 | sin.sin_port = htons(IPPORT_BOOTPS); |
592 | |
593 | /* |
594 | * Allocate buffer used for request |
595 | */ |
596 | m = m_gethdr(M_WAIT, MT_DATA); |
597 | m_clget(m, M_WAIT); |
598 | bootp = mtod(m, struct bootp*); |
599 | m->m_pkthdr.len = m->m_len = BOOTP_SIZE_MAX; |
600 | m_reset_rcvif(m); |
601 | |
602 | /* |
603 | * Build the BOOTP reqest message. |
604 | * Note: xid is host order! (opaque to server) |
605 | */ |
606 | memset((void *)bootp, 0, BOOTP_SIZE_MAX); |
607 | bootp->bp_op = BOOTREQUEST; |
608 | bootp->bp_htype = hafmt; |
609 | bootp->bp_hlen = halen; /* Hardware address length */ |
610 | bootp->bp_xid = ++xid; |
611 | memcpy(bootp->bp_chaddr, haddr, halen); |
612 | #ifdef NFS_BOOT_BOOTP_REQFILE |
613 | strncpy(bootp->bp_file, NFS_BOOT_BOOTP_REQFILE, sizeof(bootp->bp_file)); |
614 | #endif |
615 | /* Fill-in the vendor data. */ |
616 | memcpy(bootp->bp_vend, vm_rfc1048, 4); |
617 | index = 4; |
618 | #ifdef NFS_BOOT_DHCP |
619 | bootp->bp_vend[index++] = TAG_DHCP_MSGTYPE; |
620 | bootp->bp_vend[index++] = 1; |
621 | bootp->bp_vend[index++] = DHCPDISCOVER; |
622 | #endif |
623 | bootp_addvend(&bootp->bp_vend[index]); |
624 | |
625 | bpc.xid = xid; |
626 | bpc.haddr = haddr; |
627 | bpc.halen = halen; |
628 | bpc.replybuf = malloc(BOOTP_SIZE_MAX, M_DEVBUF, M_WAITOK); |
629 | if (bpc.replybuf == NULL) |
630 | panic("nfs_boot: malloc reply buf" ); |
631 | #ifdef NFS_BOOT_DHCP |
632 | bpc.expected_dhcpmsgtype = DHCPOFFER; |
633 | bpc.dhcp_ok = 0; |
634 | #endif |
635 | |
636 | error = nfs_boot_sendrecv(so, &sin, bootpset, m, |
637 | bootpcheck, NULL, NULL, &bpc, lwp); |
638 | if (error) |
639 | goto out; |
640 | |
641 | #ifdef NFS_BOOT_DHCP |
642 | if (bpc.dhcp_ok) { |
643 | u_int32_t leasetime; |
644 | index = 6; |
645 | bootp->bp_vend[index++] = DHCPREQUEST; |
646 | bootp->bp_vend[index++] = TAG_REQ_ADDR; |
647 | bootp->bp_vend[index++] = 4; |
648 | memcpy(&bootp->bp_vend[index], &bpc.replybuf->bp_yiaddr, 4); |
649 | index += 4; |
650 | bootp->bp_vend[index++] = TAG_SERVERID; |
651 | bootp->bp_vend[index++] = 4; |
652 | memcpy(&bootp->bp_vend[index], &bpc.dhcp_serverip.s_addr, 4); |
653 | index += 4; |
654 | bootp->bp_vend[index++] = TAG_LEASETIME; |
655 | bootp->bp_vend[index++] = 4; |
656 | leasetime = htonl(300); |
657 | memcpy(&bootp->bp_vend[index], &leasetime, 4); |
658 | index += 4; |
659 | bootp_addvend(&bootp->bp_vend[index]); |
660 | |
661 | bpc.expected_dhcpmsgtype = DHCPACK; |
662 | |
663 | error = nfs_boot_sendrecv(so, &sin, bootpset, m, |
664 | bootpcheck, NULL, NULL, &bpc, lwp); |
665 | if (error) |
666 | goto out; |
667 | } |
668 | #endif |
669 | |
670 | /* |
671 | * bootpcheck() has copied the receive mbuf into |
672 | * the buffer at bpc.replybuf. |
673 | */ |
674 | #ifdef NFS_BOOT_DHCP |
675 | printf("nfs_boot: %s next-server: %s\n" , |
676 | (bpc.dhcp_ok ? "DHCP" : "BOOTP" ), |
677 | #else |
678 | printf("nfs_boot: BOOTP next-server: %s\n" , |
679 | #endif |
680 | inet_ntoa(bpc.replybuf->bp_siaddr)); |
681 | |
682 | bootp_extract(bpc.replybuf, bpc.replylen, nd, flags); |
683 | |
684 | out: |
685 | if (bpc.replybuf) |
686 | free(bpc.replybuf, M_DEVBUF); |
687 | if (m) |
688 | m_freem(m); |
689 | soclose(so); |
690 | return (error); |
691 | } |
692 | |
693 | static void |
694 | (struct bootp *bootp, int replylen, |
695 | struct nfs_diskless *nd, int *flags) |
696 | { |
697 | struct sockaddr_in *sin; |
698 | struct in_addr netmask; |
699 | struct in_addr gateway; |
700 | struct in_addr rootserver; |
701 | char *myname; /* my hostname */ |
702 | char *mydomain; /* my domainname */ |
703 | char *rootpath; |
704 | uint16_t myinterfacemtu; |
705 | int mynamelen; |
706 | int mydomainlen; |
707 | int rootpathlen; |
708 | int overloaded; |
709 | u_int tag, len; |
710 | u_char *p, *limit; |
711 | |
712 | /* Default these to "unspecified". */ |
713 | netmask.s_addr = 0; |
714 | gateway.s_addr = 0; |
715 | mydomain = myname = rootpath = NULL; |
716 | mydomainlen = mynamelen = rootpathlen = 0; |
717 | |
718 | /* default root server to bootp next-server */ |
719 | rootserver = bootp->bp_siaddr; |
720 | /* assume that server name field is not overloaded by default */ |
721 | overloaded = 0; |
722 | /* MTU can't be less than IP_MIN_MTU, set to 0 to indicate unset */ |
723 | myinterfacemtu = 0; |
724 | |
725 | p = &bootp->bp_vend[4]; |
726 | limit = ((u_char*)bootp) + replylen; |
727 | while (p < limit) { |
728 | tag = *p++; |
729 | if (tag == TAG_END) |
730 | break; |
731 | if (tag == TAG_PAD) |
732 | continue; |
733 | len = *p++; |
734 | #if 0 /* already done in bootpcheck() */ |
735 | if ((p + len) > limit) { |
736 | printf("nfs_boot: option %d too long\n" , tag); |
737 | break; |
738 | } |
739 | #endif |
740 | switch (tag) { |
741 | case TAG_SUBNET_MASK: |
742 | if (len < 4) { |
743 | printf("nfs_boot: subnet mask < 4 bytes\n" ); |
744 | break; |
745 | } |
746 | memcpy(&netmask, p, 4); |
747 | break; |
748 | case TAG_GATEWAY: |
749 | /* Routers */ |
750 | if (len < 4) { |
751 | printf("nfs_boot: routers < 4 bytes\n" ); |
752 | break; |
753 | } |
754 | memcpy(&gateway, p, 4); |
755 | break; |
756 | case TAG_HOST_NAME: |
757 | if (len >= sizeof(hostname)) { |
758 | printf("nfs_boot: host name >= %lu bytes\n" , |
759 | (u_long)sizeof(hostname)); |
760 | break; |
761 | } |
762 | myname = p; |
763 | mynamelen = len; |
764 | break; |
765 | case TAG_DOMAIN_NAME: |
766 | if (len >= sizeof(domainname)) { |
767 | printf("nfs_boot: domain name >= %lu bytes\n" , |
768 | (u_long)sizeof(domainname)); |
769 | break; |
770 | } |
771 | mydomain = p; |
772 | mydomainlen = len; |
773 | break; |
774 | case TAG_ROOT_PATH: |
775 | /* Leave some room for the server name. */ |
776 | if (len >= (MNAMELEN-10)) { |
777 | printf("nfs_boot: rootpath >= %d bytes\n" , |
778 | (MNAMELEN-10)); |
779 | break; |
780 | } |
781 | rootpath = p; |
782 | rootpathlen = len; |
783 | break; |
784 | case TAG_INTERFACE_MTU: |
785 | if (len != 2) { |
786 | printf("nfs_boot: interface-mtu len != 2 (%d)" , |
787 | len); |
788 | break; |
789 | } |
790 | memcpy(&myinterfacemtu, p, 2); |
791 | myinterfacemtu = ntohs(myinterfacemtu); |
792 | break; |
793 | case TAG_SWAP_SERVER: |
794 | /* override NFS server address */ |
795 | if (len < 4) { |
796 | printf("nfs_boot: swap server < 4 bytes\n" ); |
797 | break; |
798 | } |
799 | memcpy(&rootserver, p, 4); |
800 | break; |
801 | #ifdef NFS_BOOT_DHCP |
802 | case TAG_OVERLOAD: |
803 | if (len > 0 && ((*p & 0x02) != 0)) |
804 | /* |
805 | * The server name field in the dhcp packet |
806 | * is overloaded and we can't find server |
807 | * name there. |
808 | */ |
809 | overloaded = 1; |
810 | break; |
811 | #endif |
812 | default: |
813 | break; |
814 | } |
815 | p += len; |
816 | } |
817 | |
818 | /* |
819 | * Store and print network config info. |
820 | */ |
821 | if (myname) { |
822 | myname[mynamelen] = '\0'; |
823 | strncpy(hostname, myname, sizeof(hostname)); |
824 | hostnamelen = mynamelen; |
825 | printf("nfs_boot: my_name=%s\n" , hostname); |
826 | } |
827 | if (mydomain) { |
828 | mydomain[mydomainlen] = '\0'; |
829 | strncpy(domainname, mydomain, sizeof(domainname)); |
830 | domainnamelen = mydomainlen; |
831 | printf("nfs_boot: my_domain=%s\n" , domainname); |
832 | } |
833 | if (!(*flags & NFS_BOOT_HAS_MYIP)) { |
834 | nd->nd_myip = bootp->bp_yiaddr; |
835 | printf("nfs_boot: my_addr=%s\n" , inet_ntoa(nd->nd_myip)); |
836 | *flags |= NFS_BOOT_HAS_MYIP; |
837 | } |
838 | if (!(*flags & NFS_BOOT_HAS_MASK)) { |
839 | nd->nd_mask = netmask; |
840 | printf("nfs_boot: my_mask=%s\n" , inet_ntoa(nd->nd_mask)); |
841 | *flags |= NFS_BOOT_HAS_MASK; |
842 | } |
843 | if (!(*flags & NFS_BOOT_HAS_GWIP)) { |
844 | nd->nd_gwip = gateway; |
845 | printf("nfs_boot: gateway=%s\n" , inet_ntoa(nd->nd_gwip)); |
846 | *flags |= NFS_BOOT_HAS_GWIP; |
847 | } |
848 | if (myinterfacemtu >= IP_MIN_MTU) { |
849 | nd->nd_mtu = myinterfacemtu; |
850 | printf("nfs_boot: mtu=%d\n" , nd->nd_mtu); |
851 | } |
852 | |
853 | /* |
854 | * Store the information about our NFS root mount. |
855 | * The caller will print it, so be silent here. |
856 | */ |
857 | do { |
858 | struct nfs_dlmount *ndm = &nd->nd_root; |
859 | |
860 | |
861 | if (!(*flags & NFS_BOOT_HAS_SERVADDR)) { |
862 | /* Server IP address. */ |
863 | sin = (struct sockaddr_in *) &ndm->ndm_saddr; |
864 | memset((void *)sin, 0, sizeof(*sin)); |
865 | sin->sin_len = sizeof(*sin); |
866 | sin->sin_family = AF_INET; |
867 | sin->sin_addr = rootserver; |
868 | *flags |= NFS_BOOT_HAS_SERVADDR; |
869 | } |
870 | |
871 | if (!(*flags & NFS_BOOT_HAS_SERVER)) { |
872 | /* Server name. */ |
873 | if (!overloaded && bootp->bp_sname[0] != 0 && |
874 | !memcmp(&rootserver, &bootp->bp_siaddr, |
875 | sizeof(struct in_addr))) |
876 | { |
877 | /* standard root server, we have the name */ |
878 | strncpy(ndm->ndm_host, bootp->bp_sname, |
879 | BP_SNAME_LEN-1); |
880 | *flags |= NFS_BOOT_HAS_SERVER; |
881 | } else { |
882 | /* Show the server IP address numerically. */ |
883 | strncpy(ndm->ndm_host, inet_ntoa(rootserver), |
884 | BP_SNAME_LEN-1); |
885 | *flags |= NFS_BOOT_HAS_SERVER; |
886 | } |
887 | } |
888 | |
889 | if (!(*flags & NFS_BOOT_HAS_ROOTPATH)) { |
890 | len = strlen(ndm->ndm_host); |
891 | if (rootpath && |
892 | len + 1 + rootpathlen + 1 <= sizeof(ndm->ndm_host)) |
893 | { |
894 | ndm->ndm_host[len++] = ':'; |
895 | strncpy(ndm->ndm_host + len, |
896 | rootpath, rootpathlen); |
897 | ndm->ndm_host[len + rootpathlen] = '\0'; |
898 | *flags |= NFS_BOOT_HAS_ROOTPATH; |
899 | } /* else: upper layer will handle error */ |
900 | } |
901 | } while(0); |
902 | |
903 | #ifdef TFTPROOT |
904 | #if BP_FILE_LEN > MNAMELEN |
905 | #define BOOTFILELEN MNAMELEN |
906 | #else |
907 | #define BOOTFILELEN BP_FILE_LEN |
908 | #endif |
909 | strncpy(nd->nd_bootfile, bootp->bp_file, BOOTFILELEN); |
910 | nd->nd_bootfile[BOOTFILELEN - 1] = '\0'; |
911 | #undef BOOTFILELEN |
912 | #endif /* TFTPROOT */ |
913 | } |
914 | |