]> git.proxmox.com Git - grub2.git/blob - grub-core/net/bootp.c
04fdd57de2247edf2c7f05fb85fd94dbddb4c34c
[grub2.git] / grub-core / net / bootp.c
1 /*
2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2010,2011 Free Software Foundation, Inc.
4 *
5 * GRUB is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * GRUB is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <grub/net.h>
20 #include <grub/env.h>
21 #include <grub/i18n.h>
22 #include <grub/command.h>
23 #include <grub/net/ip.h>
24 #include <grub/net/netbuff.h>
25 #include <grub/net/udp.h>
26 #include <grub/datetime.h>
27
28 static char *
29 grub_env_write_readonly (struct grub_env_var *var __attribute__ ((unused)),
30 const char *val __attribute__ ((unused)))
31 {
32 return NULL;
33 }
34
35 static void
36 set_env_limn_ro (const char *intername, const char *suffix,
37 char *value, grub_size_t len)
38 {
39 char c;
40 char varname[sizeof ("net_") + grub_strlen (intername) + sizeof ("_")
41 + grub_strlen (suffix)];
42 char *ptr;
43 grub_snprintf (varname, sizeof (varname), "net_%s_%s", intername, suffix);
44 for (ptr = varname; *ptr; ptr++)
45 if (*ptr == ':')
46 *ptr = '_';
47 c = value[len];
48 value[len] = 0;
49 grub_env_set (varname, value);
50 value[len] = c;
51 grub_register_variable_hook (varname, 0, grub_env_write_readonly);
52 }
53
54 static void
55 parse_dhcp_vendor (const char *name, void *vend, int limit, int *mask)
56 {
57 grub_uint8_t *ptr, *ptr0;
58
59 ptr = ptr0 = vend;
60
61 if (ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
62 || ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1
63 || ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2
64 || ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3)
65 return;
66 ptr = ptr + sizeof (grub_uint32_t);
67 while (ptr - ptr0 < limit)
68 {
69 grub_uint8_t tagtype;
70 grub_uint8_t taglength;
71
72 tagtype = *ptr++;
73
74 /* Pad tag. */
75 if (tagtype == GRUB_NET_BOOTP_PAD)
76 continue;
77
78 /* End tag. */
79 if (tagtype == GRUB_NET_BOOTP_END)
80 return;
81
82 taglength = *ptr++;
83
84 switch (tagtype)
85 {
86 case GRUB_NET_BOOTP_NETMASK:
87 if (taglength == 4)
88 {
89 int i;
90 for (i = 0; i < 32; i++)
91 if (!(ptr[i / 8] & (1 << (7 - (i % 8)))))
92 break;
93 *mask = i;
94 }
95 break;
96
97 case GRUB_NET_BOOTP_ROUTER:
98 if (taglength == 4)
99 {
100 grub_net_network_level_netaddress_t target;
101 grub_net_network_level_address_t gw;
102 char rname[grub_strlen (name) + sizeof (":default")];
103
104 target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
105 target.ipv4.base = 0;
106 target.ipv4.masksize = 0;
107 gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
108 grub_memcpy (&gw.ipv4, ptr, sizeof (gw.ipv4));
109 grub_snprintf (rname, sizeof (rname), "%s:default", name);
110 grub_net_add_route_gw (rname, target, gw);
111 }
112 break;
113 case GRUB_NET_BOOTP_DNS:
114 {
115 int i;
116 for (i = 0; i < taglength / 4; i++)
117 {
118 struct grub_net_network_level_address s;
119 s.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
120 s.ipv4 = grub_get_unaligned32 (ptr);
121 grub_net_add_dns_server (&s);
122 ptr += 4;
123 }
124 }
125 break;
126 case GRUB_NET_BOOTP_HOSTNAME:
127 set_env_limn_ro (name, "hostname", (char *) ptr, taglength);
128 break;
129
130 case GRUB_NET_BOOTP_DOMAIN:
131 set_env_limn_ro (name, "domain", (char *) ptr, taglength);
132 break;
133
134 case GRUB_NET_BOOTP_ROOT_PATH:
135 set_env_limn_ro (name, "rootpath", (char *) ptr, taglength);
136 break;
137
138 case GRUB_NET_BOOTP_EXTENSIONS_PATH:
139 set_env_limn_ro (name, "extensionspath", (char *) ptr, taglength);
140 break;
141
142 /* If you need any other options please contact GRUB
143 developpement team. */
144 }
145
146 ptr += taglength;
147 }
148 }
149
150 #define OFFSET_OF(x, y) ((grub_uint8_t *)((y)->x) - (grub_uint8_t *)(y))
151
152 struct grub_net_network_level_interface *
153 grub_net_configure_by_dhcp_ack (const char *name,
154 struct grub_net_card *card,
155 grub_net_interface_flags_t flags,
156 const struct grub_net_bootp_packet *bp,
157 grub_size_t size,
158 int is_def, char **device, char **path)
159 {
160 grub_net_network_level_address_t addr;
161 grub_net_link_level_address_t hwaddr;
162 struct grub_net_network_level_interface *inter;
163 int mask = -1;
164
165 addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
166 addr.ipv4 = bp->your_ip;
167
168 if (device)
169 *device = 0;
170 if (path)
171 *path = 0;
172
173 grub_memcpy (hwaddr.mac, bp->mac_addr,
174 bp->hw_len < sizeof (hwaddr.mac) ? bp->hw_len
175 : sizeof (hwaddr.mac));
176 hwaddr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
177
178 inter = grub_net_add_addr (name, card, &addr, &hwaddr, flags);
179 if (bp->gateway_ip)
180 {
181 grub_net_network_level_netaddress_t target;
182 grub_net_network_level_address_t gw;
183 char rname[grub_strlen (name) + sizeof (":gw")];
184
185 target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
186 target.ipv4.base = bp->server_ip;
187 target.ipv4.masksize = 32;
188 gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
189 gw.ipv4 = bp->gateway_ip;
190 grub_snprintf (rname, sizeof (rname), "%s:gw", name);
191 grub_net_add_route_gw (rname, target, gw);
192 }
193 if (bp->gateway_ip || bp->server_ip)
194 {
195 grub_net_network_level_netaddress_t target;
196 target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
197 target.ipv4.base = bp->gateway_ip ? bp->gateway_ip : bp->server_ip;
198 target.ipv4.masksize = 32;
199 grub_net_add_route (name, target, inter);
200 }
201
202 if (size > OFFSET_OF (boot_file, bp))
203 set_env_limn_ro (name, "boot_file", (char *) bp->boot_file,
204 sizeof (bp->boot_file));
205 if (is_def)
206 grub_net_default_server = 0;
207 if (size > OFFSET_OF (server_name, bp)
208 && bp->server_name[0])
209 {
210 set_env_limn_ro (name, "dhcp_server_name", (char *) bp->server_name,
211 sizeof (bp->server_name));
212 if (is_def && !grub_net_default_server)
213 {
214 grub_net_default_server = grub_strdup (bp->server_name);
215 grub_print_error ();
216 }
217 if (device && !*device)
218 {
219 *device = grub_xasprintf ("tftp,%s", bp->server_name);
220 grub_print_error ();
221 }
222 }
223 if (is_def && !grub_net_default_server)
224 {
225 grub_net_default_server = grub_xasprintf ("%d.%d.%d.%d",
226 ((grub_uint8_t *) &bp->server_ip)[0],
227 ((grub_uint8_t *) &bp->server_ip)[1],
228 ((grub_uint8_t *) &bp->server_ip)[2],
229 ((grub_uint8_t *) &bp->server_ip)[3]);
230 grub_print_error ();
231 }
232
233 if (device && !*device)
234 {
235 *device = grub_xasprintf ("tftp,%d.%d.%d.%d",
236 ((grub_uint8_t *) &bp->server_ip)[0],
237 ((grub_uint8_t *) &bp->server_ip)[1],
238 ((grub_uint8_t *) &bp->server_ip)[2],
239 ((grub_uint8_t *) &bp->server_ip)[3]);
240 grub_print_error ();
241 }
242 if (size > OFFSET_OF (boot_file, bp) && path)
243 {
244 *path = grub_strndup (bp->boot_file, sizeof (bp->boot_file));
245 grub_print_error ();
246 if (*path)
247 {
248 char *slash;
249 slash = grub_strrchr (*path, '/');
250 if (slash)
251 *slash = 0;
252 else
253 **path = 0;
254 }
255 }
256 if (size > OFFSET_OF (vendor, bp))
257 parse_dhcp_vendor (name, &bp->vendor, size - OFFSET_OF (vendor, bp), &mask);
258 grub_net_add_ipv4_local (inter, mask);
259
260 inter->dhcp_ack = grub_malloc (size);
261 if (inter->dhcp_ack)
262 {
263 grub_memcpy (inter->dhcp_ack, bp, size);
264 inter->dhcp_acklen = size;
265 }
266 else
267 grub_errno = GRUB_ERR_NONE;
268
269 return inter;
270 }
271
272 void
273 grub_net_process_dhcp (struct grub_net_buff *nb,
274 struct grub_net_card *card)
275 {
276 char *name;
277 struct grub_net_network_level_interface *inf;
278
279 name = grub_xasprintf ("%s:dhcp", card->name);
280 if (!name)
281 {
282 grub_print_error ();
283 return;
284 }
285 grub_net_configure_by_dhcp_ack (name, card,
286 0, (const struct grub_net_bootp_packet *) nb->data,
287 (nb->tail - nb->data), 0, 0, 0);
288 grub_free (name);
289 if (grub_errno)
290 grub_print_error ();
291 else
292 {
293 FOR_NET_NETWORK_LEVEL_INTERFACES(inf)
294 if (grub_memcmp (inf->name, card->name, grub_strlen (card->name)) == 0
295 && grub_memcmp (inf->name + grub_strlen (card->name),
296 ":dhcp_tmp", sizeof (":dhcp_tmp") - 1) == 0)
297 {
298 grub_net_network_level_interface_unregister (inf);
299 break;
300 }
301 }
302 }
303
304 static char
305 hexdigit (grub_uint8_t val)
306 {
307 if (val < 10)
308 return val + '0';
309 return val + 'a' - 10;
310 }
311
312 static grub_err_t
313 grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ ((unused)),
314 int argc, char **args)
315 {
316 struct grub_net_network_level_interface *inter;
317 int num;
318 grub_uint8_t *ptr;
319 grub_uint8_t taglength;
320
321 if (argc < 4)
322 return grub_error (GRUB_ERR_BAD_ARGUMENT,
323 N_("four arguments expected"));
324
325 FOR_NET_NETWORK_LEVEL_INTERFACES (inter)
326 if (grub_strcmp (inter->name, args[1]) == 0)
327 break;
328
329 if (!inter)
330 return grub_error (GRUB_ERR_BAD_ARGUMENT,
331 N_("unrecognised network interface `%s'"), args[1]);
332
333 if (!inter->dhcp_ack)
334 return grub_error (GRUB_ERR_IO, N_("no DHCP info found"));
335
336 if (inter->dhcp_acklen <= OFFSET_OF (vendor, inter->dhcp_ack))
337 return grub_error (GRUB_ERR_IO, N_("no DHCP options found"));
338
339 num = grub_strtoul (args[2], 0, 0);
340 if (grub_errno)
341 return grub_errno;
342
343 ptr = inter->dhcp_ack->vendor;
344
345 if (ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
346 || ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1
347 || ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2
348 || ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3)
349 return grub_error (GRUB_ERR_IO, N_("no DHCP options found"));
350 ptr = ptr + sizeof (grub_uint32_t);
351 while (1)
352 {
353 grub_uint8_t tagtype;
354
355 if (ptr >= ((grub_uint8_t *) inter->dhcp_ack) + inter->dhcp_acklen)
356 return grub_error (GRUB_ERR_IO, N_("no DHCP option %d found"), num);
357
358 tagtype = *ptr++;
359
360 /* Pad tag. */
361 if (tagtype == 0)
362 continue;
363
364 /* End tag. */
365 if (tagtype == 0xff)
366 return grub_error (GRUB_ERR_IO, N_("no DHCP option %d found"), num);
367
368 taglength = *ptr++;
369
370 if (tagtype == num)
371 break;
372 ptr += taglength;
373 }
374
375 if (grub_strcmp (args[3], "string") == 0)
376 {
377 char *val = grub_malloc (taglength + 1);
378 if (!val)
379 return grub_errno;
380 grub_memcpy (val, ptr, taglength);
381 val[taglength] = 0;
382 if (args[0][0] == '-' && args[0][1] == 0)
383 grub_printf ("%s\n", val);
384 else
385 return grub_env_set (args[0], val);
386 return GRUB_ERR_NONE;
387 }
388
389 if (grub_strcmp (args[3], "number") == 0)
390 {
391 grub_uint64_t val = 0;
392 int i;
393 for (i = 0; i < taglength; i++)
394 val = (val << 8) | ptr[i];
395 if (args[0][0] == '-' && args[0][1] == 0)
396 grub_printf ("%llu\n", (unsigned long long) val);
397 else
398 {
399 char valn[64];
400 grub_printf (valn, sizeof (valn), "%lld\n", (unsigned long long) val);
401 return grub_env_set (args[0], valn);
402 }
403 return GRUB_ERR_NONE;
404 }
405
406 if (grub_strcmp (args[3], "hex") == 0)
407 {
408 char *val = grub_malloc (2 * taglength + 1);
409 int i;
410 if (!val)
411 return grub_errno;
412 for (i = 0; i < taglength; i++)
413 {
414 val[2 * i] = hexdigit (ptr[i] >> 4);
415 val[2 * i + 1] = hexdigit (ptr[i] & 0xf);
416 }
417 val[2 * taglength] = 0;
418 if (args[0][0] == '-' && args[0][1] == 0)
419 grub_printf ("%s\n", val);
420 else
421 return grub_env_set (args[0], val);
422 return GRUB_ERR_NONE;
423 }
424
425 return grub_error (GRUB_ERR_BAD_ARGUMENT,
426 N_("unrecognised DHCP option format specification `%s'"),
427 args[3]);
428 }
429
430 /* FIXME: allow to specify mac address. */
431 static grub_err_t
432 grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)),
433 int argc, char **args)
434 {
435 struct grub_net_card *card;
436 struct grub_net_network_level_interface *ifaces;
437 grub_size_t ncards = 0;
438 unsigned j = 0;
439 int interval;
440 grub_err_t err;
441
442 FOR_NET_CARDS (card)
443 {
444 if (argc > 0 && grub_strcmp (card->name, args[0]) != 0)
445 continue;
446 ncards++;
447 }
448
449 ifaces = grub_zalloc (ncards * sizeof (ifaces[0]));
450 if (!ifaces)
451 return grub_errno;
452
453 j = 0;
454 FOR_NET_CARDS (card)
455 {
456 if (argc > 0 && grub_strcmp (card->name, args[0]) != 0)
457 continue;
458 ifaces[j].card = card;
459 ifaces[j].next = &ifaces[j+1];
460 if (j)
461 ifaces[j].prev = &ifaces[j-1].next;
462 ifaces[j].name = grub_xasprintf ("%s:dhcp_tmp", card->name);
463 card->num_ifaces++;
464 if (!ifaces[j].name)
465 {
466 unsigned i;
467 for (i = 0; i < j; i++)
468 grub_free (ifaces[i].name);
469 grub_free (ifaces);
470 return grub_errno;
471 }
472 ifaces[j].address.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV;
473 grub_memcpy (&ifaces[j].hwaddress, &card->default_address,
474 sizeof (ifaces[j].hwaddress));
475 j++;
476 }
477 ifaces[ncards - 1].next = grub_net_network_level_interfaces;
478 if (grub_net_network_level_interfaces)
479 grub_net_network_level_interfaces->prev = & ifaces[ncards - 1].next;
480 grub_net_network_level_interfaces = &ifaces[0];
481 ifaces[0].prev = &grub_net_network_level_interfaces;
482 for (interval = 200; interval < 10000; interval *= 2)
483 {
484 int done = 0;
485 for (j = 0; j < ncards; j++)
486 {
487 struct grub_net_bootp_packet *pack;
488 struct grub_datetime date;
489 grub_int32_t t = 0;
490 struct grub_net_buff *nb;
491 struct udphdr *udph;
492 grub_net_network_level_address_t target;
493 grub_net_link_level_address_t ll_target;
494
495 if (!ifaces[j].prev)
496 continue;
497 nb = grub_netbuff_alloc (sizeof (*pack));
498 if (!nb)
499 {
500 grub_netbuff_free (nb);
501 return grub_errno;
502 }
503 err = grub_netbuff_reserve (nb, sizeof (*pack) + 64 + 128);
504 if (err)
505 {
506 grub_netbuff_free (nb);
507 return err;
508 }
509 err = grub_netbuff_push (nb, sizeof (*pack) + 64);
510 if (err)
511 {
512 grub_netbuff_free (nb);
513 return err;
514 }
515 pack = (void *) nb->data;
516 done = 1;
517 grub_memset (pack, 0, sizeof (*pack) + 64);
518 pack->opcode = 1;
519 pack->hw_type = 1;
520 pack->hw_len = 6;
521 err = grub_get_datetime (&date);
522 if (err || !grub_datetime2unixtime (&date, &t))
523 {
524 grub_errno = GRUB_ERR_NONE;
525 t = 0;
526 }
527 pack->ident = grub_cpu_to_be32 (t);
528 pack->seconds = grub_cpu_to_be16 (t);
529
530 grub_memcpy (&pack->mac_addr, &ifaces[j].hwaddress.mac, 6);
531
532 grub_netbuff_push (nb, sizeof (*udph));
533
534 udph = (struct udphdr *) nb->data;
535 udph->src = grub_cpu_to_be16 (68);
536 udph->dst = grub_cpu_to_be16 (67);
537 udph->chksum = 0;
538 udph->len = grub_cpu_to_be16 (nb->tail - nb->data);
539 target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
540 target.ipv4 = 0xffffffff;
541 err = grub_net_link_layer_resolve (&ifaces[j], &target, &ll_target);
542 if (err)
543 return err;
544
545 udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP,
546 &ifaces[j].address,
547 &target);
548
549 err = grub_net_send_ip_packet (&ifaces[j], &target, &ll_target, nb,
550 GRUB_NET_IP_UDP);
551 grub_netbuff_free (nb);
552 if (err)
553 return err;
554 }
555 if (!done)
556 break;
557 grub_net_poll_cards (interval, 0);
558 }
559
560 err = GRUB_ERR_NONE;
561 for (j = 0; j < ncards; j++)
562 {
563 grub_free (ifaces[j].name);
564 if (!ifaces[j].prev)
565 continue;
566 grub_error_push ();
567 grub_net_network_level_interface_unregister (&ifaces[j]);
568 err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
569 N_("couldn't autoconfigure %s"),
570 ifaces[j].card->name);
571 }
572
573 grub_free (ifaces);
574 return err;
575 }
576
577 static grub_command_t cmd_getdhcp, cmd_bootp;
578
579 void
580 grub_bootp_init (void)
581 {
582 cmd_bootp = grub_register_command ("net_bootp", grub_cmd_bootp,
583 N_("[CARD]"),
584 N_("perform a bootp autoconfiguration"));
585 cmd_getdhcp = grub_register_command ("net_get_dhcp_option", grub_cmd_dhcpopt,
586 N_("VAR INTERFACE NUMBER DESCRIPTION"),
587 N_("retrieve DHCP option and save it into VAR. If VAR is - then print the value."));
588 }
589
590 void
591 grub_bootp_fini (void)
592 {
593 grub_unregister_command (cmd_getdhcp);
594 grub_unregister_command (cmd_bootp);
595 }