diff --git a/ChangeLog b/ChangeLog index 4aacc74b8..a2032567b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,153 @@ +2011-12-20 Vladimir Serbinenko + + IPv6, TCP, HTTP, ICMP and DNS support. Several cleanups and bugfixes. + + * grub-core/Makefile.core.def (net): Add net/dns.c, net/tcp.c, + net/icmp.c and net/icmp6.c. + (http): New module. + (priority_queue): Likewise. + * grub-core/io/bufio.c: Rewritten. + * grub-core/lib/legacy_parse.c (legacy_command): New argument type + TYPE_WITH_CONFIGFILE_OPTION. + (legacy_commands): Add bootp and dhcp. + (is_option): Handle TYPE_WITH_CONFIGFILE_OPTION. + (grub_legacy_parse): Likewise. + * grub-core/lib/priority_queue.c: New file. + * grub-core/net/arp.c: Add missing license header. + (arp_find_entry): Removed. + (arp_find_entry): Likewise. + (grub_net_arp_resolve): Rename to ... + (grub_net_arp_send_request): ...this. + (grub_net_arp_receive): New card argument. + * grub-core/net/bootp.c (parse_dhcp_vendor): Clean up. + Set router and DNS server. + (grub_net_configure_by_dhcp_ack): Handle routing information. + (grub_cmd_bootp): Set checksum. + (grub_bootp_init): Remove net_dhcp. + * grub-core/net/dns.c: New file. + * grub-core/net/drivers/efi/efinet.c (send_card_buffer): Wait for + completion. + (get_card_packet): Handle allocation. + (grub_efinet_findcards): Set mtu. + * grub-core/net/drivers/emu/emunet.c: Add missing license header. + (get_card_packet): Handle allocation. + (emucard): Set mtu. + * grub-core/net/drivers/i386/pc/pxe.c (grub_pxe_recv): Handle allocation + (GRUB_MOD_INIT): Set mtu. + * grub-core/net/drivers/ieee1275/ofnet.c (grub_ofnetcard_data): Remove + mtu. + (get_card_packet): Handle allocation. + (grub_ofnet_findcards): Set mtu. + * grub-core/net/ethernet.c (send_ethernet_packet): Add compile time + assert. + (grub_net_recv_ethernet_packet): Handle IPv6. + * grub-core/net/http.c: New file. + * grub-core/net/icmp.c: Likewise. + * grub-core/net/icmp6.c: Likewise. + * grub-core/net/ip.c (ip6addr): New type. + (ip6hdr): Likewise. + (reassemble): Likewise. + (cmp): New function. + (reassembles): New variable. + (grub_net_ip_chksum): Handle 0xffff sum and unaligned buffers. + (id): New variable. + (send_fragmented): New function. + (grub_net_send_ip_packet): Rename to ... + (grub_net_send_ip4_packet): ... this. Send fragmented if needed. + Handle non-UDP. + (grub_net_recv_ip_packets): Rename to ... + (handle_dgram): ... this. Check checksum. Handle non-UDP. + (free_rsm): New function. + (free_old_fragments): Likewise. + (grub_net_recv_ip4_packets): New function. + (grub_net_send_ip6_packet): Likewise. + (grub_net_send_ip_packet): Likewise. + (grub_net_recv_ip6_packets): Likewise. + (grub_net_recv_ip_packets): Likewise. + * grub-core/net/net.c (grub_net_link_layer_entry): New struct. + (LINK_LAYER_CACHE_SIZE): New const. + (link_layer_find_entry): New function. + (grub_net_link_layer_add_address): Likewise. + (grub_net_link_layer_resolve_check): Likewise. + (grub_net_link_layer_resolve): Likewise. + (grub_net_ipv6_get_slaac): Likewise. + (grub_net_ipv6_get_link_local): Likewise. + (grub_cmd_ipv6_autoconf): Likewise. + (parse_ip): Handle one number representation. + (parse_ip6): New functoion. + (match_net): Handle IPv6. + (grub_net_resolve_address): Handle IPv6 and DNS. + (grub_net_resolve_net_address): Handle IPv6. + (route_cmp): New function. + (grub_net_route_address): Find best route. + (grub_net_addr_to_str): Handle IPv6. + (grub_net_addr_cmp): New function. + (grub_net_add_addr): Register local route. + (print_net_address): Handle net address. + (grub_net_poll_cards): Retransmit TCP. + (grub_net_poll_cards_idle_real): Likewise. + (have_ahead): New function. + (grub_net_seek_real): Use underlying seek. + (GRUB_MOD_INIT): Register net_ipv6_autoconf and init dns. + * grub-core/net/tcp.c: New file. + * grub-core/net/tftp.c (tftp_data): Add priority_queue. + (cmp): New function. + (ack): Likewise. + (tftp_receive): Handle unordered input. + (destroy_pq): New function. + (tftp_close): Close pq. + * grub-core/net/udp.c: Put missing license header. + (grub_net_udp_socket): New function. + (udp_socket_register): Likewise. + (grub_net_udp_close): Likewise. + (grub_net_recv_udp_packet): Check checksum. + * include/grub/efi/api.h (grub_efi_simple_network): Add status. + * include/grub/misc.h (grub_memchr): New function. + * include/grub/net.h (GRUB_NET_*_SIZE): New enum. + (grub_net_card_driver): Return buf in recv. + (grub_net_slaac_mac_list): New struct. + (grub_network_level_protocol_id): Add ipv6. + (grub_net_network_level_addr): Likewise. + (grub_net_network_level_net_addr): Likewise. + (grub_net_app_protocol): Add seek. + (grub_net_socket): Removed. + (grub_net_sockets): Likewise. + (grub_net_socket_register): Likewise. + (grub_net_socket_unregister): Likewise. + (FOR_NET_SOCKETS): Likewise. + (grub_net_add_addr): Add const. + (GRUB_NET_BOOTP_*): New enum. + (grub_net_addr_cmp): New proto. + (GRUB_NET_MAX_STR_ADDR_LEN): Take IPV6 into account. + (GRUB_NET_MAX_STR_HWADDR_LEN): New define. + (grub_net_hwaddr_to_str): NEw proto. + (FOR_NET_NETWORK_LEVEL_INTERFACES): New macro. + (FOR_NET_NETWORK_LEVEL_INTERFACES_SAFE): Handle NULL. + (grub_dns_init): New proto. + (grub_dns_fini): Likewise. + (grub_net_tcp_retransmit): Likewise. + (grub_net_link_layer_add_address): Likewise. + (grub_net_link_layer_resolve_check): Likewise. + (grub_net_link_layer_resolve): Likewise. + (grub_net_dns_lookup): Likewise. + (grub_net_add_dns_server): Likewise. + (grub_net_remove_dns_server): Likewise. + (GRUB_NET_TRIES): New const. + (GRUB_NET_INTERVAL): Likewise. + * include/grub/net/arp.h: Mostly rewritten. + * include/grub/net/ethernet.h (grub_net_ethertype_t): New enum. + * include/grub/net/ip.h: Mostly rewritten. + * include/grub/net/netbuff.h: Indent. + * include/grub/net/tcp.h: New file. + * include/grub/net/udp.h: Mostly rewritten. + * include/grub/priority_queue.h: New file. + * include/grub/types.h (PRIdGRUB_SSIZE): New define. + (grub_swap_bytes64_compile_time): Likewise. + (grub_cpu_to_be16_compile_time): Likewise. + (grub_cpu_to_be32_compile_time): Likewise. + (grub_cpu_to_be64_compile_time): Likewise. + (grub_be_to_cpu64_compile_time): Likewise. + 2011-12-16 Vladimir Serbinenko * grub-core/commands/i386/pc/drivemap.c (int13slot): Replace diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index cae79b381..c00773446 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -1642,9 +1642,13 @@ module = { module = { name = net; common = net/net.c; + common = net/dns.c; common = net/bootp.c; common = net/ip.c; common = net/udp.c; + common = net/tcp.c; + common = net/icmp.c; + common = net/icmp6.c; common = net/ethernet.c; common = net/arp.c; common = net/netbuff.c; @@ -1655,6 +1659,11 @@ module = { common = net/tftp.c; }; +module = { + name = http; + common = net/http.c; +}; + module = { name = ofnet; common = net/drivers/ieee1275/ofnet.c; @@ -1721,6 +1730,11 @@ module = { enable = videomodules; }; +module = { + name = priority_queue; + common = lib/priority_queue.c; +}; + module = { name = time; common = commands/time.c; diff --git a/grub-core/io/bufio.c b/grub-core/io/bufio.c index adb38af56..0d01e62f9 100644 --- a/grub-core/io/bufio.c +++ b/grub-core/io/bufio.c @@ -35,6 +35,7 @@ struct grub_bufio grub_file_t file; grub_size_t block_size; grub_size_t buffer_len; + grub_off_t buffer_at; char buffer[0]; }; typedef struct grub_bufio *grub_bufio_t; @@ -70,6 +71,7 @@ grub_bufio_open (grub_file_t io, int size) bufio->file = io; bufio->block_size = size; bufio->buffer_len = 0; + bufio->buffer_at = 0; file->device = io->device; file->offset = 0; @@ -104,83 +106,87 @@ grub_buffile_open (const char *name, int size) static grub_ssize_t grub_bufio_read (grub_file_t file, char *buf, grub_size_t len) { - grub_size_t res = len; + grub_size_t res = 0; grub_bufio_t bufio = file->data; - grub_uint64_t pos; - if ((file->offset >= bufio->file->offset) && - (file->offset < bufio->file->offset + bufio->buffer_len)) + if (file->size == GRUB_FILE_SIZE_UNKNOWN) + file->size = bufio->file->size; + + /* First part: use whatever we already have in the buffer. */ + if ((file->offset >= bufio->buffer_at) && + (file->offset < bufio->buffer_at + bufio->buffer_len)) { grub_size_t n; + grub_uint64_t pos; - pos = file->offset - bufio->file->offset; + pos = file->offset - bufio->buffer_at; n = bufio->buffer_len - pos; if (n > len) n = len; grub_memcpy (buf, &bufio->buffer[pos], n); len -= n; - if (! len) - return res; + res += n; buf += n; - bufio->file->offset += bufio->buffer_len; - pos = 0; + } + if (! len) + return res; + + /* Need to read some more. */ + bufio->buffer_at = grub_divmod64 (file->offset + res + len, bufio->block_size, + 0) * bufio->block_size; + + /* Now read between file->offset + res and bufio->buffer_at. */ + if (file->offset + res < bufio->buffer_at) + { + grub_size_t read_now; + grub_ssize_t really_read; + read_now = bufio->buffer_at - (file->offset + res); + grub_file_seek (bufio->file, file->offset + res); + really_read = grub_file_read (bufio->file, buf, read_now); + if (grub_errno) + return -1; + if (file->size == GRUB_FILE_SIZE_UNKNOWN) + file->size = bufio->file->size; + len -= really_read; + buf += really_read; + res += really_read; + + /* Partial read. File ended unexpectedly. Save the last chunk in buffer. + */ + if (really_read != (grub_ssize_t) read_now) + { + bufio->buffer_len = really_read; + if (bufio->buffer_len > bufio->block_size) + bufio->buffer_len = bufio->block_size; + bufio->buffer_at = file->offset + res - bufio->buffer_len; + grub_memcpy (&bufio->buffer[0], buf - bufio->buffer_len, + bufio->buffer_len); + return res; + } + } + + /* Read into buffer. */ + grub_file_seek (bufio->file, bufio->buffer_at); + bufio->buffer_len = grub_file_read (bufio->file, bufio->buffer, + bufio->block_size); + if (grub_errno) + return -1; + if (file->size == GRUB_FILE_SIZE_UNKNOWN) + file->size = bufio->file->size; + + if (len < bufio->buffer_len) + { + grub_memcpy (buf, &bufio->buffer[0], len); + res += len; } else { - bufio->file->offset = grub_divmod64 (file->offset, bufio->block_size, - &pos); - bufio->file->offset *= bufio->block_size; + grub_memcpy (buf, &bufio->buffer[0], bufio->buffer_len); + res += bufio->buffer_len; } - if (pos + len >= bufio->block_size) - { - if (pos) - { - grub_size_t n; - - bufio->file->fs->read (bufio->file, bufio->buffer, - bufio->block_size); - if (grub_errno) - return -1; - - n = bufio->block_size - pos; - grub_memcpy (buf, &bufio->buffer[pos], n); - len -= n; - buf += n; - bufio->file->offset += bufio->block_size; - pos = 0; - } - - while (len >= bufio->block_size) - { - bufio->file->fs->read (bufio->file, buf, bufio->block_size); - if (grub_errno) - return -1; - - len -= bufio->block_size; - buf += bufio->block_size; - bufio->file->offset += bufio->block_size; - } - - if (! len) - { - bufio->buffer_len = 0; - return res; - } - } - - bufio->buffer_len = bufio->file->size - bufio->file->offset; - if (bufio->buffer_len > bufio->block_size) - bufio->buffer_len = bufio->block_size; - - bufio->file->fs->read (bufio->file, bufio->buffer, bufio->buffer_len); - if (grub_errno) - return -1; - - grub_memcpy (buf, &bufio->buffer[pos], len); - return res; } diff --git a/grub-core/lib/legacy_parse.c b/grub-core/lib/legacy_parse.c index e429f2f1b..2d9221bda 100644 --- a/grub-core/lib/legacy_parse.c +++ b/grub-core/lib/legacy_parse.c @@ -42,7 +42,8 @@ struct legacy_command TYPE_BOOL, TYPE_INT, TYPE_REST_VERBATIM, - TYPE_VBE_MODE + TYPE_VBE_MODE, + TYPE_WITH_CONFIGFILE_OPTION } argt[4]; enum { FLAG_IGNORE_REST = 0x001, @@ -67,7 +68,13 @@ static struct legacy_command legacy_commands[] = "Print the blocklist notation of the file FILE."}, {"boot", "boot\n", NULL, 0, 0, {}, 0, 0, "Boot the OS/chain-loader which has been loaded."}, - /* FIXME: bootp unsupported. */ + {"bootp", "net_bootp; net_ls_addr; if [ x%s = x--with-configfile ]; then " + "if net_get_dhcp_option configfile_name pxe 150 string; then " + "configfile $configfile_name; fi; fi\n", NULL, 0, 1, + {TYPE_WITH_CONFIGFILE_OPTION}, FLAG_IGNORE_REST, "[--with-configfile]", + "Initialize a network device via BOOTP. If the option `--with-configfile'" + " is given, try to load a configuration file specified by the 150 vendor" + " tag."}, {"cat", "cat '%s'\n", NULL, 0, 1, {TYPE_FILE}, 0, "FILE", "Print the contents of the file FILE."}, {"chainloader", "chainloader %s '%s'\n", NULL, 0, @@ -105,7 +112,13 @@ static struct legacy_command legacy_commands[] = "[NUM | `saved']", "Set the default entry to entry number NUM (if not specified, it is" " 0, the first entry) or the entry number saved by savedefault."}, - /* FIXME: dhcp unsupported. */ + {"dhcp", "net_bootp; net_ls_addr; if [ x%s = x--with-configfile ]; then " + "if net_get_dhcp_option configfile_name pxe 150 string; then " + "configfile $configfile_name; fi; fi\n", NULL, 0, 1, + {TYPE_WITH_CONFIGFILE_OPTION}, FLAG_IGNORE_REST, "[--with-configfile]", + "Initialize a network device via BOOTP. If the option `--with-configfile'" + " is given, try to load a configuration file specified by the 150 vendor" + " tag."}, {"displayapm", "lsapm\n", NULL, 0, 0, {}, 0, 0, "Display APM BIOS information."}, {"displaymem", "lsmmap\n", NULL, 0, 0, {}, 0, 0, @@ -414,6 +427,8 @@ is_option (enum arg_type opt, const char *curarg, grub_size_t len) { switch (opt) { + case TYPE_WITH_CONFIGFILE_OPTION: + return check_option (curarg, "--with-configfile", len); case TYPE_NOAPM_OPTION: return check_option (curarg, "--no-apm", len); case TYPE_FORCE_OPTION: @@ -665,6 +680,7 @@ grub_legacy_parse (const char *buf, char **entryname, char **suffix) case TYPE_VERBATIM: args[i] = grub_legacy_escape (curarg, curarglen); break; + case TYPE_WITH_CONFIGFILE_OPTION: case TYPE_FORCE_OPTION: case TYPE_NOAPM_OPTION: case TYPE_TYPE_OR_NOMEM_OPTION: @@ -759,6 +775,7 @@ grub_legacy_parse (const char *buf, char **entryname, char **suffix) case TYPE_FILE: case TYPE_REST_VERBATIM: case TYPE_VERBATIM: + case TYPE_WITH_CONFIGFILE_OPTION: case TYPE_FORCE_OPTION: case TYPE_NOAPM_OPTION: case TYPE_TYPE_OR_NOMEM_OPTION: diff --git a/grub-core/lib/priority_queue.c b/grub-core/lib/priority_queue.c new file mode 100644 index 000000000..a790910a8 --- /dev/null +++ b/grub-core/lib/priority_queue.c @@ -0,0 +1,257 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef TEST +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +#else +#include +#include +#include + +#include + +using namespace std; + +typedef size_t grub_size_t; +typedef int (*grub_comparator_t) (const void *a, const void *b); +typedef unsigned char grub_uint8_t; +#define grub_malloc malloc +#define grub_memcpy memcpy +#define grub_realloc realloc +#define grub_free free + +typedef enum + { + GRUB_ERR_NONE, + grub_errno + } grub_err_t; +#endif + +struct grub_priority_queue +{ + grub_size_t elsize; + grub_size_t allocated; + grub_size_t used; + grub_comparator_t cmp; + void *els; +}; + +#ifdef TEST +typedef struct grub_priority_queue *grub_priority_queue_t; +#endif + +static inline void * +element (struct grub_priority_queue *pq, grub_size_t k) +{ + return ((grub_uint8_t *) pq->els) + k * pq->elsize; +} + +static inline void +swap (struct grub_priority_queue *pq, grub_size_t m, grub_size_t n) +{ + grub_uint8_t *p1, *p2; + grub_size_t l; + p1 = (grub_uint8_t *) element (pq, m); + p2 = (grub_uint8_t *) element (pq, n); + for (l = pq->elsize; l; l--, p1++, p2++) + { + grub_uint8_t t; + t = *p1; + *p1 = *p2; + *p2 = t; + } +} + +static inline grub_size_t +parent (grub_size_t v) +{ + return (v - 1) / 2; +} + +static inline grub_size_t +left_child (grub_size_t v) +{ + return 2 * v + 1; +} + +static inline grub_size_t +right_child (grub_size_t v) +{ + return 2 * v + 2; +} + +void * +grub_priority_queue_top (grub_priority_queue_t pq) +{ + if (!pq->used) + return 0; + return element (pq, 0); +} + +void +grub_priority_queue_destroy (grub_priority_queue_t pq) +{ + grub_free (pq->els); + grub_free (pq); +} + +grub_priority_queue_t +grub_priority_queue_new (grub_size_t elsize, + grub_comparator_t cmp) +{ + struct grub_priority_queue *ret; + void *els; + els = grub_malloc (elsize * 8); + if (!els) + return 0; + ret = (struct grub_priority_queue *) grub_malloc (sizeof (*ret)); + if (!ret) + { + grub_free (els); + return 0; + } + ret->elsize = elsize; + ret->allocated = 8; + ret->used = 0; + ret->cmp = cmp; + ret->els = els; + return ret; +} + +/* Heap property: pq->cmp (element (pq, p), element (pq, parent (p))) <= 0. */ +grub_err_t +grub_priority_queue_push (grub_priority_queue_t pq, const void *el) +{ + grub_size_t p; + if (pq->used == pq->allocated) + { + void *els; + els = grub_realloc (pq->els, pq->elsize * 2 * pq->allocated); + if (!els) + return grub_errno; + pq->allocated *= 2; + pq->els = els; + } + pq->used++; + grub_memcpy (element (pq, pq->used - 1), el, pq->elsize); + for (p = pq->used - 1; p; p = parent (p)) + { + if (pq->cmp (element (pq, p), element (pq, parent (p))) <= 0) + break; + swap (pq, p, parent (p)); + } + + return GRUB_ERR_NONE; +} + +void +grub_priority_queue_pop (grub_priority_queue_t pq) +{ + grub_size_t p; + + swap (pq, 0, pq->used - 1); + pq->used--; + for (p = 0; left_child (p) < pq->used; ) + { + grub_size_t c; + if (pq->cmp (element (pq, left_child (p)), element (pq, p)) <= 0 + && (right_child (p) >= pq->used + || pq->cmp (element (pq, right_child (p)), element (pq, p)) <= 0)) + break; + if (right_child (p) >= pq->used + || pq->cmp (element (pq, left_child (p)), + element (pq, right_child (p))) > 0) + c = left_child (p); + else + c = right_child (p); + swap (pq, p, c); + p = c; + } +} + +#ifdef TEST + +static int +compar (const void *a_, const void *b_) +{ + int a = *(int *) a_; + int b = *(int *) b_; + if (a < b) + return -1; + if (a > b) + return +1; + return 0; +} + +int +main (void) +{ + priority_queue pq; + grub_priority_queue_t pq2; + int counter; + int s = 0; + pq2 = grub_priority_queue_new (sizeof (int), compar); + if (!pq2) + return 1; + srand (1); + + for (counter = 0; counter < 1000000; counter++) + { + int op = rand () % 10; + if (s && *(int *) grub_priority_queue_top (pq2) != pq.top ()) + { + printf ("Error at %d\n", counter); + return 2; + } + if (op < 3 && s) + { + grub_priority_queue_pop (pq2); + pq.pop (); + s--; + } + else + { + int v = rand (); + int e; + pq.push (v); + e = grub_priority_queue_push (pq2, &v); + if (e) + return 3; + s++; + } + } + while (s) + { + if (*(int *) grub_priority_queue_top (pq2) != pq.top ()) + { + printf ("Error at the end. %d elements remaining.\n", s); + return 4; + } + grub_priority_queue_pop (pq2); + pq.pop (); + s--; + } + printf ("All tests passed successfully\n"); + return 0; +} +#endif diff --git a/grub-core/net/arp.c b/grub-core/net/arp.c index d726f2c3a..c9a7cb21e 100644 --- a/grub-core/net/arp.c +++ b/grub-core/net/arp.c @@ -1,3 +1,21 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + #include #include #include @@ -6,82 +24,74 @@ #include #include -static struct arp_entry arp_table[10]; -static grub_int8_t new_table_entry = -1; +/* ARP header operation codes */ +enum + { + ARP_REQUEST = 1, + ARP_REPLY = 2 + }; -static void -arp_init_table (void) -{ - grub_memset (arp_table, 0, sizeof (arp_table)); - new_table_entry = 0; -} +enum + { + /* IANA ARP constant to define hardware type as ethernet. */ + GRUB_NET_ARPHRD_ETHERNET = 1 + }; + +struct arphdr { + grub_uint16_t hrd; + grub_uint16_t pro; + grub_uint8_t hln; + grub_uint8_t pln; + grub_uint16_t op; +} __attribute__ ((packed)); -static struct arp_entry * -arp_find_entry (const grub_net_network_level_address_t *proto) -{ - unsigned i; - for (i = 0; i < ARRAY_SIZE (arp_table); i++) - { - if (arp_table[i].avail == 1 && - arp_table[i].nl_address.ipv4 == proto->ipv4) - return &(arp_table[i]); - } - return NULL; -} grub_err_t -grub_net_arp_resolve (struct grub_net_network_level_interface *inf, - const grub_net_network_level_address_t *proto_addr, - grub_net_link_level_address_t *hw_addr) +grub_net_arp_send_request (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *proto_addr) { - struct arp_entry *entry; struct grub_net_buff nb; struct arphdr *arp_header; grub_net_link_level_address_t target_hw_addr; - char *aux, arp_data[128]; + grub_uint8_t *aux, arp_data[128]; grub_err_t err; int i; + grub_size_t addrlen; + grub_uint16_t etherpro; + grub_uint8_t *nbd; - if (proto_addr->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4 - && proto_addr->ipv4 == 0xffffffff) + if (proto_addr->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4) { - hw_addr->type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; - grub_memset (hw_addr->mac, -1, 6); - return GRUB_ERR_NONE; + addrlen = 4; + etherpro = GRUB_NET_ETHERTYPE_IP; } + else + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "unsupported address family"); - /* Check cache table. */ - entry = arp_find_entry (proto_addr); - if (entry) - { - *hw_addr = entry->ll_address; - return GRUB_ERR_NONE; - } /* Build a request packet. */ nb.head = arp_data; nb.end = arp_data + sizeof (arp_data); grub_netbuff_clear (&nb); grub_netbuff_reserve (&nb, 128); - err = grub_netbuff_push (&nb, sizeof (*arp_header) + 2 * (6 + 4)); + err = grub_netbuff_push (&nb, sizeof (*arp_header) + 2 * (6 + addrlen)); if (err) return err; arp_header = (struct arphdr *) nb.data; arp_header->hrd = grub_cpu_to_be16 (GRUB_NET_ARPHRD_ETHERNET); - arp_header->pro = grub_cpu_to_be16 (GRUB_NET_ETHERTYPE_IP); - /* FIXME Add support to ipv6 address. */ arp_header->hln = 6; - arp_header->pln = 4; + arp_header->pro = grub_cpu_to_be16 (etherpro); + arp_header->pln = addrlen; arp_header->op = grub_cpu_to_be16 (ARP_REQUEST); - aux = (char *) arp_header + sizeof (*arp_header); + aux = (grub_uint8_t *) arp_header + sizeof (*arp_header); /* Sender hardware address. */ grub_memcpy (aux, &inf->hwaddress.mac, 6); aux += 6; /* Sender protocol address */ grub_memcpy (aux, &inf->address.ipv4, 4); - aux += 4; + aux += addrlen; /* Target hardware address */ for (i = 0; i < 6; i++) aux[i] = 0x00; @@ -90,77 +100,78 @@ grub_net_arp_resolve (struct grub_net_network_level_interface *inf, grub_memcpy (aux, &proto_addr->ipv4, 4); grub_memset (&target_hw_addr.mac, 0xff, 6); + nbd = nb.data; send_ethernet_packet (inf, &nb, target_hw_addr, GRUB_NET_ETHERTYPE_ARP); - for (i = 0; i < 3; i++) + for (i = 0; i < GRUB_NET_TRIES; i++) { - entry = arp_find_entry (proto_addr); - if (entry) - { - grub_memcpy (hw_addr, &entry->ll_address, sizeof (*hw_addr)); - return GRUB_ERR_NONE; - } - grub_net_poll_cards (200); + if (grub_net_link_layer_resolve_check (inf, proto_addr)) + return GRUB_ERR_NONE; + grub_net_poll_cards (GRUB_NET_INTERVAL); + if (grub_net_link_layer_resolve_check (inf, proto_addr)) + return GRUB_ERR_NONE; + nb.data = nbd; + send_ethernet_packet (inf, &nb, target_hw_addr, GRUB_NET_ETHERTYPE_ARP); } - return grub_error (GRUB_ERR_TIMEOUT, "timeout: could not resolve hardware address"); + return GRUB_ERR_NONE; } grub_err_t -grub_net_arp_receive (struct grub_net_buff *nb) +grub_net_arp_receive (struct grub_net_buff *nb, + struct grub_net_card *card) { struct arphdr *arp_header = (struct arphdr *) nb->data; - struct arp_entry *entry; - grub_uint8_t *sender_hardware_address, *sender_protocol_address; - grub_uint8_t *target_hardware_address, *target_protocol_address; - grub_net_network_level_address_t hwaddress; + grub_uint8_t *sender_hardware_address; + grub_uint8_t *target_hardware_address; + grub_net_network_level_address_t sender_addr, target_addr; + grub_net_link_level_address_t sender_hw_addr; struct grub_net_network_level_interface *inf; + grub_uint8_t *sender_protocol_address, *target_protocol_address; sender_hardware_address = (grub_uint8_t *) arp_header + sizeof (*arp_header); sender_protocol_address = sender_hardware_address + arp_header->hln; target_hardware_address = sender_protocol_address + arp_header->pln; target_protocol_address = target_hardware_address + arp_header->hln; - grub_memcpy (&hwaddress.ipv4, sender_protocol_address, 4); - - /* Check if the sender is in the cache table. */ - entry = arp_find_entry (&hwaddress); - /* Update sender hardware address. */ - if (entry) - grub_memcpy (entry->ll_address.mac, sender_hardware_address, 6); - else + if (grub_be_to_cpu16 (arp_header->pro) == GRUB_NET_ETHERTYPE_IP + && arp_header->pln == 4) { - /* Add sender to cache table. */ - if (new_table_entry == -1) - arp_init_table (); - entry = &(arp_table[new_table_entry]); - entry->avail = 1; - grub_memcpy (&entry->nl_address.ipv4, sender_protocol_address, 4); - grub_memcpy (entry->ll_address.mac, sender_hardware_address, 6); - new_table_entry++; - if (new_table_entry == ARRAY_SIZE (arp_table)) - new_table_entry = 0; + sender_addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + target_addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + grub_memcpy (&sender_addr.ipv4, sender_protocol_address, 4); + grub_memcpy (&target_addr.ipv4, target_protocol_address, 4); } + else + return GRUB_ERR_NONE; + + sender_hw_addr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + grub_memcpy (sender_hw_addr.mac, sender_hardware_address, + sizeof (sender_hw_addr.mac)); + grub_net_link_layer_add_address (card, &sender_addr, &sender_hw_addr, 1); FOR_NET_NETWORK_LEVEL_INTERFACES (inf) { /* Am I the protocol address target? */ - if (inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4 - && grub_memcmp (target_protocol_address, &inf->address.ipv4, 4) == 0 + if (grub_net_addr_cmp (&inf->address, &target_addr) == 0 && grub_be_to_cpu16 (arp_header->op) == ARP_REQUEST) { - grub_net_link_level_address_t aux; - /* Swap hardware fields */ - grub_memcpy (target_hardware_address, sender_hardware_address, - arp_header->hln); + grub_net_link_level_address_t target; + /* We've already checked that pln is either 4 or 16. */ + char tmp[arp_header->pln]; + + target.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + grub_memcpy (target.mac, sender_hardware_address, 6); + grub_memcpy (target_hardware_address, target.mac, 6); grub_memcpy (sender_hardware_address, inf->hwaddress.mac, 6); - grub_memcpy (aux.mac, sender_protocol_address, 6); + + grub_memcpy (tmp, sender_protocol_address, arp_header->pln); grub_memcpy (sender_protocol_address, target_protocol_address, arp_header->pln); - grub_memcpy (target_protocol_address, aux.mac, arp_header->pln); + grub_memcpy (target_protocol_address, tmp, arp_header->pln); + /* Change operation to REPLY and send packet */ arp_header->op = grub_be_to_cpu16 (ARP_REPLY); - grub_memcpy (aux.mac, target_hardware_address, 6); - send_ethernet_packet (inf, nb, aux, GRUB_NET_ETHERTYPE_ARP); + send_ethernet_packet (inf, nb, target, GRUB_NET_ETHERTYPE_ARP); } } return GRUB_ERR_NONE; diff --git a/grub-core/net/bootp.c b/grub-core/net/bootp.c index ba6a29020..ea8943a32 100644 --- a/grub-core/net/bootp.c +++ b/grub-core/net/bootp.c @@ -68,30 +68,59 @@ parse_dhcp_vendor (const char *name, void *vend, int limit) tagtype = *ptr++; /* Pad tag. */ - if (tagtype == 0) + if (tagtype == GRUB_NET_BOOTP_PAD) continue; /* End tag. */ - if (tagtype == 0xff) + if (tagtype == GRUB_NET_BOOTP_END) return; taglength = *ptr++; switch (tagtype) { - case 12: + case GRUB_NET_BOOTP_ROUTER: + if (taglength == 4) + { + grub_net_network_level_netaddress_t target; + grub_net_network_level_address_t gw; + char rname[grub_strlen (name) + sizeof (":default")]; + + target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + target.ipv4.base = 0; + target.ipv4.masksize = 0; + gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + grub_memcpy (&gw.ipv4, ptr, sizeof (gw.ipv4)); + grub_snprintf (rname, sizeof (rname), "%s:default", name); + grub_net_add_route_gw (rname, target, gw); + } + break; + case GRUB_NET_BOOTP_DNS: + { + int i; + for (i = 0; i < taglength / 4; i++) + { + struct grub_net_network_level_address s; + s.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + s.ipv4 = grub_get_unaligned32 (ptr); + grub_net_add_dns_server (&s); + ptr += 4; + } + } + break; + case GRUB_NET_BOOTP_HOSTNAME: set_env_limn_ro (name, "hostname", (char *) ptr, taglength); break; - case 15: + case GRUB_NET_BOOTP_DOMAIN: set_env_limn_ro (name, "domain", (char *) ptr, taglength); break; - case 17: + case GRUB_NET_BOOTP_ROOT_PATH: set_env_limn_ro (name, "rootpath", (char *) ptr, taglength); break; - case 18: + case GRUB_NET_BOOTP_EXTENSIONS_PATH: set_env_limn_ro (name, "extensionspath", (char *) ptr, taglength); break; @@ -130,27 +159,29 @@ grub_net_configure_by_dhcp_ack (const char *name, : sizeof (hwaddr.mac)); hwaddr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; - inter = grub_net_add_addr (name, card, addr, hwaddr, flags); - { - grub_net_network_level_netaddress_t target; - grub_net_network_level_address_t gw; - char rname[grub_strlen (name) + sizeof ("_gw")]; + inter = grub_net_add_addr (name, card, &addr, &hwaddr, flags); + if (bp->gateway_ip) + { + grub_net_network_level_netaddress_t target; + grub_net_network_level_address_t gw; + char rname[grub_strlen (name) + sizeof (":gw")]; - target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; - target.ipv4.base = bp->server_ip; - target.ipv4.masksize = 32; - gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; - gw.ipv4 = bp->gateway_ip; - grub_snprintf (rname, sizeof (rname), "%s_gw", name); - grub_net_add_route_gw (rname, target, gw); - } - { - grub_net_network_level_netaddress_t target; - target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; - target.ipv4.base = bp->gateway_ip; - target.ipv4.masksize = 32; - grub_net_add_route (name, target, inter); - } + target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + target.ipv4.base = bp->server_ip; + target.ipv4.masksize = 32; + gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + gw.ipv4 = bp->gateway_ip; + grub_snprintf (rname, sizeof (rname), "%s:gw", name); + grub_net_add_route_gw (rname, target, gw); + } + if (bp->gateway_ip || bp->server_ip) + { + grub_net_network_level_netaddress_t target; + target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + target.ipv4.base = bp->gateway_ip ? bp->gateway_ip : bp->server_ip; + target.ipv4.masksize = 32; + grub_net_add_route (name, target, inter); + } if (size > OFFSET_OF (boot_file, bp)) set_env_limn_ro (name, "boot_file", (char *) bp->boot_file, @@ -377,6 +408,7 @@ grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ ((unused)), "unrecognised format specification %s", args[3]); } +/* FIXME: allow to specify mac address. */ static grub_err_t grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)), int argc, char **args) @@ -439,6 +471,7 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)), struct grub_net_buff *nb; struct udphdr *udph; grub_net_network_level_address_t target; + grub_net_link_level_address_t ll_target; if (!ifaces[j].prev) continue; @@ -473,7 +506,7 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)), t = 0; } pack->ident = grub_cpu_to_be32 (t); - pack->seconds = 0;//grub_cpu_to_be16 (t); + pack->seconds = grub_cpu_to_be16 (t); grub_memcpy (&pack->mac_addr, &ifaces[j].hwaddress.mac, 6); @@ -484,11 +517,18 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)), udph->dst = grub_cpu_to_be16 (67); udph->chksum = 0; udph->len = grub_cpu_to_be16 (nb->tail - nb->data); - target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; target.ipv4 = 0xffffffff; + err = grub_net_link_layer_resolve (&ifaces[j], &target, &ll_target); + if (err) + return err; - err = grub_net_send_ip_packet (&ifaces[j], &target, nb); + udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP, + &ifaces[j].address, + &target); + + err = grub_net_send_ip_packet (&ifaces[j], &target, &ll_target, nb, + GRUB_NET_IP_UDP); grub_netbuff_free (nb); if (err) return err; @@ -514,7 +554,7 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)), return err; } -static grub_command_t cmd_dhcp, cmd_getdhcp, cmd_bootp; +static grub_command_t cmd_getdhcp, cmd_bootp; void grub_bootp_init (void) @@ -522,9 +562,6 @@ grub_bootp_init (void) cmd_bootp = grub_register_command ("net_bootp", grub_cmd_bootp, N_("[CARD]"), N_("perform a bootp autoconfiguration")); - cmd_dhcp = grub_register_command ("net_dhcp", grub_cmd_bootp, - N_("[CARD]"), - N_("perform a bootp autoconfiguration")); cmd_getdhcp = grub_register_command ("net_get_dhcp_option", grub_cmd_dhcpopt, N_("VAR INTERFACE NUMBER DESCRIPTION"), N_("retrieve DHCP option and save it into VAR. If VAR is - then print the value.")); @@ -534,6 +571,5 @@ void grub_bootp_fini (void) { grub_unregister_command (cmd_getdhcp); - grub_unregister_command (cmd_dhcp); grub_unregister_command (cmd_bootp); } diff --git a/grub-core/net/dns.c b/grub-core/net/dns.c new file mode 100644 index 000000000..bad2745a7 --- /dev/null +++ b/grub-core/net/dns.c @@ -0,0 +1,695 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +struct dns_cache_element +{ + char *name; + grub_size_t naddresses; + struct grub_net_network_level_address *addresses; + grub_uint64_t limit_time; +}; + +#define DNS_CACHE_SIZE 1021 +#define DNS_HASH_BASE 423 + +static struct dns_cache_element dns_cache[DNS_CACHE_SIZE]; +static struct grub_net_network_level_address *dns_servers; +static grub_size_t dns_nservers, dns_servers_alloc; + +grub_err_t +grub_net_add_dns_server (const struct grub_net_network_level_address *s) +{ + if (dns_servers_alloc <= dns_nservers) + { + int na = dns_servers_alloc * 2; + struct grub_net_network_level_address *ns; + if (na < 8) + na = 8; + ns = grub_malloc (na * sizeof (ns[0])); + if (!ns) + return grub_errno; + dns_servers_alloc = na; + dns_servers = ns; + } + dns_servers[dns_nservers++] = *s; + return GRUB_ERR_NONE; +} + +void +grub_net_remove_dns_server (const struct grub_net_network_level_address *s) +{ + grub_size_t i; + for (i = 0; i < dns_nservers; i++) + if (grub_net_addr_cmp (s, &dns_servers[i]) == 0) + break; + if (i < dns_nservers) + { + dns_servers[i] = dns_servers[dns_nservers - 1]; + dns_nservers--; + } +} + +struct dns_header +{ + grub_uint16_t id; + grub_uint8_t flags; + grub_uint8_t ra_z_r_code; + grub_uint16_t qdcount; + grub_uint16_t ancount; + grub_uint16_t nscount; + grub_uint16_t arcount; +} __attribute__ ((packed)); + +enum + { + FLAGS_RESPONSE = 0x80, + FLAGS_OPCODE = 0x78, + FLAGS_RD = 0x01 + }; + +enum + { + ERRCODE_MASK = 0x0f + }; + +enum + { + DNS_PORT = 53 + }; + +struct recv_data +{ + grub_size_t *naddresses; + struct grub_net_network_level_address **addresses; + int cache; + grub_uint16_t id; + int dns_err; + char *name; + const char *oname; +}; + +static inline int +hash (const char *str) +{ + int v = 0, xn = 1; + const char *ptr; + for (ptr = str; *ptr; ) + { + v = (v + xn * *ptr); + xn = (DNS_HASH_BASE * xn) % DNS_CACHE_SIZE; + ptr++; + if (((ptr - str) & 0x3ff) == 0) + v %= DNS_CACHE_SIZE; + } + return v % DNS_CACHE_SIZE; +} + +static int +check_name_real (const grub_uint8_t *name_at, const grub_uint8_t *head, + const grub_uint8_t *tail, const char *check_with, + int *length, char *set) +{ + const char *readable_ptr = check_with; + const grub_uint8_t *ptr; + char *optr = set; + int bytes_processed = 0; + if (length) + *length = 0; + for (ptr = name_at; ptr < tail && bytes_processed < tail - head + 2; ) + { + /* End marker. */ + if (!*ptr) + { + if (length && *length) + (*length)--; + if (optr && optr != set) + optr--; + if (optr) + *optr = 0; + return !readable_ptr || (*readable_ptr == 0); + } + if (*ptr & 0xc0) + { + bytes_processed += 2; + if (ptr + 1 >= tail) + return 0; + ptr = head + (((ptr[0] & 0x3f) << 8) | ptr[1]); + continue; + } + if (readable_ptr && grub_memcmp (ptr + 1, readable_ptr, *ptr) != 0) + return 0; + if (grub_memchr (ptr + 1, 0, *ptr) + || grub_memchr (ptr + 1, '.', *ptr)) + return 0; + if (readable_ptr) + readable_ptr += *ptr; + if (readable_ptr && *readable_ptr != '.' && *readable_ptr != 0) + return 0; + bytes_processed += *ptr + 1; + if (length) + *length += *ptr + 1; + if (optr) + { + grub_memcpy (optr, ptr + 1, *ptr); + optr += *ptr; + } + if (optr) + *optr++ = '.'; + if (readable_ptr && *readable_ptr) + readable_ptr++; + ptr += *ptr + 1; + } + return 0; +} + +static int +check_name (const grub_uint8_t *name_at, const grub_uint8_t *head, + const grub_uint8_t *tail, const char *check_with) +{ + return check_name_real (name_at, head, tail, check_with, NULL, NULL); +} + +static char * +get_name (const grub_uint8_t *name_at, const grub_uint8_t *head, + const grub_uint8_t *tail) +{ + int length; + char *ret; + + if (!check_name_real (name_at, head, tail, NULL, &length, NULL)) + return NULL; + ret = grub_malloc (length + 1); + if (!ret) + return NULL; + if (!check_name_real (name_at, head, tail, NULL, NULL, ret)) + { + grub_free (ret); + return NULL; + } + return ret; +} + +enum + { + DNS_CLASS_A = 1, + DNS_CLASS_CNAME = 5, + DNS_CLASS_AAAA = 28 + }; + +static grub_err_t +recv_hook (grub_net_udp_socket_t sock __attribute__ ((unused)), + struct grub_net_buff *nb, + void *data_) +{ + struct dns_header *head; + struct recv_data *data = data_; + int i, j; + grub_uint8_t *ptr, *reparse_ptr; + int redirect_cnt = 0; + char *redirect_save = NULL; + grub_uint32_t ttl_all = ~0U; + + head = (struct dns_header *) nb->data; + ptr = (grub_uint8_t *) (head + 1); + if (ptr >= nb->tail) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + if (head->id != data->id) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (!(head->flags & FLAGS_RESPONSE) || (head->flags & FLAGS_OPCODE)) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (head->ra_z_r_code & ERRCODE_MASK) + { + data->dns_err = 1; + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + for (i = 0; i < grub_cpu_to_be16 (head->qdcount); i++) + { + if (ptr >= nb->tail) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + while (ptr < nb->tail && !((*ptr & 0xc0) || *ptr == 0)) + ptr += *ptr + 1; + if (ptr < nb->tail && (*ptr & 0xc0)) + ptr++; + ptr++; + ptr += 4; + } + *data->addresses = grub_malloc (sizeof ((*data->addresses)[0]) + * grub_cpu_to_be16 (head->ancount)); + if (!*data->addresses) + { + grub_errno = GRUB_ERR_NONE; + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + reparse_ptr = ptr; + reparse: + for (i = 0, ptr = reparse_ptr; i < grub_cpu_to_be16 (head->ancount); i++) + { + int ignored = 0; + grub_uint8_t class; + grub_uint32_t ttl = 0; + grub_uint16_t length; + if (ptr >= nb->tail) + { + if (!*data->naddresses) + grub_free (*data->addresses); + return GRUB_ERR_NONE; + } + ignored = !check_name (ptr, nb->data, nb->tail, data->name); + while (ptr < nb->tail && !((*ptr & 0xc0) || *ptr == 0)) + ptr += *ptr + 1; + if (ptr < nb->tail && (*ptr & 0xc0)) + ptr++; + ptr++; + if (ptr + 10 >= nb->tail) + { + if (!*data->naddresses) + grub_free (*data->addresses); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (*ptr++ != 0) + ignored = 1; + class = *ptr++; + if (*ptr++ != 0) + ignored = 1; + if (*ptr++ != 1) + ignored = 1; + for (j = 0; j < 4; j++) + { + ttl <<= 8; + ttl |= *ptr++; + } + length = *ptr++ << 8; + length |= *ptr++; + if (ptr + length > nb->tail) + { + if (!*data->naddresses) + grub_free (*data->addresses); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (!ignored) + { + if (ttl_all > ttl) + ttl_all = ttl; + switch (class) + { + case DNS_CLASS_A: + if (length != 4) + break; + (*data->addresses)[*data->naddresses].type + = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + grub_memcpy (&(*data->addresses)[*data->naddresses].ipv4, + ptr, 4); + (*data->naddresses)++; + break; + case DNS_CLASS_AAAA: + if (length != 16) + break; + (*data->addresses)[*data->naddresses].type + = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + grub_memcpy (&(*data->addresses)[*data->naddresses].ipv6, + ptr, 16); + (*data->naddresses)++; + break; + case DNS_CLASS_CNAME: + if (!(redirect_cnt & (redirect_cnt - 1))) + { + grub_free (redirect_save); + redirect_save = data->name; + } + else + grub_free (data->name); + redirect_cnt++; + data->name = get_name (ptr, nb->data, nb->tail); + if (!data->name) + { + data->dns_err = 1; + grub_errno = 0; + return GRUB_ERR_NONE; + } + grub_dprintf ("dns", "CNAME %s\n", data->name); + if (grub_strcmp (redirect_save, data->name) == 0) + { + data->dns_err = 1; + grub_free (redirect_save); + return GRUB_ERR_NONE; + } + goto reparse; + } + } + ptr += length; + } + if (ttl_all && *data->naddresses && data->cache) + { + int h; + grub_dprintf ("dns", "caching for %d seconds\n", ttl_all); + h = hash (data->oname); + grub_free (dns_cache[h].name); + dns_cache[h].name = 0; + grub_free (dns_cache[h].addresses); + dns_cache[h].addresses = 0; + dns_cache[h].name = grub_strdup (data->oname); + dns_cache[h].naddresses = *data->naddresses; + dns_cache[h].addresses = grub_malloc (*data->naddresses + * sizeof (dns_cache[h].addresses[0])); + dns_cache[h].limit_time = grub_get_time_ms () + 1000 * ttl_all; + if (!dns_cache[h].addresses || !dns_cache[h].name) + { + grub_free (dns_cache[h].name); + dns_cache[h].name = 0; + grub_free (dns_cache[h].addresses); + dns_cache[h].addresses = 0; + } + grub_memcpy (dns_cache[h].addresses, *data->addresses, + *data->naddresses + * sizeof (dns_cache[h].addresses[0])); + } + grub_netbuff_free (nb); + grub_free (redirect_save); + return GRUB_ERR_NONE; +} + +grub_err_t +grub_net_dns_lookup (const char *name, + const struct grub_net_network_level_address *servers, + grub_size_t n_servers, + grub_size_t *naddresses, + struct grub_net_network_level_address **addresses, + int cache) +{ + grub_size_t send_servers = 0; + grub_size_t i, j; + struct grub_net_buff *nb; + grub_net_udp_socket_t sockets[n_servers]; + grub_uint8_t *optr; + const char *iptr; + struct dns_header *head; + static grub_uint16_t id = 1; + grub_err_t err = GRUB_ERR_NONE; + struct recv_data data = {naddresses, addresses, cache, + grub_cpu_to_be16 (id++), 0, 0, name}; + grub_uint8_t *nbd; + int have_server = 0; + + if (!servers) + { + servers = dns_servers; + n_servers = dns_nservers; + } + + if (!n_servers) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no DNS servers"); + + *naddresses = 0; + if (cache) + { + int h; + h = hash (name); + if (dns_cache[h].name && grub_strcmp (dns_cache[h].name, name) == 0 + && grub_get_time_ms () < dns_cache[h].limit_time) + { + grub_dprintf ("dns", "retrieved from cache\n"); + *addresses = grub_malloc (dns_cache[h].naddresses + * sizeof ((*addresses)[0])); + if (!*addresses) + return grub_errno; + *naddresses = dns_cache[h].naddresses; + grub_memcpy (*addresses, dns_cache[h].addresses, + dns_cache[h].naddresses + * sizeof ((*addresses)[0])); + return GRUB_ERR_NONE; + } + } + + data.name = grub_strdup (name); + if (!data.name) + return grub_errno; + + nb = grub_netbuff_alloc (GRUB_NET_OUR_MAX_IP_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE + + GRUB_NET_UDP_HEADER_SIZE + + sizeof (struct dns_header) + + grub_strlen (name) + 2 + 4 + + 2 + 4); + if (!nb) + { + grub_free (data.name); + return grub_errno; + } + grub_netbuff_reserve (nb, GRUB_NET_OUR_MAX_IP_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE + + GRUB_NET_UDP_HEADER_SIZE); + grub_netbuff_put (nb, sizeof (struct dns_header) + + grub_strlen (name) + 2 + 4 + 2 + 4); + head = (struct dns_header *) nb->data; + optr = (grub_uint8_t *) (head + 1); + for (iptr = name; *iptr; ) + { + const char *dot; + dot = grub_strchr (iptr, '.'); + if (!dot) + dot = iptr + grub_strlen (iptr); + if ((dot - iptr) >= 64) + { + grub_free (data.name); + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "domain component is too long"); + } + *optr = (dot - iptr); + optr++; + grub_memcpy (optr, iptr, dot - iptr); + optr += dot - iptr; + iptr = dot; + if (*iptr) + iptr++; + } + *optr++ = 0; + + /* Type: A. */ + *optr++ = 0; + *optr++ = 1; + + /* Class. */ + *optr++ = 0; + *optr++ = 1; + + /* Compressed name. */ + *optr++ = 0xc0; + *optr++ = 0x0c; + /* Type: AAAA. */ + *optr++ = 0; + *optr++ = 28; + + /* Class. */ + *optr++ = 0; + *optr++ = 1; + + head->id = data.id; + head->flags = FLAGS_RD; + head->ra_z_r_code = 0; + head->qdcount = grub_cpu_to_be16_compile_time (2); + head->ancount = grub_cpu_to_be16_compile_time (0); + head->nscount = grub_cpu_to_be16_compile_time (0); + head->arcount = grub_cpu_to_be16_compile_time (0); + + nbd = nb->data; + + for (i = 0; i < n_servers * 4; i++) + { + /* Connect to a next server. */ + while (!(i & 1) && send_servers < n_servers) + { + sockets[send_servers] = grub_net_udp_open (servers[send_servers], + DNS_PORT, + recv_hook, + &data); + send_servers++; + if (!sockets[send_servers - 1]) + { + err = grub_errno; + grub_errno = GRUB_ERR_NONE; + } + else + { + have_server = 1; + break; + } + } + if (!have_server) + goto out; + if (*data.naddresses) + goto out; + for (j = 0; j < send_servers; j++) + { + grub_err_t err2; + if (!sockets[j]) + continue; + nb->data = nbd; + err2 = grub_net_send_udp_packet (sockets[j], nb); + if (err2) + { + grub_errno = GRUB_ERR_NONE; + err = err2; + } + if (*data.naddresses) + goto out; + } + grub_net_poll_cards (200); + } + out: + grub_free (data.name); + grub_netbuff_free (nb); + for (j = 0; j < send_servers; j++) + grub_net_udp_close (sockets[j]); + + if (*data.naddresses) + return GRUB_ERR_NONE; + if (data.dns_err) + return grub_error (GRUB_ERR_NET_NO_DOMAIN, "no DNS domain found"); + + if (err) + { + grub_errno = err; + return err; + } + return grub_error (GRUB_ERR_TIMEOUT, "no DNS reply received"); +} + +static grub_err_t +grub_cmd_nslookup (struct grub_command *cmd __attribute__ ((unused)), + int argc, char **args) +{ + grub_err_t err; + struct grub_net_network_level_address server; + grub_size_t naddresses, i; + struct grub_net_network_level_address *addresses; + if (argc != 2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "2 arguments expected"); + err = grub_net_resolve_address (args[1], &server); + if (err) + return err; + + err = grub_net_dns_lookup (args[0], &server, 1, &naddresses, &addresses, 0); + if (err) + return err; + for (i = 0; i < naddresses; i++) + { + char buf[GRUB_NET_MAX_STR_ADDR_LEN]; + grub_net_addr_to_str (&addresses[i], buf); + grub_printf ("%s\n", buf); + } + grub_free (addresses); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_list_dns (struct grub_command *cmd __attribute__ ((unused)), + int argc __attribute__ ((unused)), + char **args __attribute__ ((unused))) +{ + grub_size_t i; + for (i = 0; i < dns_nservers; i++) + { + char buf[GRUB_NET_MAX_STR_ADDR_LEN]; + grub_net_addr_to_str (&dns_servers[i], buf); + grub_printf ("%s\n", buf); + } + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_add_dns (struct grub_command *cmd __attribute__ ((unused)), + int argc, char **args) +{ + grub_err_t err; + struct grub_net_network_level_address server; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "1 argument expected"); + err = grub_net_resolve_address (args[0], &server); + if (err) + return err; + + return grub_net_add_dns_server (&server); +} + +static grub_err_t +grub_cmd_del_dns (struct grub_command *cmd __attribute__ ((unused)), + int argc, char **args) +{ + grub_err_t err; + struct grub_net_network_level_address server; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "1 argument expected"); + err = grub_net_resolve_address (args[1], &server); + if (err) + return err; + + return grub_net_add_dns_server (&server); +} + +static grub_command_t cmd, cmd_add, cmd_del, cmd_list; + +void +grub_dns_init (void) +{ + cmd = grub_register_command ("net_nslookup", grub_cmd_nslookup, + "ADDRESS DNSSERVER", + N_("Perform a DNS lookup")); + cmd_add = grub_register_command ("net_add_dns", grub_cmd_add_dns, + "DNSSERVER", + N_("Add a DNS server")); + cmd_del = grub_register_command ("net_del_dns", grub_cmd_del_dns, + "DNSSERVER", + N_("Remove a DNS server")); + cmd_list = grub_register_command ("net_ls_dns", grub_cmd_list_dns, + NULL, N_("List DNS servers")); +} + +void +grub_dns_fini (void) +{ + grub_unregister_command (cmd); + grub_unregister_command (cmd_add); + grub_unregister_command (cmd_del); + grub_unregister_command (cmd_list); +} diff --git a/grub-core/net/drivers/efi/efinet.c b/grub-core/net/drivers/efi/efinet.c index 5c6aac608..2a392bc98 100644 --- a/grub-core/net/drivers/efi/efinet.c +++ b/grub-core/net/drivers/efi/efinet.c @@ -35,50 +35,83 @@ send_card_buffer (const struct grub_net_card *dev, { grub_efi_status_t st; grub_efi_simple_network_t *net = dev->efi_net; + grub_uint64_t limit_time = grub_get_time_ms () + 4000; st = efi_call_7 (net->transmit, net, 0, (pack->tail - pack->data), pack->data, NULL, NULL, NULL); if (st != GRUB_EFI_SUCCESS) - return grub_error (GRUB_ERR_IO, "Couldn't send network packet."); - return GRUB_ERR_NONE; + return grub_error (GRUB_ERR_IO, "couldn't send network packet"); + while (1) + { + void *txbuf = NULL; + st = efi_call_3 (net->get_status, net, 0, &txbuf); + if (st != GRUB_EFI_SUCCESS) + return grub_error (GRUB_ERR_IO, "couldn't send network packet"); + if (txbuf) + return GRUB_ERR_NONE; + if (limit_time < grub_get_time_ms ()) + return grub_error (GRUB_ERR_TIMEOUT, "couldn't send network packet"); + } } -static grub_ssize_t -get_card_packet (const struct grub_net_card *dev, - struct grub_net_buff *nb) +static struct grub_net_buff * +get_card_packet (const struct grub_net_card *dev) { grub_efi_simple_network_t *net = dev->efi_net; grub_err_t err; grub_efi_status_t st; - grub_efi_uintn_t bufsize = 1500; + grub_efi_uintn_t bufsize = 1536; + struct grub_net_buff *nb; - err = grub_netbuff_clear (nb); - if (err) - return -1; + nb = grub_netbuff_alloc (bufsize); + if (!nb) + return NULL; - err = grub_netbuff_put (nb, 1500); - if (err) - return -1; + /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible + by 4. So that IP header is aligned on 4 bytes. */ + grub_netbuff_reserve (nb, 2); + if (!nb) + { + grub_netbuff_free (nb); + return NULL; + } st = efi_call_7 (net->receive, net, NULL, &bufsize, nb->data, NULL, NULL, NULL); if (st == GRUB_EFI_BUFFER_TOO_SMALL) { - err = grub_netbuff_put (nb, bufsize - 1500); - if (err) - return -1; + grub_netbuff_free (nb); + + bufsize = ALIGN_UP (bufsize, 32); + + nb = grub_netbuff_alloc (bufsize); + if (!nb) + return NULL; + + /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible + by 4. So that IP header is aligned on 4 bytes. */ + grub_netbuff_reserve (nb, 2); + if (!nb) + { + grub_netbuff_free (nb); + return NULL; + } + st = efi_call_7 (net->receive, net, NULL, &bufsize, nb->data, NULL, NULL, NULL); } if (st != GRUB_EFI_SUCCESS) { - grub_netbuff_clear (nb); - return -1; + grub_netbuff_free (nb); + return NULL; } - err = grub_netbuff_unput (nb, (nb->tail - nb->data) - bufsize); + err = grub_netbuff_put (nb, bufsize); if (err) - return -1; + { + grub_netbuff_free (nb); + return NULL; + } - return bufsize; + return nb; } static struct grub_net_card_driver efidriver = @@ -136,6 +169,7 @@ grub_efinet_findcards (void) card->driver = &efidriver; card->flags = 0; card->default_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + card->mtu = net->mode->max_packet_size; grub_memcpy (card->default_address.mac, net->mode->current_address, sizeof (card->default_address.mac)); diff --git a/grub-core/net/drivers/emu/emunet.c b/grub-core/net/drivers/emu/emunet.c index d1e49a2f4..2aba28d23 100644 --- a/grub-core/net/drivers/emu/emunet.c +++ b/grub-core/net/drivers/emu/emunet.c @@ -1,3 +1,20 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ #include #include @@ -11,6 +28,8 @@ #include #include +GRUB_MOD_LICENSE ("GPLv3+"); + static int fd; static grub_err_t @@ -26,19 +45,34 @@ send_card_buffer (const struct grub_net_card *dev __attribute__ ((unused)), return GRUB_ERR_NONE; } -static grub_ssize_t -get_card_packet (const struct grub_net_card *dev __attribute__ ((unused)), - struct grub_net_buff *pack) +static struct grub_net_buff * +get_card_packet (const struct grub_net_card *dev __attribute__ ((unused))) { ssize_t actual; + struct grub_net_buff *nb; - grub_netbuff_clear (pack); - actual = read (fd, pack->data, 1500); + nb = grub_netbuff_alloc (1536); + if (!nb) + return NULL; + + /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible + by 4. So that IP header is aligned on 4 bytes. */ + grub_netbuff_reserve (nb, 2); + if (!nb) + { + grub_netbuff_free (nb); + return NULL; + } + + actual = read (fd, nb->data, 1536); if (actual < 0) - return -1; - grub_netbuff_put (pack, actual); + { + grub_netbuff_free (nb); + return NULL; + } + grub_netbuff_put (nb, actual); - return actual; + return nb; } static struct grub_net_card_driver emudriver = @@ -52,6 +86,7 @@ static struct grub_net_card emucard = { .name = "emu0", .driver = &emudriver, + .mtu = 1500, .default_address = { .type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET, {.mac = {0, 1, 2, 3, 4, 5}} diff --git a/grub-core/net/drivers/i386/pc/pxe.c b/grub-core/net/drivers/i386/pc/pxe.c index cd598ea72..3343533ef 100644 --- a/grub-core/net/drivers/i386/pc/pxe.c +++ b/grub-core/net/drivers/i386/pc/pxe.c @@ -164,14 +164,13 @@ grub_pxe_scan (void) return bangpxe; } -static grub_ssize_t -grub_pxe_recv (const struct grub_net_card *dev __attribute__ ((unused)), - struct grub_net_buff *buf) +static struct grub_net_buff * +grub_pxe_recv (const struct grub_net_card *dev __attribute__ ((unused))) { struct grub_pxe_undi_isr *isr; static int in_progress = 0; - char *ptr, *end; - int len; + grub_uint8_t *ptr, *end; + struct grub_net_buff *buf; isr = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; @@ -183,7 +182,7 @@ grub_pxe_recv (const struct grub_net_card *dev __attribute__ ((unused)), if (isr->status || isr->func_flag != GRUB_PXE_ISR_OUT_OURS) { in_progress = 0; - return -1; + return NULL; } grub_memset (isr, 0, sizeof (*isr)); isr->func_flag = GRUB_PXE_ISR_IN_PROCESS; @@ -201,17 +200,27 @@ grub_pxe_recv (const struct grub_net_card *dev __attribute__ ((unused)), if (isr->status || isr->func_flag == GRUB_PXE_ISR_OUT_DONE) { in_progress = 0; - return -1; + return NULL; } grub_memset (isr, 0, sizeof (*isr)); isr->func_flag = GRUB_PXE_ISR_IN_GET_NEXT; grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry); } - grub_netbuff_put (buf, isr->frame_len); + buf = grub_netbuff_alloc (isr->frame_len); + if (!buf) + return NULL; + /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible + by 4. So that IP header is aligned on 4 bytes. */ + grub_netbuff_reserve (buf, 2); + if (!buf) + { + grub_netbuff_free (buf); + return NULL; + } ptr = buf->data; end = ptr + isr->frame_len; - len = isr->frame_len; + grub_netbuff_put (buf, isr->frame_len); grub_memcpy (ptr, LINEAR (isr->buffer), isr->buffer_len); ptr += isr->buffer_len; while (ptr < end) @@ -222,7 +231,8 @@ grub_pxe_recv (const struct grub_net_card *dev __attribute__ ((unused)), if (isr->status || isr->func_flag != GRUB_PXE_ISR_OUT_RECEIVE) { in_progress = 1; - return -1; + grub_netbuff_free (buf); + return NULL; } grub_memcpy (ptr, LINEAR (isr->buffer), isr->buffer_len); @@ -230,7 +240,7 @@ grub_pxe_recv (const struct grub_net_card *dev __attribute__ ((unused)), } in_progress = 1; - return len; + return buf; } static grub_err_t @@ -345,6 +355,7 @@ GRUB_MOD_INIT(pxe) if (i == sizeof (grub_pxe_card.default_address.mac)) grub_memcpy (grub_pxe_card.default_address.mac, ui->permanent_addr, sizeof (grub_pxe_card.default_address.mac)); + grub_pxe_card.mtu = ui->mtu; grub_pxe_card.default_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; diff --git a/grub-core/net/drivers/ieee1275/ofnet.c b/grub-core/net/drivers/ieee1275/ofnet.c index 7a1edcc1b..227b88aa8 100644 --- a/grub-core/net/drivers/ieee1275/ofnet.c +++ b/grub-core/net/drivers/ieee1275/ofnet.c @@ -28,7 +28,6 @@ struct grub_ofnetcard_data { char *path; grub_ieee1275_ihandle_t handle; - grub_uint32_t mtu; }; static grub_err_t @@ -74,25 +73,35 @@ send_card_buffer (const struct grub_net_card *dev, struct grub_net_buff *pack) return GRUB_ERR_NONE; } -static grub_ssize_t -get_card_packet (const struct grub_net_card *dev, struct grub_net_buff *nb) +static struct grub_net_buff * +get_card_packet (const struct grub_net_card *dev) { grub_ssize_t actual; int rc; struct grub_ofnetcard_data *data = dev->data; grub_uint64_t start_time; + struct grub_net_buff *nb; - grub_netbuff_clear (nb); + nb = grub_netbuff_alloc (dev->mtu + 64); + /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible + by 4. So that IP header is aligned on 4 bytes. */ + grub_netbuff_reserve (nb, 2); + if (!nb) + { + grub_netbuff_free (nb); + return NULL; + } start_time = grub_get_time_ms (); do - rc = grub_ieee1275_read (data->handle, nb->data, data->mtu, &actual); + rc = grub_ieee1275_read (data->handle, nb->data, dev->mtu + 64, &actual); while ((actual <= 0 || rc < 0) && (grub_get_time_ms () - start_time < 200)); if (actual) { grub_netbuff_put (nb, actual); - return actual; + return nb; } - return -1; + grub_netbuff_free (nb); + return NULL; } static struct grub_net_card_driver ofdriver = @@ -228,12 +237,15 @@ grub_ofnet_findcards (void) grub_ieee1275_finddevice (ofdata->path, &devhandle); - if (grub_ieee1275_get_integer_property - (devhandle, "max-frame-size", &(ofdata->mtu), - sizeof (ofdata->mtu), 0)) - { - ofdata->mtu = 1500; - } + { + grub_uint32_t t; + if (grub_ieee1275_get_integer_property (devhandle, + "max-frame-size", &t, + sizeof (t), 0)) + card->mtu = 1500; + else + card->mtu = t; + } if (grub_ieee1275_get_property (devhandle, "mac-address", &(lla.mac), 6, 0) diff --git a/grub-core/net/ethernet.c b/grub-core/net/ethernet.c index acd33bcf6..b38e2c83e 100644 --- a/grub-core/net/ethernet.c +++ b/grub-core/net/ethernet.c @@ -52,11 +52,13 @@ grub_err_t send_ethernet_packet (struct grub_net_network_level_interface *inf, struct grub_net_buff *nb, grub_net_link_level_address_t target_addr, - grub_uint16_t ethertype) + grub_net_ethertype_t ethertype) { struct etherhdr *eth; grub_err_t err; + COMPILE_TIME_ASSERT (sizeof (*eth) < GRUB_NET_MAX_LINK_HEADER_SIZE); + err = grub_netbuff_push (nb, sizeof (*eth)); if (err) return err; @@ -78,14 +80,15 @@ send_ethernet_packet (struct grub_net_network_level_interface *inf, } grub_err_t -grub_net_recv_ethernet_packet (struct grub_net_buff * nb, - const struct grub_net_card * card) +grub_net_recv_ethernet_packet (struct grub_net_buff *nb, + struct grub_net_card *card) { struct etherhdr *eth; struct llchdr *llch; struct snaphdr *snaph; - grub_uint16_t type; + grub_net_ethertype_t type; grub_net_link_level_address_t hwaddress; + grub_net_link_level_address_t src_hwaddress; grub_err_t err; eth = (struct etherhdr *) nb->data; @@ -111,19 +114,20 @@ grub_net_recv_ethernet_packet (struct grub_net_buff * nb, hwaddress.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; grub_memcpy (hwaddress.mac, eth->dst, sizeof (hwaddress.mac)); + src_hwaddress.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + grub_memcpy (src_hwaddress.mac, eth->src, sizeof (src_hwaddress.mac)); - /* ARP packet. */ - if (type == GRUB_NET_ETHERTYPE_ARP) + switch (type) { - grub_net_arp_receive (nb); + /* ARP packet. */ + case GRUB_NET_ETHERTYPE_ARP: + grub_net_arp_receive (nb, card); grub_netbuff_free (nb); return GRUB_ERR_NONE; - } - /* IP packet. */ - if (type == GRUB_NET_ETHERTYPE_IP) - { - grub_net_recv_ip_packets (nb, card, &hwaddress); - return GRUB_ERR_NONE; + /* IP packet. */ + case GRUB_NET_ETHERTYPE_IP: + case GRUB_NET_ETHERTYPE_IP6: + return grub_net_recv_ip_packets (nb, card, &hwaddress, &src_hwaddress); } grub_netbuff_free (nb); return GRUB_ERR_NONE; diff --git a/grub-core/net/http.c b/grub-core/net/http.c new file mode 100644 index 000000000..9f6628cb9 --- /dev/null +++ b/grub-core/net/http.c @@ -0,0 +1,510 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +enum + { + HTTP_PORT = 80 + }; + + +typedef struct http_data +{ + char *current_line; + grub_size_t current_line_len; + int headers_recv; + int first_line_recv; + int size_recv; + grub_net_tcp_socket_t sock; + char *filename; + grub_err_t err; + char *errmsg; + int chunked; + grub_size_t chunk_rem; + int in_chunk_len; +} *http_data_t; + +static grub_off_t +have_ahead (struct grub_file *file) +{ + grub_net_t net = file->device->net; + grub_off_t ret = net->offset; + struct grub_net_packet *pack; + for (pack = net->packs.first; pack; pack = pack->next) + ret += pack->nb->tail - pack->nb->data; + return ret; +} + +static grub_err_t +parse_line (grub_file_t file, http_data_t data, char *ptr, grub_size_t len) +{ + char *end = ptr + len; + while (end > ptr && *(end - 1) == '\r') + end--; + *end = 0; + /* Trailing CRLF. */ + if (data->in_chunk_len == 1) + { + data->in_chunk_len = 2; + return GRUB_ERR_NONE; + } + if (data->in_chunk_len == 2) + { + data->chunk_rem = grub_strtoul (ptr, 0, 16); + grub_errno = GRUB_ERR_NONE; + if (data->chunk_rem == 0) + { + file->device->net->eof = 1; + if (file->size == GRUB_FILE_SIZE_UNKNOWN) + file->size = have_ahead (file); + } + data->in_chunk_len = 0; + return GRUB_ERR_NONE; + } + if (ptr == end) + { + data->headers_recv = 1; + if (data->chunked) + data->in_chunk_len = 2; + return GRUB_ERR_NONE; + } + + if (!data->first_line_recv) + { + int code; + if (grub_memcmp (ptr, "HTTP/1.1 ", sizeof ("HTTP/1.1 ") - 1) != 0) + return grub_error (GRUB_ERR_NET_INVALID_RESPONSE, + "unsupported HTTP response"); + ptr += sizeof ("HTTP/1.1 ") - 1; + code = grub_strtoul (ptr, &ptr, 10); + if (grub_errno) + return grub_errno; + switch (code) + { + case 200: + break; + case 404: + data->err = GRUB_ERR_FILE_NOT_FOUND; + data->errmsg = grub_xasprintf ("file `%s' not found", data->filename); + return GRUB_ERR_NONE; + default: + data->err = GRUB_ERR_NET_UNKNOWN_ERROR; + data->errmsg = grub_xasprintf ("unsupported HTTP error %d: %s", + code, ptr); + return GRUB_ERR_NONE; + } + data->first_line_recv = 1; + return GRUB_ERR_NONE; + } + if (grub_memcmp (ptr, "Content-Length: ", sizeof ("Content-Length: ") - 1) + == 0 && !data->size_recv) + { + ptr += sizeof ("Content-Length: ") - 1; + file->size = grub_strtoull (ptr, &ptr, 10); + data->size_recv = 1; + return GRUB_ERR_NONE; + } + if (grub_memcmp (ptr, "Transfer-Encoding: chunked", + sizeof ("Transfer-Encoding: chunked") - 1) == 0) + { + data->chunked = 1; + return GRUB_ERR_NONE; + } + + return GRUB_ERR_NONE; +} + +static void +http_err (grub_net_tcp_socket_t sock __attribute__ ((unused)), + void *f) +{ + grub_file_t file = f; + http_data_t data = file->data; + + if (data->sock) + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + if (data->current_line) + grub_free (data->current_line); + grub_free (data); + file->device->net->eof = 1; + if (file->size == GRUB_FILE_SIZE_UNKNOWN) + file->size = have_ahead (file); +} + +static grub_err_t +http_receive (grub_net_tcp_socket_t sock __attribute__ ((unused)), + struct grub_net_buff *nb, + void *f) +{ + grub_file_t file = f; + http_data_t data = file->data; + grub_err_t err; + + while (1) + { + char *ptr = (char *) nb->data; + if ((!data->headers_recv || data->in_chunk_len) && data->current_line) + { + int have_line = 1; + char *t; + ptr = grub_memchr (nb->data, '\n', nb->tail - nb->data); + if (ptr) + ptr++; + else + { + have_line = 0; + ptr = (char *) nb->tail; + } + t = grub_realloc (data->current_line, + data->current_line_len + (ptr - (char *) nb->data)); + if (!t) + { + grub_netbuff_free (nb); + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + return grub_errno; + } + + data->current_line = t; + grub_memcpy (data->current_line + data->current_line_len, + nb->data, ptr - (char *) nb->data); + data->current_line_len += ptr - (char *) nb->data; + if (!have_line) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + err = parse_line (file, data, data->current_line, + data->current_line_len); + grub_free (data->current_line); + data->current_line = 0; + data->current_line_len = 0; + if (err) + { + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + grub_netbuff_free (nb); + return err; + } + } + + while (ptr < (char *) nb->tail && (!data->headers_recv + || data->in_chunk_len)) + { + char *ptr2; + ptr2 = grub_memchr (ptr, '\n', (char *) nb->tail - ptr); + if (!ptr2) + { + data->current_line = grub_malloc ((char *) nb->tail - ptr); + if (!data->current_line) + { + grub_netbuff_free (nb); + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + return grub_errno; + } + data->current_line_len = (char *) nb->tail - ptr; + grub_memcpy (data->current_line, ptr, data->current_line_len); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + err = parse_line (file, data, ptr, ptr2 - ptr); + if (err) + { + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + grub_netbuff_free (nb); + return err; + } + ptr = ptr2 + 1; + } + + if (((char *) nb->tail - ptr) <= 0) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + err = grub_netbuff_pull (nb, ptr - (char *) nb->data); + if (err) + { + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + grub_netbuff_free (nb); + return err; + } + if (!(data->chunked && (grub_ssize_t) data->chunk_rem + < nb->tail - nb->data)) + { + grub_net_put_packet (&file->device->net->packs, nb); + if (data->chunked) + data->chunk_rem -= nb->tail - nb->data; + return GRUB_ERR_NONE; + } + if (data->chunk_rem) + { + struct grub_net_buff *nb2; + nb2 = grub_netbuff_alloc (data->chunk_rem); + if (!nb2) + return grub_errno; + grub_netbuff_put (nb2, data->chunk_rem); + grub_memcpy (nb2->data, nb->data, data->chunk_rem); + grub_net_put_packet (&file->device->net->packs, nb2); + grub_netbuff_pull (nb, data->chunk_rem); + } + data->in_chunk_len = 1; + } +} + +static grub_err_t +http_establish (struct grub_file *file, grub_off_t offset, int initial) +{ + http_data_t data = file->data; + grub_uint8_t *ptr; + int i; + struct grub_net_buff *nb; + grub_err_t err; + + nb = grub_netbuff_alloc (GRUB_NET_TCP_RESERVE_SIZE + + sizeof ("GET ") - 1 + + grub_strlen (data->filename) + + sizeof (" HTTP/1.1\r\nHost: ") - 1 + + grub_strlen (file->device->net->server) + + sizeof ("\r\nUser-Agent: " PACKAGE_STRING + "\r\n") - 1 + + sizeof ("Content-Range: bytes XXXXXXXXXXXXXXXXXXXX" + "-XXXXXXXXXXXXXXXXXXXX/" + "XXXXXXXXXXXXXXXXXXXX\r\n\r\n")); + if (!nb) + return grub_errno; + + grub_netbuff_reserve (nb, GRUB_NET_TCP_RESERVE_SIZE); + ptr = nb->tail; + err = grub_netbuff_put (nb, sizeof ("GET ") - 1); + if (err) + { + grub_netbuff_free (nb); + return err; + } + grub_memcpy (ptr, "GET ", sizeof ("GET ") - 1); + + ptr = nb->tail; + + err = grub_netbuff_put (nb, grub_strlen (data->filename)); + if (err) + { + grub_netbuff_free (nb); + return err; + } + grub_memcpy (ptr, data->filename, grub_strlen (data->filename)); + + ptr = nb->tail; + err = grub_netbuff_put (nb, sizeof (" HTTP/1.1\r\nHost: ") - 1); + if (err) + { + grub_netbuff_free (nb); + return err; + } + grub_memcpy (ptr, " HTTP/1.1\r\nHost: ", + sizeof (" HTTP/1.1\r\nHost: ") - 1); + + ptr = nb->tail; + err = grub_netbuff_put (nb, grub_strlen (file->device->net->server)); + if (err) + { + grub_netbuff_free (nb); + return err; + } + grub_memcpy (ptr, file->device->net->server, + grub_strlen (file->device->net->server)); + + ptr = nb->tail; + err = grub_netbuff_put (nb, + sizeof ("\r\nUser-Agent: " PACKAGE_STRING "\r\n") + - 1); + if (err) + { + grub_netbuff_free (nb); + return err; + } + grub_memcpy (ptr, "\r\nUser-Agent: " PACKAGE_STRING "\r\n", + sizeof ("\r\nUser-Agent: " PACKAGE_STRING "\r\n") - 1); + if (!initial) + { + ptr = nb->tail; + grub_snprintf ((char *) ptr, + sizeof ("Content-Range: bytes XXXXXXXXXXXXXXXXXXXX-" + "XXXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXX\r\n" + "\r\n"), + "Content-Range: bytes %" PRIuGRUB_UINT64_T "-%" + PRIuGRUB_UINT64_T "/%" PRIuGRUB_UINT64_T "\r\n\r\n", + offset, file->size - 1, file->size); + grub_netbuff_put (nb, grub_strlen ((char *) ptr)); + } + ptr = nb->tail; + grub_netbuff_put (nb, 2); + grub_memcpy (ptr, "\r\n", 2); + + data->sock = grub_net_tcp_open (file->device->net->server, + HTTP_PORT, http_receive, + http_err, http_err, + file); + if (!data->sock) + { + grub_netbuff_free (nb); + return grub_errno; + } + + // grub_net_poll_cards (5000); + + err = grub_net_send_tcp_packet (data->sock, nb, 1); + if (err) + { + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + return err; + } + + for (i = 0; !data->headers_recv && i < 100; i++) + { + grub_net_tcp_retransmit (); + grub_net_poll_cards (300); + } + + if (!data->headers_recv) + { + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + if (data->err) + { + char *str = data->errmsg; + err = grub_error (data->err, "%s", str); + grub_free (str); + return data->err; + } + return grub_error (GRUB_ERR_TIMEOUT, "timeout opening http"); + } + return GRUB_ERR_NONE; +} + +static grub_err_t +http_seek (struct grub_file *file, grub_off_t off) +{ + struct http_data *old_data, *data; + grub_err_t err; + old_data = file->data; + /* FIXME: Reuse socket? */ + grub_net_tcp_close (old_data->sock, GRUB_NET_TCP_ABORT); + + while (file->device->net->packs.first) + { + grub_netbuff_free (file->device->net->packs.first->nb); + grub_net_remove_packet (file->device->net->packs.first); + } + + file->device->net->offset = off; + + data = grub_zalloc (sizeof (*data)); + if (!data) + return grub_errno; + + data->size_recv = 1; + data->filename = old_data->filename; + if (!data->filename) + { + grub_free (data); + return grub_errno; + } + grub_free (old_data); + + file->data = data; + err = http_establish (file, off, 0); + if (err) + { + grub_free (data->filename); + grub_free (data); + return err; + } + return GRUB_ERR_NONE; +} + +static grub_err_t +http_open (struct grub_file *file, const char *filename) +{ + grub_err_t err; + struct http_data *data; + + data = grub_zalloc (sizeof (*data)); + if (!data) + return grub_errno; + file->size = GRUB_FILE_SIZE_UNKNOWN; + + data->filename = grub_strdup (filename); + if (!data->filename) + { + grub_free (data); + return grub_errno; + } + + file->not_easily_seekable = 0; + file->data = data; + + err = http_establish (file, 0, 1); + if (err) + { + grub_free (data->filename); + grub_free (data); + return err; + } + + return GRUB_ERR_NONE; +} + +static grub_err_t +http_close (struct grub_file *file) +{ + http_data_t data = file->data; + + if (data->sock) + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + if (data->current_line) + grub_free (data->current_line); + grub_free (data); + return GRUB_ERR_NONE; +} + +static struct grub_net_app_protocol grub_http_protocol = + { + .name = "http", + .open = http_open, + .close = http_close, + .seek = http_seek + }; + +GRUB_MOD_INIT (http) +{ + grub_net_app_level_register (&grub_http_protocol); +} + +GRUB_MOD_FINI (http) +{ + grub_net_app_level_unregister (&grub_http_protocol); +} diff --git a/grub-core/net/icmp.c b/grub-core/net/icmp.c new file mode 100644 index 000000000..e55a0d86a --- /dev/null +++ b/grub-core/net/icmp.c @@ -0,0 +1,122 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include + +struct icmp_header +{ + grub_uint8_t type; + grub_uint8_t code; + grub_uint16_t checksum; +} __attribute__ ((packed)); + +struct ping_header +{ + grub_uint16_t id; + grub_uint16_t seq; +} __attribute__ ((packed)); + +enum + { + ICMP_ECHO_REPLY = 0, + ICMP_ECHO = 8, + }; + +grub_err_t +grub_net_recv_icmp_packet (struct grub_net_buff *nb, + struct grub_net_network_level_interface *inf, + const grub_net_link_level_address_t *ll_src, + const grub_net_network_level_address_t *src) +{ + struct icmp_header *icmph; + grub_err_t err; + grub_uint16_t checksum; + + /* Ignore broadcast. */ + if (!inf) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + icmph = (struct icmp_header *) nb->data; + + if (nb->tail - nb->data < (grub_ssize_t) sizeof (*icmph)) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + checksum = icmph->checksum; + icmph->checksum = 0; + if (checksum != grub_net_ip_chksum (nb->data, nb->tail - nb->data)) + { + icmph->checksum = checksum; + return GRUB_ERR_NONE; + } + icmph->checksum = checksum; + + err = grub_netbuff_pull (nb, sizeof (*icmph)); + if (err) + return err; + + switch (icmph->type) + { + case ICMP_ECHO: + { + struct grub_net_buff *nb_reply; + struct icmp_header *icmphr; + if (icmph->code) + break; + nb_reply = grub_netbuff_alloc (nb->tail - nb->data + 512); + if (!nb_reply) + { + grub_netbuff_free (nb); + return grub_errno; + } + err = grub_netbuff_reserve (nb_reply, nb->tail - nb->data + 512); + if (err) + goto ping_fail; + err = grub_netbuff_push (nb_reply, nb->tail - nb->data); + if (err) + goto ping_fail; + grub_memcpy (nb_reply->data, nb->data, nb->tail - nb->data); + err = grub_netbuff_push (nb_reply, sizeof (*icmphr)); + if (err) + goto ping_fail; + icmphr = (struct icmp_header *) nb_reply->data; + icmphr->type = ICMP_ECHO_REPLY; + icmphr->code = 0; + icmphr->checksum = 0; + icmphr->checksum = grub_net_ip_chksum ((void *) nb_reply->data, + nb_reply->tail - nb_reply->data); + err = grub_net_send_ip_packet (inf, src, ll_src, + nb_reply, GRUB_NET_IP_ICMP); + + ping_fail: + grub_netbuff_free (nb); + grub_netbuff_free (nb_reply); + return err; + } + }; + + grub_netbuff_free (nb); + return GRUB_ERR_NONE; +} diff --git a/grub-core/net/icmp6.c b/grub-core/net/icmp6.c new file mode 100644 index 000000000..9a5f3cafe --- /dev/null +++ b/grub-core/net/icmp6.c @@ -0,0 +1,529 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include + +struct icmp_header +{ + grub_uint8_t type; + grub_uint8_t code; + grub_uint16_t checksum; +} __attribute__ ((packed)); + +struct ping_header +{ + grub_uint16_t id; + grub_uint16_t seq; +} __attribute__ ((packed)); + +struct router_adv +{ + grub_uint8_t ttl; + grub_uint8_t flags; + grub_uint16_t router_lifetime; + grub_uint32_t reachable_time; + grub_uint32_t retrans_timer; + grub_uint8_t options[0]; +} __attribute__ ((packed)); + +struct option_header +{ + grub_uint8_t type; + grub_uint8_t len; +} __attribute__ ((packed)); + +struct prefix_option +{ + struct option_header header; + grub_uint8_t prefixlen; + grub_uint8_t flags; + grub_uint32_t valid_lifetime; + grub_uint32_t prefered_lifetime; + grub_uint32_t reserved; + grub_uint64_t prefix[2]; +} __attribute__ ((packed)); + +struct neighbour_solicit +{ + grub_uint32_t reserved; + grub_uint64_t target[2]; +} __attribute__ ((packed)); + +struct neighbour_advertise +{ + grub_uint32_t flags; + grub_uint64_t target[2]; +} __attribute__ ((packed)); + +enum + { + FLAG_SLAAC = 0x40 + }; + +enum + { + ICMP6_ECHO = 128, + ICMP6_ECHO_REPLY = 129, + ICMP6_ROUTER_ADVERTISE = 134, + ICMP6_NEIGHBOUR_SOLICIT = 135, + ICMP6_NEIGHBOUR_ADVERTISE = 136, + }; + +enum + { + OPTION_SOURCE_LINK_LAYER_ADDRESS = 1, + OPTION_TARGET_LINK_LAYER_ADDRESS = 2, + OPTION_PREFIX = 3 + }; + +enum + { + FLAG_SOLICITED = (1 << 30), + FLAG_OVERRIDE = (1 << 29) + }; + +grub_err_t +grub_net_recv_icmp6_packet (struct grub_net_buff *nb, + struct grub_net_card *card, + struct grub_net_network_level_interface *inf, + const grub_net_link_level_address_t *ll_src, + const grub_net_network_level_address_t *source, + const grub_net_network_level_address_t *dest, + grub_uint8_t ttl) +{ + struct icmp_header *icmph; + grub_err_t err; + grub_uint16_t checksum; + + icmph = (struct icmp_header *) nb->data; + + if (nb->tail - nb->data < (grub_ssize_t) sizeof (*icmph)) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + checksum = icmph->checksum; + icmph->checksum = 0; + if (checksum != grub_net_ip_transport_checksum (nb, + GRUB_NET_IP_ICMPV6, + source, + dest)) + { + grub_dprintf ("net", "invalid ICMPv6 checksum: %04x instead of %04x\n", + checksum, + grub_net_ip_transport_checksum (nb, + GRUB_NET_IP_ICMPV6, + source, + dest)); + icmph->checksum = checksum; + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + icmph->checksum = checksum; + + err = grub_netbuff_pull (nb, sizeof (*icmph)); + if (err) + { + grub_netbuff_free (nb); + return err; + } + + grub_dprintf ("net", "ICMPv6 message: %02x, %02x\n", + icmph->type, icmph->code); + switch (icmph->type) + { + case ICMP6_ECHO: + /* Don't accept multicast pings. */ + if (!inf) + break; + { + struct grub_net_buff *nb_reply; + struct icmp_header *icmphr; + if (icmph->code) + break; + nb_reply = grub_netbuff_alloc (nb->tail - nb->data + 512); + if (!nb_reply) + { + grub_netbuff_free (nb); + return grub_errno; + } + err = grub_netbuff_reserve (nb_reply, nb->tail - nb->data + 512); + if (err) + goto ping_fail; + err = grub_netbuff_push (nb_reply, nb->tail - nb->data); + if (err) + goto ping_fail; + grub_memcpy (nb_reply->data, nb->data, nb->tail - nb->data); + err = grub_netbuff_push (nb_reply, sizeof (*icmphr)); + if (err) + goto ping_fail; + icmphr = (struct icmp_header *) nb_reply->data; + icmphr->type = ICMP6_ECHO_REPLY; + icmphr->code = 0; + icmphr->checksum = 0; + icmphr->checksum = grub_net_ip_transport_checksum (nb_reply, + GRUB_NET_IP_ICMPV6, + &inf->address, + source); + err = grub_net_send_ip_packet (inf, source, ll_src, nb_reply, + GRUB_NET_IP_ICMPV6); + + ping_fail: + grub_netbuff_free (nb); + grub_netbuff_free (nb_reply); + return err; + } + case ICMP6_NEIGHBOUR_SOLICIT: + { + struct neighbour_solicit *nbh; + struct grub_net_buff *nb_reply; + struct option_header *ohdr; + struct neighbour_advertise *adv; + struct icmp_header *icmphr; + grub_uint8_t *ptr; + + if (icmph->code) + break; + if (ttl != 0xff) + break; + nbh = (struct neighbour_solicit *) nb->data; + err = grub_netbuff_pull (nb, sizeof (struct router_adv)); + if (err) + { + grub_netbuff_free (nb); + return err; + } + for (ptr = (grub_uint8_t *) nb->data; ptr < nb->tail; + ptr += ohdr->len * 8) + { + ohdr = (struct option_header *) ptr; + if (ohdr->len == 0 || ptr + 8 * ohdr->len > nb->tail) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (ohdr->type == OPTION_SOURCE_LINK_LAYER_ADDRESS + && ohdr->len == 1) + { + grub_net_link_level_address_t ll_address; + ll_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + grub_memcpy (ll_address.mac, ohdr + 1, sizeof (ll_address.mac)); + grub_net_link_layer_add_address (card, source, &ll_address, 0); + } + } + FOR_NET_NETWORK_LEVEL_INTERFACES (inf) + { + if (inf->card == card + && inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 + && grub_memcmp (&inf->address.ipv6, &nbh->target, 16) == 0) + break; + } + if (!inf) + break; + + nb_reply = grub_netbuff_alloc (sizeof (struct neighbour_advertise) + + sizeof (struct option_header) + + 6 + + sizeof (struct icmp_header) + + GRUB_NET_OUR_IPV6_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE); + if (!nb_reply) + { + grub_netbuff_free (nb); + return grub_errno; + } + err = grub_netbuff_reserve (nb_reply, + sizeof (struct neighbour_advertise) + + sizeof (struct option_header) + + 6 + + sizeof (struct icmp_header) + + GRUB_NET_OUR_IPV6_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE); + if (err) + goto ndp_fail; + + err = grub_netbuff_push (nb_reply, 6); + if (err) + goto ndp_fail; + grub_memcpy (nb_reply->data, inf->hwaddress.mac, 6); + err = grub_netbuff_push (nb_reply, sizeof (*ohdr)); + if (err) + goto ndp_fail; + ohdr = (struct option_header *) nb_reply->data; + ohdr->type = OPTION_TARGET_LINK_LAYER_ADDRESS; + ohdr->len = 1; + err = grub_netbuff_push (nb_reply, sizeof (*adv)); + if (err) + goto ndp_fail; + adv = (struct neighbour_advertise *) nb_reply->data; + adv->flags = grub_cpu_to_be32_compile_time (FLAG_SOLICITED + | FLAG_OVERRIDE); + grub_memcpy (&adv->target, &nbh->target, 16); + + err = grub_netbuff_push (nb_reply, sizeof (*icmphr)); + if (err) + goto ndp_fail; + icmphr = (struct icmp_header *) nb_reply->data; + icmphr->type = ICMP6_NEIGHBOUR_ADVERTISE; + icmphr->code = 0; + icmphr->checksum = 0; + icmphr->checksum = grub_net_ip_transport_checksum (nb_reply, + GRUB_NET_IP_ICMPV6, + &inf->address, + source); + err = grub_net_send_ip_packet (inf, source, ll_src, nb_reply, + GRUB_NET_IP_ICMPV6); + + ndp_fail: + grub_netbuff_free (nb); + grub_netbuff_free (nb_reply); + return err; + } + case ICMP6_NEIGHBOUR_ADVERTISE: + { + struct neighbour_advertise *nbh; + grub_uint8_t *ptr; + struct option_header *ohdr; + + if (icmph->code) + break; + if (ttl != 0xff) + break; + nbh = (struct neighbour_advertise *) nb->data; + err = grub_netbuff_pull (nb, sizeof (*nbh)); + if (err) + { + grub_netbuff_free (nb); + return err; + } + + for (ptr = (grub_uint8_t *) nb->data; ptr < nb->tail; + ptr += ohdr->len * 8) + { + ohdr = (struct option_header *) ptr; + if (ohdr->len == 0 || ptr + 8 * ohdr->len > nb->tail) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (ohdr->type == OPTION_TARGET_LINK_LAYER_ADDRESS + && ohdr->len == 1) + { + grub_net_link_level_address_t ll_address; + ll_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + grub_memcpy (ll_address.mac, ohdr + 1, sizeof (ll_address.mac)); + grub_net_link_layer_add_address (card, source, &ll_address, 0); + } + } + break; + } + case ICMP6_ROUTER_ADVERTISE: + { + grub_uint8_t *ptr; + struct option_header *ohdr; + if (icmph->code) + break; + err = grub_netbuff_pull (nb, sizeof (struct router_adv)); + if (err) + { + grub_netbuff_free (nb); + return err; + } + for (ptr = (grub_uint8_t *) nb->data; ptr < nb->tail; + ptr += ohdr->len * 8) + { + ohdr = (struct option_header *) ptr; + if (ohdr->len == 0 || ptr + 8 * ohdr->len > nb->tail) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (ohdr->type == OPTION_SOURCE_LINK_LAYER_ADDRESS + && ohdr->len == 1) + { + grub_net_link_level_address_t ll_address; + ll_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + grub_memcpy (ll_address.mac, ohdr + 1, sizeof (ll_address.mac)); + grub_net_link_layer_add_address (card, source, &ll_address, 0); + } + if (ohdr->type == OPTION_PREFIX && ohdr->len == 4) + { + struct prefix_option *opt = (struct prefix_option *) ptr; + struct grub_net_slaac_mac_list *slaac; + if (!(opt->flags & FLAG_SLAAC) + || (grub_be_to_cpu64 (opt->prefix[0]) >> 48) == 0xfe80 + || (grub_be_to_cpu32 (opt->prefered_lifetime) + > grub_be_to_cpu32 (opt->valid_lifetime)) + || opt->prefixlen != 64) + { + grub_dprintf ("net", "discarded prefix: %d, %d, %d, %d\n", + !(opt->flags & FLAG_SLAAC), + (grub_be_to_cpu64 (opt->prefix[0]) >> 48) == 0xfe80, + (grub_be_to_cpu32 (opt->prefered_lifetime) + > grub_be_to_cpu32 (opt->valid_lifetime)), + opt->prefixlen != 64); + continue; + } + for (slaac = card->slaac_list; slaac; slaac = slaac->next) + { + grub_net_network_level_address_t addr; + grub_net_network_level_netaddress_t netaddr; + + if (slaac->address.type + != GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET) + continue; + addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + addr.ipv6[0] = opt->prefix[0]; + addr.ipv6[1] = grub_net_ipv6_get_id (&slaac->address); + netaddr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + netaddr.ipv6.base[0] = opt->prefix[0]; + netaddr.ipv6.base[1] = 0; + netaddr.ipv6.masksize = 64; + + FOR_NET_NETWORK_LEVEL_INTERFACES (inf) + { + if (inf->card == card + && grub_net_addr_cmp (&inf->address, &addr) == 0) + break; + } + /* Update lease time if needed here once we have + lease times. */ + if (inf) + continue; + + grub_dprintf ("net", "creating slaac\n"); + + { + char name[grub_strlen (slaac->name) + + sizeof (":XXXXXXXXXXXXXXXXXXXX")]; + grub_snprintf (name, sizeof (name), "%s:%d", + slaac->name, slaac->slaac_counter++); + inf = grub_net_add_addr (name, + card, &addr, + &slaac->address, 0); + grub_net_add_route (name, netaddr, inf); + } + } + } + } + if (ptr != nb->tail) + break; + } + }; + + grub_netbuff_free (nb); + return GRUB_ERR_NONE; +} + +grub_err_t +grub_net_icmp6_send_request (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *proto_addr) +{ + struct grub_net_buff *nb; + grub_err_t err = GRUB_ERR_NONE; + int i; + struct option_header *ohdr; + struct neighbour_solicit *sol; + struct icmp_header *icmphr; + grub_net_network_level_address_t multicast; + grub_net_link_level_address_t ll_multicast; + grub_uint8_t *nbd; + multicast.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + multicast.ipv6[0] = grub_be_to_cpu64_compile_time (0xff02ULL << 48); + multicast.ipv6[1] = (grub_be_to_cpu64_compile_time (0x01ff000000ULL) + | (proto_addr->ipv6[1] + & grub_be_to_cpu64_compile_time (0xffffff))); + + err = grub_net_link_layer_resolve (inf, &multicast, &ll_multicast); + if (err) + return err; + + nb = grub_netbuff_alloc (sizeof (struct neighbour_solicit) + + sizeof (struct option_header) + + 6 + + sizeof (struct icmp_header) + + GRUB_NET_OUR_IPV6_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE); + if (!nb) + return grub_errno; + err = grub_netbuff_reserve (nb, + sizeof (struct neighbour_solicit) + + sizeof (struct option_header) + + 6 + + sizeof (struct icmp_header) + + GRUB_NET_OUR_IPV6_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE); + err = grub_netbuff_push (nb, 6); + if (err) + goto fail; + + grub_memcpy (nb->data, inf->hwaddress.mac, 6); + err = grub_netbuff_push (nb, sizeof (*ohdr)); + if (err) + goto fail; + + ohdr = (struct option_header *) nb->data; + ohdr->type = OPTION_SOURCE_LINK_LAYER_ADDRESS; + ohdr->len = 1; + err = grub_netbuff_push (nb, sizeof (*sol)); + if (err) + goto fail; + + sol = (struct neighbour_solicit *) nb->data; + sol->reserved = 0; + grub_memcpy (&sol->target, &proto_addr->ipv6, 16); + + err = grub_netbuff_push (nb, sizeof (*icmphr)); + if (err) + goto fail; + + icmphr = (struct icmp_header *) nb->data; + icmphr->type = ICMP6_NEIGHBOUR_SOLICIT; + icmphr->code = 0; + icmphr->checksum = 0; + icmphr->checksum = grub_net_ip_transport_checksum (nb, + GRUB_NET_IP_ICMPV6, + &inf->address, + &multicast); + nbd = nb->data; + err = grub_net_send_ip_packet (inf, &multicast, &ll_multicast, nb, + GRUB_NET_IP_ICMPV6); + if (err) + goto fail; + + for (i = 0; i < GRUB_NET_TRIES; i++) + { + if (grub_net_link_layer_resolve_check (inf, proto_addr)) + break; + grub_net_poll_cards (GRUB_NET_INTERVAL); + if (grub_net_link_layer_resolve_check (inf, proto_addr)) + break; + nb->data = nbd; + err = grub_net_send_ip_packet (inf, &multicast, &ll_multicast, nb, + GRUB_NET_IP_ICMPV6); + if (err) + break; + } + + fail: + grub_netbuff_free (nb); + return err; +} diff --git a/grub-core/net/ip.c b/grub-core/net/ip.c index 642a67f18..f3ec74d57 100644 --- a/grub-core/net/ip.c +++ b/grub-core/net/ip.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include struct iphdr { grub_uint8_t verhdrlen; @@ -35,46 +37,165 @@ struct iphdr { grub_uint8_t protocol; grub_uint16_t chksum; grub_uint32_t src; - grub_uint32_t dest; + grub_uint32_t dest; } __attribute__ ((packed)) ; -struct ip6hdr +enum { - grub_uint8_t version:4, priority:4; - grub_uint8_t flow_lbl[3]; - grub_uint16_t payload_len; - grub_uint8_t nexthdr; - grub_uint8_t hop_limit; - grub_uint8_t saddr[16]; - grub_uint8_t daddr[16]; -} __attribute__ ((packed)); + DONT_FRAGMENT = 0x4000, + MORE_FRAGMENTS = 0x2000, + OFFSET_MASK = 0x1fff +}; + +typedef grub_uint64_t ip6addr[2]; + +struct ip6hdr { + grub_uint32_t version_class_flow; + grub_uint16_t len; + grub_uint8_t protocol; + grub_uint8_t ttl; + ip6addr src; + ip6addr dest; +} __attribute__ ((packed)) ; + +static int +cmp (const void *a__, const void *b__) +{ + struct grub_net_buff *a_ = *(struct grub_net_buff **) a__; + struct grub_net_buff *b_ = *(struct grub_net_buff **) b__; + struct iphdr *a = (struct iphdr *) a_->data; + struct iphdr *b = (struct iphdr *) b_->data; + /* We want the first elements to be on top. */ + if ((grub_be_to_cpu16 (a->frags) & OFFSET_MASK) + < (grub_be_to_cpu16 (b->frags) & OFFSET_MASK)) + return +1; + if ((grub_be_to_cpu16 (a->frags) & OFFSET_MASK) + > (grub_be_to_cpu16 (b->frags) & OFFSET_MASK)) + return -1; + return 0; +} + +struct reassemble +{ + struct reassemble *next; + grub_uint32_t source; + grub_uint32_t dest; + grub_uint16_t id; + grub_uint8_t proto; + grub_uint64_t last_time; + grub_priority_queue_t pq; + grub_uint8_t *asm_buffer; + grub_size_t total_len; + grub_size_t cur_ptr; + grub_uint8_t ttl; +}; + +static struct reassemble *reassembles; grub_uint16_t -grub_net_ip_chksum (void *ipv, int len) +grub_net_ip_chksum (void *ipv, grub_size_t len) { grub_uint16_t *ip = (grub_uint16_t *) ipv; grub_uint32_t sum = 0; - len >>= 1; - while (len--) + for (; len >= 2; len -= 2) { - sum += grub_be_to_cpu16 (*(ip++)); + sum += grub_be_to_cpu16 (grub_get_unaligned16 (ip++)); + if (sum > 0xFFFF) + sum -= 0xFFFF; + } + if (len) + { + sum += *((grub_uint8_t *) ip) << 8; if (sum > 0xFFFF) sum -= 0xFFFF; } + if (sum >= 0xFFFF) + sum -= 0xFFFF; + return grub_cpu_to_be16 ((~sum) & 0x0000FFFF); } -grub_err_t -grub_net_send_ip_packet (struct grub_net_network_level_interface * inf, - const grub_net_network_level_address_t * target, - struct grub_net_buff * nb) +static int id = 0x2400; + +static grub_err_t +send_fragmented (struct grub_net_network_level_interface * inf, + const grub_net_network_level_address_t * target, + struct grub_net_buff * nb, + grub_net_ip_protocol_t proto, + grub_net_link_level_address_t ll_target_addr) +{ + grub_size_t off = 0; + grub_size_t fraglen; + grub_err_t err; + + fraglen = (inf->card->mtu - sizeof (struct iphdr)) & ~7; + id++; + + while (nb->tail - nb->data) + { + grub_size_t len = fraglen; + struct grub_net_buff *nb2; + struct iphdr *iph; + + if ((grub_ssize_t) len > nb->tail - nb->data) + len = nb->tail - nb->data; + nb2 = grub_netbuff_alloc (fraglen + sizeof (struct iphdr) + + GRUB_NET_MAX_LINK_HEADER_SIZE); + if (!nb2) + return grub_errno; + err = grub_netbuff_reserve (nb2, GRUB_NET_MAX_LINK_HEADER_SIZE); + if (err) + return err; + err = grub_netbuff_put (nb2, sizeof (struct iphdr)); + if (err) + return err; + + iph = (struct iphdr *) nb2->data; + iph->verhdrlen = ((4 << 4) | 5); + iph->service = 0; + iph->len = grub_cpu_to_be16 (len + sizeof (struct iphdr)); + iph->ident = grub_cpu_to_be16 (id); + iph->frags = grub_cpu_to_be16 (off | (((grub_ssize_t) len + == nb->tail - nb->data) + ? 0 : MORE_FRAGMENTS)); + iph->ttl = 0xff; + iph->protocol = proto; + iph->src = inf->address.ipv4; + iph->dest = target->ipv4; + off += len / 8; + + iph->chksum = 0; + iph->chksum = grub_net_ip_chksum ((void *) nb2->data, sizeof (*iph)); + err = grub_netbuff_put (nb2, len); + if (err) + return err; + grub_memcpy (iph + 1, nb->data, len); + err = grub_netbuff_pull (nb, len); + if (err) + return err; + err = send_ethernet_packet (inf, nb2, ll_target_addr, + GRUB_NET_ETHERTYPE_IP); + if (err) + return err; + } + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_net_send_ip4_packet (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *target, + const grub_net_link_level_address_t *ll_target_addr, + struct grub_net_buff *nb, + grub_net_ip_protocol_t proto) { struct iphdr *iph; - static int id = 0x2400; - grub_net_link_level_address_t ll_target_addr; - grub_err_t err; + + COMPILE_TIME_ASSERT (GRUB_NET_OUR_IPV4_HEADER_SIZE == sizeof (*iph)); + + if (nb->tail - nb->data + sizeof (struct iphdr) > inf->card->mtu) + return send_fragmented (inf, target, nb, proto, *ll_target_addr); grub_netbuff_push (nb, sizeof (*iph)); iph = (struct iphdr *) nb->data; @@ -85,75 +206,495 @@ grub_net_send_ip_packet (struct grub_net_network_level_interface * inf, iph->ident = grub_cpu_to_be16 (++id); iph->frags = 0; iph->ttl = 0xff; - iph->protocol = 0x11; + iph->protocol = proto; iph->src = inf->address.ipv4; iph->dest = target->ipv4; iph->chksum = 0; iph->chksum = grub_net_ip_chksum ((void *) nb->data, sizeof (*iph)); - /* Determine link layer target address via ARP. */ - err = grub_net_arp_resolve (inf, target, &ll_target_addr); - if (err) - return err; - return send_ethernet_packet (inf, nb, ll_target_addr, + return send_ethernet_packet (inf, nb, *ll_target_addr, GRUB_NET_ETHERTYPE_IP); } -grub_err_t -grub_net_recv_ip_packets (struct grub_net_buff * nb, - const struct grub_net_card * card, - const grub_net_link_level_address_t * hwaddress) +static grub_err_t +handle_dgram (struct grub_net_buff *nb, + struct grub_net_card *card, + const grub_net_link_level_address_t *source_hwaddress, + const grub_net_link_level_address_t *hwaddress, + grub_net_ip_protocol_t proto, + const grub_net_network_level_address_t *source, + const grub_net_network_level_address_t *dest, + grub_uint8_t ttl) { - struct iphdr *iph = (struct iphdr *) nb->data; - grub_err_t err; struct grub_net_network_level_interface *inf = NULL; - - err = grub_netbuff_pull (nb, sizeof (*iph)); - if (err) - return err; - + grub_err_t err; + int multicast = 0; + /* DHCP needs special treatment since we don't know IP yet. */ { struct udphdr *udph; udph = (struct udphdr *) nb->data; - if (iph->protocol == IP_UDP && grub_be_to_cpu16 (udph->dst) == 68) + if (proto == GRUB_NET_IP_UDP && grub_be_to_cpu16 (udph->dst) == 68) { FOR_NET_NETWORK_LEVEL_INTERFACES (inf) if (inf->card == card && inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV && grub_net_hwaddr_cmp (&inf->hwaddress, hwaddress) == 0) { + if (udph->chksum) + { + grub_uint16_t chk, expected; + chk = udph->chksum; + udph->chksum = 0; + expected = grub_net_ip_transport_checksum (nb, + GRUB_NET_IP_UDP, + source, + dest); + if (expected != chk) + { + grub_dprintf ("net", "Invalid UDP checksum. " + "Expected %x, got %x\n", + grub_be_to_cpu16 (expected), + grub_be_to_cpu16 (chk)); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + udph->chksum = chk; + } + err = grub_netbuff_pull (nb, sizeof (*udph)); if (err) return err; grub_net_process_dhcp (nb, inf->card); grub_netbuff_free (nb); + return GRUB_ERR_NONE; } + grub_netbuff_free (nb); return GRUB_ERR_NONE; } } - if (!inf) - { - FOR_NET_NETWORK_LEVEL_INTERFACES (inf) + FOR_NET_NETWORK_LEVEL_INTERFACES (inf) + { + if (inf->card == card + && grub_net_addr_cmp (&inf->address, dest) == 0 + && grub_net_hwaddr_cmp (&inf->hwaddress, hwaddress) == 0) + break; + /* Solicited node multicast. */ + if (inf->card == card + && inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 + && dest->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 + && dest->ipv6[0] == grub_be_to_cpu64_compile_time (0xff02ULL << 48) + && dest->ipv6[1] == (grub_be_to_cpu64_compile_time (0x01ff000000ULL) + | (inf->address.ipv6[1] + & grub_be_to_cpu64_compile_time (0xffffff))) + && hwaddress->type == GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET + && hwaddress->mac[0] == 0x33 && hwaddress->mac[1] == 0x33 + && hwaddress->mac[2] == 0xff + && hwaddress->mac[3] == ((grub_be_to_cpu64 (inf->address.ipv6[1]) + >> 16) & 0xff) + && hwaddress->mac[4] == ((grub_be_to_cpu64 (inf->address.ipv6[1]) + >> 8) & 0xff) + && hwaddress->mac[5] == ((grub_be_to_cpu64 (inf->address.ipv6[1]) + >> 0) & 0xff)) { - if (inf->card == card - && inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4 - && inf->address.ipv4 == iph->dest - && grub_net_hwaddr_cmp (&inf->hwaddress, hwaddress) == 0) - break; + multicast = 1; + break; } - } - - switch (iph->protocol) + } + + if (!inf && !(dest->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 + && dest->ipv6[0] == grub_be_to_cpu64_compile_time (0xff02ULL + << 48) + && dest->ipv6[1] == grub_be_to_cpu64_compile_time (1))) { - case IP_UDP: - return grub_net_recv_udp_packet (nb, inf); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (multicast) + inf = NULL; + + switch (proto) + { + case GRUB_NET_IP_UDP: + return grub_net_recv_udp_packet (nb, inf, source); + case GRUB_NET_IP_TCP: + return grub_net_recv_tcp_packet (nb, inf, source); + case GRUB_NET_IP_ICMP: + return grub_net_recv_icmp_packet (nb, inf, source_hwaddress, source); + case GRUB_NET_IP_ICMPV6: + return grub_net_recv_icmp6_packet (nb, card, inf, source_hwaddress, + source, dest, ttl); default: grub_netbuff_free (nb); break; } + return GRUB_ERR_NONE; +} + +static void +free_rsm (struct reassemble *rsm) +{ + struct grub_net_buff **nb; + while ((nb = grub_priority_queue_top (rsm->pq))) + { + grub_netbuff_free (*nb); + grub_priority_queue_pop (rsm->pq); + } + grub_free (rsm->asm_buffer); + grub_priority_queue_destroy (rsm->pq); +} + +static void +free_old_fragments (void) +{ + struct reassemble *rsm, **prev; + grub_uint64_t limit_time = grub_get_time_ms () - 90000; + + for (prev = &reassembles, rsm = *prev; rsm; prev = &rsm->next, rsm = *prev) + if (rsm->last_time < limit_time) + { + *prev = rsm->next; + free_rsm (rsm); + } +} + +static grub_err_t +grub_net_recv_ip4_packets (struct grub_net_buff *nb, + struct grub_net_card *card, + const grub_net_link_level_address_t *hwaddress, + const grub_net_link_level_address_t *src_hwaddress) +{ + struct iphdr *iph = (struct iphdr *) nb->data; + grub_err_t err; + struct reassemble *rsm, **prev; + + if ((iph->verhdrlen >> 4) != 4) + { + grub_dprintf ("net", "Bad IP version: %d\n", (iph->verhdrlen >> 4)); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + if ((iph->verhdrlen & 0xf) < 5) + { + grub_dprintf ("net", "IP header too short: %d\n", + (iph->verhdrlen & 0xf)); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + if (nb->tail - nb->data < (grub_ssize_t) ((iph->verhdrlen & 0xf) + * sizeof (grub_uint32_t))) + { + grub_dprintf ("net", "IP packet too short: %" PRIdGRUB_SSIZE "\n", + (nb->tail - nb->data)); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + /* Check size. */ + { + grub_size_t expected_size = grub_be_to_cpu16 (iph->len); + grub_size_t actual_size = (nb->tail - nb->data); + if (actual_size > expected_size) + { + err = grub_netbuff_unput (nb, actual_size - expected_size); + if (err) + { + grub_netbuff_free (nb); + return err; + } + } + if (actual_size < expected_size) + { + grub_dprintf ("net", "Cut IP packet actual: %" PRIuGRUB_SIZE + ", expected %" PRIuGRUB_SIZE "\n", actual_size, + expected_size); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + } + + /* Unfragmented packet. Good. */ + if (((grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS) == 0) + && (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) == 0) + { + grub_net_network_level_address_t source; + grub_net_network_level_address_t dest; + + err = grub_netbuff_pull (nb, ((iph->verhdrlen & 0xf) + * sizeof (grub_uint32_t))); + if (err) + { + grub_netbuff_free (nb); + return err; + } + + source.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + source.ipv4 = iph->src; + + dest.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + dest.ipv4 = iph->dest; + + return handle_dgram (nb, card, src_hwaddress, hwaddress, iph->protocol, + &source, &dest, iph->ttl); + } + + for (prev = &reassembles, rsm = *prev; rsm; prev = &rsm->next, rsm = *prev) + if (rsm->source == iph->src && rsm->dest == iph->dest + && rsm->id == iph->ident && rsm->proto == iph->protocol) + break; + if (!rsm) + { + rsm = grub_malloc (sizeof (*rsm)); + if (!rsm) + return grub_errno; + rsm->source = iph->src; + rsm->dest = iph->dest; + rsm->id = iph->ident; + rsm->proto = iph->protocol; + rsm->next = reassembles; + reassembles = rsm; + prev = &reassembles; + rsm->pq = grub_priority_queue_new (sizeof (struct grub_net_buff **), cmp); + if (!rsm->pq) + { + grub_free (rsm); + return grub_errno; + } + rsm->asm_buffer = 0; + rsm->total_len = 0; + rsm->cur_ptr = 0; + rsm->ttl = 0xff; + } + if (rsm->ttl > iph->ttl) + rsm->ttl = iph->ttl; + rsm->last_time = grub_get_time_ms (); + free_old_fragments (); + + err = grub_priority_queue_push (rsm->pq, &nb); + if (err) + return err; + + if (!(grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS)) + { + rsm->total_len = (8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) + + (nb->tail - nb->data)); + rsm->total_len -= ((iph->verhdrlen & 0xf) * sizeof (grub_uint32_t)); + rsm->asm_buffer = grub_zalloc (rsm->total_len); + if (!rsm->asm_buffer) + { + *prev = rsm->next; + free_rsm (rsm); + return grub_errno; + } + } + if (!rsm->asm_buffer) + return GRUB_ERR_NONE; + + while (1) + { + struct grub_net_buff **nb_top_p, *nb_top; + grub_size_t copy; + grub_uint8_t *res; + grub_size_t res_len; + struct grub_net_buff *ret; + grub_net_ip_protocol_t proto; + grub_uint32_t src; + grub_uint32_t dst; + grub_net_network_level_address_t source; + grub_net_network_level_address_t dest; + grub_uint8_t ttl; + + nb_top_p = grub_priority_queue_top (rsm->pq); + if (!nb_top_p) + return GRUB_ERR_NONE; + nb_top = *nb_top_p; + grub_priority_queue_pop (rsm->pq); + iph = (struct iphdr *) nb_top->data; + err = grub_netbuff_pull (nb_top, ((iph->verhdrlen & 0xf) + * sizeof (grub_uint32_t))); + if (err) + { + grub_netbuff_free (nb_top); + return err; + } + if (rsm->cur_ptr < (grub_size_t) 8 * (grub_be_to_cpu16 (iph->frags) + & OFFSET_MASK)) + return GRUB_ERR_NONE; + + rsm->cur_ptr = (8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) + + (nb_top->tail - nb_top->head)); + if ((grub_size_t) 8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) + >= rsm->total_len) + { + grub_netbuff_free (nb_top); + continue; + } + copy = nb_top->tail - nb_top->data; + if (rsm->total_len - 8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) + < copy) + copy = rsm->total_len - 8 * (grub_be_to_cpu16 (iph->frags) + & OFFSET_MASK); + grub_memcpy (&rsm->asm_buffer[8 * (grub_be_to_cpu16 (iph->frags) + & OFFSET_MASK)], + nb_top->data, copy); + + if ((grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS)) + continue; + + res = rsm->asm_buffer; + proto = rsm->proto; + src = rsm->source; + dst = rsm->dest; + ttl = rsm->ttl; + + rsm->asm_buffer = 0; + res_len = rsm->total_len; + *prev = rsm->next; + free_rsm (rsm); + ret = grub_malloc (sizeof (*ret)); + if (!ret) + { + grub_free (res); + return grub_errno; + } + ret->data = ret->head = res; + ret->tail = ret->end = res + res_len; + + source.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + source.ipv4 = src; + + dest.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + dest.ipv4 = dst; + + return handle_dgram (ret, card, src_hwaddress, + hwaddress, proto, &source, &dest, + ttl); + } return GRUB_ERR_NONE; } + +static grub_err_t +grub_net_send_ip6_packet (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *target, + const grub_net_link_level_address_t *ll_target_addr, + struct grub_net_buff *nb, + grub_net_ip_protocol_t proto) +{ + struct ip6hdr *iph; + + COMPILE_TIME_ASSERT (GRUB_NET_OUR_IPV6_HEADER_SIZE == sizeof (*iph)); + + if (nb->tail - nb->data + sizeof (struct iphdr) > inf->card->mtu) + return grub_error (GRUB_ERR_NET_PACKET_TOO_BIG, "packet too big"); + + grub_netbuff_push (nb, sizeof (*iph)); + iph = (struct ip6hdr *) nb->data; + + iph->version_class_flow = grub_cpu_to_be32 ((6 << 28)); + iph->len = grub_cpu_to_be16 (nb->tail - nb->data - sizeof (*iph)); + iph->protocol = proto; + iph->ttl = 0xff; + grub_memcpy (&iph->src, inf->address.ipv6, sizeof (iph->src)); + grub_memcpy (&iph->dest, target->ipv6, sizeof (iph->dest)); + + return send_ethernet_packet (inf, nb, *ll_target_addr, + GRUB_NET_ETHERTYPE_IP6); +} + +grub_err_t +grub_net_send_ip_packet (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *target, + const grub_net_link_level_address_t *ll_target_addr, + struct grub_net_buff *nb, + grub_net_ip_protocol_t proto) +{ + switch (target->type) + { + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4: + return grub_net_send_ip4_packet (inf, target, ll_target_addr, nb, proto); + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6: + return grub_net_send_ip6_packet (inf, target, ll_target_addr, nb, proto); + default: + return grub_error (GRUB_ERR_BAD_ARGUMENT, "not an IP"); + } +} + +static grub_err_t +grub_net_recv_ip6_packets (struct grub_net_buff *nb, + struct grub_net_card *card, + const grub_net_link_level_address_t *hwaddress, + const grub_net_link_level_address_t *src_hwaddress) +{ + struct ip6hdr *iph = (struct ip6hdr *) nb->data; + grub_err_t err; + grub_net_network_level_address_t source; + grub_net_network_level_address_t dest; + + if (nb->tail - nb->data < (grub_ssize_t) sizeof (*iph)) + { + grub_dprintf ("net", "IP packet too short: %" PRIdGRUB_SSIZE "\n", + nb->tail - nb->data); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + err = grub_netbuff_pull (nb, sizeof (*iph)); + if (err) + { + grub_netbuff_free (nb); + return err; + } + + /* Check size. */ + { + grub_size_t expected_size = grub_be_to_cpu16 (iph->len); + grub_size_t actual_size = (nb->tail - nb->data); + if (actual_size > expected_size) + { + err = grub_netbuff_unput (nb, actual_size - expected_size); + if (err) + { + grub_netbuff_free (nb); + return err; + } + } + if (actual_size < expected_size) + { + grub_dprintf ("net", "Cut IP packet actual: %" PRIuGRUB_SIZE + ", expected %" PRIuGRUB_SIZE "\n", actual_size, + expected_size); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + } + + source.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + dest.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + grub_memcpy (source.ipv6, &iph->src, sizeof (source.ipv6)); + grub_memcpy (dest.ipv6, &iph->dest, sizeof (dest.ipv6)); + + return handle_dgram (nb, card, src_hwaddress, hwaddress, iph->protocol, + &source, &dest, iph->ttl); +} + +grub_err_t +grub_net_recv_ip_packets (struct grub_net_buff *nb, + struct grub_net_card *card, + const grub_net_link_level_address_t *hwaddress, + const grub_net_link_level_address_t *src_hwaddress) +{ + struct iphdr *iph = (struct iphdr *) nb->data; + + if ((iph->verhdrlen >> 4) == 4) + return grub_net_recv_ip4_packets (nb, card, hwaddress, src_hwaddress); + if ((iph->verhdrlen >> 4) == 6) + return grub_net_recv_ip6_packets (nb, card, hwaddress, src_hwaddress); + grub_dprintf ("net", "Bad IP version: %d\n", (iph->verhdrlen >> 4)); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; +} diff --git a/grub-core/net/net.c b/grub-core/net/net.c index d4e445e71..d64206432 100644 --- a/grub-core/net/net.c +++ b/grub-core/net/net.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -55,6 +57,142 @@ struct grub_net_card *grub_net_cards = NULL; struct grub_net_network_level_protocol *grub_net_network_level_protocols = NULL; static struct grub_fs grub_net_fs; +struct grub_net_link_layer_entry { + int avail; + grub_net_network_level_address_t nl_address; + grub_net_link_level_address_t ll_address; +}; + +#define LINK_LAYER_CACHE_SIZE 256 + +static struct grub_net_link_layer_entry * +link_layer_find_entry (const grub_net_network_level_address_t *proto, + const struct grub_net_card *card) +{ + unsigned i; + if (!card->link_layer_table) + return NULL; + for (i = 0; i < LINK_LAYER_CACHE_SIZE; i++) + { + if (card->link_layer_table[i].avail == 1 + && grub_net_addr_cmp (&card->link_layer_table[i].nl_address, + proto) == 0) + return &card->link_layer_table[i]; + } + return NULL; +} + +void +grub_net_link_layer_add_address (struct grub_net_card *card, + const grub_net_network_level_address_t *nl, + const grub_net_link_level_address_t *ll, + int override) +{ + struct grub_net_link_layer_entry *entry; + + /* Check if the sender is in the cache table. */ + entry = link_layer_find_entry (nl, card); + /* Update sender hardware address. */ + if (entry && override) + grub_memcpy (&entry->ll_address, ll, sizeof (entry->ll_address)); + if (entry) + return; + + /* Add sender to cache table. */ + if (card->link_layer_table == NULL) + card->link_layer_table = grub_zalloc (LINK_LAYER_CACHE_SIZE + * sizeof (card->link_layer_table[0])); + entry = &(card->link_layer_table[card->new_ll_entry]); + entry->avail = 1; + grub_memcpy (&entry->ll_address, ll, sizeof (entry->ll_address)); + grub_memcpy (&entry->nl_address, nl, sizeof (entry->nl_address)); + card->new_ll_entry++; + if (card->new_ll_entry == LINK_LAYER_CACHE_SIZE) + card->new_ll_entry = 0; +} + +int +grub_net_link_layer_resolve_check (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *proto_addr) +{ + struct grub_net_link_layer_entry *entry; + + if (proto_addr->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4 + && proto_addr->ipv4 == 0xffffffff) + return 1; + entry = link_layer_find_entry (proto_addr, inf->card); + if (entry) + return 1; + return 0; +} + +grub_err_t +grub_net_link_layer_resolve (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *proto_addr, + grub_net_link_level_address_t *hw_addr) +{ + struct grub_net_link_layer_entry *entry; + grub_err_t err; + + if ((proto_addr->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4 + && proto_addr->ipv4 == 0xffffffff) + || proto_addr->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV + || (proto_addr->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 + && proto_addr->ipv6[0] == grub_be_to_cpu64_compile_time (0xff02ULL + << 48) + && proto_addr->ipv6[1] == (grub_be_to_cpu64_compile_time (1)))) + { + hw_addr->type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + grub_memset (hw_addr->mac, -1, 6); + return GRUB_ERR_NONE; + } + + if (proto_addr->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 + && ((grub_be_to_cpu64 (proto_addr->ipv6[0]) >> 56) == 0xff)) + { + hw_addr->type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET; + hw_addr->mac[0] = 0x33; + hw_addr->mac[1] = 0x33; + hw_addr->mac[2] = ((grub_be_to_cpu64 (proto_addr->ipv6[1]) >> 24) & 0xff); + hw_addr->mac[3] = ((grub_be_to_cpu64 (proto_addr->ipv6[1]) >> 16) & 0xff); + hw_addr->mac[4] = ((grub_be_to_cpu64 (proto_addr->ipv6[1]) >> 8) & 0xff); + hw_addr->mac[5] = ((grub_be_to_cpu64 (proto_addr->ipv6[1]) >> 0) & 0xff); + return GRUB_ERR_NONE; + } + + /* Check cache table. */ + entry = link_layer_find_entry (proto_addr, inf->card); + if (entry) + { + *hw_addr = entry->ll_address; + return GRUB_ERR_NONE; + } + switch (proto_addr->type) + { + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4: + err = grub_net_arp_send_request (inf, proto_addr); + break; + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6: + err = grub_net_icmp6_send_request (inf, proto_addr); + break; + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV: + return grub_error (GRUB_ERR_BUG, "shouldn't reach here"); + default: + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "unsupported address type %d", proto_addr->type); + } + if (err) + return err; + entry = link_layer_find_entry (proto_addr, inf->card); + if (entry) + { + *hw_addr = entry->ll_address; + return GRUB_ERR_NONE; + } + return grub_error (GRUB_ERR_TIMEOUT, + "timeout: could not resolve hardware address"); +} + void grub_net_card_unregister (struct grub_net_card *card) { @@ -72,6 +210,151 @@ grub_net_card_unregister (struct grub_net_card *card) GRUB_AS_LIST (card)); } +static struct grub_net_slaac_mac_list * +grub_net_ipv6_get_slaac (struct grub_net_card *card, + const grub_net_link_level_address_t *hwaddr) +{ + struct grub_net_slaac_mac_list *slaac; + char *ptr; + + for (slaac = card->slaac_list; slaac; slaac = slaac->next) + if (grub_net_hwaddr_cmp (&slaac->address, hwaddr) == 0) + return slaac; + + slaac = grub_zalloc (sizeof (*slaac)); + if (!slaac) + return NULL; + + slaac->name = grub_malloc (grub_strlen (card->name) + + GRUB_NET_MAX_STR_HWADDR_LEN + + sizeof (":slaac")); + ptr = grub_stpcpy (slaac->name, card->name); + if (grub_net_hwaddr_cmp (&card->default_address, hwaddr) != 0) + { + ptr = grub_stpcpy (ptr, ":"); + grub_net_hwaddr_to_str (hwaddr, ptr); + ptr += grub_strlen (ptr); + } + ptr = grub_stpcpy (ptr, ":slaac"); + + grub_memcpy (&slaac->address, hwaddr, sizeof (slaac->address)); + slaac->next = card->slaac_list; + card->slaac_list = slaac; + return slaac; +} + +struct grub_net_network_level_interface * +grub_net_ipv6_get_link_local (struct grub_net_card *card, + const grub_net_link_level_address_t *hwaddr) +{ + struct grub_net_network_level_interface *inf; + char name[grub_strlen (card->name) + + GRUB_NET_MAX_STR_HWADDR_LEN + + sizeof (":link")]; + char *ptr; + grub_net_network_level_address_t addr; + + addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + addr.ipv6[0] = grub_cpu_to_be64 (0xfe80ULL << 48); + addr.ipv6[1] = grub_net_ipv6_get_id (hwaddr); + + FOR_NET_NETWORK_LEVEL_INTERFACES (inf) + { + if (inf->card == card + && grub_net_hwaddr_cmp (&inf->hwaddress, hwaddr) == 0 + && grub_net_addr_cmp (&inf->address, &addr) == 0) + return inf; + } + + ptr = grub_stpcpy (name, card->name); + if (grub_net_hwaddr_cmp (&card->default_address, hwaddr) != 0) + { + ptr = grub_stpcpy (ptr, ":"); + grub_net_hwaddr_to_str (hwaddr, ptr); + ptr += grub_strlen (ptr); + } + ptr = grub_stpcpy (ptr, ":link"); + return grub_net_add_addr (name, card, &addr, hwaddr, 0); +} + +/* FIXME: allow to specify mac address. */ +static grub_err_t +grub_cmd_ipv6_autoconf (struct grub_command *cmd __attribute__ ((unused)), + int argc, char **args) +{ + struct grub_net_card *card; + struct grub_net_network_level_interface **ifaces; + grub_size_t ncards = 0; + unsigned j = 0; + int interval; + grub_err_t err; + struct grub_net_slaac_mac_list **slaacs; + + FOR_NET_CARDS (card) + { + if (argc > 0 && grub_strcmp (card->name, args[0]) != 0) + continue; + ncards++; + } + + ifaces = grub_zalloc (ncards * sizeof (ifaces[0])); + slaacs = grub_zalloc (ncards * sizeof (slaacs[0])); + if (!ifaces || !slaacs) + { + grub_free (ifaces); + grub_free (slaacs); + return grub_errno; + } + + FOR_NET_CARDS (card) + { + if (argc > 0 && grub_strcmp (card->name, args[0]) != 0) + continue; + ifaces[j] = grub_net_ipv6_get_link_local (card, &card->default_address); + if (!ifaces[j]) + { + grub_free (ifaces); + grub_free (slaacs); + return grub_errno; + } + slaacs[j] = grub_net_ipv6_get_slaac (card, &card->default_address); + if (!slaacs[j]) + { + grub_free (ifaces); + grub_free (slaacs); + return grub_errno; + } + j++; + } + + for (interval = 200; interval < 10000; interval *= 2) + { + /* FIXME: send router solicitation. */ + int done = 1; + for (j = 0; j < ncards; j++) + { + if (slaacs[j]->slaac_counter) + continue; + done = 0; + } + if (done) + break; + grub_net_poll_cards (interval); + } + + err = GRUB_ERR_NONE; + for (j = 0; j < ncards; j++) + { + if (slaacs[j]->slaac_counter) + continue; + err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "couldn't configure %s", + ifaces[j]->card->name); + } + + grub_free (ifaces); + grub_free (slaacs); + return err; +} static inline void grub_net_route_register (struct grub_net_route *route) @@ -93,18 +376,23 @@ static int parse_ip (const char *val, grub_uint32_t *ip, const char **rest) { grub_uint32_t newip = 0; - unsigned long t; int i; const char *ptr = val; for (i = 0; i < 4; i++) { + unsigned long t; t = grub_strtoul (ptr, (char **) &ptr, 0); if (grub_errno) { grub_errno = GRUB_ERR_NONE; return 0; } + if (*ptr != '.' && i == 0) + { + newip = t; + break; + } if (t & ~0xff) return 0; newip >>= 8; @@ -115,7 +403,56 @@ parse_ip (const char *val, grub_uint32_t *ip, const char **rest) } *ip = grub_cpu_to_le32 (newip); if (rest) - *rest = ptr - 1; + *rest = (ptr - 1); + return 1; +} + +static int +parse_ip6 (const char *val, grub_uint64_t *ip, const char **rest) +{ + grub_uint16_t newip[8]; + const char *ptr = val; + int word, quaddot = -1; + + if (ptr[0] == ':' && ptr[1] != ':') + return 0; + if (ptr[0] == ':') + ptr++; + + for (word = 0; word < 8; word++) + { + unsigned long t; + if (*ptr == ':') + { + quaddot = word; + word--; + ptr++; + continue; + } + t = grub_strtoul (ptr, (char **) &ptr, 16); + if (grub_errno) + { + grub_errno = GRUB_ERR_NONE; + break; + } + if (t & ~0xffff) + return 0; + newip[word] = grub_cpu_to_be16 (t); + if (*ptr != ':') + break; + ptr++; + } + if (quaddot == -1 && word < 7) + return 0; + if (quaddot != -1) + { + grub_memmove (&newip[quaddot + 7 - word], &newip[quaddot], + (word - quaddot + 1) * sizeof (newip[0])); + grub_memset (&newip[quaddot], 0, (7 - word) * sizeof (newip[0])); + } + grub_memcpy (ip, newip, 16); + if (rest) + *rest = ptr; return 1; } @@ -131,10 +468,30 @@ match_net (const grub_net_network_level_netaddress_t *net, return 0; case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4: { - grub_int32_t mask = ((1 << net->ipv4.masksize) - 1) << (32 - net->ipv4.masksize); + grub_uint32_t mask = (0xffffffffU << (32 - net->ipv4.masksize)); + if (net->ipv4.masksize == 0) + mask = 0; return ((grub_be_to_cpu32 (net->ipv4.base) & mask) == (grub_be_to_cpu32 (addr->ipv4) & mask)); } + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6: + { + grub_uint64_t mask[2]; + if (net->ipv6.masksize <= 64) + { + mask[0] = 0xffffffffffffffffULL << (64 - net->ipv6.masksize); + mask[1] = 0; + } + else + { + mask[0] = 0xffffffffffffffffULL; + mask[1] = 0xffffffffffffffffULL << (128 - net->ipv6.masksize); + } + return (((grub_be_to_cpu64 (net->ipv6.base[0]) & mask[0]) + == (grub_be_to_cpu64 (addr->ipv6[0]) & mask[0])) + && ((grub_be_to_cpu64 (net->ipv6.base[1]) & mask[1]) + == (grub_be_to_cpu64 (addr->ipv6[1]) & mask[1]))); + } } return 0; } @@ -143,13 +500,31 @@ grub_err_t grub_net_resolve_address (const char *name, grub_net_network_level_address_t *addr) { - if (parse_ip (name, &addr->ipv4, NULL)) + const char *rest; + grub_err_t err; + grub_size_t naddresses; + struct grub_net_network_level_address *addresses; + + if (parse_ip (name, &addr->ipv4, &rest) && *rest == 0) { addr->type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; return GRUB_ERR_NONE; } - return grub_error (GRUB_ERR_NET_BAD_ADDRESS, N_("unrecognised address %s"), - name); + if (parse_ip6 (name, addr->ipv6, &rest) && *rest == 0) + { + addr->type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + return GRUB_ERR_NONE; + } + err = grub_net_dns_lookup (name, 0, 0, &naddresses, &addresses, 1); + if (err) + return err; + if (!naddresses) + grub_error (GRUB_ERR_NET_BAD_ADDRESS, N_("unresolvable address %s"), + name); + /* FIXME: use other results as well. */ + *addr = addresses[0]; + grub_free (addresses); + return GRUB_ERR_NONE; } grub_err_t @@ -162,17 +537,70 @@ grub_net_resolve_net_address (const char *name, addr->type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; if (*rest == '/') { - addr->ipv4.masksize = grub_strtoul (rest + 1, NULL, 0); - if (!grub_errno) - return GRUB_ERR_NONE; + addr->ipv4.masksize = grub_strtoul (rest + 1, (char **) &rest, 0); + if (!grub_errno && *rest == 0) + return GRUB_ERR_NONE; + grub_errno = GRUB_ERR_NONE; + } + else if (*rest == 0) + { + addr->ipv4.masksize = 32; + return GRUB_ERR_NONE; + } + } + if (parse_ip6 (name, addr->ipv6.base, &rest)) + { + addr->type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + if (*rest == '/') + { + addr->ipv6.masksize = grub_strtoul (rest + 1, (char **) &rest, 0); + if (!grub_errno && *rest == 0) + return GRUB_ERR_NONE; + grub_errno = GRUB_ERR_NONE; + } + else if (*rest == 0) + { + addr->ipv6.masksize = 128; + return GRUB_ERR_NONE; } - addr->ipv4.masksize = 32; - return GRUB_ERR_NONE; } return grub_error (GRUB_ERR_NET_BAD_ADDRESS, N_("unrecognised address %s"), name); } +static int +route_cmp (const struct grub_net_route *a, const struct grub_net_route *b) +{ + if (a == NULL && b == NULL) + return 0; + if (b == NULL) + return +1; + if (a == NULL) + return -1; + if (a->target.type < b->target.type) + return -1; + if (a->target.type > b->target.type) + return +1; + switch (a->target.type) + { + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV: + break; + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6: + if (a->target.ipv6.masksize > b->target.ipv6.masksize) + return +1; + if (a->target.ipv6.masksize < b->target.ipv6.masksize) + return -1; + break; + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4: + if (a->target.ipv4.masksize > b->target.ipv4.masksize) + return +1; + if (a->target.ipv4.masksize < b->target.ipv4.masksize) + return -1; + break; + } + return 0; +} + grub_err_t grub_net_route_address (grub_net_network_level_address_t addr, grub_net_network_level_address_t *gateway, @@ -191,25 +619,27 @@ grub_net_route_address (grub_net_network_level_address_t addr, for (depth = 0; depth < routecnt + 2; depth++) { + struct grub_net_route *bestroute = NULL; FOR_NET_ROUTES(route) { if (depth && prot != route->prot) continue; if (!match_net (&route->target, &curtarget)) continue; - - if (route->is_gateway) - { - if (depth == 0) - *gateway = route->gw; - curtarget = route->gw; - break; - } - *interf = route->interface; - return GRUB_ERR_NONE; + if (route_cmp (route, bestroute) > 0) + bestroute = route; } - if (route == NULL) + if (bestroute == NULL) return grub_error (GRUB_ERR_NET_NO_ROUTE, "destination unreachable"); + + if (!bestroute->is_gateway) + { + *interf = bestroute->interface; + return GRUB_ERR_NONE; + } + if (depth == 0) + *gateway = bestroute->gw; + curtarget = bestroute->gw; } return grub_error (GRUB_ERR_NET_ROUTE_LOOP, "route loop detected"); @@ -249,6 +679,27 @@ grub_net_addr_to_str (const grub_net_network_level_address_t *target, char *buf) case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV: grub_strcpy (buf, "temporary"); return; + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6: + { + char *ptr = buf; + grub_uint64_t n = grub_be_to_cpu64 (target->ipv6[0]); + int i; + for (i = 0; i < 4; i++) + { + grub_snprintf (ptr, 6, "%" PRIxGRUB_UINT64_T ":", + (n >> (48 - 16 * i)) & 0xffff); + ptr += grub_strlen (ptr); + } + n = grub_be_to_cpu64 (target->ipv6[1]); + for (i = 0; i < 3; i++) + { + grub_snprintf (ptr, 6, "%" PRIxGRUB_UINT64_T ":", + (n >> (48 - 16 * i)) & 0xffff); + ptr += grub_strlen (ptr); + } + grub_snprintf (ptr, 5, "%" PRIxGRUB_UINT64_T, n & 0xffff); + return; + } case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4: { grub_uint32_t n = grub_be_to_cpu32 (target->ipv4); @@ -258,18 +709,13 @@ grub_net_addr_to_str (const grub_net_network_level_address_t *target, char *buf) } return; } - grub_printf ("Unknown address type %d\n", target->type); + grub_snprintf (buf, GRUB_NET_MAX_STR_ADDR_LEN, + "Unknown address type %d", target->type); } -/* - Currently suppoerted adresses: - ethernet: XX:XX:XX:XX:XX:XX - */ -#define MAX_STR_HWADDR_LEN (sizeof ("XX:XX:XX:XX:XX:XX")) - -static void -hwaddr_to_str (const grub_net_link_level_address_t *addr, char *str) +void +grub_net_hwaddr_to_str (const grub_net_link_level_address_t *addr, char *str) { str[0] = 0; switch (addr->type) @@ -280,7 +726,7 @@ hwaddr_to_str (const grub_net_link_level_address_t *addr, char *str) unsigned i; for (ptr = str, i = 0; i < ARRAY_SIZE (addr->mac); i++) { - grub_snprintf (ptr, MAX_STR_HWADDR_LEN - (ptr - str), + grub_snprintf (ptr, GRUB_NET_MAX_STR_HWADDR_LEN - (ptr - str), "%02x:", addr->mac[i] & 0xff); ptr += (sizeof ("XX:") - 1); } @@ -307,6 +753,27 @@ grub_net_hwaddr_cmp (const grub_net_link_level_address_t *a, return 1; } +int +grub_net_addr_cmp (const grub_net_network_level_address_t *a, + const grub_net_network_level_address_t *b) +{ + if (a->type < b->type) + return -1; + if (a->type > b->type) + return +1; + switch (a->type) + { + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4: + return grub_memcmp (&a->ipv4, &b->ipv4, sizeof (a->ipv4)); + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6: + return grub_memcmp (&a->ipv6, &b->ipv6, sizeof (a->ipv6)); + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV: + return 0; + } + grub_printf ("Unsupported address type %d\n", a->type); + return 1; +} + /* FIXME: implement this. */ static char * hwaddr_set_env (struct grub_env_var *var __attribute__ ((unused)), @@ -327,9 +794,9 @@ static void grub_net_network_level_interface_register (struct grub_net_network_level_interface *inter) { { - char buf[MAX_STR_HWADDR_LEN]; + char buf[GRUB_NET_MAX_STR_HWADDR_LEN]; char name[grub_strlen (inter->name) + sizeof ("net__mac")]; - hwaddr_to_str (&inter->hwaddress, buf); + grub_net_hwaddr_to_str (&inter->hwaddress, buf); grub_snprintf (name, sizeof (name), "net_%s_mac", inter->name); grub_env_set (name, buf); grub_register_variable_hook (name, 0, hwaddr_set_env); @@ -355,8 +822,8 @@ grub_net_network_level_interface_register (struct grub_net_network_level_interfa struct grub_net_network_level_interface * grub_net_add_addr (const char *name, struct grub_net_card *card, - grub_net_network_level_address_t addr, - grub_net_link_level_address_t hwaddress, + const grub_net_network_level_address_t *addr, + const grub_net_link_level_address_t *hwaddress, grub_net_interface_flags_t flags) { struct grub_net_network_level_interface *inter; @@ -366,8 +833,8 @@ grub_net_add_addr (const char *name, return NULL; inter->name = grub_strdup (name); - grub_memcpy (&(inter->address), &addr, sizeof (inter->address)); - grub_memcpy (&(inter->hwaddress), &hwaddress, sizeof (inter->hwaddress)); + grub_memcpy (&(inter->address), addr, sizeof (inter->address)); + grub_memcpy (&(inter->hwaddress), hwaddress, sizeof (inter->hwaddress)); inter->flags = flags; inter->card = card; inter->dhcp_ack = NULL; @@ -375,6 +842,43 @@ grub_net_add_addr (const char *name, grub_net_network_level_interface_register (inter); + if (addr->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4) + { + int mask = -1; + grub_uint32_t ip_cpu = grub_be_to_cpu32 (addr->ipv4); + if (!(ip_cpu & 0x80000000)) + mask = 8; + else if (!(ip_cpu & 0x40000000)) + mask = 16; + else if (!(ip_cpu & 0x20000000)) + mask = 24; + else + mask = -1; + if (mask != -1) + { + struct grub_net_route *route; + + route = grub_zalloc (sizeof (*route)); + if (!route) + return NULL; + + route->name = grub_xasprintf ("%s:local", name); + if (!route->name) + { + grub_free (route); + return NULL; + } + + route->target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4; + route->target.ipv4.base = grub_cpu_to_be32 (ip_cpu & (0xffffffff << (32 - mask))); + route->target.ipv4.masksize = mask; + route->is_gateway = 0; + route->interface = inter; + + grub_net_route_register (route); + } + } + return inter; } @@ -408,7 +912,7 @@ grub_cmd_addaddr (struct grub_command *cmd __attribute__ ((unused)), if (card->flags & GRUB_NET_CARD_HWADDRESS_IMMUTABLE) flags |= GRUB_NET_INTERFACE_HWADDRESS_IMMUTABLE; - grub_net_add_addr (args[0], card, addr, card->default_address, + grub_net_add_addr (args[0], card, &addr, &card->default_address, flags); return grub_errno; } @@ -532,7 +1036,7 @@ print_net_address (const grub_net_network_level_netaddress_t *target) { case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV: grub_printf ("temporary\n"); - break; + return; case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4: { grub_uint32_t n = grub_be_to_cpu32 (target->ipv4.base); @@ -543,6 +1047,16 @@ print_net_address (const grub_net_network_level_netaddress_t *target) target->ipv4.masksize); } return; + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6: + { + char buf[GRUB_NET_MAX_STR_ADDR_LEN]; + struct grub_net_network_level_address base; + base.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6; + grub_memcpy (&base.ipv6, &target->ipv6, 16); + grub_net_addr_to_str (&base, buf); + grub_printf ("%s/%d ", buf, target->ipv6.masksize); + } + return; } grub_printf ("Unknown address type %d\n", target->type); } @@ -585,8 +1099,8 @@ grub_cmd_listcards (struct grub_command *cmd __attribute__ ((unused)), struct grub_net_card *card; FOR_NET_CARDS(card) { - char buf[MAX_STR_HWADDR_LEN]; - hwaddr_to_str (&card->default_address, buf); + char buf[GRUB_NET_MAX_STR_HWADDR_LEN]; + grub_net_hwaddr_to_str (&card->default_address, buf); grub_printf ("%s %s\n", card->name, buf); } return GRUB_ERR_NONE; @@ -600,9 +1114,9 @@ grub_cmd_listaddrs (struct grub_command *cmd __attribute__ ((unused)), struct grub_net_network_level_interface *inf; FOR_NET_NETWORK_LEVEL_INTERFACES (inf) { - char bufh[MAX_STR_HWADDR_LEN]; + char bufh[GRUB_NET_MAX_STR_HWADDR_LEN]; char bufn[GRUB_NET_MAX_STR_ADDR_LEN]; - hwaddr_to_str (&inf->hwaddress, bufh); + grub_net_hwaddr_to_str (&inf->hwaddress, bufh); grub_net_addr_to_str (&inf->address, bufn); grub_printf ("%s %s %s\n", inf->name, bufh, bufn); } @@ -764,23 +1278,20 @@ receive_packets (struct grub_net_card *card) /* Maybe should be better have a fixed number of packets for each card and just mark them as used and not used. */ struct grub_net_buff *nb; - grub_ssize_t actual; - nb = grub_netbuff_alloc (1500); + + nb = card->driver->recv (card); if (!nb) { - grub_print_error (); - card->last_poll = grub_get_time_ms (); - return; - } - - actual = card->driver->recv (card, nb); - if (actual < 0) - { - grub_netbuff_free (nb); card->last_poll = grub_get_time_ms (); break; } grub_net_recv_ethernet_packet (nb, card); + if (grub_errno) + { + grub_dprintf ("net", "error receiving: %d: %s\n", grub_errno, + grub_errmsg); + grub_errno = GRUB_ERR_NONE; + } } grub_print_error (); } @@ -796,6 +1307,7 @@ grub_net_poll_cards (unsigned time) while ((grub_get_time_ms () - start_time) < time) receive_packets (card); } + grub_net_tcp_retransmit (); } static void @@ -810,23 +1322,25 @@ grub_net_poll_cards_idle_real (void) || ctime >= card->last_poll + card->idle_poll_delay_ms) receive_packets (card); } + grub_net_tcp_retransmit (); } /* Read from the packets list*/ static grub_ssize_t grub_net_fs_read_real (grub_file_t file, char *buf, grub_size_t len) { - grub_net_t sock = file->device->net; + grub_net_t net = file->device->net; struct grub_net_buff *nb; char *ptr = buf; grub_size_t amount, total = 0; int try = 0; - while (try <= 3) + + while (try <= GRUB_NET_TRIES) { - while (sock->packs.first) + while (net->packs.first) { try = 0; - nb = sock->packs.first->nb; + nb = net->packs.first->nb; amount = nb->tail - nb->data; if (amount > len) amount = len; @@ -841,7 +1355,7 @@ grub_net_fs_read_real (grub_file_t file, char *buf, grub_size_t len) if (amount == (grub_size_t) (nb->tail - nb->data)) { grub_netbuff_free (nb); - grub_net_remove_packet (sock->packs.first); + grub_net_remove_packet (net->packs.first); } else nb->data += amount; @@ -849,47 +1363,67 @@ grub_net_fs_read_real (grub_file_t file, char *buf, grub_size_t len) if (!len) return total; } - if (!sock->eof) + if (!net->eof) { try++; - grub_net_poll_cards (200); + grub_net_poll_cards (GRUB_NET_INTERVAL); } else return total; } - return total; + grub_error (GRUB_ERR_TIMEOUT, "timeout reading '%s'", net->name); + return -1; +} + +static grub_off_t +have_ahead (struct grub_file *file) +{ + grub_net_t net = file->device->net; + grub_off_t ret = net->offset; + struct grub_net_packet *pack; + for (pack = net->packs.first; pack; pack = pack->next) + ret += pack->nb->tail - pack->nb->data; + return ret; } static grub_err_t grub_net_seek_real (struct grub_file *file, grub_off_t offset) { - grub_size_t len = offset - file->device->net->offset; - - if (!len) + if (offset == file->device->net->offset) return GRUB_ERR_NONE; - if (file->device->net->offset > offset) + if (offset > file->device->net->offset) { - grub_err_t err; - while (file->device->net->packs.first) + if (!file->device->net->protocol->seek || have_ahead (file) >= offset) { - grub_netbuff_free (file->device->net->packs.first->nb); - grub_net_remove_packet (file->device->net->packs.first); + grub_net_fs_read_real (file, NULL, + offset - file->device->net->offset); + return grub_errno; } - file->device->net->protocol->close (file); - - file->device->net->packs.first = NULL; - file->device->net->packs.last = NULL; - file->device->net->offset = 0; - file->device->net->eof = 0; - err = file->device->net->protocol->open (file, file->device->net->name); - if (err) - return err; - len = offset; + return file->device->net->protocol->seek (file, offset); } - grub_net_fs_read_real (file, NULL, len); - return GRUB_ERR_NONE; + { + grub_err_t err; + if (file->device->net->protocol->seek) + return file->device->net->protocol->seek (file, offset); + while (file->device->net->packs.first) + { + grub_netbuff_free (file->device->net->packs.first->nb); + grub_net_remove_packet (file->device->net->packs.first); + } + file->device->net->protocol->close (file); + + file->device->net->packs.first = NULL; + file->device->net->packs.last = NULL; + file->device->net->offset = 0; + file->device->net->eof = 0; + err = file->device->net->protocol->open (file, file->device->net->name); + if (err) + return err; + grub_net_fs_read_real (file, NULL, offset); + return grub_errno; + } } static grub_ssize_t @@ -941,13 +1475,18 @@ static struct grub_preboot *fini_hnd; static grub_command_t cmd_addaddr, cmd_deladdr, cmd_addroute, cmd_delroute; static grub_command_t cmd_lsroutes, cmd_lscards; -static grub_command_t cmd_lsaddr; +static grub_command_t cmd_lsaddr, cmd_slaac; GRUB_MOD_INIT(net) { cmd_addaddr = grub_register_command ("net_add_addr", grub_cmd_addaddr, N_("SHORTNAME CARD ADDRESS [HWADDRESS]"), N_("Add a network address.")); + cmd_slaac = grub_register_command ("net_ipv6_autoconf", + grub_cmd_ipv6_autoconf, + "[CARD [HWADDRESS]]", + N_("Perform an IPV6 autoconfiguration")); + cmd_deladdr = grub_register_command ("net_del_addr", grub_cmd_deladdr, N_("SHORTNAME"), N_("Delete a network address.")); @@ -964,6 +1503,7 @@ GRUB_MOD_INIT(net) cmd_lsaddr = grub_register_command ("net_ls_addr", grub_cmd_listaddrs, "", N_("list network addresses")); grub_bootp_init (); + grub_dns_init (); grub_fs_register (&grub_net_fs); grub_net_open = grub_net_open_real; @@ -976,6 +1516,7 @@ GRUB_MOD_INIT(net) GRUB_MOD_FINI(net) { grub_bootp_fini (); + grub_dns_fini (); grub_unregister_command (cmd_addaddr); grub_unregister_command (cmd_deladdr); grub_unregister_command (cmd_addroute); @@ -983,6 +1524,7 @@ GRUB_MOD_FINI(net) grub_unregister_command (cmd_lsroutes); grub_unregister_command (cmd_lscards); grub_unregister_command (cmd_lsaddr); + grub_unregister_command (cmd_slaac); grub_fs_unregister (&grub_net_fs); grub_net_open = NULL; grub_net_fini_hw (0); diff --git a/grub-core/net/netbuff.c b/grub-core/net/netbuff.c index bee207f90..242b154e0 100644 --- a/grub-core/net/netbuff.c +++ b/grub-core/net/netbuff.c @@ -21,7 +21,6 @@ #include #include - grub_err_t grub_netbuff_put (struct grub_net_buff *nb, grub_size_t len) { @@ -90,7 +89,7 @@ grub_netbuff_alloc (grub_size_t len) nb = (struct grub_net_buff *) ((grub_properly_aligned_t *) data + len / sizeof (grub_properly_aligned_t)); nb->head = nb->data = nb->tail = data; - nb->end = (char *) nb; + nb->end = (grub_uint8_t *) nb; return nb; } diff --git a/grub-core/net/tcp.c b/grub-core/net/tcp.c new file mode 100644 index 000000000..1201220b5 --- /dev/null +++ b/grub-core/net/tcp.c @@ -0,0 +1,975 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#define TCP_SYN_RETRANSMISSION_TIMEOUT GRUB_NET_INTERVAL +#define TCP_SYN_RETRANSMISSION_COUNT GRUB_NET_TRIES +#define TCP_RETRANSMISSION_TIMEOUT GRUB_NET_INTERVAL +#define TCP_RETRANSMISSION_COUNT GRUB_NET_TRIES + +struct unacked +{ + struct unacked *next; + struct grub_net_buff *nb; + grub_uint64_t last_try; + int try_count; +}; + +enum + { + TCP_FIN = 0x1, + TCP_SYN = 0x2, + TCP_RST = 0x4, + TCP_PUSH = 0x8, + TCP_ACK = 0x10, + TCP_URG = 0x20, + }; + +struct grub_net_tcp_socket +{ + struct grub_net_tcp_socket *next; + + int established; + int i_closed; + int they_closed; + int in_port; + int out_port; + int errors; + int they_reseted; + int i_reseted; + grub_uint32_t my_start_seq; + grub_uint32_t my_cur_seq; + grub_uint32_t their_start_seq; + grub_uint32_t their_cur_seq; + grub_uint16_t my_window; + struct unacked *unack_first; + struct unacked *unack_last; + grub_err_t (*recv_hook) (grub_net_tcp_socket_t sock, struct grub_net_buff *nb, + void *recv); + void (*error_hook) (grub_net_tcp_socket_t sock, void *recv); + void (*fin_hook) (grub_net_tcp_socket_t sock, void *recv); + void *hook_data; + grub_net_network_level_address_t out_nla; + grub_net_link_level_address_t ll_target_addr; + struct grub_net_network_level_interface *inf; + grub_net_packets_t packs; + grub_priority_queue_t pq; +}; + +struct grub_net_tcp_listen +{ + struct grub_net_tcp_listen *next; + + grub_uint16_t port; + const struct grub_net_network_level_interface *inf; + + grub_err_t (*listen_hook) (grub_net_tcp_listen_t listen, + grub_net_tcp_socket_t sock, + void *data); + void *hook_data; +}; + +struct tcphdr +{ + grub_uint16_t src; + grub_uint16_t dst; + grub_uint32_t seqnr; + grub_uint32_t ack; + grub_uint16_t flags; + grub_uint16_t window; + grub_uint16_t checksum; + grub_uint16_t urgent; +} __attribute__ ((packed)); + +struct tcp_pseudohdr +{ + grub_uint32_t src; + grub_uint32_t dst; + grub_uint8_t zero; + grub_uint8_t proto; + grub_uint16_t tcp_length; +} __attribute__ ((packed)); + +struct tcp6_pseudohdr +{ + grub_uint64_t src[2]; + grub_uint64_t dst[2]; + grub_uint32_t tcp_length; + grub_uint8_t zero[3]; + grub_uint8_t proto; +} __attribute__ ((packed)); + +static struct grub_net_tcp_socket *tcp_sockets; +static struct grub_net_tcp_listen *tcp_listens; + +#define FOR_TCP_SOCKETS(var) FOR_LIST_ELEMENTS (var, tcp_sockets) +#define FOR_TCP_LISTENS(var) FOR_LIST_ELEMENTS (var, tcp_listens) + +grub_net_tcp_listen_t +grub_net_tcp_listen (grub_uint16_t port, + const struct grub_net_network_level_interface *inf, + grub_err_t (*listen_hook) (grub_net_tcp_listen_t listen, + grub_net_tcp_socket_t sock, + void *data), + void *hook_data) +{ + grub_net_tcp_listen_t ret; + ret = grub_malloc (sizeof (*ret)); + if (!ret) + return NULL; + ret->listen_hook = listen_hook; + ret->hook_data = hook_data; + ret->port = port; + ret->inf = inf; + grub_list_push (GRUB_AS_LIST_P (&tcp_listens), GRUB_AS_LIST (ret)); + return ret; +} + +void +grub_net_tcp_stop_listen (grub_net_tcp_listen_t listen) +{ + grub_list_remove (GRUB_AS_LIST_P (&tcp_listens), + GRUB_AS_LIST (listen)); +} + +static inline void +tcp_socket_register (grub_net_tcp_socket_t sock) +{ + grub_list_push (GRUB_AS_LIST_P (&tcp_sockets), + GRUB_AS_LIST (sock)); +} + +static void +error (grub_net_tcp_socket_t sock) +{ + struct unacked *unack, *next; + + if (sock->error_hook) + sock->error_hook (sock, sock->hook_data); + + for (unack = sock->unack_first; unack; unack = next) + { + next = unack->next; + grub_netbuff_free (unack->nb); + grub_free (unack); + } + + sock->unack_first = NULL; + sock->unack_last = NULL; +} + +static grub_err_t +tcp_send (struct grub_net_buff *nb, grub_net_tcp_socket_t socket) +{ + grub_err_t err; + grub_uint8_t *nbd; + struct unacked *unack; + struct tcphdr *tcph; + grub_size_t size; + + tcph = (struct tcphdr *) nb->data; + + tcph->seqnr = grub_cpu_to_be32 (socket->my_cur_seq); + size = (nb->tail - nb->data - (grub_be_to_cpu16 (tcph->flags) >> 12) * 4); + if (grub_be_to_cpu16 (tcph->flags) & TCP_FIN) + size++; + socket->my_cur_seq += size; + tcph->src = grub_cpu_to_be16 (socket->in_port); + tcph->dst = grub_cpu_to_be16 (socket->out_port); + tcph->checksum = 0; + tcph->checksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_TCP, + &socket->inf->address, + &socket->out_nla); + nbd = nb->data; + if (size) + { + unack = grub_malloc (sizeof (*unack)); + if (!unack) + return grub_errno; + + unack->next = NULL; + unack->nb = nb; + unack->try_count = 1; + unack->last_try = grub_get_time_ms (); + if (!socket->unack_last) + socket->unack_first = socket->unack_last = unack; + else + socket->unack_last->next = unack; + } + + err = grub_net_send_ip_packet (socket->inf, &(socket->out_nla), + &(socket->ll_target_addr), nb, + GRUB_NET_IP_TCP); + if (err) + return err; + nb->data = nbd; + if (!size) + grub_netbuff_free (nb); + return GRUB_ERR_NONE; +} + +void +grub_net_tcp_close (grub_net_tcp_socket_t sock, + int discard_received) +{ + struct grub_net_buff *nb_fin; + struct tcphdr *tcph_fin; + grub_err_t err; + + if (discard_received != GRUB_NET_TCP_CONTINUE_RECEIVING) + { + sock->recv_hook = NULL; + sock->error_hook = NULL; + sock->fin_hook = NULL; + } + + if (discard_received == GRUB_NET_TCP_ABORT) + sock->i_reseted = 1; + + if (sock->i_closed) + return; + + sock->i_closed = 1; + + nb_fin = grub_netbuff_alloc (sizeof (*tcph_fin) + + GRUB_NET_OUR_MAX_IP_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE); + if (!nb_fin) + return; + err = grub_netbuff_reserve (nb_fin, GRUB_NET_OUR_MAX_IP_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE); + if (err) + { + grub_netbuff_free (nb_fin); + grub_dprintf ("net", "error closing socket\n"); + grub_errno = GRUB_ERR_NONE; + return; + } + + err = grub_netbuff_put (nb_fin, sizeof (*tcph_fin)); + if (err) + { + grub_netbuff_free (nb_fin); + grub_dprintf ("net", "error closing socket\n"); + grub_errno = GRUB_ERR_NONE; + return; + } + tcph_fin = (void *) nb_fin->data; + tcph_fin->ack = grub_cpu_to_be32 (sock->their_cur_seq); + tcph_fin->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_FIN + | TCP_ACK); + tcph_fin->window = grub_cpu_to_be16_compile_time (0); + tcph_fin->urgent = 0; + err = tcp_send (nb_fin, sock); + if (err) + { + grub_netbuff_free (nb_fin); + grub_dprintf ("net", "error closing socket\n"); + grub_errno = GRUB_ERR_NONE; + } + return; +} + +static void +ack_real (grub_net_tcp_socket_t sock, int res) +{ + struct grub_net_buff *nb_ack; + struct tcphdr *tcph_ack; + grub_err_t err; + + nb_ack = grub_netbuff_alloc (sizeof (*tcph_ack) + 128); + if (!nb_ack) + return; + err = grub_netbuff_reserve (nb_ack, 128); + if (err) + { + grub_netbuff_free (nb_ack); + grub_dprintf ("net", "error closing socket\n"); + grub_errno = GRUB_ERR_NONE; + return; + } + + err = grub_netbuff_put (nb_ack, sizeof (*tcph_ack)); + if (err) + { + grub_netbuff_free (nb_ack); + grub_dprintf ("net", "error closing socket\n"); + grub_errno = GRUB_ERR_NONE; + return; + } + tcph_ack = (void *) nb_ack->data; + if (res) + { + tcph_ack->ack = grub_cpu_to_be32_compile_time (0); + tcph_ack->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_RST); + tcph_ack->window = grub_cpu_to_be16_compile_time (0); + } + else + { + tcph_ack->ack = grub_cpu_to_be32 (sock->their_cur_seq); + tcph_ack->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_ACK); + tcph_ack->window = grub_cpu_to_be16 (sock->my_window); + } + tcph_ack->urgent = 0; + tcph_ack->src = grub_cpu_to_be16 (sock->in_port); + tcph_ack->dst = grub_cpu_to_be16 (sock->out_port); + err = tcp_send (nb_ack, sock); + if (err) + { + grub_dprintf ("net", "error acking socket\n"); + grub_errno = GRUB_ERR_NONE; + } +} + +static void +ack (grub_net_tcp_socket_t sock) +{ + ack_real (sock, 0); +} + +static void +reset (grub_net_tcp_socket_t sock) +{ + ack_real (sock, 1); +} + +void +grub_net_tcp_retransmit (void) +{ + grub_net_tcp_socket_t sock; + grub_uint64_t ctime = grub_get_time_ms (); + grub_uint64_t limit_time = ctime - TCP_RETRANSMISSION_TIMEOUT; + + FOR_TCP_SOCKETS (sock) + { + struct unacked *unack; + for (unack = sock->unack_first; unack; unack = unack->next) + { + struct tcphdr *tcph; + grub_uint8_t *nbd; + grub_err_t err; + + if (unack->last_try > limit_time) + continue; + + if (unack->try_count > TCP_RETRANSMISSION_COUNT) + { + error (sock); + break; + } + unack->try_count++; + unack->last_try = ctime; + nbd = unack->nb->data; + tcph = (struct tcphdr *) nbd; + + if ((tcph->flags & grub_cpu_to_be16_compile_time (TCP_ACK)) + && tcph->ack != grub_cpu_to_be32 (sock->their_cur_seq)) + { + tcph->checksum = 0; + tcph->checksum = grub_net_ip_transport_checksum (unack->nb, + GRUB_NET_IP_TCP, + &sock->inf->address, + &sock->out_nla); + } + + err = grub_net_send_ip_packet (sock->inf, &(sock->out_nla), + &(sock->ll_target_addr), unack->nb, + GRUB_NET_IP_TCP); + unack->nb->data = nbd; + if (err) + { + grub_dprintf ("net", "TCP retransmit failed: %s\n", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + } + } + } +} + +grub_uint16_t +grub_net_ip_transport_checksum (struct grub_net_buff *nb, + grub_uint16_t proto, + const grub_net_network_level_address_t *src, + const grub_net_network_level_address_t *dst) +{ + grub_uint16_t a, b = 0; + grub_uint32_t c; + a = ~grub_be_to_cpu16 (grub_net_ip_chksum ((void *) nb->data, + nb->tail - nb->data)); + + switch (dst->type) + { + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4: + { + struct tcp_pseudohdr ph; + ph.src = src->ipv4; + ph.dst = dst->ipv4; + ph.zero = 0; + ph.tcp_length = grub_cpu_to_be16 (nb->tail - nb->data); + ph.proto = proto; + b = ~grub_be_to_cpu16 (grub_net_ip_chksum ((void *) &ph, sizeof (ph))); + break; + } + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6: + { + struct tcp6_pseudohdr ph; + grub_memcpy (ph.src, src->ipv6, sizeof (ph.src)); + grub_memcpy (ph.dst, dst->ipv6, sizeof (ph.dst)); + grub_memset (ph.zero, 0, sizeof (ph.zero)); + ph.tcp_length = grub_cpu_to_be32 (nb->tail - nb->data); + ph.proto = proto; + b = ~grub_be_to_cpu16 (grub_net_ip_chksum ((void *) &ph, sizeof (ph))); + break; + } + case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV: + b = 0; + break; + } + c = (grub_uint32_t) a + (grub_uint32_t) b; + if (c >= 0xffff) + c -= 0xffff; + return grub_cpu_to_be16 (~c); +} + +/* FIXME: overflow. */ +static int +cmp (const void *a__, const void *b__) +{ + struct grub_net_buff *a_ = *(struct grub_net_buff **) a__; + struct grub_net_buff *b_ = *(struct grub_net_buff **) b__; + struct tcphdr *a = (struct tcphdr *) a_->data; + struct tcphdr *b = (struct tcphdr *) b_->data; + /* We want the first elements to be on top. */ + if (grub_be_to_cpu32 (a->seqnr) < grub_be_to_cpu32 (b->seqnr)) + return +1; + if (grub_be_to_cpu32 (a->seqnr) > grub_be_to_cpu32 (b->seqnr)) + return -1; + return 0; +} + +static void +destroy_pq (grub_net_tcp_socket_t sock) +{ + struct grub_net_buff **nb_p; + while ((nb_p = grub_priority_queue_top (sock->pq))) + { + grub_netbuff_free (*nb_p); + grub_priority_queue_pop (sock->pq); + } + + grub_priority_queue_destroy (sock->pq); +} + +grub_err_t +grub_net_tcp_accept (grub_net_tcp_socket_t sock, + grub_err_t (*recv_hook) (grub_net_tcp_socket_t sock, + struct grub_net_buff *nb, + void *data), + void (*error_hook) (grub_net_tcp_socket_t sock, + void *data), + void (*fin_hook) (grub_net_tcp_socket_t sock, + void *data), + void *hook_data) +{ + struct grub_net_buff *nb_ack; + struct tcphdr *tcph; + grub_err_t err; + + sock->recv_hook = recv_hook; + sock->error_hook = error_hook; + sock->fin_hook = fin_hook; + sock->hook_data = hook_data; + nb_ack = grub_netbuff_alloc (sizeof (*tcph) + + GRUB_NET_OUR_MAX_IP_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE); + if (!nb_ack) + return grub_errno; + err = grub_netbuff_reserve (nb_ack, GRUB_NET_OUR_MAX_IP_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE); + if (err) + { + grub_netbuff_free (nb_ack); + return err; + } + + err = grub_netbuff_put (nb_ack, sizeof (*tcph)); + if (err) + { + grub_netbuff_free (nb_ack); + return err; + } + tcph = (void *) nb_ack->data; + tcph->ack = grub_cpu_to_be32 (sock->their_cur_seq); + tcph->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_SYN | TCP_ACK); + tcph->window = grub_cpu_to_be16 (sock->my_window); + tcph->urgent = 0; + sock->established = 1; + tcp_socket_register (sock); + err = tcp_send (nb_ack, sock); + if (err) + return err; + sock->my_cur_seq++; + return GRUB_ERR_NONE; +} + +grub_net_tcp_socket_t +grub_net_tcp_open (char *server, + grub_uint16_t out_port, + grub_err_t (*recv_hook) (grub_net_tcp_socket_t sock, + struct grub_net_buff *nb, + void *data), + void (*error_hook) (grub_net_tcp_socket_t sock, + void *data), + void (*fin_hook) (grub_net_tcp_socket_t sock, + void *data), + void *hook_data) +{ + grub_err_t err; + grub_net_network_level_address_t addr; + struct grub_net_network_level_interface *inf; + grub_net_network_level_address_t gateway; + grub_net_tcp_socket_t socket; + static grub_uint16_t in_port = 21550; + struct grub_net_buff *nb; + struct tcphdr *tcph; + int i; + grub_uint8_t *nbd; + grub_net_link_level_address_t ll_target_addr; + + err = grub_net_resolve_address (server, &addr); + if (err) + return NULL; + + if (addr.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4 + && addr.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "not an IP address"); + return NULL; + } + + err = grub_net_route_address (addr, &gateway, &inf); + if (err) + return NULL; + + err = grub_net_link_layer_resolve (inf, &gateway, &ll_target_addr); + if (err) + return NULL; + + socket = grub_zalloc (sizeof (*socket)); + if (socket == NULL) + return NULL; + + socket->out_port = out_port; + socket->inf = inf; + socket->out_nla = addr; + socket->ll_target_addr = ll_target_addr; + socket->in_port = in_port++; + socket->recv_hook = recv_hook; + socket->error_hook = error_hook; + socket->fin_hook = fin_hook; + socket->hook_data = hook_data; + + nb = grub_netbuff_alloc (sizeof (*tcph) + 128); + if (!nb) + return NULL; + err = grub_netbuff_reserve (nb, 128); + if (err) + { + grub_netbuff_free (nb); + return NULL; + } + + err = grub_netbuff_put (nb, sizeof (*tcph)); + if (err) + { + grub_netbuff_free (nb); + return NULL; + } + socket->pq = grub_priority_queue_new (sizeof (struct grub_net_buff *), cmp); + if (!socket->pq) + { + grub_netbuff_free (nb); + return NULL; + } + + tcph = (void *) nb->data; + socket->my_start_seq = grub_get_time_ms (); + socket->my_cur_seq = socket->my_start_seq + 1; + socket->my_window = 8192; + tcph->seqnr = grub_cpu_to_be32 (socket->my_start_seq); + tcph->ack = grub_cpu_to_be32_compile_time (0); + tcph->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_SYN); + tcph->window = grub_cpu_to_be16 (socket->my_window); + tcph->urgent = 0; + tcph->src = grub_cpu_to_be16 (socket->in_port); + tcph->dst = grub_cpu_to_be16 (socket->out_port); + tcph->checksum = 0; + tcph->checksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_TCP, + &socket->inf->address, + &socket->out_nla); + + tcp_socket_register (socket); + + nbd = nb->data; + for (i = 0; i < TCP_SYN_RETRANSMISSION_COUNT; i++) + { + int j; + nb->data = nbd; + err = grub_net_send_ip_packet (socket->inf, &(socket->out_nla), + &(socket->ll_target_addr), nb, + GRUB_NET_IP_TCP); + if (err) + { + grub_list_remove (GRUB_AS_LIST_P (&tcp_sockets), + GRUB_AS_LIST (socket)); + grub_free (socket); + grub_netbuff_free (nb); + return NULL; + } + for (j = 0; (j < TCP_SYN_RETRANSMISSION_TIMEOUT / 50 + && !socket->established); j++) + grub_net_poll_cards (50); + if (socket->established) + break; + } + if (!socket->established) + { + grub_list_remove (GRUB_AS_LIST_P (&tcp_sockets), + GRUB_AS_LIST (socket)); + if (socket->they_reseted) + grub_error (GRUB_ERR_NET_PORT_CLOSED, "port closed"); + else + grub_error (GRUB_ERR_NET_NO_ANSWER, "no answer"); + + grub_netbuff_free (nb); + destroy_pq (socket); + grub_free (socket); + return NULL; + } + + grub_netbuff_free (nb); + return socket; +} + +grub_err_t +grub_net_send_tcp_packet (const grub_net_tcp_socket_t socket, + struct grub_net_buff *nb, int push) +{ + struct tcphdr *tcph; + grub_err_t err; + grub_ssize_t fraglen; + COMPILE_TIME_ASSERT (sizeof (struct tcphdr) == GRUB_NET_TCP_HEADER_SIZE); + if (socket->out_nla.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4) + fraglen = (socket->inf->card->mtu - GRUB_NET_OUR_IPV4_HEADER_SIZE + - sizeof (*tcph)); + else + fraglen = 1280 - GRUB_NET_OUR_IPV6_HEADER_SIZE; + + while (nb->tail - nb->data > fraglen) + { + struct grub_net_buff *nb2; + + nb2 = grub_netbuff_alloc (fraglen + sizeof (*tcph) + + GRUB_NET_OUR_MAX_IP_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE); + if (!nb2) + return grub_errno; + err = grub_netbuff_reserve (nb2, GRUB_NET_MAX_LINK_HEADER_SIZE + + GRUB_NET_OUR_MAX_IP_HEADER_SIZE); + if (err) + return err; + err = grub_netbuff_put (nb2, sizeof (*tcph)); + if (err) + return err; + + tcph = (struct tcphdr *) nb2->data; + tcph->ack = grub_cpu_to_be32 (socket->their_cur_seq); + tcph->flags = grub_cpu_to_be16_compile_time ((5 << 12) | TCP_ACK); + tcph->window = grub_cpu_to_be16 (socket->my_window); + tcph->urgent = 0; + err = grub_netbuff_put (nb2, fraglen); + if (err) + return err; + grub_memcpy (tcph + 1, nb->data, fraglen); + err = grub_netbuff_pull (nb, fraglen); + if (err) + return err; + + err = tcp_send (nb2, socket); + if (err) + return err; + } + + err = grub_netbuff_push (nb, sizeof (*tcph)); + if (err) + return err; + + tcph = (struct tcphdr *) nb->data; + tcph->ack = grub_cpu_to_be32 (socket->their_cur_seq); + tcph->flags = (grub_cpu_to_be16_compile_time ((5 << 12) | TCP_ACK) + | (push ? grub_cpu_to_be16_compile_time (TCP_PUSH) : 0)); + tcph->window = grub_cpu_to_be16 (socket->my_window); + tcph->urgent = 0; + return tcp_send (nb, socket); +} + +grub_err_t +grub_net_recv_tcp_packet (struct grub_net_buff *nb, + struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *source) +{ + struct tcphdr *tcph; + grub_net_tcp_socket_t sock; + grub_err_t err; + + /* Ignore broadcast. */ + if (!inf) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + tcph = (struct tcphdr *) nb->data; + if ((grub_be_to_cpu16 (tcph->flags) >> 12) < 5) + { + grub_dprintf ("net", "TCP header too short: %u\n", + grub_be_to_cpu16 (tcph->flags) >> 12); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (nb->tail - nb->data < (grub_ssize_t) ((grub_be_to_cpu16 (tcph->flags) + >> 12) * sizeof (grub_uint32_t))) + { + grub_dprintf ("net", "TCP packet too short: %" PRIuGRUB_SIZE "\n", + nb->tail - nb->data); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + FOR_TCP_SOCKETS (sock) + { + if (!(grub_be_to_cpu16 (tcph->dst) == sock->in_port + && grub_be_to_cpu16 (tcph->src) == sock->out_port + && inf == sock->inf + && grub_net_addr_cmp (source, &sock->out_nla) == 0)) + continue; + if (tcph->checksum) + { + grub_uint16_t chk, expected; + chk = tcph->checksum; + tcph->checksum = 0; + expected = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_TCP, + &sock->out_nla, + &sock->inf->address); + if (expected != chk) + { + grub_dprintf ("net", "Invalid TCP checksum. " + "Expected %x, got %x\n", + grub_be_to_cpu16 (expected), + grub_be_to_cpu16 (chk)); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + tcph->checksum = chk; + } + + if ((grub_be_to_cpu16 (tcph->flags) & TCP_SYN) + && (grub_be_to_cpu16 (tcph->flags) & TCP_ACK) + && !sock->established) + { + sock->their_start_seq = grub_be_to_cpu32 (tcph->seqnr); + sock->their_cur_seq = sock->their_start_seq + 1; + sock->established = 1; + } + + if (grub_be_to_cpu16 (tcph->flags) & TCP_RST) + { + sock->they_reseted = 1; + + error (sock); + + grub_netbuff_free (nb); + + return GRUB_ERR_NONE; + } + + if (grub_be_to_cpu16 (tcph->flags) & TCP_ACK) + { + struct unacked *unack, *next; + grub_uint32_t acked = grub_be_to_cpu32 (tcph->ack); + for (unack = sock->unack_first; unack; unack = next) + { + grub_uint32_t seqnr; + struct tcphdr *unack_tcph; + next = unack->next; + seqnr = grub_be_to_cpu32 (((struct tcphdr *) unack->nb->data) + ->seqnr); + unack_tcph = (struct tcphdr *) unack->nb->data; + seqnr += (unack->nb->tail - unack->nb->data + - (grub_be_to_cpu16 (unack_tcph->flags) >> 12) * 4); + if (grub_be_to_cpu16 (unack_tcph->flags) & TCP_FIN) + seqnr++; + + if (seqnr > acked) + break; + grub_netbuff_free (unack->nb); + grub_free (unack); + } + sock->unack_first = unack; + if (!sock->unack_first) + sock->unack_last = NULL; + } + + if (grub_be_to_cpu32 (tcph->seqnr) < sock->their_cur_seq) + { + ack (sock); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + if (sock->i_reseted && (nb->tail - nb->data + - (grub_be_to_cpu16 (tcph->flags) + >> 12) * sizeof (grub_uint32_t)) > 0) + { + reset (sock); + } + + err = grub_priority_queue_push (sock->pq, &nb); + if (err) + { + grub_netbuff_free (nb); + return err; + } + + { + struct grub_net_buff **nb_top_p, *nb_top; + int do_ack = 0; + int just_closed = 0; + while (1) + { + nb_top_p = grub_priority_queue_top (sock->pq); + if (!nb_top_p) + return GRUB_ERR_NONE; + nb_top = *nb_top_p; + tcph = (struct tcphdr *) nb_top->data; + if (grub_be_to_cpu32 (tcph->seqnr) >= sock->their_cur_seq) + break; + grub_netbuff_free (nb_top); + grub_priority_queue_pop (sock->pq); + } + if (grub_be_to_cpu32 (tcph->seqnr) != sock->their_cur_seq) + return GRUB_ERR_NONE; + while (1) + { + nb_top_p = grub_priority_queue_top (sock->pq); + if (!nb_top_p) + break; + nb_top = *nb_top_p; + tcph = (struct tcphdr *) nb_top->data; + + if (grub_be_to_cpu32 (tcph->seqnr) != sock->their_cur_seq) + break; + grub_priority_queue_pop (sock->pq); + + err = grub_netbuff_pull (nb_top, (grub_be_to_cpu16 (tcph->flags) + >> 12) * sizeof (grub_uint32_t)); + if (err) + { + grub_netbuff_free (nb_top); + return err; + } + + sock->their_cur_seq += (nb_top->tail - nb_top->data); + if (grub_be_to_cpu16 (tcph->flags) & TCP_FIN) + { + sock->they_closed = 1; + just_closed = 1; + sock->their_cur_seq++; + do_ack = 1; + } + /* If there is data, puts packet in socket list. */ + if ((nb_top->tail - nb_top->data) > 0) + { + grub_net_put_packet (&sock->packs, nb_top); + do_ack = 1; + } + else + grub_netbuff_free (nb); + } + if (do_ack) + ack (sock); + while (sock->packs.first) + { + nb = sock->packs.first->nb; + if (sock->recv_hook) + sock->recv_hook (sock, sock->packs.first->nb, sock->hook_data); + else + grub_netbuff_free (nb); + grub_net_remove_packet (sock->packs.first); + } + + if (sock->fin_hook && just_closed) + sock->fin_hook (sock, sock->hook_data); + } + + return GRUB_ERR_NONE; + } + if (grub_be_to_cpu16 (tcph->flags) & TCP_SYN) + { + grub_net_tcp_listen_t listen; + + FOR_TCP_LISTENS (listen) + { + if (!(grub_be_to_cpu16 (tcph->dst) == listen->port + && (inf == listen->inf || listen->inf == NULL))) + continue; + sock = grub_zalloc (sizeof (*sock)); + if (sock == NULL) + return grub_errno; + + sock->out_port = grub_be_to_cpu16 (tcph->src); + sock->in_port = grub_be_to_cpu16 (tcph->dst); + sock->inf = inf; + sock->out_nla = *source; + sock->their_start_seq = grub_be_to_cpu32 (tcph->seqnr); + sock->their_cur_seq = sock->their_start_seq + 1; + sock->my_cur_seq = sock->my_start_seq = grub_get_time_ms (); + sock->my_window = 8192; + + sock->pq = grub_priority_queue_new (sizeof (struct grub_net_buff *), + cmp); + if (!sock->pq) + { + grub_netbuff_free (nb); + return grub_errno; + } + + err = listen->listen_hook (listen, sock, listen->hook_data); + + grub_netbuff_free (nb); + return err; + + } + } + grub_netbuff_free (nb); + return GRUB_ERR_NONE; +} diff --git a/grub-core/net/tftp.c b/grub-core/net/tftp.c index be1534021..c05ef24d4 100644 --- a/grub-core/net/tftp.c +++ b/grub-core/net/tftp.c @@ -25,6 +25,7 @@ #include #include #include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -102,24 +103,63 @@ typedef struct tftp_data grub_uint64_t block; grub_uint32_t block_size; int have_oack; - grub_net_socket_t sock; + grub_net_udp_socket_t sock; + grub_priority_queue_t pq; } *tftp_data_t; +static int +cmp (const void *a__, const void *b__) +{ + struct grub_net_buff *a_ = *(struct grub_net_buff **) a__; + struct grub_net_buff *b_ = *(struct grub_net_buff **) b__; + struct tftphdr *a = (struct tftphdr *) a_->data; + struct tftphdr *b = (struct tftphdr *) b_->data; + /* We want the first elements to be on top. */ + if (grub_be_to_cpu16 (a->u.data.block) < grub_be_to_cpu16 (b->u.data.block)) + return +1; + if (grub_be_to_cpu16 (a->u.data.block) > grub_be_to_cpu16 (b->u.data.block)) + return -1; + return 0; +} + static grub_err_t -tftp_receive (grub_net_socket_t sock __attribute__ ((unused)), +ack (grub_net_udp_socket_t sock, grub_uint16_t block) +{ + struct tftphdr *tftph_ack; + grub_uint8_t nbdata[512]; + struct grub_net_buff nb_ack; + grub_err_t err; + + nb_ack.head = nbdata; + nb_ack.end = nbdata + sizeof (nbdata); + grub_netbuff_clear (&nb_ack); + grub_netbuff_reserve (&nb_ack, 512); + err = grub_netbuff_push (&nb_ack, sizeof (tftph_ack->opcode) + + sizeof (tftph_ack->u.ack.block)); + if (err) + return err; + + tftph_ack = (struct tftphdr *) nb_ack.data; + tftph_ack->opcode = grub_cpu_to_be16 (TFTP_ACK); + tftph_ack->u.ack.block = block; + + err = grub_net_send_udp_packet (sock, &nb_ack); + return err; +} + +static grub_err_t +tftp_receive (grub_net_udp_socket_t sock __attribute__ ((unused)), struct grub_net_buff *nb, void *f) { grub_file_t file = f; struct tftphdr *tftph = (void *) nb->data; - char nbdata[512]; tftp_data_t data = file->data; grub_err_t err; - char *ptr; - struct grub_net_buff nb_ack; + grub_uint8_t *ptr; - nb_ack.head = nbdata; - nb_ack.end = nbdata + sizeof (nbdata); + if (nb->tail - nb->data < (grub_ssize_t) sizeof (tftph->opcode)) + return grub_error (GRUB_ERR_OUT_OF_RANGE, "TFTP packet too small"); tftph = (struct tftphdr *) nb->data; switch (grub_be_to_cpu16 (tftph->opcode)) @@ -130,76 +170,99 @@ tftp_receive (grub_net_socket_t sock __attribute__ ((unused)), for (ptr = nb->data + sizeof (tftph->opcode); ptr < nb->tail;) { if (grub_memcmp (ptr, "tsize\0", sizeof ("tsize\0") - 1) == 0) - { - data->file_size = grub_strtoul (ptr + sizeof ("tsize\0") - 1, - 0, 0); - } + data->file_size = grub_strtoul ((char *) ptr + sizeof ("tsize\0") + - 1, 0, 0); if (grub_memcmp (ptr, "blksize\0", sizeof ("blksize\0") - 1) == 0) - { - data->block_size = grub_strtoul (ptr + sizeof ("blksize\0") - 1, - 0, 0); - } + data->block_size = grub_strtoul ((char *) ptr + sizeof ("blksize\0") + - 1, 0, 0); while (ptr < nb->tail && *ptr) ptr++; ptr++; } data->block = 0; grub_netbuff_free (nb); - break; - case TFTP_DATA: - err = grub_netbuff_pull (nb, sizeof (tftph->opcode) + - sizeof (tftph->u.data.block)); + err = ack (data->sock, 0); if (err) return err; - if (grub_be_to_cpu16 (tftph->u.data.block) == data->block + 1) - { - unsigned size = nb->tail - nb->data; - data->block++; - if (size < data->block_size) - { - file->device->net->eof = 1; - } - /* Prevent garbage in broken cards. */ - if (size > data->block_size) - { - err = grub_netbuff_unput (nb, size - data->block_size); - if (err) - return err; - } - /* If there is data, puts packet in socket list. */ - if ((nb->tail - nb->data) > 0) - grub_net_put_packet (&file->device->net->packs, nb); - else - grub_netbuff_free (nb); - } - else - { - grub_netbuff_free (nb); - return GRUB_ERR_NONE; - } - break; + return GRUB_ERR_NONE; + case TFTP_DATA: + if (nb->tail - nb->data < (grub_ssize_t) (sizeof (tftph->opcode) + + sizeof (tftph->u.data.block))) + return grub_error (GRUB_ERR_OUT_OF_RANGE, "TFTP packet too small"); + err = ack (data->sock, tftph->u.data.block); + if (err) + return err; + + err = grub_priority_queue_push (data->pq, &nb); + if (err) + return err; + + { + struct grub_net_buff **nb_top_p, *nb_top; + while (1) + { + nb_top_p = grub_priority_queue_top (data->pq); + if (!nb_top_p) + return GRUB_ERR_NONE; + nb_top = *nb_top_p; + tftph = (struct tftphdr *) nb_top->data; + if (grub_be_to_cpu16 (tftph->u.data.block) >= data->block + 1) + break; + grub_priority_queue_pop (data->pq); + } + if (grub_be_to_cpu16 (tftph->u.data.block) == data->block + 1) + { + unsigned size; + + grub_priority_queue_pop (data->pq); + + err = grub_netbuff_pull (nb_top, sizeof (tftph->opcode) + + sizeof (tftph->u.data.block)); + if (err) + return err; + size = nb_top->tail - nb_top->data; + + data->block++; + if (size < data->block_size) + { + file->device->net->eof = 1; + grub_net_udp_close (data->sock); + data->sock = NULL; + } + /* Prevent garbage in broken cards. Is it still necessary + given that IP implementation has been fixed? + */ + if (size > data->block_size) + { + err = grub_netbuff_unput (nb_top, size - data->block_size); + if (err) + return err; + } + /* If there is data, puts packet in socket list. */ + if ((nb_top->tail - nb_top->data) > 0) + grub_net_put_packet (&file->device->net->packs, nb_top); + else + grub_netbuff_free (nb); + } + } + return GRUB_ERR_NONE; case TFTP_ERROR: grub_netbuff_free (nb); return grub_error (GRUB_ERR_IO, (char *) tftph->u.err.errmsg); + default: + grub_netbuff_free (nb); + return GRUB_ERR_NONE; } - grub_netbuff_clear (&nb_ack); - grub_netbuff_reserve (&nb_ack, 512); - err = grub_netbuff_push (&nb_ack, sizeof (tftph->opcode) - + sizeof (tftph->u.ack.block)); - if (err) - return err; +} - tftph = (struct tftphdr *) nb_ack.data; - tftph->opcode = grub_cpu_to_be16 (TFTP_ACK); - tftph->u.ack.block = grub_cpu_to_be16 (data->block); +static void +destroy_pq (tftp_data_t data) +{ + struct grub_net_buff **nb_p; + while ((nb_p = grub_priority_queue_top (data->pq))) + grub_netbuff_free (*nb_p); - err = grub_net_send_udp_packet (data->sock, &nb_ack); - if (file->device->net->eof) - { - grub_net_udp_close (data->sock); - data->sock = NULL; - } - return err; + grub_priority_queue_destroy (data->pq); } static grub_err_t @@ -210,10 +273,12 @@ tftp_open (struct grub_file *file, const char *filename) int i; int rrqlen; int hdrlen; - char open_data[1500]; + grub_uint8_t open_data[1500]; struct grub_net_buff nb; tftp_data_t data; grub_err_t err; + grub_uint8_t *nbd; + grub_net_network_level_address_t addr; data = grub_zalloc (sizeof (*data)); if (!data) @@ -265,39 +330,48 @@ tftp_open (struct grub_file *file, const char *filename) file->not_easily_seekable = 1; file->data = data; - data->sock = grub_net_udp_open (file->device->net->server, - TFTP_SERVER_PORT, tftp_receive, - file); - if (!data->sock) + + data->pq = grub_priority_queue_new (sizeof (struct grub_net_buff *), cmp); + if (!data->pq) return grub_errno; - err = grub_net_send_udp_packet (data->sock, &nb); + err = grub_net_resolve_address (file->device->net->server, &addr); if (err) { - grub_net_udp_close (data->sock); + destroy_pq (data); return err; } - /* Receive OACK packet. */ - for (i = 0; i < 3; i++) + data->sock = grub_net_udp_open (addr, + TFTP_SERVER_PORT, tftp_receive, + file); + if (!data->sock) { - grub_net_poll_cards (100); - if (grub_errno) - return grub_errno; - if (data->have_oack) - break; - /* Retry. */ + destroy_pq (data); + return grub_errno; + } + + /* Receive OACK packet. */ + nbd = nb.data; + for (i = 0; i < GRUB_NET_TRIES; i++) + { + nb.data = nbd; err = grub_net_send_udp_packet (data->sock, &nb); if (err) { grub_net_udp_close (data->sock); + destroy_pq (data); return err; } + grub_net_poll_cards (GRUB_NET_INTERVAL); + if (data->have_oack) + break; } if (!data->have_oack) { grub_net_udp_close (data->sock); + destroy_pq (data); return grub_error (GRUB_ERR_TIMEOUT, "Time out opening tftp."); } file->size = data->file_size; @@ -312,7 +386,7 @@ tftp_close (struct grub_file *file) if (data->sock) { - char nbdata[512]; + grub_uint8_t nbdata[512]; grub_err_t err; struct grub_net_buff nb_err; struct tftphdr *tftph; @@ -338,6 +412,7 @@ tftp_close (struct grub_file *file) grub_print_error (); grub_net_udp_close (data->sock); } + destroy_pq (data); grub_free (data); return GRUB_ERR_NONE; } diff --git a/grub-core/net/udp.c b/grub-core/net/udp.c index 47a67a967..8ca8ebb0a 100644 --- a/grub-core/net/udp.c +++ b/grub-core/net/udp.c @@ -1,96 +1,208 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + #include #include #include #include #include -grub_net_socket_t -grub_net_udp_open (char *server, +struct grub_net_udp_socket +{ + struct grub_net_udp_socket *next; + + enum { GRUB_NET_SOCKET_START, + GRUB_NET_SOCKET_ESTABLISHED, + GRUB_NET_SOCKET_CLOSED } status; + int in_port; + int out_port; + grub_err_t (*recv_hook) (grub_net_udp_socket_t sock, struct grub_net_buff *nb, + void *recv); + void *recv_hook_data; + grub_net_network_level_address_t out_nla; + grub_net_link_level_address_t ll_target_addr; + struct grub_net_network_level_interface *inf; +}; + +static struct grub_net_udp_socket *udp_sockets; + +#define FOR_UDP_SOCKETS(var) for (var = udp_sockets; var; var = var->next) + +static inline void +udp_socket_register (grub_net_udp_socket_t sock) +{ + grub_list_push (GRUB_AS_LIST_P (&udp_sockets), + GRUB_AS_LIST (sock)); +} + +void +grub_net_udp_close (grub_net_udp_socket_t sock) +{ + grub_list_remove (GRUB_AS_LIST_P (&udp_sockets), + GRUB_AS_LIST (sock)); + grub_free (sock); +} + +grub_net_udp_socket_t +grub_net_udp_open (grub_net_network_level_address_t addr, grub_uint16_t out_port, - grub_err_t (*recv_hook) (grub_net_socket_t sock, + grub_err_t (*recv_hook) (grub_net_udp_socket_t sock, struct grub_net_buff *nb, void *data), void *recv_hook_data) { grub_err_t err; - grub_net_network_level_address_t addr; struct grub_net_network_level_interface *inf; grub_net_network_level_address_t gateway; - grub_net_socket_t socket; + grub_net_udp_socket_t socket; static int in_port = 25300; + grub_net_link_level_address_t ll_target_addr; - err = grub_net_resolve_address (server, &addr); - if (err) - return NULL; + if (addr.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4 + && addr.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "not an IP address"); + return NULL; + } err = grub_net_route_address (addr, &gateway, &inf); if (err) return NULL; + err = grub_net_link_layer_resolve (inf, &gateway, &ll_target_addr); + if (err) + return NULL; + socket = grub_zalloc (sizeof (*socket)); if (socket == NULL) return NULL; - socket->x_out_port = out_port; - socket->x_inf = inf; - socket->x_out_nla = addr; - socket->x_in_port = in_port++; - socket->x_status = GRUB_NET_SOCKET_START; + socket->out_port = out_port; + socket->inf = inf; + socket->out_nla = addr; + socket->ll_target_addr = ll_target_addr; + socket->in_port = in_port++; + socket->status = GRUB_NET_SOCKET_START; socket->recv_hook = recv_hook; socket->recv_hook_data = recv_hook_data; - grub_net_socket_register (socket); + udp_socket_register (socket); return socket; } grub_err_t -grub_net_send_udp_packet (const grub_net_socket_t socket, +grub_net_send_udp_packet (const grub_net_udp_socket_t socket, struct grub_net_buff *nb) { struct udphdr *udph; grub_err_t err; + COMPILE_TIME_ASSERT (GRUB_NET_UDP_HEADER_SIZE == sizeof (*udph)); + err = grub_netbuff_push (nb, sizeof (*udph)); if (err) return err; udph = (struct udphdr *) nb->data; - udph->src = grub_cpu_to_be16 (socket->x_in_port); - udph->dst = grub_cpu_to_be16 (socket->x_out_port); + udph->src = grub_cpu_to_be16 (socket->in_port); + udph->dst = grub_cpu_to_be16 (socket->out_port); - /* No chechksum. */ udph->chksum = 0; udph->len = grub_cpu_to_be16 (nb->tail - nb->data); - return grub_net_send_ip_packet (socket->x_inf, &(socket->x_out_nla), nb); + udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP, + &socket->inf->address, + &socket->out_nla); + + return grub_net_send_ip_packet (socket->inf, &(socket->out_nla), + &(socket->ll_target_addr), nb, + GRUB_NET_IP_UDP); } grub_err_t -grub_net_recv_udp_packet (struct grub_net_buff * nb, - struct grub_net_network_level_interface * inf) +grub_net_recv_udp_packet (struct grub_net_buff *nb, + struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *source) { struct udphdr *udph; - grub_net_socket_t sock; + grub_net_udp_socket_t sock; grub_err_t err; - udph = (struct udphdr *) nb->data; - err = grub_netbuff_pull (nb, sizeof (*udph)); - if (err) - return err; - FOR_NET_SOCKETS (sock) + /* Ignore broadcast. */ + if (!inf) + { + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + udph = (struct udphdr *) nb->data; + if (nb->tail - nb->data < (grub_ssize_t) sizeof (*udph)) + { + grub_dprintf ("net", "UDP packet too short: %" PRIuGRUB_SIZE "\n", + nb->tail - nb->data); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + + FOR_UDP_SOCKETS (sock) { - if (grub_be_to_cpu16 (udph->dst) == sock->x_in_port - && inf == sock->x_inf && sock->recv_hook) + if (grub_be_to_cpu16 (udph->dst) == sock->in_port + && inf == sock->inf + && grub_net_addr_cmp (source, &sock->out_nla) == 0 + && (sock->status == GRUB_NET_SOCKET_START + || grub_be_to_cpu16 (udph->src) == sock->out_port)) { - if (sock->x_status == GRUB_NET_SOCKET_START) + if (udph->chksum) { - sock->x_out_port = grub_be_to_cpu16 (udph->src); - sock->x_status = GRUB_NET_SOCKET_ESTABLISHED; + grub_uint16_t chk, expected; + chk = udph->chksum; + udph->chksum = 0; + expected = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP, + &sock->out_nla, + &sock->inf->address); + if (expected != chk) + { + grub_dprintf ("net", "Invalid UDP checksum. " + "Expected %x, got %x\n", + grub_be_to_cpu16 (expected), + grub_be_to_cpu16 (chk)); + grub_netbuff_free (nb); + return GRUB_ERR_NONE; + } + udph->chksum = chk; } + if (sock->status == GRUB_NET_SOCKET_START) + { + sock->out_port = grub_be_to_cpu16 (udph->src); + sock->status = GRUB_NET_SOCKET_ESTABLISHED; + } + + err = grub_netbuff_pull (nb, sizeof (*udph)); + if (err) + return err; + /* App protocol remove its own reader. */ - sock->recv_hook (sock, nb, sock->recv_hook_data); + if (sock->recv_hook) + sock->recv_hook (sock, nb, sock->recv_hook_data); + else + grub_netbuff_free (nb); return GRUB_ERR_NONE; } } diff --git a/include/grub/efi/api.h b/include/grub/efi/api.h index 7df3602f1..b3ec6636b 100644 --- a/include/grub/efi/api.h +++ b/include/grub/efi/api.h @@ -1301,7 +1301,9 @@ struct grub_efi_simple_network void (*statistics) (void); void (*mcastiptomac) (void); void (*nvdata) (void); - void (*getstatus) (void); + grub_efi_status_t (*get_status) (struct grub_efi_simple_network *this, + grub_uint32_t *int_status, + void **txbuf); grub_efi_status_t (*transmit) (struct grub_efi_simple_network *this, grub_efi_uintn_t header_size, grub_efi_uintn_t buffer_size, diff --git a/include/grub/err.h b/include/grub/err.h index 40f39dd5d..3a75f5698 100644 --- a/include/grub/err.h +++ b/include/grub/err.h @@ -59,8 +59,14 @@ typedef enum GRUB_ERR_NET_BAD_ADDRESS, GRUB_ERR_NET_ROUTE_LOOP, GRUB_ERR_NET_NO_ROUTE, + GRUB_ERR_NET_NO_ANSWER, GRUB_ERR_WAIT, - GRUB_ERR_BUG + GRUB_ERR_BUG, + GRUB_ERR_NET_PORT_CLOSED, + GRUB_ERR_NET_INVALID_RESPONSE, + GRUB_ERR_NET_UNKNOWN_ERROR, + GRUB_ERR_NET_PACKET_TOO_BIG, + GRUB_ERR_NET_NO_DOMAIN } grub_err_t; diff --git a/include/grub/misc.h b/include/grub/misc.h index 46715f77c..e1b13eeba 100644 --- a/include/grub/misc.h +++ b/include/grub/misc.h @@ -370,6 +370,20 @@ void EXPORT_FUNC (__deregister_frame_info) (void); /* Inline functions. */ +static inline char * +grub_memchr (const void *p, int c, grub_size_t len) +{ + const char *s = p; + const char *e = s + len; + + for (; s < e; s++) + if (*s == c) + return (char *) s; + + return 0; +} + + static inline unsigned int grub_abs (int x) { diff --git a/include/grub/net.h b/include/grub/net.h index 2ec89e191..3913272eb 100644 --- a/include/grub/net.h +++ b/include/grub/net.h @@ -27,6 +27,19 @@ #include #include +enum + { + GRUB_NET_MAX_LINK_HEADER_SIZE = 64, + GRUB_NET_UDP_HEADER_SIZE = 8, + GRUB_NET_TCP_HEADER_SIZE = 20, + GRUB_NET_OUR_IPV4_HEADER_SIZE = 20, + GRUB_NET_OUR_IPV6_HEADER_SIZE = 40, + GRUB_NET_OUR_MAX_IP_HEADER_SIZE = 40, + GRUB_NET_TCP_RESERVE_SIZE = GRUB_NET_TCP_HEADER_SIZE + + GRUB_NET_OUR_IPV4_HEADER_SIZE + + GRUB_NET_MAX_LINK_HEADER_SIZE + }; + typedef enum grub_link_level_protocol_id { GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET @@ -64,8 +77,7 @@ struct grub_net_card_driver void (*close) (const struct grub_net_card *dev); grub_err_t (*send) (const struct grub_net_card *dev, struct grub_net_buff *buf); - grub_ssize_t (*recv) (const struct grub_net_card *dev, - struct grub_net_buff *buf); + struct grub_net_buff * (*recv) (const struct grub_net_card *dev); }; typedef struct grub_net_packet @@ -86,6 +98,16 @@ typedef struct grub_net_packets #include #endif +struct grub_net_slaac_mac_list +{ + struct grub_net_slaac_mac_list *next; + grub_net_link_level_address_t address; + int slaac_counter; + char *name; +}; + +struct grub_net_link_layer_entry; + struct grub_net_card { struct grub_net_card *next; @@ -97,6 +119,10 @@ struct grub_net_card int opened; unsigned idle_poll_delay_ms; grub_uint64_t last_poll; + grub_size_t mtu; + struct grub_net_slaac_mac_list *slaac_list; + grub_ssize_t new_ll_entry; + struct grub_net_link_layer_entry *link_layer_table; union { #ifdef GRUB_MACHINE_EFI @@ -116,7 +142,8 @@ struct grub_net_network_level_interface; typedef enum grub_network_level_protocol_id { GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV, - GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4 + GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4, + GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6 } grub_network_level_protocol_id_t; typedef struct grub_net_network_level_address @@ -125,6 +152,7 @@ typedef struct grub_net_network_level_address union { grub_uint32_t ipv4; + grub_uint64_t ipv6[2]; }; } grub_net_network_level_address_t; @@ -137,6 +165,10 @@ typedef struct grub_net_network_level_netaddress grub_uint32_t base; int masksize; } ipv4; + struct { + grub_uint64_t base[2]; + int masksize; + } ipv6; }; } grub_net_network_level_netaddress_t; @@ -193,43 +225,10 @@ struct grub_net_app_protocol int (*hook) (const char *filename, const struct grub_dirhook_info *info)); grub_err_t (*open) (struct grub_file *file, const char *filename); + grub_err_t (*seek) (struct grub_file *file, grub_off_t off); grub_err_t (*close) (struct grub_file *file); }; -struct grub_net_socket -{ - struct grub_net_socket *next; - - enum { GRUB_NET_SOCKET_START, - GRUB_NET_SOCKET_ESTABLISHED, - GRUB_NET_SOCKET_CLOSED } x_status; - int x_in_port; - int x_out_port; - grub_err_t (*recv_hook) (grub_net_socket_t sock, struct grub_net_buff *nb, - void *recv); - void *recv_hook_data; - grub_net_network_level_address_t x_out_nla; - struct grub_net_network_level_interface *x_inf; -}; - -extern struct grub_net_socket *grub_net_sockets; - -static inline void -grub_net_socket_register (grub_net_socket_t sock) -{ - grub_list_push (GRUB_AS_LIST_P (&grub_net_sockets), - GRUB_AS_LIST (sock)); -} - -static inline void -grub_net_socket_unregister (grub_net_socket_t sock) -{ - grub_list_remove (GRUB_AS_LIST_P (&grub_net_sockets), - GRUB_AS_LIST (sock)); -} - -#define FOR_NET_SOCKETS(var) for (var = grub_net_sockets; var; var = var->next) - typedef struct grub_net { char *server; @@ -297,8 +296,8 @@ grub_net_session_recv (struct grub_net_session *session, void *buf, struct grub_net_network_level_interface * grub_net_add_addr (const char *name, struct grub_net_card *card, - grub_net_network_level_address_t addr, - grub_net_link_level_address_t hwaddress, + const grub_net_network_level_address_t *addr, + const grub_net_link_level_address_t *hwaddress, grub_net_interface_flags_t flags); extern struct grub_net_network_level_interface *grub_net_network_level_interfaces; @@ -338,7 +337,7 @@ void grub_net_card_unregister (struct grub_net_card *card); #define FOR_NET_CARDS(var) for (var = grub_net_cards; var; var = var->next) -#define FOR_NET_CARDS_SAFE(var, next) for (var = grub_net_cards, next = var->next; var; var = next, next = var->next) +#define FOR_NET_CARDS_SAFE(var, next) for (var = grub_net_cards, next = (var ? var->next : 0); var; var = next, next = (var ? var->next : 0)) struct grub_net_session * @@ -397,6 +396,18 @@ struct grub_net_bootp_packet #define GRUB_NET_BOOTP_RFC1048_MAGIC_2 0x53 #define GRUB_NET_BOOTP_RFC1048_MAGIC_3 0x63 +enum + { + GRUB_NET_BOOTP_PAD = 0x00, + GRUB_NET_BOOTP_ROUTER = 0x03, + GRUB_NET_BOOTP_DNS = 0x06, + GRUB_NET_BOOTP_HOSTNAME = 0x0c, + GRUB_NET_BOOTP_DOMAIN = 0x0f, + GRUB_NET_BOOTP_ROOT_PATH = 0x11, + GRUB_NET_BOOTP_EXTENSIONS_PATH = 0x12, + GRUB_NET_BOOTP_END = 0xff + }; + struct grub_net_network_level_interface * grub_net_configure_by_dhcp_ack (const char *name, struct grub_net_card *card, @@ -412,19 +423,35 @@ grub_net_process_dhcp (struct grub_net_buff *nb, int grub_net_hwaddr_cmp (const grub_net_link_level_address_t *a, const grub_net_link_level_address_t *b); +int +grub_net_addr_cmp (const grub_net_network_level_address_t *a, + const grub_net_network_level_address_t *b); /* - Currently suppoerted adresses: + Currently supported adresses: IPv4: XXX.XXX.XXX.XXX + IPv6: XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX */ -#define GRUB_NET_MAX_STR_ADDR_LEN sizeof ("XXX.XXX.XXX.XXX") +#define GRUB_NET_MAX_STR_ADDR_LEN sizeof ("XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") + +/* + Currently suppoerted adresses: + ethernet: XX:XX:XX:XX:XX:XX + */ + +#define GRUB_NET_MAX_STR_HWADDR_LEN (sizeof ("XX:XX:XX:XX:XX:XX")) void grub_net_addr_to_str (const grub_net_network_level_address_t *target, char *buf); +void +grub_net_hwaddr_to_str (const grub_net_link_level_address_t *addr, char *str); -#define FOR_NET_NETWORK_LEVEL_INTERFACES_SAFE(var,next) for (var = grub_net_network_level_interfaces, next = var->next; var; var = next, next = var->next) +extern struct grub_net_network_level_interface *grub_net_network_level_interfaces; +#define FOR_NET_NETWORK_LEVEL_INTERFACES(var) for (var = grub_net_network_level_interfaces; var; var = var->next) + +#define FOR_NET_NETWORK_LEVEL_INTERFACES_SAFE(var,next) for (var = grub_net_network_level_interfaces, next = (var ? var->next : 0); var; var = next, next = (var ? var->next : 0)) void grub_net_poll_cards (unsigned time); @@ -432,6 +459,9 @@ grub_net_poll_cards (unsigned time); void grub_bootp_init (void); void grub_bootp_fini (void); +void grub_dns_init (void); +void grub_dns_fini (void); + static inline void grub_net_network_level_interface_unregister (struct grub_net_network_level_interface *inter) { @@ -443,6 +473,37 @@ grub_net_network_level_interface_unregister (struct grub_net_network_level_inter inter->prev = 0; } +void +grub_net_tcp_retransmit (void); + +void +grub_net_link_layer_add_address (struct grub_net_card *card, + const grub_net_network_level_address_t *nl, + const grub_net_link_level_address_t *ll, + int override); +int +grub_net_link_layer_resolve_check (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *proto_addr); +grub_err_t +grub_net_link_layer_resolve (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *proto_addr, + grub_net_link_level_address_t *hw_addr); +grub_err_t +grub_net_dns_lookup (const char *name, + const struct grub_net_network_level_address *servers, + grub_size_t n_servers, + grub_size_t *naddresses, + struct grub_net_network_level_address **addresses, + int cache); +grub_err_t +grub_net_add_dns_server (const struct grub_net_network_level_address *s); +void +grub_net_remove_dns_server (const struct grub_net_network_level_address *s); + + extern char *grub_net_default_server; +#define GRUB_NET_TRIES 40 +#define GRUB_NET_INTERVAL 400 + #endif /* ! GRUB_NET_HEADER */ diff --git a/include/grub/net/arp.h b/include/grub/net/arp.h index c60ea333f..bb1703622 100644 --- a/include/grub/net/arp.h +++ b/include/grub/net/arp.h @@ -1,36 +1,31 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + #ifndef GRUB_NET_ARP_HEADER #define GRUB_NET_ARP_HEADER 1 #include #include -enum -{ -/* IANA ARP constant to define hardware type as ethernet. */ - GRUB_NET_ARPHRD_ETHERNET = 1 -}; +extern grub_err_t grub_net_arp_receive (struct grub_net_buff *nb, + struct grub_net_card *card); -/* ARP header operation codes */ -#define ARP_REQUEST 1 -#define ARP_REPLY 2 - -struct arp_entry { - int avail; - grub_net_network_level_address_t nl_address; - grub_net_link_level_address_t ll_address; -}; - -struct arphdr { - grub_uint16_t hrd; - grub_uint16_t pro; - grub_uint8_t hln; - grub_uint8_t pln; - grub_uint16_t op; -} __attribute__ ((packed)); - -extern grub_err_t grub_net_arp_receive(struct grub_net_buff *nb); - -extern grub_err_t grub_net_arp_resolve(struct grub_net_network_level_interface *inf, - const grub_net_network_level_address_t *addr, - grub_net_link_level_address_t *hw_addr); +grub_err_t +grub_net_arp_send_request (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *proto_addr); #endif diff --git a/include/grub/net/ethernet.h b/include/grub/net/ethernet.h index a68aafd96..23a935e98 100644 --- a/include/grub/net/ethernet.h +++ b/include/grub/net/ethernet.h @@ -22,19 +22,20 @@ #include /* IANA Ethertype */ -enum -{ - GRUB_NET_ETHERTYPE_IP = 0x0800, - GRUB_NET_ETHERTYPE_ARP = 0x0806 -}; +typedef enum + { + GRUB_NET_ETHERTYPE_IP = 0x0800, + GRUB_NET_ETHERTYPE_ARP = 0x0806, + GRUB_NET_ETHERTYPE_IP6 = 0x86DD, + } grub_net_ethertype_t; grub_err_t send_ethernet_packet (struct grub_net_network_level_interface *inf, struct grub_net_buff *nb, grub_net_link_level_address_t target_addr, - grub_uint16_t ethertype); + grub_net_ethertype_t ethertype); grub_err_t grub_net_recv_ethernet_packet (struct grub_net_buff *nb, - const struct grub_net_card *card); + struct grub_net_card *card); #endif diff --git a/include/grub/net/ip.h b/include/grub/net/ip.h index 9bed1e19c..7a8e61479 100644 --- a/include/grub/net/ip.h +++ b/include/grub/net/ip.h @@ -21,22 +21,75 @@ #include #include -enum +typedef enum grub_net_ip_protocol { - IP_UDP = 0x11 /* UDP protocol */ - }; -#define IP_BROADCAST 0xFFFFFFFF + GRUB_NET_IP_ICMP = 1, + GRUB_NET_IP_TCP = 6, + GRUB_NET_IP_UDP = 17, + GRUB_NET_IP_ICMPV6 = 58 + } grub_net_ip_protocol_t; +#define GRUB_NET_IP_BROADCAST 0xFFFFFFFF -grub_uint16_t grub_net_ip_chksum(void *ipv, int len); +static inline grub_uint64_t +grub_net_ipv6_get_id (const grub_net_link_level_address_t *addr) +{ + return grub_cpu_to_be64 (((grub_uint64_t) (addr->mac[0] ^ 2) << 56) + | ((grub_uint64_t) addr->mac[1] << 48) + | ((grub_uint64_t) addr->mac[2] << 40) + | 0xfffe000000ULL + | ((grub_uint64_t) addr->mac[3] << 16) + | ((grub_uint64_t) addr->mac[4] << 8) + | ((grub_uint64_t) addr->mac[5])); +} + +grub_uint16_t grub_net_ip_chksum(void *ipv, grub_size_t len); grub_err_t grub_net_recv_ip_packets (struct grub_net_buff *nb, - const struct grub_net_card *card, - const grub_net_link_level_address_t *hwaddress); + struct grub_net_card *card, + const grub_net_link_level_address_t *hwaddress, + const grub_net_link_level_address_t *src_hwaddress); grub_err_t grub_net_send_ip_packet (struct grub_net_network_level_interface *inf, const grub_net_network_level_address_t *target, - struct grub_net_buff *nb); + const grub_net_link_level_address_t *ll_target_addr, + struct grub_net_buff *nb, + grub_net_ip_protocol_t proto); + +grub_err_t +grub_net_recv_icmp_packet (struct grub_net_buff *nb, + struct grub_net_network_level_interface *inf, + const grub_net_link_level_address_t *ll_src, + const grub_net_network_level_address_t *src); +grub_err_t +grub_net_recv_icmp6_packet (struct grub_net_buff *nb, + struct grub_net_card *card, + struct grub_net_network_level_interface *inf, + const grub_net_link_level_address_t *ll_src, + const grub_net_network_level_address_t *source, + const grub_net_network_level_address_t *dest, + grub_uint8_t ttl); +grub_err_t +grub_net_recv_udp_packet (struct grub_net_buff *nb, + struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *src); +grub_err_t +grub_net_recv_tcp_packet (struct grub_net_buff *nb, + struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *source); + +grub_uint16_t +grub_net_ip_transport_checksum (struct grub_net_buff *nb, + grub_uint16_t proto, + const grub_net_network_level_address_t *src, + const grub_net_network_level_address_t *dst); + +struct grub_net_network_level_interface * +grub_net_ipv6_get_link_local (struct grub_net_card *card, + const grub_net_link_level_address_t *hwaddr); +grub_err_t +grub_net_icmp6_send_request (struct grub_net_network_level_interface *inf, + const grub_net_network_level_address_t *proto_addr); #endif diff --git a/include/grub/net/netbuff.h b/include/grub/net/netbuff.h index 245e813c3..c745d51d7 100644 --- a/include/grub/net/netbuff.h +++ b/include/grub/net/netbuff.h @@ -8,23 +8,23 @@ struct grub_net_buff { - /*Pointer to the start of the buffer*/ - char *head; - /*Pointer to the data */ - char *data; - /*Pointer to the tail */ - char *tail; - /*Pointer to the end of the buffer*/ - char *end; + /* Pointer to the start of the buffer. */ + grub_uint8_t *head; + /* Pointer to the data. */ + grub_uint8_t *data; + /* Pointer to the tail. */ + grub_uint8_t *tail; + /* Pointer to the end of the buffer. */ + grub_uint8_t *end; }; -grub_err_t grub_netbuff_put (struct grub_net_buff *net_buff ,grub_size_t len); -grub_err_t grub_netbuff_unput (struct grub_net_buff *net_buff ,grub_size_t len); -grub_err_t grub_netbuff_push (struct grub_net_buff *net_buff ,grub_size_t len); -grub_err_t grub_netbuff_pull (struct grub_net_buff *net_buff ,grub_size_t len); -grub_err_t grub_netbuff_reserve (struct grub_net_buff *net_buff ,grub_size_t len); +grub_err_t grub_netbuff_put (struct grub_net_buff *net_buff, grub_size_t len); +grub_err_t grub_netbuff_unput (struct grub_net_buff *net_buff, grub_size_t len); +grub_err_t grub_netbuff_push (struct grub_net_buff *net_buff, grub_size_t len); +grub_err_t grub_netbuff_pull (struct grub_net_buff *net_buff, grub_size_t len); +grub_err_t grub_netbuff_reserve (struct grub_net_buff *net_buff, grub_size_t len); grub_err_t grub_netbuff_clear (struct grub_net_buff *net_buff); -struct grub_net_buff * grub_netbuff_alloc ( grub_size_t len ); +struct grub_net_buff * grub_netbuff_alloc (grub_size_t len); grub_err_t grub_netbuff_free (struct grub_net_buff *net_buff); #endif diff --git a/include/grub/net/tcp.h b/include/grub/net/tcp.h new file mode 100644 index 000000000..62bfd2eba --- /dev/null +++ b/include/grub/net/tcp.h @@ -0,0 +1,79 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_NET_TCP_HEADER +#define GRUB_NET_TCP_HEADER 1 +#include +#include + +struct grub_net_tcp_socket; +typedef struct grub_net_tcp_socket *grub_net_tcp_socket_t; + +struct grub_net_tcp_listen; +typedef struct grub_net_tcp_listen *grub_net_tcp_listen_t; + +grub_net_tcp_socket_t +grub_net_tcp_open (char *server, + grub_uint16_t out_port, + grub_err_t (*recv_hook) (grub_net_tcp_socket_t sock, + struct grub_net_buff *nb, + void *data), + void (*error_hook) (grub_net_tcp_socket_t sock, + void *data), + void (*fin_hook) (grub_net_tcp_socket_t sock, + void *data), + void *hook_data); + +grub_net_tcp_listen_t +grub_net_tcp_listen (grub_uint16_t port, + const struct grub_net_network_level_interface *inf, + grub_err_t (*listen_hook) (grub_net_tcp_listen_t listen, + grub_net_tcp_socket_t sock, + void *data), + void *hook_data); + +void +grub_net_tcp_stop_listen (grub_net_tcp_listen_t listen); + +grub_err_t +grub_net_send_tcp_packet (const grub_net_tcp_socket_t socket, + struct grub_net_buff *nb, + int push); + +enum + { + GRUB_NET_TCP_CONTINUE_RECEIVING, + GRUB_NET_TCP_DISCARD, + GRUB_NET_TCP_ABORT + }; + +void +grub_net_tcp_close (grub_net_tcp_socket_t sock, int discard_received); + +grub_err_t +grub_net_tcp_accept (grub_net_tcp_socket_t sock, + grub_err_t (*recv_hook) (grub_net_tcp_socket_t sock, + struct grub_net_buff *nb, + void *data), + void (*error_hook) (grub_net_tcp_socket_t sock, + void *data), + void (*fin_hook) (grub_net_tcp_socket_t sock, + void *data), + void *hook_data); + +#endif diff --git a/include/grub/net/udp.h b/include/grub/net/udp.h index 5aacf8abb..1a7efa777 100644 --- a/include/grub/net/udp.h +++ b/include/grub/net/udp.h @@ -1,3 +1,21 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + #ifndef GRUB_NET_UDP_HEADER #define GRUB_NET_UDP_HEADER 1 #include @@ -11,28 +29,23 @@ struct udphdr grub_uint16_t chksum; } __attribute__ ((packed)); +struct grub_net_udp_socket; +typedef struct grub_net_udp_socket *grub_net_udp_socket_t; -grub_net_socket_t -grub_net_udp_open (char *server, +grub_net_udp_socket_t +grub_net_udp_open (grub_net_network_level_address_t addr, grub_uint16_t out_port, - grub_err_t (*recv_hook) (grub_net_socket_t sock, + grub_err_t (*recv_hook) (grub_net_udp_socket_t sock, struct grub_net_buff *nb, void *data), void *recv_hook_data); -static inline void -grub_net_udp_close (grub_net_socket_t sock) -{ - grub_net_socket_unregister (sock); - grub_free (sock); -} +void +grub_net_udp_close (grub_net_udp_socket_t sock); grub_err_t -grub_net_send_udp_packet (const grub_net_socket_t socket, struct grub_net_buff *nb); - -grub_err_t -grub_net_recv_udp_packet (struct grub_net_buff *nb, - struct grub_net_network_level_interface *inf); +grub_net_send_udp_packet (const grub_net_udp_socket_t socket, + struct grub_net_buff *nb); #endif diff --git a/include/grub/priority_queue.h b/include/grub/priority_queue.h new file mode 100644 index 000000000..a5d98c8c9 --- /dev/null +++ b/include/grub/priority_queue.h @@ -0,0 +1,36 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_PRIORITY_QUEUE_HEADER +#define GRUB_PRIORITY_QUEUE_HEADER 1 + +#include +#include + +struct grub_priority_queue; +typedef struct grub_priority_queue *grub_priority_queue_t; +typedef int (*grub_comparator_t) (const void *a, const void *b); + +grub_priority_queue_t grub_priority_queue_new (grub_size_t elsize, + grub_comparator_t cmp); +void grub_priority_queue_destroy (grub_priority_queue_t pq); +void *grub_priority_queue_top (grub_priority_queue_t pq); +void grub_priority_queue_pop (grub_priority_queue_t pq); +grub_err_t grub_priority_queue_push (grub_priority_queue_t pq, const void *el); + +#endif diff --git a/include/grub/types.h b/include/grub/types.h index f8059c1ab..89c1697d2 100644 --- a/include/grub/types.h +++ b/include/grub/types.h @@ -98,13 +98,15 @@ typedef grub_uint64_t grub_size_t; typedef grub_int64_t grub_ssize_t; # if GRUB_CPU_SIZEOF_LONG == 8 -# define PRIxGRUB_SIZE "lx" -# define PRIxGRUB_ADDR "lx" -# define PRIuGRUB_SIZE "lu" +# define PRIxGRUB_SIZE "lx" +# define PRIxGRUB_ADDR "lx" +# define PRIuGRUB_SIZE "lu" +# define PRIdGRUB_SSIZE "ld" # else -# define PRIxGRUB_SIZE "llx" -# define PRIxGRUB_ADDR "llx" -# define PRIuGRUB_SIZE "llu" +# define PRIxGRUB_SIZE "llx" +# define PRIxGRUB_ADDR "llx" +# define PRIuGRUB_SIZE "llu" +# define PRIdGRUB_SSIZE "lld" # endif #else typedef grub_uint32_t grub_addr_t; @@ -114,6 +116,7 @@ typedef grub_int32_t grub_ssize_t; # define PRIxGRUB_SIZE "x" # define PRIxGRUB_ADDR "x" # define PRIuGRUB_SIZE "u" +# define PRIdGRUB_SSIZE "d" #endif #define GRUB_UCHAR_MAX 0xFF @@ -149,6 +152,18 @@ typedef grub_uint64_t grub_disk_addr_t; #define grub_swap_bytes16_compile_time(x) ((((x) & 0xff) << 8) | (((x) & 0xff00) >> 8)) #define grub_swap_bytes32_compile_time(x) ((((x) & 0xff) << 24) | (((x) & 0xff00) << 8) | (((x) & 0xff0000) >> 8) | (((x) & 0xff000000UL) >> 24)) +#define grub_swap_bytes64_compile_time(x) \ +({ \ + grub_uint64_t _x = (x); \ + (grub_uint64_t) ((_x << 56) \ + | ((_x & (grub_uint64_t) 0xFF00ULL) << 40) \ + | ((_x & (grub_uint64_t) 0xFF0000ULL) << 24) \ + | ((_x & (grub_uint64_t) 0xFF000000ULL) << 8) \ + | ((_x & (grub_uint64_t) 0xFF00000000ULL) >> 8) \ + | ((_x & (grub_uint64_t) 0xFF0000000000ULL) >> 24) \ + | ((_x & (grub_uint64_t) 0xFF000000000000ULL) >> 40) \ + | (_x >> 56)); \ +}) #if defined(__GNUC__) && (__GNUC__ > 3) && (__GNUC__ > 4 || __GNUC_MINOR__ >= 3) static inline grub_uint32_t grub_swap_bytes32(grub_uint32_t x) @@ -197,6 +212,10 @@ static inline grub_uint64_t grub_swap_bytes64(grub_uint64_t x) # define grub_be_to_cpu16(x) ((grub_uint16_t) (x)) # define grub_be_to_cpu32(x) ((grub_uint32_t) (x)) # define grub_be_to_cpu64(x) ((grub_uint64_t) (x)) +# define grub_cpu_to_be16_compile_time(x) ((grub_uint16_t) (x)) +# define grub_cpu_to_be32_compile_time(x) ((grub_uint32_t) (x)) +# define grub_cpu_to_be64_compile_time(x) ((grub_uint64_t) (x)) +# define grub_be_to_cpu64_compile_time(x) ((grub_uint64_t) (x)) # define grub_cpu_to_le32_compile_time(x) grub_swap_bytes32_compile_time(x) # define grub_cpu_to_le16_compile_time(x) grub_swap_bytes16_compile_time(x) #else /* ! WORDS_BIGENDIAN */ @@ -212,8 +231,13 @@ static inline grub_uint64_t grub_swap_bytes64(grub_uint64_t x) # define grub_be_to_cpu16(x) grub_swap_bytes16(x) # define grub_be_to_cpu32(x) grub_swap_bytes32(x) # define grub_be_to_cpu64(x) grub_swap_bytes64(x) +# define grub_cpu_to_be16_compile_time(x) grub_swap_bytes16_compile_time(x) +# define grub_cpu_to_be32_compile_time(x) grub_swap_bytes32_compile_time(x) +# define grub_cpu_to_be64_compile_time(x) grub_swap_bytes64_compile_time(x) +# define grub_be_to_cpu64_compile_time(x) grub_swap_bytes64_compile_time(x) # define grub_cpu_to_le16_compile_time(x) ((grub_uint16_t) (x)) # define grub_cpu_to_le32_compile_time(x) ((grub_uint32_t) (x)) + #endif /* ! WORDS_BIGENDIAN */ static inline grub_uint16_t grub_get_unaligned16 (const void *ptr)