summaryrefslogtreecommitdiffstats
path: root/grub-core/net
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/net')
-rw-r--r--grub-core/net/arp.c189
-rw-r--r--grub-core/net/bootp.c925
-rw-r--r--grub-core/net/dns.c785
-rw-r--r--grub-core/net/drivers/efi/efinet.c404
-rw-r--r--grub-core/net/drivers/emu/emunet.c116
-rw-r--r--grub-core/net/drivers/i386/pc/pxe.c419
-rw-r--r--grub-core/net/drivers/ieee1275/ofnet.c565
-rw-r--r--grub-core/net/drivers/uboot/ubootnet.c161
-rw-r--r--grub-core/net/ethernet.c171
-rw-r--r--grub-core/net/http.c562
-rw-r--r--grub-core/net/icmp.c112
-rw-r--r--grub-core/net/icmp6.c679
-rw-r--r--grub-core/net/ip.c739
-rw-r--r--grub-core/net/net.c1952
-rw-r--r--grub-core/net/netbuff.c133
-rw-r--r--grub-core/net/tcp.c1020
-rw-r--r--grub-core/net/tftp.c479
-rw-r--r--grub-core/net/udp.c211
18 files changed, 9622 insertions, 0 deletions
diff --git a/grub-core/net/arp.c b/grub-core/net/arp.c
new file mode 100644
index 0000000..54306e3
--- /dev/null
+++ b/grub-core/net/arp.c
@@ -0,0 +1,189 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net/arp.h>
+#include <grub/net/netbuff.h>
+#include <grub/mm.h>
+#include <grub/net.h>
+#include <grub/net/ethernet.h>
+#include <grub/net/ip.h>
+#include <grub/time.h>
+
+/* ARP header operation codes */
+enum
+ {
+ ARP_REQUEST = 1,
+ ARP_REPLY = 2
+ };
+
+enum
+ {
+ /* IANA ARP constant to define hardware type as ethernet. */
+ GRUB_NET_ARPHRD_ETHERNET = 1
+ };
+
+struct arppkt {
+ grub_uint16_t hrd;
+ grub_uint16_t pro;
+ grub_uint8_t hln;
+ grub_uint8_t pln;
+ grub_uint16_t op;
+ grub_uint8_t sender_mac[6];
+ grub_uint32_t sender_ip;
+ grub_uint8_t recv_mac[6];
+ grub_uint32_t recv_ip;
+} GRUB_PACKED;
+
+static int have_pending;
+static grub_uint32_t pending_req;
+
+grub_err_t
+grub_net_arp_send_request (struct grub_net_network_level_interface *inf,
+ const grub_net_network_level_address_t *proto_addr)
+{
+ struct grub_net_buff nb;
+ struct arppkt *arp_packet;
+ grub_net_link_level_address_t target_mac_addr;
+ grub_err_t err;
+ int i;
+ grub_uint8_t *nbd;
+ grub_uint8_t arp_data[128];
+
+ if (proto_addr->type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4)
+ return grub_error (GRUB_ERR_BUG, "unsupported address family");
+
+ /* 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_packet));
+ if (err)
+ return err;
+
+ arp_packet = (struct arppkt *) nb.data;
+ arp_packet->hrd = grub_cpu_to_be16_compile_time (GRUB_NET_ARPHRD_ETHERNET);
+ arp_packet->hln = 6;
+ arp_packet->pro = grub_cpu_to_be16_compile_time (GRUB_NET_ETHERTYPE_IP);
+ arp_packet->pln = 4;
+ arp_packet->op = grub_cpu_to_be16_compile_time (ARP_REQUEST);
+ /* Sender hardware address. */
+ grub_memcpy (arp_packet->sender_mac, &inf->hwaddress.mac, 6);
+ arp_packet->sender_ip = inf->address.ipv4;
+ grub_memset (arp_packet->recv_mac, 0, 6);
+ arp_packet->recv_ip = proto_addr->ipv4;
+ /* Target protocol address */
+ grub_memset (&target_mac_addr.mac, 0xff, 6);
+
+ nbd = nb.data;
+ send_ethernet_packet (inf, &nb, target_mac_addr, GRUB_NET_ETHERTYPE_ARP);
+ for (i = 0; i < GRUB_NET_TRIES; i++)
+ {
+ if (grub_net_link_layer_resolve_check (inf, proto_addr))
+ return GRUB_ERR_NONE;
+ pending_req = proto_addr->ipv4;
+ have_pending = 0;
+ grub_net_poll_cards (GRUB_NET_INTERVAL + (i * GRUB_NET_INTERVAL_ADDITION),
+ &have_pending);
+ if (grub_net_link_layer_resolve_check (inf, proto_addr))
+ return GRUB_ERR_NONE;
+ nb.data = nbd;
+ send_ethernet_packet (inf, &nb, target_mac_addr, GRUB_NET_ETHERTYPE_ARP);
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_net_arp_receive (struct grub_net_buff *nb, struct grub_net_card *card,
+ grub_uint16_t *vlantag)
+{
+ struct arppkt *arp_packet = (struct arppkt *) nb->data;
+ grub_net_network_level_address_t sender_addr, target_addr;
+ grub_net_link_level_address_t sender_mac_addr;
+ struct grub_net_network_level_interface *inf;
+
+ if (arp_packet->pro != grub_cpu_to_be16_compile_time (GRUB_NET_ETHERTYPE_IP)
+ || arp_packet->pln != 4 || arp_packet->hln != 6
+ || nb->tail - nb->data < (int) sizeof (*arp_packet))
+ return GRUB_ERR_NONE;
+
+ sender_addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ target_addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ sender_addr.ipv4 = arp_packet->sender_ip;
+ target_addr.ipv4 = arp_packet->recv_ip;
+ if (arp_packet->sender_ip == pending_req)
+ have_pending = 1;
+
+ sender_mac_addr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
+ grub_memcpy (sender_mac_addr.mac, arp_packet->sender_mac,
+ sizeof (sender_mac_addr.mac));
+ grub_net_link_layer_add_address (card, &sender_addr, &sender_mac_addr, 1);
+
+ FOR_NET_NETWORK_LEVEL_INTERFACES (inf)
+ {
+ /* Verify vlantag id */
+ if (inf->card == card && inf->vlantag != *vlantag)
+ {
+ grub_dprintf ("net", "invalid vlantag! %x != %x\n",
+ inf->vlantag, *vlantag);
+ break;
+ }
+
+ /* Am I the protocol address target? */
+ if (grub_net_addr_cmp (&inf->address, &target_addr) == 0
+ && arp_packet->op == grub_cpu_to_be16_compile_time (ARP_REQUEST))
+ {
+ grub_net_link_level_address_t target;
+ struct grub_net_buff nb_reply;
+ struct arppkt *arp_reply;
+ grub_uint8_t arp_data[128];
+ grub_err_t err;
+
+ nb_reply.head = arp_data;
+ nb_reply.end = arp_data + sizeof (arp_data);
+ grub_netbuff_clear (&nb_reply);
+ grub_netbuff_reserve (&nb_reply, 128);
+
+ err = grub_netbuff_push (&nb_reply, sizeof (*arp_packet));
+ if (err)
+ return err;
+
+ arp_reply = (struct arppkt *) nb_reply.data;
+
+ arp_reply->hrd = grub_cpu_to_be16_compile_time (GRUB_NET_ARPHRD_ETHERNET);
+ arp_reply->pro = grub_cpu_to_be16_compile_time (GRUB_NET_ETHERTYPE_IP);
+ arp_reply->pln = 4;
+ arp_reply->hln = 6;
+ arp_reply->op = grub_cpu_to_be16_compile_time (ARP_REPLY);
+ arp_reply->sender_ip = arp_packet->recv_ip;
+ arp_reply->recv_ip = arp_packet->sender_ip;
+ arp_reply->hln = 6;
+
+ target.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
+ grub_memcpy (target.mac, arp_packet->sender_mac, 6);
+ grub_memcpy (arp_reply->sender_mac, inf->hwaddress.mac, 6);
+ grub_memcpy (arp_reply->recv_mac, arp_packet->sender_mac, 6);
+
+ /* Change operation to REPLY and send packet */
+ send_ethernet_packet (inf, &nb_reply, target, GRUB_NET_ETHERTYPE_ARP);
+ }
+ }
+ return GRUB_ERR_NONE;
+}
diff --git a/grub-core/net/bootp.c b/grub-core/net/bootp.c
new file mode 100644
index 0000000..6fb5627
--- /dev/null
+++ b/grub-core/net/bootp.c
@@ -0,0 +1,925 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net.h>
+#include <grub/env.h>
+#include <grub/i18n.h>
+#include <grub/command.h>
+#include <grub/net/ip.h>
+#include <grub/net/netbuff.h>
+#include <grub/net/udp.h>
+#include <grub/datetime.h>
+
+struct grub_dhcp_discover_options
+{
+ grub_uint8_t magic[4];
+ struct
+ {
+ grub_uint8_t code;
+ grub_uint8_t len;
+ grub_uint8_t data;
+ } GRUB_PACKED message_type;
+ grub_uint8_t end;
+} GRUB_PACKED;
+
+struct grub_dhcp_request_options
+{
+ grub_uint8_t magic[4];
+ struct
+ {
+ grub_uint8_t code;
+ grub_uint8_t len;
+ grub_uint8_t data;
+ } GRUB_PACKED message_type;
+ struct
+ {
+ grub_uint8_t type;
+ grub_uint8_t len;
+ grub_uint32_t data;
+ } GRUB_PACKED server_identifier;
+ struct
+ {
+ grub_uint8_t type;
+ grub_uint8_t len;
+ grub_uint32_t data;
+ } GRUB_PACKED requested_ip;
+ struct
+ {
+ grub_uint8_t type;
+ grub_uint8_t len;
+ grub_uint8_t data[7];
+ } GRUB_PACKED parameter_request;
+ grub_uint8_t end;
+} GRUB_PACKED;
+
+enum
+{
+ GRUB_DHCP_OPT_OVERLOAD_FILE = 1,
+ GRUB_DHCP_OPT_OVERLOAD_SNAME = 2,
+};
+enum
+{
+ GRUB_DHCP_MESSAGE_UNKNOWN,
+ GRUB_DHCP_MESSAGE_DISCOVER,
+ GRUB_DHCP_MESSAGE_OFFER,
+ GRUB_DHCP_MESSAGE_REQUEST,
+ GRUB_DHCP_MESSAGE_DECLINE,
+ GRUB_DHCP_MESSAGE_ACK,
+ GRUB_DHCP_MESSAGE_NAK,
+ GRUB_DHCP_MESSAGE_RELEASE,
+ GRUB_DHCP_MESSAGE_INFORM,
+};
+
+#define GRUB_BOOTP_MAX_OPTIONS_SIZE 64
+
+/* Max timeout when waiting for BOOTP/DHCP reply */
+#define GRUB_DHCP_MAX_PACKET_TIMEOUT 32
+
+#define GRUB_BOOTP_MAX_OPTIONS_SIZE 64
+
+/* Max timeout when waiting for BOOTP/DHCP reply */
+#define GRUB_DHCP_MAX_PACKET_TIMEOUT 32
+
+static char
+hexdigit (grub_uint8_t val)
+{
+ if (val < 10)
+ return val + '0';
+ return val + 'a' - 10;
+}
+
+static const void *
+find_dhcp_option (const struct grub_net_bootp_packet *bp, grub_size_t size,
+ grub_uint8_t opt_code, grub_uint8_t *opt_len)
+{
+ const grub_uint8_t *ptr;
+ grub_uint8_t overload = 0;
+ int end = 0;
+ grub_size_t i;
+
+ if (opt_len)
+ *opt_len = 0;
+
+ /* Is the packet big enough to hold at least the magic cookie? */
+ if (size < sizeof (*bp) + sizeof (grub_uint32_t))
+ return NULL;
+
+ /*
+ * Pointer arithmetic to point behind the common stub packet, where
+ * the options start.
+ */
+ ptr = (grub_uint8_t *) (bp + 1);
+
+ if (ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
+ || ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1
+ || ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2
+ || ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3)
+ return NULL;
+
+ size -= sizeof (*bp);
+ i = sizeof (grub_uint32_t);
+
+again:
+ while (i < size)
+ {
+ grub_uint8_t tagtype;
+ grub_uint8_t taglength;
+
+ tagtype = ptr[i++];
+
+ /* Pad tag. */
+ if (tagtype == GRUB_NET_BOOTP_PAD)
+ continue;
+
+ /* End tag. */
+ if (tagtype == GRUB_NET_BOOTP_END)
+ {
+ end = 1;
+ break;
+ }
+
+ if (i >= size)
+ return NULL;
+
+ taglength = ptr[i++];
+ if (i + taglength >= size)
+ return NULL;
+
+ grub_dprintf("net", "DHCP option %u (0x%02x) found with length %u.\n",
+ tagtype, tagtype, taglength);
+
+ /* FIXME RFC 3396 options concatentation */
+ if (tagtype == opt_code)
+ {
+ if (opt_len)
+ *opt_len = taglength;
+ return &ptr[i];
+ }
+
+ if (tagtype == GRUB_NET_DHCP_OVERLOAD && taglength == 1)
+ overload = ptr[i];
+
+ i += taglength;
+ }
+
+ if (!end)
+ return NULL;
+
+ /* RFC2131, 4.1, 23ff:
+ * If the options in a DHCP message extend into the 'sname' and 'file'
+ * fields, the 'option overload' option MUST appear in the 'options'
+ * field, with value 1, 2 or 3, as specified in RFC 1533. If the
+ * 'option overload' option is present in the 'options' field, the
+ * options in the 'options' field MUST be terminated by an 'end' option,
+ * and MAY contain one or more 'pad' options to fill the options field.
+ * The options in the 'sname' and 'file' fields (if in use as indicated
+ * by the 'options overload' option) MUST begin with the first octet of
+ * the field, MUST be terminated by an 'end' option, and MUST be
+ * followed by 'pad' options to fill the remainder of the field. Any
+ * individual option in the 'options', 'sname' and 'file' fields MUST be
+ * entirely contained in that field. The options in the 'options' field
+ * MUST be interpreted first, so that any 'option overload' options may
+ * be interpreted. The 'file' field MUST be interpreted next (if the
+ * 'option overload' option indicates that the 'file' field contains
+ * DHCP options), followed by the 'sname' field.
+ *
+ * FIXME: We do not explicitly check for trailing 'pad' options here.
+ */
+ end = 0;
+ if (overload & GRUB_DHCP_OPT_OVERLOAD_FILE)
+ {
+ overload &= ~GRUB_DHCP_OPT_OVERLOAD_FILE;
+ ptr = (grub_uint8_t *) &bp->boot_file[0];
+ size = sizeof (bp->boot_file);
+ i = 0;
+ goto again;
+ }
+
+ if (overload & GRUB_DHCP_OPT_OVERLOAD_SNAME)
+ {
+ overload &= ~GRUB_DHCP_OPT_OVERLOAD_SNAME;
+ ptr = (grub_uint8_t *) &bp->server_name[0];
+ size = sizeof (bp->server_name);
+ i = 0;
+ goto again;
+ }
+
+ return NULL;
+}
+
+#define OFFSET_OF(x, y) ((grub_size_t)((grub_uint8_t *)((y)->x) - (grub_uint8_t *)(y)))
+
+struct grub_net_network_level_interface *
+grub_net_configure_by_dhcp_ack (const char *name,
+ struct grub_net_card *card,
+ grub_net_interface_flags_t flags,
+ const struct grub_net_bootp_packet *bp,
+ grub_size_t size,
+ int is_def, char **device, char **path)
+{
+ grub_net_network_level_address_t addr;
+ grub_net_link_level_address_t hwaddr;
+ struct grub_net_network_level_interface *inter;
+ int mask = -1;
+ char server_ip[sizeof ("xxx.xxx.xxx.xxx")];
+ const grub_uint8_t *opt;
+ grub_uint8_t opt_len, overload = 0;
+ const char *boot_file = 0, *server_name = 0;
+ grub_size_t boot_file_len, server_name_len;
+
+ addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ addr.ipv4 = bp->your_ip;
+
+ if (device)
+ *device = 0;
+ if (path)
+ *path = 0;
+
+ grub_memcpy (hwaddr.mac, bp->mac_addr,
+ bp->hw_len < sizeof (hwaddr.mac) ? bp->hw_len
+ : sizeof (hwaddr.mac));
+ hwaddr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
+
+ inter = grub_net_add_addr (name, card, &addr, &hwaddr, flags);
+ if (!inter)
+ return 0;
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_DHCP_OVERLOAD, &opt_len);
+ if (opt && opt_len == 1)
+ overload = *opt;
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_DHCP_TFTP_SERVER_NAME, &opt_len);
+ if (opt && opt_len)
+ {
+ server_name = (const char *) opt;
+ server_name_len = opt_len;
+ }
+ else if (size > OFFSET_OF (server_name, bp) && !(overload & GRUB_DHCP_OPT_OVERLOAD_SNAME) &&
+ bp->server_name[0])
+ {
+ server_name = bp->server_name;
+ server_name_len = sizeof (bp->server_name);
+ }
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_DHCP_BOOTFILE_NAME, &opt_len);
+ if (opt && opt_len)
+ {
+ boot_file = (const char *) opt;
+ boot_file_len = opt_len;
+ }
+ else if (size > OFFSET_OF (boot_file, bp) && !(overload && GRUB_DHCP_OPT_OVERLOAD_FILE) &&
+ bp->boot_file[0])
+ {
+ boot_file = bp->boot_file;
+ boot_file_len = sizeof (bp->boot_file);
+ }
+
+ if (bp->server_ip)
+ {
+ grub_snprintf (server_ip, sizeof (server_ip), "%d.%d.%d.%d",
+ ((grub_uint8_t *) &bp->server_ip)[0],
+ ((grub_uint8_t *) &bp->server_ip)[1],
+ ((grub_uint8_t *) &bp->server_ip)[2],
+ ((grub_uint8_t *) &bp->server_ip)[3]);
+ grub_env_set_net_property (name, "next_server", server_ip, sizeof (server_ip));
+ grub_print_error ();
+ }
+
+ if (is_def)
+ grub_net_default_server = 0;
+ if (is_def && !grub_net_default_server && bp->server_ip)
+ {
+ grub_net_default_server = grub_strdup (server_ip);
+ grub_print_error ();
+ }
+
+ if (is_def)
+ {
+ grub_env_set ("net_default_interface", name);
+ grub_env_export ("net_default_interface");
+ }
+
+ if (device && !*device && bp->server_ip)
+ {
+ *device = grub_xasprintf ("tftp,%s", server_ip);
+ grub_print_error ();
+ }
+
+ if (server_name)
+ {
+ grub_env_set_net_property (name, "dhcp_server_name", server_name, server_name_len);
+ if (is_def && !grub_net_default_server)
+ {
+ grub_net_default_server = grub_strdup (server_name);
+ grub_print_error ();
+ }
+ if (device && !*device)
+ {
+ *device = grub_xasprintf ("tftp,%s", server_name);
+ grub_print_error ();
+ }
+ }
+
+ if (boot_file)
+ {
+ grub_env_set_net_property (name, "boot_file", boot_file, boot_file_len);
+ if (path)
+ {
+ *path = grub_strndup (boot_file, boot_file_len);
+ grub_print_error ();
+ if (*path)
+ {
+ char *slash;
+ slash = grub_strrchr (*path, '/');
+ if (slash)
+ *slash = 0;
+ else
+ **path = 0;
+ }
+ }
+ }
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_NETMASK, &opt_len);
+ if (opt && opt_len == 4)
+ {
+ int i;
+ for (i = 0; i < 32; i++)
+ if (!(opt[i / 8] & (1 << (7 - (i % 8)))))
+ break;
+ mask = i;
+ }
+ grub_net_add_ipv4_local (inter, mask);
+
+ /* We do not implement dead gateway detection and the first entry SHOULD
+ be preferred one */
+ opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_ROUTER, &opt_len);
+ if (opt && opt_len && !(opt_len & 3))
+ {
+ grub_net_network_level_netaddress_t target;
+ grub_net_network_level_address_t gw;
+ char *rname;
+
+ target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ target.ipv4.base = 0;
+ target.ipv4.masksize = 0;
+ gw.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ gw.ipv4 = grub_get_unaligned32 (opt);
+ rname = grub_xasprintf ("%s:default", name);
+ if (rname)
+ grub_net_add_route_gw (rname, target, gw, 0);
+ grub_free (rname);
+ }
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_DNS, &opt_len);
+ if (opt && opt_len && !(opt_len & 3))
+ {
+ int i;
+ for (i = 0; i < opt_len / 4; i++)
+ {
+ struct grub_net_network_level_address s;
+
+ s.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ s.ipv4 = grub_get_unaligned32 (opt);
+ s.option = DNS_OPTION_PREFER_IPV4;
+ grub_net_add_dns_server (&s);
+ opt += 4;
+ }
+ }
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_HOSTNAME, &opt_len);
+ if (opt && opt_len)
+ grub_env_set_net_property (name, "hostname", (const char *) opt, opt_len);
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_DOMAIN, &opt_len);
+ if (opt && opt_len)
+ grub_env_set_net_property (name, "domain", (const char *) opt, opt_len);
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_ROOT_PATH, &opt_len);
+ if (opt && opt_len)
+ grub_env_set_net_property (name, "rootpath", (const char *) opt, opt_len);
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_EXTENSIONS_PATH, &opt_len);
+ if (opt && opt_len)
+ grub_env_set_net_property (name, "extensionspath", (const char *) opt, opt_len);
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_CLIENT_ID, &opt_len);
+ if (opt && opt_len)
+ grub_env_set_net_property (name, "clientid", (const char *) opt, opt_len);
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_BOOTP_CLIENT_UUID, &opt_len);
+ if (opt && opt_len == 17)
+ {
+ /* The format is 9cfe245e-d0c8-bd45-a79f-54ea5fbd3d97 */
+ char *val;
+ int i, j = 0;
+
+ opt += 1;
+ opt_len -= 1;
+
+ val = grub_malloc (2 * opt_len + 4 + 1);
+ if (!val)
+ return inter;
+
+ for (i = 0; i < opt_len; i++)
+ {
+ val[2 * i + j] = hexdigit (opt[i] >> 4);
+ val[2 * i + 1 + j] = hexdigit (opt[i] & 0xf);
+
+ if ((i == 3) || (i == 5) || (i == 7) || (i == 9))
+ {
+ j++;
+ val[2 * i + 1+ j] = '-';
+ }
+ }
+ grub_env_set_net_property (name, "clientuuid", (char *) val, 2 * opt_len + 4);
+ grub_free (val);
+ }
+
+ inter->dhcp_ack = grub_malloc (size);
+ if (inter->dhcp_ack)
+ {
+ grub_memcpy (inter->dhcp_ack, bp, size);
+ inter->dhcp_acklen = size;
+ }
+ else
+ grub_errno = GRUB_ERR_NONE;
+
+ return inter;
+}
+
+static grub_err_t
+send_dhcp_packet (struct grub_net_network_level_interface *iface)
+{
+ grub_err_t err;
+ struct grub_net_bootp_packet *pack;
+ struct grub_datetime date;
+ grub_int64_t t = 0;
+ struct grub_net_buff *nb;
+ struct udphdr *udph;
+ grub_net_network_level_address_t target;
+ grub_net_link_level_address_t ll_target;
+
+ static struct grub_dhcp_discover_options discover_options =
+ {
+ {
+ GRUB_NET_BOOTP_RFC1048_MAGIC_0,
+ GRUB_NET_BOOTP_RFC1048_MAGIC_1,
+ GRUB_NET_BOOTP_RFC1048_MAGIC_2,
+ GRUB_NET_BOOTP_RFC1048_MAGIC_3,
+ },
+ {
+ GRUB_NET_DHCP_MESSAGE_TYPE,
+ sizeof (discover_options.message_type.data),
+ GRUB_DHCP_MESSAGE_DISCOVER,
+ },
+ GRUB_NET_BOOTP_END,
+ };
+
+ static struct grub_dhcp_request_options request_options =
+ {
+ {
+ GRUB_NET_BOOTP_RFC1048_MAGIC_0,
+ GRUB_NET_BOOTP_RFC1048_MAGIC_1,
+ GRUB_NET_BOOTP_RFC1048_MAGIC_2,
+ GRUB_NET_BOOTP_RFC1048_MAGIC_3,
+ },
+ {
+ GRUB_NET_DHCP_MESSAGE_TYPE,
+ sizeof (request_options.message_type.data),
+ GRUB_DHCP_MESSAGE_REQUEST,
+ },
+ {
+ GRUB_NET_DHCP_SERVER_IDENTIFIER,
+ sizeof (request_options.server_identifier.data),
+ 0,
+ },
+ {
+ GRUB_NET_DHCP_REQUESTED_IP_ADDRESS,
+ sizeof (request_options.requested_ip.data),
+ 0,
+ },
+ {
+ GRUB_NET_DHCP_PARAMETER_REQUEST_LIST,
+ sizeof (request_options.parameter_request.data),
+ {
+ GRUB_NET_BOOTP_NETMASK,
+ GRUB_NET_BOOTP_ROUTER,
+ GRUB_NET_BOOTP_DNS,
+ GRUB_NET_BOOTP_DOMAIN,
+ GRUB_NET_BOOTP_HOSTNAME,
+ GRUB_NET_BOOTP_ROOT_PATH,
+ GRUB_NET_BOOTP_EXTENSIONS_PATH,
+ },
+ },
+ GRUB_NET_BOOTP_END,
+ };
+
+ COMPILE_TIME_ASSERT (sizeof (discover_options) <= GRUB_BOOTP_MAX_OPTIONS_SIZE);
+ COMPILE_TIME_ASSERT (sizeof (request_options) <= GRUB_BOOTP_MAX_OPTIONS_SIZE);
+
+ nb = grub_netbuff_alloc (sizeof (*pack) + GRUB_BOOTP_MAX_OPTIONS_SIZE + 128);
+ if (!nb)
+ return grub_errno;
+
+ err = grub_netbuff_reserve (nb, sizeof (*pack) + GRUB_BOOTP_MAX_OPTIONS_SIZE + 128);
+ if (err)
+ goto out;
+
+ err = grub_netbuff_push (nb, GRUB_BOOTP_MAX_OPTIONS_SIZE);
+ if (err)
+ goto out;
+
+ grub_memset (nb->data, 0, GRUB_BOOTP_MAX_OPTIONS_SIZE);
+ if (!iface->srv_id)
+ {
+ grub_memcpy (nb->data, &discover_options, sizeof (discover_options));
+ }
+ else
+ {
+ struct grub_dhcp_request_options *ro = (struct grub_dhcp_request_options *) nb->data;
+
+ grub_memcpy (nb->data, &request_options, sizeof (request_options));
+ /* my_ip and srv_id are stored in network order so do not need conversion. */
+ grub_set_unaligned32 (&ro->server_identifier.data, iface->srv_id);
+ grub_set_unaligned32 (&ro->requested_ip.data, iface->my_ip);
+ }
+
+ err = grub_netbuff_push (nb, sizeof (*pack));
+ if (err)
+ goto out;
+
+ pack = (void *) nb->data;
+ grub_memset (pack, 0, sizeof (*pack));
+ pack->opcode = 1;
+ pack->hw_type = 1;
+ pack->hw_len = 6;
+ err = grub_get_datetime (&date);
+ if (err || !grub_datetime2unixtime (&date, &t))
+ {
+ grub_errno = GRUB_ERR_NONE;
+ t = 0;
+ }
+ pack->seconds = grub_cpu_to_be16 (t);
+ if (!iface->srv_id)
+ iface->xid = pack->ident = grub_cpu_to_be32 (t);
+ else
+ pack->ident = iface->xid;
+
+ grub_memcpy (&pack->mac_addr, &iface->hwaddress.mac, 6);
+
+ grub_netbuff_push (nb, sizeof (*udph));
+
+ udph = (struct udphdr *) nb->data;
+ udph->src = grub_cpu_to_be16_compile_time (68);
+ udph->dst = grub_cpu_to_be16_compile_time (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 (iface, &target, &ll_target);
+ if (err)
+ goto out;
+
+ udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP,
+ &iface->address,
+ &target);
+
+ err = grub_net_send_ip_packet (iface, &target, &ll_target, nb,
+ GRUB_NET_IP_UDP);
+
+out:
+ grub_netbuff_free (nb);
+ return err;
+}
+
+/*
+ * This is called directly from net/ip.c:handle_dgram(), because those
+ * BOOTP/DHCP packets are a bit special due to their improper
+ * sender/receiver IP fields.
+ */
+void
+grub_net_process_dhcp (struct grub_net_buff *nb,
+ struct grub_net_network_level_interface *iface)
+{
+ char *name;
+ struct grub_net_card *card = iface->card;
+ const struct grub_net_bootp_packet *bp = (const struct grub_net_bootp_packet *) nb->data;
+ grub_size_t size = nb->tail - nb->data;
+ const grub_uint8_t *opt;
+ grub_uint8_t opt_len, type;
+ grub_uint32_t srv_id = 0;
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_DHCP_MESSAGE_TYPE, &opt_len);
+ if (opt && opt_len == 1)
+ type = *opt;
+ else
+ type = GRUB_DHCP_MESSAGE_UNKNOWN;
+
+ opt = find_dhcp_option (bp, size, GRUB_NET_DHCP_SERVER_IDENTIFIER, &opt_len);
+ if (opt && opt_len == sizeof (srv_id))
+ srv_id = grub_get_unaligned32 (opt);
+
+ /*
+ * If we received BOOTP reply or DHCPACK, proceed with configuration.
+ * Otherwise store offered address and server id for later processing
+ * of DHCPACK.
+ * xid and srv_id are stored in network order so do not need conversion.
+ */
+ if ((!iface->srv_id && type == GRUB_DHCP_MESSAGE_UNKNOWN)
+ || (iface->srv_id && type == GRUB_DHCP_MESSAGE_ACK
+ && bp->ident == iface->xid
+ && srv_id == iface->srv_id))
+ {
+ name = grub_xasprintf ("%s:dhcp", card->name);
+ if (!name)
+ {
+ grub_print_error ();
+ return;
+ }
+ grub_net_configure_by_dhcp_ack (name, card, 0, bp, size, 0, 0, 0);
+ grub_free (name);
+ if (grub_errno)
+ grub_print_error ();
+ else
+ grub_net_network_level_interface_unregister (iface);
+ }
+ else if (!iface->srv_id && type == GRUB_DHCP_MESSAGE_OFFER && srv_id)
+ {
+ iface->srv_id = srv_id;
+ iface->my_ip = bp->your_ip;
+ /* Reset retransmission timer */
+ iface->dhcp_tmo = iface->dhcp_tmo_left = 1;
+ }
+ else if (iface->srv_id && type == GRUB_DHCP_MESSAGE_NAK
+ && bp->ident == iface->xid
+ && srv_id == iface->srv_id)
+ {
+ iface->xid = iface->srv_id = iface->my_ip = 0;
+ /* Reset retransmission timer */
+ iface->dhcp_tmo = iface->dhcp_tmo_left = 1;
+ }
+}
+
+static grub_err_t
+grub_cmd_dhcpopt (struct grub_command *cmd __attribute__ ((unused)),
+ int argc, char **args)
+{
+ struct grub_net_network_level_interface *inter;
+ unsigned num;
+ const grub_uint8_t *ptr;
+ grub_uint8_t taglength;
+
+ if (argc < 4)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("four arguments expected"));
+
+ FOR_NET_NETWORK_LEVEL_INTERFACES (inter)
+ if (grub_strcmp (inter->name, args[1]) == 0)
+ break;
+
+ if (!inter)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("unrecognised network interface `%s'"), args[1]);
+
+ if (!inter->dhcp_ack)
+ return grub_error (GRUB_ERR_IO, N_("no DHCP info found"));
+
+ ptr = inter->dhcp_ack->vendor;
+
+ /* This duplicates check in find_dhcp_option to preserve previous error return */
+ if (inter->dhcp_acklen < OFFSET_OF (vendor, inter->dhcp_ack) + sizeof (grub_uint32_t)
+ || ptr[0] != GRUB_NET_BOOTP_RFC1048_MAGIC_0
+ || ptr[1] != GRUB_NET_BOOTP_RFC1048_MAGIC_1
+ || ptr[2] != GRUB_NET_BOOTP_RFC1048_MAGIC_2
+ || ptr[3] != GRUB_NET_BOOTP_RFC1048_MAGIC_3)
+ return grub_error (GRUB_ERR_IO, N_("no DHCP options found"));
+
+ num = grub_strtoul (args[2], 0, 0);
+ if (grub_errno)
+ return grub_errno;
+
+ /* Exclude PAD (0) and END (255) option codes */
+ if (num == 0 || num > 254)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid DHCP option code"));
+
+ ptr = find_dhcp_option (inter->dhcp_ack, inter->dhcp_acklen, num, &taglength);
+ if (!ptr)
+ return grub_error (GRUB_ERR_IO, N_("no DHCP option %u found"), num);
+
+ if (grub_strcmp (args[3], "string") == 0)
+ {
+ grub_err_t err = GRUB_ERR_NONE;
+ char *val = grub_malloc (taglength + 1);
+ if (!val)
+ return grub_errno;
+ grub_memcpy (val, ptr, taglength);
+ val[taglength] = 0;
+ if (args[0][0] == '-' && args[0][1] == 0)
+ grub_printf ("%s\n", val);
+ else
+ err = grub_env_set (args[0], val);
+ grub_free (val);
+ return err;
+ }
+
+ if (grub_strcmp (args[3], "number") == 0)
+ {
+ grub_uint64_t val = 0;
+ int i;
+ for (i = 0; i < taglength; i++)
+ val = (val << 8) | ptr[i];
+ if (args[0][0] == '-' && args[0][1] == 0)
+ grub_printf ("%llu\n", (unsigned long long) val);
+ else
+ {
+ char valn[64];
+ grub_snprintf (valn, sizeof (valn), "%lld\n", (unsigned long long) val);
+ return grub_env_set (args[0], valn);
+ }
+ return GRUB_ERR_NONE;
+ }
+
+ if (grub_strcmp (args[3], "hex") == 0)
+ {
+ grub_err_t err = GRUB_ERR_NONE;
+ char *val = grub_malloc (2 * taglength + 1);
+ int i;
+ if (!val)
+ return grub_errno;
+ for (i = 0; i < taglength; i++)
+ {
+ val[2 * i] = hexdigit (ptr[i] >> 4);
+ val[2 * i + 1] = hexdigit (ptr[i] & 0xf);
+ }
+ val[2 * taglength] = 0;
+ if (args[0][0] == '-' && args[0][1] == 0)
+ grub_printf ("%s\n", val);
+ else
+ err = grub_env_set (args[0], val);
+ grub_free (val);
+ return err;
+ }
+
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("unrecognised DHCP option 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)
+{
+ struct grub_net_card *card;
+ struct grub_net_network_level_interface *ifaces;
+ grub_size_t ncards = 0;
+ unsigned j = 0;
+ grub_err_t err;
+ unsigned i;
+
+ FOR_NET_CARDS (card)
+ {
+ if (argc > 0 && grub_strcmp (card->name, args[0]) != 0)
+ continue;
+ ncards++;
+ }
+
+ if (ncards == 0)
+ return grub_error (GRUB_ERR_NET_NO_CARD, N_("no network card found"));
+
+ ifaces = grub_calloc (ncards, sizeof (ifaces[0]));
+ if (!ifaces)
+ return grub_errno;
+
+ j = 0;
+ FOR_NET_CARDS (card)
+ {
+ if (argc > 0 && grub_strcmp (card->name, args[0]) != 0)
+ continue;
+ ifaces[j].card = card;
+ ifaces[j].next = &ifaces[j+1];
+ if (j)
+ ifaces[j].prev = &ifaces[j-1].next;
+ ifaces[j].name = grub_xasprintf ("%s:dhcp_tmp", card->name);
+ card->num_ifaces++;
+ if (!ifaces[j].name)
+ {
+ for (i = 0; i < j; i++)
+ grub_free (ifaces[i].name);
+ grub_free (ifaces);
+ return grub_errno;
+ }
+ ifaces[j].address.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV;
+ grub_memcpy (&ifaces[j].hwaddress, &card->default_address,
+ sizeof (ifaces[j].hwaddress));
+ ifaces[j].dhcp_tmo = ifaces[j].dhcp_tmo_left = 1;
+ j++;
+ }
+ ifaces[ncards - 1].next = grub_net_network_level_interfaces;
+ if (grub_net_network_level_interfaces)
+ grub_net_network_level_interfaces->prev = & ifaces[ncards - 1].next;
+ grub_net_network_level_interfaces = &ifaces[0];
+ ifaces[0].prev = &grub_net_network_level_interfaces;
+
+ /*
+ * Running DHCP restransmission timer is kept per interface in dhcp_tmo_left.
+ * When it runs off, dhcp_tmo is increased exponentionally and dhcp_tmo_left
+ * initialized to it. Max value is 32 which gives approximately 12s total per
+ * packet timeout assuming 200ms poll tick. Timeout is reset when DHCP OFFER
+ * is received, so total timeout is 25s in the worst case.
+ *
+ * DHCP NAK also resets timer and transaction starts again.
+ *
+ * Total wait time is limited to ~25s to prevent endless loop in case of
+ * permanent NAK
+ */
+ for (i = 0; i < GRUB_DHCP_MAX_PACKET_TIMEOUT * 4; i++)
+ {
+ int need_poll = 0;
+ for (j = 0; j < ncards; j++)
+ {
+ if (!ifaces[j].prev ||
+ ifaces[j].dhcp_tmo > GRUB_DHCP_MAX_PACKET_TIMEOUT)
+ continue;
+
+ if (--ifaces[j].dhcp_tmo_left)
+ {
+ need_poll = 1;
+ continue;
+ }
+
+ ifaces[j].dhcp_tmo *= 2;
+ if (ifaces[j].dhcp_tmo > GRUB_DHCP_MAX_PACKET_TIMEOUT)
+ continue;
+
+ err = send_dhcp_packet (&ifaces[j]);
+ if (err)
+ {
+ grub_print_error ();
+ /* To ignore it during next poll */
+ ifaces[j].dhcp_tmo = GRUB_DHCP_MAX_PACKET_TIMEOUT + 1;
+ continue;
+ }
+ ifaces[j].dhcp_tmo_left = ifaces[j].dhcp_tmo;
+ need_poll = 1;
+ }
+ if (!need_poll)
+ break;
+ grub_net_poll_cards (200, 0);
+ }
+
+ err = GRUB_ERR_NONE;
+ for (j = 0; j < ncards; j++)
+ {
+ grub_free (ifaces[j].name);
+ if (!ifaces[j].prev)
+ continue;
+ grub_error_push ();
+ grub_net_network_level_interface_unregister (&ifaces[j]);
+ err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+ N_("couldn't autoconfigure %s"),
+ ifaces[j].card->name);
+ }
+
+ grub_free (ifaces);
+ return err;
+}
+
+static grub_command_t cmd_getdhcp, cmd_bootp, cmd_dhcp;
+
+void
+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 DHCP 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."));
+}
+
+void
+grub_bootp_fini (void)
+{
+ grub_unregister_command (cmd_getdhcp);
+ grub_unregister_command (cmd_bootp);
+ grub_unregister_command (cmd_dhcp);
+}
diff --git a/grub-core/net/dns.c b/grub-core/net/dns.c
new file mode 100644
index 0000000..906ec7d
--- /dev/null
+++ b/grub-core/net/dns.c
@@ -0,0 +1,785 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net.h>
+#include <grub/net/udp.h>
+#include <grub/command.h>
+#include <grub/i18n.h>
+#include <grub/err.h>
+#include <grub/time.h>
+#include <grub/safemath.h>
+
+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
+
+typedef enum grub_dns_qtype_id
+ {
+ GRUB_DNS_QTYPE_A = 1,
+ GRUB_DNS_QTYPE_AAAA = 28
+ } grub_dns_qtype_id_t;
+
+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;
+ grub_size_t sz;
+
+ if (na < 8)
+ na = 8;
+
+ if (grub_mul (na, sizeof (ns[0]), &sz))
+ return GRUB_ERR_OUT_OF_RANGE;
+
+ ns = grub_realloc (dns_servers, sz);
+ 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;
+} GRUB_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;
+ int stop;
+};
+
+static inline int
+hash (const char *str)
+{
+ unsigned 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;
+
+ /* Code apparently assumed that only one packet is received as response.
+ We may get multiple responses due to network condition, so check here
+ and quit early. */
+ if (*data->addresses)
+ {
+ grub_netbuff_free (nb);
+ return GRUB_ERR_NONE;
+ }
+
+ 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_be_to_cpu16 (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_calloc (grub_be_to_cpu16 (head->ancount),
+ sizeof ((*data->addresses)[0]));
+ 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_be_to_cpu16 (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)++;
+ data->stop = 1;
+ 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)++;
+ data->stop = 1;
+ 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_calloc (*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;
+ grub_uint8_t *optr;
+ const char *iptr;
+ struct dns_header *head;
+ static grub_uint16_t id = 1;
+ grub_uint8_t *qtypeptr;
+ grub_err_t err = GRUB_ERR_NONE;
+ struct recv_data data = {naddresses, addresses, cache,
+ grub_cpu_to_be16 (id++), 0, 0, name, 0};
+ grub_uint8_t *nbd;
+ grub_size_t try_server = 0;
+
+ if (!servers)
+ {
+ servers = dns_servers;
+ n_servers = dns_nservers;
+ }
+
+ if (!n_servers)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("no DNS servers configured"));
+
+ *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;
+ }
+ }
+
+ sockets = grub_calloc (n_servers, sizeof (sockets[0]));
+ if (!sockets)
+ return grub_errno;
+
+ data.name = grub_strdup (name);
+ if (!data.name)
+ {
+ grub_free (sockets);
+ 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);
+ if (!nb)
+ {
+ grub_free (sockets);
+ 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);
+ 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 (sockets);
+ grub_free (data.name);
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("domain name 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. */
+ *optr++ = 0;
+ qtypeptr = optr++;
+
+ /* 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 (1);
+ 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) && try_server < n_servers)
+ {
+ sockets[send_servers] = grub_net_udp_open (servers[try_server++],
+ DNS_PORT,
+ recv_hook,
+ &data);
+ if (!sockets[send_servers])
+ {
+ err = grub_errno;
+ grub_errno = GRUB_ERR_NONE;
+ }
+ else
+ {
+ send_servers++;
+ break;
+ }
+ }
+ if (!send_servers)
+ goto out;
+ if (*data.naddresses)
+ goto out;
+ for (j = 0; j < send_servers; j++)
+ {
+ grub_err_t err2;
+
+ grub_size_t t = 0;
+ do
+ {
+ nb->data = nbd;
+ if (servers[j].option == DNS_OPTION_IPV4 ||
+ ((servers[j].option == DNS_OPTION_PREFER_IPV4) && (t++ == 0)) ||
+ ((servers[j].option == DNS_OPTION_PREFER_IPV6) && (t++ == 1)))
+ *qtypeptr = GRUB_DNS_QTYPE_A;
+ else
+ *qtypeptr = GRUB_DNS_QTYPE_AAAA;
+
+ grub_dprintf ("dns", "QTYPE: %u QNAME: %s\n", *qtypeptr, name);
+
+ err2 = grub_net_send_udp_packet (sockets[j], nb);
+ if (err2)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ err = err2;
+ }
+ if (*data.naddresses)
+ goto out;
+ }
+ while (t == 1);
+ }
+ grub_net_poll_cards (200, &data.stop);
+ }
+ out:
+ grub_free (data.name);
+ grub_netbuff_free (nb);
+ for (j = 0; j < send_servers; j++)
+ grub_net_udp_close (sockets[j]);
+
+ grub_free (sockets);
+
+ if (*data.naddresses)
+ return GRUB_ERR_NONE;
+ if (data.dns_err)
+ return grub_error (GRUB_ERR_NET_NO_DOMAIN,
+ N_("no DNS record found"));
+
+ if (err)
+ {
+ grub_errno = err;
+ return err;
+ }
+ return grub_error (GRUB_ERR_TIMEOUT,
+ N_("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 cmd_server;
+ struct grub_net_network_level_address *servers;
+ grub_size_t nservers, i, naddresses = 0;
+ struct grub_net_network_level_address *addresses = 0;
+ if (argc != 2 && argc != 1)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("two arguments expected"));
+ if (argc == 2)
+ {
+ err = grub_net_resolve_address (args[1], &cmd_server);
+ if (err)
+ return err;
+ servers = &cmd_server;
+ nservers = 1;
+ }
+ else
+ {
+ servers = dns_servers;
+ nservers = dns_nservers;
+ }
+
+ grub_net_dns_lookup (args[0], servers, nservers, &naddresses,
+ &addresses, 0);
+
+ 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);
+ if (naddresses)
+ return GRUB_ERR_NONE;
+ return grub_error (GRUB_ERR_NET_NO_DOMAIN, N_("no DNS record found"));
+}
+
+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;
+ const char *strtype = "";
+
+ for (i = 0; i < dns_nservers; i++)
+ {
+ switch (dns_servers[i].option)
+ {
+ case DNS_OPTION_IPV4:
+ strtype = _("only ipv4");
+ break;
+
+ case DNS_OPTION_IPV6:
+ strtype = _("only ipv6");
+ break;
+
+ case DNS_OPTION_PREFER_IPV4:
+ strtype = _("prefer ipv4");
+ break;
+
+ case DNS_OPTION_PREFER_IPV6:
+ strtype = _("prefer ipv6");
+ break;
+ }
+
+ char buf[GRUB_NET_MAX_STR_ADDR_LEN];
+ grub_net_addr_to_str (&dns_servers[i], buf);
+ grub_printf ("%s (%s)\n", buf, strtype);
+ }
+ 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) || (argc > 2))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
+ else if (argc == 1)
+ server.option = DNS_OPTION_PREFER_IPV4;
+ else
+ {
+ if (grub_strcmp (args[1], "--only-ipv4") == 0)
+ server.option = DNS_OPTION_IPV4;
+ else if (grub_strcmp (args[1], "--only-ipv6") == 0)
+ server.option = DNS_OPTION_IPV6;
+ else if (grub_strcmp (args[1], "--prefer-ipv4") == 0)
+ server.option = DNS_OPTION_PREFER_IPV4;
+ else if (grub_strcmp (args[1], "--prefer-ipv6") == 0)
+ server.option = DNS_OPTION_PREFER_IPV6;
+ else
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid argument"));
+ }
+
+ 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, N_("one 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,
+ N_("ADDRESS DNSSERVER"),
+ N_("Perform a DNS lookup"));
+ cmd_add = grub_register_command ("net_add_dns", grub_cmd_add_dns,
+ N_("DNSSERVER"),
+ N_("Add a DNS server"));
+ cmd_del = grub_register_command ("net_del_dns", grub_cmd_del_dns,
+ N_("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
new file mode 100644
index 0000000..5388f95
--- /dev/null
+++ b/grub-core/net/drivers/efi/efinet.c
@@ -0,0 +1,404 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net/netbuff.h>
+#include <grub/dl.h>
+#include <grub/net.h>
+#include <grub/time.h>
+#include <grub/efi/api.h>
+#include <grub/efi/efi.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* GUID. */
+static grub_efi_guid_t net_io_guid = GRUB_EFI_SIMPLE_NETWORK_GUID;
+static grub_efi_guid_t pxe_io_guid = GRUB_EFI_PXE_GUID;
+
+static grub_err_t
+send_card_buffer (struct grub_net_card *dev,
+ struct grub_net_buff *pack)
+{
+ grub_efi_status_t st;
+ grub_efi_simple_network_t *net = dev->efi_net;
+ grub_uint64_t limit_time = grub_get_time_ms () + 4000;
+ void *txbuf;
+
+ if (dev->txbusy)
+ while (1)
+ {
+ txbuf = NULL;
+ st = efi_call_3 (net->get_status, net, 0, &txbuf);
+ if (st != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_IO,
+ N_("couldn't send network packet"));
+ /*
+ Some buggy firmware could return an arbitrary address instead of the
+ txbuf address we trasmitted, so just check that txbuf is non NULL
+ for success. This is ok because we open the SNP protocol in
+ exclusive mode so we know we're the only ones transmitting on this
+ box and since we only transmit one packet at a time we know our
+ transmit was successfull.
+ */
+ if (txbuf)
+ {
+ dev->txbusy = 0;
+ break;
+ }
+ if (limit_time < grub_get_time_ms ())
+ return grub_error (GRUB_ERR_TIMEOUT,
+ N_("couldn't send network packet"));
+ }
+
+ dev->last_pkt_size = (pack->tail - pack->data);
+ if (dev->last_pkt_size > dev->mtu)
+ dev->last_pkt_size = dev->mtu;
+
+ grub_memcpy (dev->txbuf, pack->data, dev->last_pkt_size);
+
+ st = efi_call_7 (net->transmit, net, 0, dev->last_pkt_size,
+ dev->txbuf, NULL, NULL, NULL);
+ if (st != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_IO, N_("couldn't send network packet"));
+
+ /*
+ The card may have sent out the packet immediately - set txbusy
+ to 0 in this case.
+ Cases were observed where checking txbuf at the next call
+ of send_card_buffer() is too late: 0 is returned in txbuf and
+ we run in the GRUB_ERR_TIMEOUT case above.
+ Perhaps a timeout in the FW has discarded the recycle buffer.
+ */
+ txbuf = NULL;
+ st = efi_call_3 (net->get_status, net, 0, &txbuf);
+ dev->txbusy = !(st == GRUB_EFI_SUCCESS && txbuf);
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_net_buff *
+get_card_packet (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 = dev->rcvbufsize;
+ struct grub_net_buff *nb;
+ int i;
+
+ for (i = 0; i < 2; i++)
+ {
+ if (!dev->rcvbuf)
+ dev->rcvbuf = grub_malloc (dev->rcvbufsize);
+ if (!dev->rcvbuf)
+ return NULL;
+
+ st = efi_call_7 (net->receive, net, NULL, &bufsize,
+ dev->rcvbuf, NULL, NULL, NULL);
+ if (st != GRUB_EFI_BUFFER_TOO_SMALL)
+ break;
+ dev->rcvbufsize = 2 * ALIGN_UP (dev->rcvbufsize > bufsize
+ ? dev->rcvbufsize : bufsize, 64);
+ grub_free (dev->rcvbuf);
+ dev->rcvbuf = 0;
+ }
+
+ if (st != GRUB_EFI_SUCCESS)
+ return NULL;
+
+ nb = grub_netbuff_alloc (bufsize + 2);
+ 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. */
+ if (grub_netbuff_reserve (nb, 2))
+ {
+ grub_netbuff_free (nb);
+ return NULL;
+ }
+ grub_memcpy (nb->data, dev->rcvbuf, bufsize);
+ err = grub_netbuff_put (nb, bufsize);
+ if (err)
+ {
+ grub_netbuff_free (nb);
+ return NULL;
+ }
+
+ return nb;
+}
+
+static grub_err_t
+open_card (struct grub_net_card *dev)
+{
+ grub_efi_simple_network_t *net;
+
+ /* Try to reopen SNP exlusively to close any active MNP protocol instance
+ that may compete for packet polling
+ */
+ net = grub_efi_open_protocol (dev->efi_handle, &net_io_guid,
+ GRUB_EFI_OPEN_PROTOCOL_BY_EXCLUSIVE);
+ if (net)
+ {
+ if (net->mode->state == GRUB_EFI_NETWORK_STOPPED
+ && efi_call_1 (net->start, net) != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_NET_NO_CARD, "%s: net start failed",
+ dev->name);
+
+ if (net->mode->state == GRUB_EFI_NETWORK_STOPPED)
+ return grub_error (GRUB_ERR_NET_NO_CARD, "%s: card stopped",
+ dev->name);
+
+ if (net->mode->state == GRUB_EFI_NETWORK_STARTED
+ && efi_call_3 (net->initialize, net, 0, 0) != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_NET_NO_CARD, "%s: net initialize failed",
+ dev->name);
+
+ /* Enable hardware receive filters if driver declares support for it.
+ We need unicast and broadcast and additionaly all nodes and
+ solicited multicast for IPv6. Solicited multicast is per-IPv6
+ address and we currently do not have API to do it so simply
+ try to enable receive of all multicast packets or evertyhing in
+ the worst case (i386 PXE driver always enables promiscuous too).
+
+ This does trust firmware to do what it claims to do.
+ */
+ if (net->mode->receive_filter_mask)
+ {
+ grub_uint32_t filters = GRUB_EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
+ GRUB_EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST |
+ GRUB_EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST;
+
+ filters &= net->mode->receive_filter_mask;
+ if (!(filters & GRUB_EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST))
+ filters |= (net->mode->receive_filter_mask &
+ GRUB_EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS);
+
+ efi_call_6 (net->receive_filters, net, filters, 0, 0, 0, NULL);
+ }
+
+ efi_call_4 (grub_efi_system_table->boot_services->close_protocol,
+ dev->efi_net, &net_io_guid,
+ grub_efi_image_handle, dev->efi_handle);
+ dev->efi_net = net;
+ }
+
+ /* If it failed we just try to run as best as we can */
+ return GRUB_ERR_NONE;
+}
+
+static void
+close_card (struct grub_net_card *dev)
+{
+ efi_call_1 (dev->efi_net->shutdown, dev->efi_net);
+ efi_call_1 (dev->efi_net->stop, dev->efi_net);
+ efi_call_4 (grub_efi_system_table->boot_services->close_protocol,
+ dev->efi_net, &net_io_guid,
+ grub_efi_image_handle, dev->efi_handle);
+}
+
+static struct grub_net_card_driver efidriver =
+ {
+ .name = "efinet",
+ .open = open_card,
+ .close = close_card,
+ .send = send_card_buffer,
+ .recv = get_card_packet
+ };
+
+grub_efi_handle_t
+grub_efinet_get_device_handle (struct grub_net_card *card)
+{
+ if (!card || card->driver != &efidriver)
+ return 0;
+ return card->efi_handle;
+}
+
+static void
+grub_efinet_findcards (void)
+{
+ grub_efi_uintn_t num_handles;
+ grub_efi_handle_t *handles;
+ grub_efi_handle_t *handle;
+ int i = 0;
+
+ /* Find handles which support the disk io interface. */
+ handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &net_io_guid,
+ 0, &num_handles);
+ if (! handles)
+ return;
+ for (handle = handles; num_handles--; handle++)
+ {
+ grub_efi_simple_network_t *net;
+ struct grub_net_card *card;
+ grub_efi_device_path_t *dp, *parent = NULL, *child = NULL;
+
+ /* EDK2 UEFI PXE driver creates IPv4 and IPv6 messaging devices as
+ children of main MAC messaging device. We only need one device with
+ bound SNP per physical card, otherwise they compete with each other
+ when polling for incoming packets.
+ */
+ dp = grub_efi_get_device_path (*handle);
+ if (!dp)
+ continue;
+ for (; ! GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp); dp = GRUB_EFI_NEXT_DEVICE_PATH (dp))
+ {
+ parent = child;
+ child = dp;
+ }
+ if (child
+ && GRUB_EFI_DEVICE_PATH_TYPE (child) == GRUB_EFI_MESSAGING_DEVICE_PATH_TYPE
+ && (GRUB_EFI_DEVICE_PATH_SUBTYPE (child) == GRUB_EFI_IPV4_DEVICE_PATH_SUBTYPE
+ || GRUB_EFI_DEVICE_PATH_SUBTYPE (child) == GRUB_EFI_IPV6_DEVICE_PATH_SUBTYPE)
+ && parent
+ && GRUB_EFI_DEVICE_PATH_TYPE (parent) == GRUB_EFI_MESSAGING_DEVICE_PATH_TYPE
+ && GRUB_EFI_DEVICE_PATH_SUBTYPE (parent) == GRUB_EFI_MAC_ADDRESS_DEVICE_PATH_SUBTYPE)
+ continue;
+
+ net = grub_efi_open_protocol (*handle, &net_io_guid,
+ GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+ if (! net)
+ /* This should not happen... Why? */
+ continue;
+
+ if (net->mode->state == GRUB_EFI_NETWORK_STOPPED
+ && efi_call_1 (net->start, net) != GRUB_EFI_SUCCESS)
+ continue;
+
+ if (net->mode->state == GRUB_EFI_NETWORK_STOPPED)
+ continue;
+
+ if (net->mode->state == GRUB_EFI_NETWORK_STARTED
+ && efi_call_3 (net->initialize, net, 0, 0) != GRUB_EFI_SUCCESS)
+ continue;
+
+ card = grub_zalloc (sizeof (struct grub_net_card));
+ if (!card)
+ {
+ grub_print_error ();
+ grub_free (handles);
+ return;
+ }
+
+ card->mtu = net->mode->max_packet_size;
+ card->txbufsize = ALIGN_UP (card->mtu, 64) + 256;
+ card->txbuf = grub_zalloc (card->txbufsize);
+ if (!card->txbuf)
+ {
+ grub_print_error ();
+ grub_free (handles);
+ grub_free (card);
+ return;
+ }
+ card->txbusy = 0;
+
+ card->rcvbufsize = ALIGN_UP (card->mtu, 64) + 256;
+
+ card->name = grub_xasprintf ("efinet%d", i++);
+ card->driver = &efidriver;
+ card->flags = 0;
+ card->default_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
+ grub_memcpy (card->default_address.mac,
+ net->mode->current_address,
+ sizeof (card->default_address.mac));
+ card->efi_net = net;
+ card->efi_handle = *handle;
+
+ grub_net_card_register (card);
+ }
+ grub_free (handles);
+}
+
+static void
+grub_efi_net_config_real (grub_efi_handle_t hnd, char **device,
+ char **path)
+{
+ struct grub_net_card *card;
+ grub_efi_device_path_t *dp;
+
+ dp = grub_efi_get_device_path (hnd);
+ if (! dp)
+ return;
+
+ FOR_NET_CARDS (card)
+ {
+ grub_efi_device_path_t *cdp;
+ struct grub_efi_pxe *pxe;
+ struct grub_efi_pxe_mode *pxe_mode;
+ if (card->driver != &efidriver)
+ continue;
+ cdp = grub_efi_get_device_path (card->efi_handle);
+ if (! cdp)
+ continue;
+ if (grub_efi_compare_device_paths (dp, cdp) != 0)
+ {
+ grub_efi_device_path_t *ldp, *dup_dp, *dup_ldp;
+ int match;
+
+ /* EDK2 UEFI PXE driver creates pseudo devices with type IPv4/IPv6
+ as children of Ethernet card and binds PXE and Load File protocols
+ to it. Loaded Image Device Path protocol will point to these pseudo
+ devices. We skip them when enumerating cards, so here we need to
+ find matching MAC device.
+ */
+ ldp = grub_efi_find_last_device_path (dp);
+ if (GRUB_EFI_DEVICE_PATH_TYPE (ldp) != GRUB_EFI_MESSAGING_DEVICE_PATH_TYPE
+ || (GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) != GRUB_EFI_IPV4_DEVICE_PATH_SUBTYPE
+ && GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) != GRUB_EFI_IPV6_DEVICE_PATH_SUBTYPE))
+ continue;
+ dup_dp = grub_efi_duplicate_device_path (dp);
+ if (!dup_dp)
+ continue;
+ dup_ldp = grub_efi_find_last_device_path (dup_dp);
+ dup_ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
+ dup_ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ dup_ldp->length = sizeof (*dup_ldp);
+ match = grub_efi_compare_device_paths (dup_dp, cdp) == 0;
+ grub_free (dup_dp);
+ if (!match)
+ continue;
+ }
+ pxe = grub_efi_open_protocol (hnd, &pxe_io_guid,
+ GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+ if (! pxe)
+ continue;
+ pxe_mode = pxe->mode;
+ grub_net_configure_by_dhcp_ack (card->name, card, 0,
+ (struct grub_net_bootp_packet *)
+ &pxe_mode->dhcp_ack,
+ sizeof (pxe_mode->dhcp_ack),
+ 1, device, path);
+ return;
+ }
+}
+
+GRUB_MOD_INIT(efinet)
+{
+ grub_efinet_findcards ();
+ grub_efi_net_config = grub_efi_net_config_real;
+}
+
+GRUB_MOD_FINI(efinet)
+{
+ struct grub_net_card *card, *next;
+
+ FOR_NET_CARDS_SAFE (card, next)
+ if (card->driver == &efidriver)
+ grub_net_card_unregister (card);
+}
+
diff --git a/grub-core/net/drivers/emu/emunet.c b/grub-core/net/drivers/emu/emunet.c
new file mode 100644
index 0000000..b194920
--- /dev/null
+++ b/grub-core/net/drivers/emu/emunet.c
@@ -0,0 +1,116 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2010,2011,2012,2013 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/net/netbuff.h>
+#include <grub/net.h>
+#include <grub/term.h>
+#include <grub/i18n.h>
+#include <grub/emu/net.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_err_t
+send_card_buffer (struct grub_net_card *dev __attribute__ ((unused)),
+ struct grub_net_buff *pack);
+
+static struct grub_net_buff *
+get_card_packet (struct grub_net_card *dev __attribute__ ((unused)));
+
+static struct grub_net_card_driver emudriver =
+ {
+ .name = "emu",
+ .send = send_card_buffer,
+ .recv = get_card_packet
+ };
+
+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}}
+ },
+ .flags = 0
+ };
+
+static grub_err_t
+send_card_buffer (struct grub_net_card *dev __attribute__ ((unused)),
+ struct grub_net_buff *pack)
+{
+ grub_ssize_t actual;
+
+ actual = grub_emunet_send (pack->data, pack->tail - pack->data);
+ if (actual < 0)
+ return grub_error (GRUB_ERR_IO, N_("couldn't send network packet"));
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_net_buff *
+get_card_packet (struct grub_net_card *dev __attribute__ ((unused)))
+{
+ grub_ssize_t actual;
+ struct grub_net_buff *nb;
+
+ nb = grub_netbuff_alloc (emucard.mtu + 36 + 2);
+ 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 = grub_emunet_receive (nb->data, emucard.mtu + 36);
+ if (actual < 0)
+ {
+ grub_netbuff_free (nb);
+ return NULL;
+ }
+ grub_netbuff_put (nb, actual);
+
+ return nb;
+}
+
+static int registered = 0;
+
+GRUB_MOD_INIT(emunet)
+{
+ if (!grub_emunet_create (&emucard.mtu))
+ {
+ grub_net_card_register (&emucard);
+ registered = 1;
+ }
+}
+
+GRUB_MOD_FINI(emunet)
+{
+ if (registered)
+ {
+ grub_emunet_close ();
+ grub_net_card_unregister (&emucard);
+ registered = 0;
+ }
+}
diff --git a/grub-core/net/drivers/i386/pc/pxe.c b/grub-core/net/drivers/i386/pc/pxe.c
new file mode 100644
index 0000000..3f4152d
--- /dev/null
+++ b/grub-core/net/drivers/i386/pc/pxe.c
@@ -0,0 +1,419 @@
+/* pxe.c - Driver to provide access to the pxe filesystem */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008,2009,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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/net.h>
+#include <grub/mm.h>
+#include <grub/file.h>
+#include <grub/misc.h>
+#include <grub/env.h>
+#include <grub/i18n.h>
+#include <grub/loader.h>
+
+#include <grub/machine/pxe.h>
+#include <grub/machine/int.h>
+#include <grub/machine/memory.h>
+#include <grub/machine/kernel.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define SEGMENT(x) ((x) >> 4)
+#define OFFSET(x) ((x) & 0xF)
+#define SEGOFS(x) ((SEGMENT(x) << 16) + OFFSET(x))
+#define LINEAR(x) (void *) ((((x) >> 16) << 4) + ((x) & 0xFFFF))
+
+struct grub_pxe_undi_open
+{
+ grub_uint16_t status;
+ grub_uint16_t open_flag;
+ grub_uint16_t pkt_filter;
+ grub_uint16_t mcast_count;
+ grub_uint8_t mcast[8][6];
+} GRUB_PACKED;
+
+struct grub_pxe_undi_info
+{
+ grub_uint16_t status;
+ grub_uint16_t base_io;
+ grub_uint16_t int_number;
+ grub_uint16_t mtu;
+ grub_uint16_t hwtype;
+ grub_uint16_t hwaddrlen;
+ grub_uint8_t current_addr[16];
+ grub_uint8_t permanent_addr[16];
+ grub_uint32_t romaddr;
+ grub_uint16_t rxbufct;
+ grub_uint16_t txbufct;
+} GRUB_PACKED;
+
+
+struct grub_pxe_undi_isr
+{
+ grub_uint16_t status;
+ grub_uint16_t func_flag;
+ grub_uint16_t buffer_len;
+ grub_uint16_t frame_len;
+ grub_uint16_t frame_hdr_len;
+ grub_uint32_t buffer;
+ grub_uint8_t prot_type;
+ grub_uint8_t pkt_type;
+} GRUB_PACKED;
+
+enum
+ {
+ GRUB_PXE_ISR_IN_START = 1,
+ GRUB_PXE_ISR_IN_PROCESS,
+ GRUB_PXE_ISR_IN_GET_NEXT
+ };
+
+enum
+ {
+ GRUB_PXE_ISR_OUT_OURS = 0,
+ GRUB_PXE_ISR_OUT_NOT_OURS = 1
+ };
+
+enum
+ {
+ GRUB_PXE_ISR_OUT_DONE = 0,
+ GRUB_PXE_ISR_OUT_TRANSMIT = 2,
+ GRUB_PXE_ISR_OUT_RECEIVE = 3,
+ GRUB_PXE_ISR_OUT_BUSY = 4,
+ };
+
+struct grub_pxe_undi_transmit
+{
+ grub_uint16_t status;
+ grub_uint8_t protocol;
+ grub_uint8_t xmitflag;
+ grub_uint32_t dest;
+ grub_uint32_t tbd;
+ grub_uint32_t reserved[2];
+} GRUB_PACKED;
+
+struct grub_pxe_undi_tbd
+{
+ grub_uint16_t len;
+ grub_uint32_t buf;
+ grub_uint16_t blk_count;
+ struct
+ {
+ grub_uint8_t ptr_type;
+ grub_uint8_t reserved;
+ grub_uint16_t len;
+ grub_uint32_t ptr;
+ } blocks[8];
+} GRUB_PACKED;
+
+struct grub_pxe_bangpxe *grub_pxe_pxenv;
+static grub_uint32_t pxe_rm_entry = 0;
+
+static struct grub_pxe_bangpxe *
+grub_pxe_scan (void)
+{
+ struct grub_bios_int_registers regs;
+ struct grub_pxenv *pxenv;
+ struct grub_pxe_bangpxe *bangpxe;
+
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+
+ regs.ebx = 0;
+ regs.ecx = 0;
+ regs.eax = 0x5650;
+ regs.es = 0;
+
+ grub_bios_interrupt (0x1a, &regs);
+
+ if ((regs.eax & 0xffff) != 0x564e)
+ return NULL;
+
+ pxenv = (struct grub_pxenv *) ((regs.es << 4) + (regs.ebx & 0xffff));
+ if (grub_memcmp (pxenv->signature, GRUB_PXE_SIGNATURE,
+ sizeof (pxenv->signature))
+ != 0)
+ return NULL;
+
+ if (pxenv->version < 0x201)
+ return NULL;
+
+ bangpxe = (void *) ((((pxenv->pxe_ptr & 0xffff0000) >> 16) << 4)
+ + (pxenv->pxe_ptr & 0xffff));
+
+ if (!bangpxe)
+ return NULL;
+
+ if (grub_memcmp (bangpxe->signature, GRUB_PXE_BANGPXE_SIGNATURE,
+ sizeof (bangpxe->signature)) != 0)
+ return NULL;
+
+ pxe_rm_entry = bangpxe->rm_entry;
+
+ return bangpxe;
+}
+
+static struct grub_net_buff *
+grub_pxe_recv (struct grub_net_card *dev __attribute__ ((unused)))
+{
+ struct grub_pxe_undi_isr *isr;
+ static int in_progress = 0;
+ grub_uint8_t *ptr, *end;
+ struct grub_net_buff *buf;
+
+ isr = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+
+ if (!in_progress)
+ {
+ grub_memset (isr, 0, sizeof (*isr));
+ isr->func_flag = GRUB_PXE_ISR_IN_START;
+ grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
+ /* Normally according to the specification we should also check
+ that isr->func_flag != GRUB_PXE_ISR_OUT_OURS but unfortunately it
+ breaks on intel cards.
+ */
+ if (isr->status)
+ {
+ in_progress = 0;
+ return NULL;
+ }
+ grub_memset (isr, 0, sizeof (*isr));
+ isr->func_flag = GRUB_PXE_ISR_IN_PROCESS;
+ grub_pxe_call (GRUB_PXENV_UNDI_ISR, isr, pxe_rm_entry);
+ }
+ else
+ {
+ 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);
+ }
+
+ while (isr->func_flag != GRUB_PXE_ISR_OUT_RECEIVE)
+ {
+ if (isr->status || isr->func_flag == GRUB_PXE_ISR_OUT_DONE)
+ {
+ in_progress = 0;
+ 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);
+ }
+
+ buf = grub_netbuff_alloc (isr->frame_len + 2);
+ 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. */
+ if (grub_netbuff_reserve (buf, 2))
+ {
+ grub_netbuff_free (buf);
+ return NULL;
+ }
+ ptr = buf->data;
+ end = ptr + 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)
+ {
+ 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);
+ if (isr->status || isr->func_flag != GRUB_PXE_ISR_OUT_RECEIVE)
+ {
+ in_progress = 1;
+ grub_netbuff_free (buf);
+ return NULL;
+ }
+
+ grub_memcpy (ptr, LINEAR (isr->buffer), isr->buffer_len);
+ ptr += isr->buffer_len;
+ }
+ in_progress = 1;
+
+ return buf;
+}
+
+static grub_err_t
+grub_pxe_send (struct grub_net_card *dev __attribute__ ((unused)),
+ struct grub_net_buff *pack)
+{
+ struct grub_pxe_undi_transmit *trans;
+ struct grub_pxe_undi_tbd *tbd;
+ char *buf;
+
+ trans = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+ grub_memset (trans, 0, sizeof (*trans));
+ tbd = (void *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + 128);
+ grub_memset (tbd, 0, sizeof (*tbd));
+ buf = (void *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + 256);
+ grub_memcpy (buf, pack->data, pack->tail - pack->data);
+
+ trans->tbd = SEGOFS ((grub_addr_t) tbd);
+ trans->protocol = 0;
+ tbd->len = pack->tail - pack->data;
+ tbd->buf = SEGOFS ((grub_addr_t) buf);
+
+ grub_pxe_call (GRUB_PXENV_UNDI_TRANSMIT, trans, pxe_rm_entry);
+ if (trans->status)
+ return grub_error (GRUB_ERR_IO, N_("couldn't send network packet"));
+ return 0;
+}
+
+static void
+grub_pxe_close (struct grub_net_card *dev __attribute__ ((unused)))
+{
+ if (pxe_rm_entry)
+ grub_pxe_call (GRUB_PXENV_UNDI_CLOSE,
+ (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ pxe_rm_entry);
+}
+
+static grub_err_t
+grub_pxe_open (struct grub_net_card *dev __attribute__ ((unused)))
+{
+ struct grub_pxe_undi_open *ou;
+ ou = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+ grub_memset (ou, 0, sizeof (*ou));
+ ou->pkt_filter = 4;
+ grub_pxe_call (GRUB_PXENV_UNDI_OPEN, ou, pxe_rm_entry);
+
+ if (ou->status)
+ return grub_error (GRUB_ERR_IO, "can't open UNDI");
+ return GRUB_ERR_NONE;
+}
+
+struct grub_net_card_driver grub_pxe_card_driver =
+{
+ .open = grub_pxe_open,
+ .close = grub_pxe_close,
+ .send = grub_pxe_send,
+ .recv = grub_pxe_recv
+};
+
+struct grub_net_card grub_pxe_card =
+{
+ .driver = &grub_pxe_card_driver,
+ .name = "pxe"
+};
+
+static grub_err_t
+grub_pxe_shutdown (int flags)
+{
+ if (flags & GRUB_LOADER_FLAG_PXE_NOT_UNLOAD)
+ return GRUB_ERR_NONE;
+ if (!pxe_rm_entry)
+ return GRUB_ERR_NONE;
+
+ grub_pxe_call (GRUB_PXENV_UNDI_CLOSE,
+ (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ pxe_rm_entry);
+ grub_pxe_call (GRUB_PXENV_UNDI_SHUTDOWN,
+ (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ pxe_rm_entry);
+ grub_pxe_call (GRUB_PXENV_UNLOAD_STACK,
+ (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ pxe_rm_entry);
+ pxe_rm_entry = 0;
+ grub_net_card_unregister (&grub_pxe_card);
+
+ return GRUB_ERR_NONE;
+}
+
+/* Nothing we can do. */
+static grub_err_t
+grub_pxe_restore (void)
+{
+ return GRUB_ERR_NONE;
+}
+
+void *
+grub_pxe_get_cached (grub_uint16_t type)
+{
+ struct grub_pxenv_get_cached_info ci;
+ ci.packet_type = type;
+ ci.buffer = 0;
+ ci.buffer_size = 0;
+ grub_pxe_call (GRUB_PXENV_GET_CACHED_INFO, &ci, pxe_rm_entry);
+ if (ci.status)
+ return 0;
+
+ return LINEAR (ci.buffer);
+}
+
+static void
+grub_pc_net_config_real (char **device, char **path)
+{
+ struct grub_net_bootp_packet *bp;
+
+ bp = grub_pxe_get_cached (GRUB_PXENV_PACKET_TYPE_DHCP_ACK);
+
+ if (!bp)
+ return;
+ grub_net_configure_by_dhcp_ack ("pxe", &grub_pxe_card, 0,
+ bp, GRUB_PXE_BOOTP_SIZE,
+ 1, device, path);
+
+}
+
+static struct grub_preboot *fini_hnd;
+
+GRUB_MOD_INIT(pxe)
+{
+ struct grub_pxe_bangpxe *pxenv;
+ struct grub_pxe_undi_info *ui;
+ unsigned i;
+
+ pxenv = grub_pxe_scan ();
+ if (! pxenv)
+ return;
+
+ ui = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+ grub_memset (ui, 0, sizeof (*ui));
+ grub_pxe_call (GRUB_PXENV_UNDI_GET_INFORMATION, ui, pxe_rm_entry);
+
+ grub_memcpy (grub_pxe_card.default_address.mac, ui->current_addr,
+ sizeof (grub_pxe_card.default_address.mac));
+ for (i = 0; i < sizeof (grub_pxe_card.default_address.mac); i++)
+ if (grub_pxe_card.default_address.mac[i] != 0)
+ break;
+ if (i != sizeof (grub_pxe_card.default_address.mac))
+ {
+ for (i = 0; i < sizeof (grub_pxe_card.default_address.mac); i++)
+ if (grub_pxe_card.default_address.mac[i] != 0xff)
+ break;
+ }
+ 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;
+
+ grub_net_card_register (&grub_pxe_card);
+ grub_pc_net_config = grub_pc_net_config_real;
+ fini_hnd = grub_loader_register_preboot_hook (grub_pxe_shutdown,
+ grub_pxe_restore,
+ GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
+}
+
+GRUB_MOD_FINI(pxe)
+{
+ grub_pc_net_config = 0;
+ grub_pxe_shutdown (0);
+ grub_loader_unregister_preboot_hook (fini_hnd);
+}
diff --git a/grub-core/net/drivers/ieee1275/ofnet.c b/grub-core/net/drivers/ieee1275/ofnet.c
new file mode 100644
index 0000000..ac4e62a
--- /dev/null
+++ b/grub-core/net/drivers/ieee1275/ofnet.c
@@ -0,0 +1,565 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net/netbuff.h>
+#include <grub/ieee1275/ieee1275.h>
+#include <grub/dl.h>
+#include <grub/net.h>
+#include <grub/time.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+struct grub_ofnetcard_data
+{
+ char *path;
+ char *suffix;
+ grub_ieee1275_ihandle_t handle;
+};
+
+static grub_err_t
+card_open (struct grub_net_card *dev)
+{
+ int status;
+ struct grub_ofnetcard_data *data = dev->data;
+
+ status = grub_ieee1275_open (data->path, &(data->handle));
+
+ if (status)
+ return grub_error (GRUB_ERR_IO, "Couldn't open network card.");
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+card_close (struct grub_net_card *dev)
+{
+ struct grub_ofnetcard_data *data = dev->data;
+
+ if (data->handle)
+ grub_ieee1275_close (data->handle);
+}
+
+static grub_err_t
+send_card_buffer (struct grub_net_card *dev, struct grub_net_buff *pack)
+{
+ grub_ssize_t actual;
+ int status;
+ struct grub_ofnetcard_data *data = dev->data;
+ grub_size_t len;
+
+ len = (pack->tail - pack->data);
+ if (len > dev->mtu)
+ len = dev->mtu;
+
+ grub_memcpy (dev->txbuf, pack->data, len);
+ status = grub_ieee1275_write (data->handle, dev->txbuf,
+ len, &actual);
+
+ if (status)
+ return grub_error (GRUB_ERR_IO, N_("couldn't send network packet"));
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_net_buff *
+get_card_packet (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;
+
+ start_time = grub_get_time_ms ();
+ do
+ rc = grub_ieee1275_read (data->handle, dev->rcvbuf, dev->rcvbufsize, &actual);
+ while ((actual <= 0 || rc < 0) && (grub_get_time_ms () - start_time < 200));
+
+ if (actual <= 0)
+ return NULL;
+
+ nb = grub_netbuff_alloc (actual + 2);
+ 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);
+
+ grub_memcpy (nb->data, dev->rcvbuf, actual);
+
+ if (grub_netbuff_put (nb, actual))
+ {
+ grub_netbuff_free (nb);
+ return NULL;
+ }
+
+ return nb;
+}
+
+static struct grub_net_card_driver ofdriver =
+ {
+ .name = "ofnet",
+ .open = card_open,
+ .close = card_close,
+ .send = send_card_buffer,
+ .recv = get_card_packet
+ };
+
+static const struct
+{
+ const char *name;
+ int offset;
+}
+
+bootp_response_properties[] =
+ {
+ { .name = "bootp-response", .offset = 0},
+ { .name = "dhcp-response", .offset = 0},
+ { .name = "bootpreply-packet", .offset = 0x2a},
+ };
+
+enum
+{
+ BOOTARGS_SERVER_ADDR,
+ BOOTARGS_FILENAME,
+ BOOTARGS_CLIENT_ADDR,
+ BOOTARGS_GATEWAY_ADDR,
+ BOOTARGS_BOOTP_RETRIES,
+ BOOTARGS_TFTP_RETRIES,
+ BOOTARGS_SUBNET_MASK,
+ BOOTARGS_BLOCKSIZE
+};
+
+static int
+grub_ieee1275_parse_bootpath (const char *devpath, char *bootpath,
+ char **device, struct grub_net_card **card)
+{
+ char *args;
+ char *comma_char = 0;
+ char *equal_char = 0;
+ grub_size_t field_counter = 0;
+ grub_net_network_level_address_t client_addr = {0, {0}, 0}, gateway_addr = {0, {0}, 0}, subnet_mask = {0, {0}, 0};
+ grub_net_link_level_address_t hw_addr = {0, {{0, 0, 0, 0, 0, 0}}};
+ grub_net_interface_flags_t flags = 0;
+ struct grub_net_network_level_interface *inter = NULL;
+ grub_uint16_t vlantag = 0;
+
+ hw_addr.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
+
+ args = bootpath + grub_strlen (devpath) + 1;
+ do
+ {
+ comma_char = grub_strchr (args, ',');
+ if (comma_char != 0)
+ *comma_char = 0;
+
+ /* Check if it's an option (like speed=auto) and not a default parameter */
+ equal_char = grub_strchr (args, '=');
+ if (equal_char != 0)
+ {
+ *equal_char = 0;
+ grub_env_set_net_property ((*card)->name, args, equal_char + 1,
+ grub_strlen(equal_char + 1));
+
+ if ((grub_strcmp (args, "vtag") == 0) &&
+ (grub_strlen (equal_char + 1) == 8))
+ vlantag = grub_strtoul (equal_char + 1 + 4, 0, 16);
+
+ *equal_char = '=';
+ }
+ else
+ {
+ switch (field_counter++)
+ {
+ case BOOTARGS_SERVER_ADDR:
+ *device = grub_xasprintf ("tftp,%s", args);
+ if (!*device)
+ return grub_errno;
+ break;
+
+ case BOOTARGS_CLIENT_ADDR:
+ grub_net_resolve_address (args, &client_addr);
+ break;
+
+ case BOOTARGS_GATEWAY_ADDR:
+ grub_net_resolve_address (args, &gateway_addr);
+ break;
+
+ case BOOTARGS_SUBNET_MASK:
+ grub_net_resolve_address (args, &subnet_mask);
+ break;
+ }
+ }
+ args = comma_char + 1;
+ if (comma_char != 0)
+ *comma_char = ',';
+ } while (comma_char != 0);
+
+ if ((client_addr.ipv4 != 0) && (subnet_mask.ipv4 != 0))
+ {
+ grub_ieee1275_phandle_t devhandle;
+ grub_ieee1275_finddevice (devpath, &devhandle);
+ grub_ieee1275_get_property (devhandle, "mac-address",
+ hw_addr.mac, sizeof(hw_addr.mac), 0);
+ inter = grub_net_add_addr ((*card)->name, *card, &client_addr, &hw_addr,
+ flags);
+ inter->vlantag = vlantag;
+ grub_net_add_ipv4_local (inter,
+ __builtin_ctz (~grub_le_to_cpu32 (subnet_mask.ipv4)));
+
+ }
+
+ if (gateway_addr.ipv4 != 0)
+ {
+ grub_net_network_level_netaddress_t target;
+ char *rname;
+
+ target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ target.ipv4.base = 0;
+ target.ipv4.masksize = 0;
+ rname = grub_xasprintf ("%s:default", ((*card)->name));
+ if (rname)
+ grub_net_add_route_gw (rname, target, gateway_addr, inter);
+ else
+ return grub_errno;
+ }
+
+ return 0;
+}
+
+static void
+grub_ieee1275_net_config_real (const char *devpath, char **device, char **path,
+ char *bootpath)
+{
+ struct grub_net_card *card;
+
+ /* FIXME: Check that it's the right card. */
+ FOR_NET_CARDS (card)
+ {
+ char *bootp_response;
+ char *canon;
+ char c;
+ struct grub_ofnetcard_data *data;
+
+ grub_ssize_t size = -1;
+ unsigned int i;
+
+ if (card->driver != &ofdriver)
+ continue;
+
+ data = card->data;
+ c = *data->suffix;
+ *data->suffix = '\0';
+ canon = grub_ieee1275_canonicalise_devname (data->path);
+ *data->suffix = c;
+ if (grub_strcmp (devpath, canon) != 0)
+ {
+ grub_free (canon);
+ continue;
+ }
+ grub_free (canon);
+
+ grub_ieee1275_parse_bootpath (devpath, bootpath, device, &card);
+
+ for (i = 0; i < ARRAY_SIZE (bootp_response_properties); i++)
+ if (grub_ieee1275_get_property_length (grub_ieee1275_chosen,
+ bootp_response_properties[i].name,
+ &size) >= 0)
+ break;
+
+ if (size < 0)
+ return;
+
+ bootp_response = grub_malloc (size);
+ if (!bootp_response)
+ {
+ grub_print_error ();
+ return;
+ }
+ if (grub_ieee1275_get_property (grub_ieee1275_chosen,
+ bootp_response_properties[i].name,
+ bootp_response, size, 0) < 0)
+ return;
+
+ grub_net_configure_by_dhcp_ack (card->name, card, 0,
+ (struct grub_net_bootp_packet *)
+ (bootp_response
+ + bootp_response_properties[i].offset),
+ size - bootp_response_properties[i].offset,
+ 1, device, path);
+ grub_free (bootp_response);
+ return;
+ }
+}
+
+/* Allocate memory with alloc-mem */
+static void *
+grub_ieee1275_alloc_mem (grub_size_t len)
+{
+ struct alloc_args
+ {
+ struct grub_ieee1275_common_hdr common;
+ grub_ieee1275_cell_t method;
+ grub_ieee1275_cell_t len;
+ grub_ieee1275_cell_t catch;
+ grub_ieee1275_cell_t result;
+ }
+ args;
+
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_CANNOT_INTERPRET))
+ {
+ grub_error (GRUB_ERR_UNKNOWN_COMMAND, N_("interpret is not supported"));
+ return NULL;
+ }
+
+ INIT_IEEE1275_COMMON (&args.common, "interpret", 2, 2);
+ args.len = len;
+ args.method = (grub_ieee1275_cell_t) "alloc-mem";
+
+ if (IEEE1275_CALL_ENTRY_FN (&args) == -1 || args.catch)
+ {
+ grub_error (GRUB_ERR_INVALID_COMMAND, N_("alloc-mem failed"));
+ return NULL;
+ }
+ else
+ return (void *)args.result;
+}
+
+/* Free memory allocated by alloc-mem */
+static grub_err_t
+grub_ieee1275_free_mem (void *addr, grub_size_t len)
+{
+ struct free_args
+ {
+ struct grub_ieee1275_common_hdr common;
+ grub_ieee1275_cell_t method;
+ grub_ieee1275_cell_t len;
+ grub_ieee1275_cell_t addr;
+ grub_ieee1275_cell_t catch;
+ }
+ args;
+
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_CANNOT_INTERPRET))
+ {
+ grub_error (GRUB_ERR_UNKNOWN_COMMAND, N_("interpret is not supported"));
+ return grub_errno;
+ }
+
+ INIT_IEEE1275_COMMON (&args.common, "interpret", 3, 1);
+ args.addr = (grub_ieee1275_cell_t)addr;
+ args.len = len;
+ args.method = (grub_ieee1275_cell_t) "free-mem";
+
+ if (IEEE1275_CALL_ENTRY_FN(&args) == -1 || args.catch)
+ {
+ grub_error (GRUB_ERR_INVALID_COMMAND, N_("free-mem failed"));
+ return grub_errno;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static void *
+ofnet_alloc_netbuf (grub_size_t len)
+{
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_VIRT_TO_REAL_BROKEN))
+ return grub_ieee1275_alloc_mem (len);
+ else
+ return grub_zalloc (len);
+}
+
+static void
+ofnet_free_netbuf (void *addr, grub_size_t len)
+{
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_VIRT_TO_REAL_BROKEN))
+ grub_ieee1275_free_mem (addr, len);
+ else
+ grub_free (addr);
+}
+
+static int
+search_net_devices (struct grub_ieee1275_devalias *alias)
+{
+ struct grub_ofnetcard_data *ofdata;
+ struct grub_net_card *card;
+ grub_ieee1275_phandle_t devhandle;
+ grub_net_link_level_address_t lla;
+ grub_ssize_t prop_size;
+ grub_uint64_t prop;
+ grub_uint8_t *pprop;
+ char *shortname;
+ char need_suffix = 1;
+
+ if (grub_strcmp (alias->type, "network") != 0)
+ return 0;
+
+ ofdata = grub_malloc (sizeof (struct grub_ofnetcard_data));
+ if (!ofdata)
+ {
+ grub_print_error ();
+ return 1;
+ }
+ card = grub_zalloc (sizeof (struct grub_net_card));
+ if (!card)
+ {
+ grub_free (ofdata);
+ grub_print_error ();
+ return 1;
+ }
+
+#define SUFFIX ":speed=auto,duplex=auto,1.1.1.1,dummy,1.1.1.1,1.1.1.1,5,5,1.1.1.1,512"
+
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_OFNET_SUFFIX))
+ need_suffix = 0;
+
+ /* sun4v vnet devices do not support setting duplex/speed */
+ {
+ char *ptr;
+
+ grub_ieee1275_finddevice (alias->path, &devhandle);
+
+ grub_ieee1275_get_property_length (devhandle, "compatible", &prop_size);
+ if (prop_size > 0)
+ {
+ pprop = grub_malloc (prop_size);
+ if (!pprop)
+ {
+ grub_free (card);
+ grub_free (ofdata);
+ grub_print_error ();
+ return 1;
+ }
+
+ if (!grub_ieee1275_get_property (devhandle, "compatible",
+ pprop, prop_size, NULL))
+ {
+ for (ptr = (char *) pprop; ptr - (char *) pprop < prop_size;
+ ptr += grub_strlen (ptr) + 1)
+ {
+ if (!grub_strcmp(ptr, "SUNW,sun4v-network"))
+ need_suffix = 0;
+ }
+ }
+
+ grub_free (pprop);
+ }
+ }
+
+ if (need_suffix)
+ ofdata->path = grub_malloc (grub_strlen (alias->path) + sizeof (SUFFIX));
+ else
+ ofdata->path = grub_malloc (grub_strlen (alias->path) + 1);
+ if (!ofdata->path)
+ {
+ grub_print_error ();
+ return 0;
+ }
+ ofdata->suffix = grub_stpcpy (ofdata->path, alias->path);
+ if (need_suffix)
+ grub_memcpy (ofdata->suffix, SUFFIX, sizeof (SUFFIX));
+ else
+ *ofdata->suffix = '\0';
+
+ grub_ieee1275_finddevice (ofdata->path, &devhandle);
+
+ {
+ 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;
+ }
+
+ pprop = (grub_uint8_t *) &prop;
+ if (grub_ieee1275_get_property (devhandle, "mac-address",
+ pprop, sizeof(prop), &prop_size)
+ && grub_ieee1275_get_property (devhandle, "local-mac-address",
+ pprop, sizeof(prop), &prop_size))
+ {
+ grub_error (GRUB_ERR_IO, "Couldn't retrieve mac address.");
+ grub_print_error ();
+ return 0;
+ }
+
+ if (prop_size == 8)
+ grub_memcpy (&lla.mac, pprop+2, 6);
+ else
+ grub_memcpy (&lla.mac, pprop, 6);
+
+ lla.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
+ card->default_address = lla;
+
+ card->txbufsize = ALIGN_UP (card->mtu, 64) + 256;
+ card->rcvbufsize = ALIGN_UP (card->mtu, 64) + 256;
+
+ card->txbuf = ofnet_alloc_netbuf (card->txbufsize);
+ if (!card->txbuf)
+ goto fail_netbuf;
+
+ card->rcvbuf = ofnet_alloc_netbuf (card->rcvbufsize);
+ if (!card->rcvbuf)
+ {
+ grub_error_push ();
+ ofnet_free_netbuf (card->txbuf, card->txbufsize);
+ grub_error_pop ();
+ goto fail_netbuf;
+ }
+ card->driver = NULL;
+ card->data = ofdata;
+ card->flags = 0;
+ shortname = grub_ieee1275_get_devname (alias->path);
+ card->name = grub_xasprintf ("ofnet_%s", shortname ? : alias->path);
+ card->idle_poll_delay_ms = 10;
+ grub_free (shortname);
+
+ card->driver = &ofdriver;
+ grub_net_card_register (card);
+ return 0;
+
+fail_netbuf:
+ grub_free (ofdata->path);
+ grub_free (ofdata);
+ grub_free (card);
+ grub_print_error ();
+ return 0;
+}
+
+static void
+grub_ofnet_findcards (void)
+{
+ /* Look at all nodes for devices of the type network. */
+ grub_ieee1275_devices_iterate (search_net_devices);
+}
+
+GRUB_MOD_INIT(ofnet)
+{
+ grub_ofnet_findcards ();
+ grub_ieee1275_net_config = grub_ieee1275_net_config_real;
+}
+
+GRUB_MOD_FINI(ofnet)
+{
+ struct grub_net_card *card, *next;
+
+ FOR_NET_CARDS_SAFE (card, next)
+ if (card->driver && grub_strcmp (card->driver->name, "ofnet") == 0)
+ grub_net_card_unregister (card);
+ grub_ieee1275_net_config = 0;
+}
diff --git a/grub-core/net/drivers/uboot/ubootnet.c b/grub-core/net/drivers/uboot/ubootnet.c
new file mode 100644
index 0000000..056052e
--- /dev/null
+++ b/grub-core/net/drivers/uboot/ubootnet.c
@@ -0,0 +1,161 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net/netbuff.h>
+#include <grub/uboot/disk.h>
+#include <grub/uboot/uboot.h>
+#include <grub/uboot/api_public.h>
+#include <grub/dl.h>
+#include <grub/net.h>
+#include <grub/time.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_err_t
+card_open (struct grub_net_card *dev)
+{
+ int status;
+
+ status = grub_uboot_dev_open (dev->data);
+ if (status)
+ return grub_error (GRUB_ERR_IO, "Couldn't open network card.");
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+card_close (struct grub_net_card *dev)
+{
+ grub_uboot_dev_close (dev->data);
+}
+
+static grub_err_t
+send_card_buffer (struct grub_net_card *dev, struct grub_net_buff *pack)
+{
+ int status;
+ grub_size_t len;
+
+ len = (pack->tail - pack->data);
+ if (len > dev->mtu)
+ len = dev->mtu;
+
+ grub_memcpy (dev->txbuf, pack->data, len);
+ status = grub_uboot_dev_send (dev->data, dev->txbuf, len);
+
+ if (status)
+ return grub_error (GRUB_ERR_IO, N_("couldn't send network packet"));
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_net_buff *
+get_card_packet (struct grub_net_card *dev)
+{
+ int rc;
+ grub_uint64_t start_time;
+ struct grub_net_buff *nb;
+ int actual;
+
+ nb = grub_netbuff_alloc (dev->mtu + 64 + 2);
+ 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);
+
+ start_time = grub_get_time_ms ();
+ do
+ {
+ rc = grub_uboot_dev_recv (dev->data, nb->data, dev->mtu + 64, &actual);
+ grub_dprintf ("net", "rc=%d, actual=%d, time=%lld\n", rc, actual,
+ grub_get_time_ms () - start_time);
+ }
+ while ((actual <= 0 || rc < 0) && (grub_get_time_ms () - start_time < 200));
+ if (actual > 0)
+ {
+ grub_netbuff_put (nb, actual);
+ return nb;
+ }
+ grub_netbuff_free (nb);
+ return NULL;
+}
+
+static struct grub_net_card_driver ubootnet =
+ {
+ .name = "ubnet",
+ .open = card_open,
+ .close = card_close,
+ .send = send_card_buffer,
+ .recv = get_card_packet
+ };
+
+GRUB_MOD_INIT (ubootnet)
+{
+ int devcount, i;
+ int nfound = 0;
+
+ devcount = grub_uboot_dev_enum ();
+
+ for (i = 0; i < devcount; i++)
+ {
+ struct device_info *devinfo = grub_uboot_dev_get (i);
+ struct grub_net_card *card;
+
+ if (!(devinfo->type & DEV_TYP_NET))
+ continue;
+
+ card = grub_zalloc (sizeof (struct grub_net_card));
+ if (!card)
+ {
+ grub_print_error ();
+ return;
+ }
+
+ /* FIXME: Any way to check this? */
+ card->mtu = 1500;
+
+ grub_memcpy (&(card->default_address.mac), &devinfo->di_net.hwaddr, 6);
+ card->default_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
+
+ card->txbufsize = ALIGN_UP (card->mtu, 64) + 256;
+ card->txbuf = grub_zalloc (card->txbufsize);
+ if (!card->txbuf)
+ {
+ grub_free (card);
+ grub_print_error ();
+ continue;
+ }
+
+ card->data = devinfo;
+ card->flags = 0;
+ card->name = grub_xasprintf ("ubnet_%d", ++nfound);
+ card->idle_poll_delay_ms = 10;
+
+ card->driver = &ubootnet;
+ grub_net_card_register (card);
+ }
+}
+
+GRUB_MOD_FINI (ubootnet)
+{
+ struct grub_net_card *card, *next;
+
+ FOR_NET_CARDS_SAFE (card, next)
+ if (card->driver && grub_strcmp (card->driver->name, "ubnet") == 0)
+ grub_net_card_unregister (card);
+}
diff --git a/grub-core/net/ethernet.c b/grub-core/net/ethernet.c
new file mode 100644
index 0000000..4d7ceed
--- /dev/null
+++ b/grub-core/net/ethernet.c
@@ -0,0 +1,171 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/env.h>
+#include <grub/net/ethernet.h>
+#include <grub/net/ip.h>
+#include <grub/net/arp.h>
+#include <grub/net/netbuff.h>
+#include <grub/net.h>
+#include <grub/time.h>
+#include <grub/net/arp.h>
+
+#define LLCADDRMASK 0x7f
+
+struct etherhdr
+{
+ grub_uint8_t dst[6];
+ grub_uint8_t src[6];
+ grub_uint16_t type;
+} GRUB_PACKED;
+
+struct llchdr
+{
+ grub_uint8_t dsap;
+ grub_uint8_t ssap;
+ grub_uint8_t ctrl;
+} GRUB_PACKED;
+
+struct snaphdr
+{
+ grub_uint8_t oui[3];
+ grub_uint16_t type;
+} GRUB_PACKED;
+
+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_net_ethertype_t ethertype)
+{
+ struct etherhdr *eth;
+ grub_err_t err;
+ grub_uint8_t etherhdr_size;
+ grub_uint16_t vlantag_id = VLANTAG_IDENTIFIER;
+
+ etherhdr_size = sizeof (*eth);
+ COMPILE_TIME_ASSERT (sizeof (*eth) + 4 < GRUB_NET_MAX_LINK_HEADER_SIZE);
+
+ /* Increase ethernet header in case of vlantag */
+ if (inf->vlantag != 0)
+ etherhdr_size += 4;
+
+ err = grub_netbuff_push (nb, etherhdr_size);
+ if (err)
+ return err;
+ eth = (struct etherhdr *) nb->data;
+ grub_memcpy (eth->dst, target_addr.mac, 6);
+ grub_memcpy (eth->src, inf->hwaddress.mac, 6);
+
+ eth->type = grub_cpu_to_be16 (ethertype);
+ if (!inf->card->opened)
+ {
+ err = GRUB_ERR_NONE;
+ if (inf->card->driver->open)
+ err = inf->card->driver->open (inf->card);
+ if (err)
+ return err;
+ inf->card->opened = 1;
+ }
+
+ /* Check and add a vlan-tag if needed. */
+ if (inf->vlantag != 0)
+ {
+ /* Move eth type to the right */
+ grub_memcpy ((char *) nb->data + etherhdr_size - 2,
+ (char *) nb->data + etherhdr_size - 6, 2);
+
+ /* Add the tag in the middle */
+ grub_memcpy ((char *) nb->data + etherhdr_size - 6, &vlantag_id, 2);
+ grub_memcpy ((char *) nb->data + etherhdr_size - 4, (char *) &(inf->vlantag), 2);
+ }
+
+ return inf->card->driver->send (inf->card, nb);
+}
+
+grub_err_t
+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_net_ethertype_t type;
+ grub_net_link_level_address_t hwaddress;
+ grub_net_link_level_address_t src_hwaddress;
+ grub_err_t err;
+ grub_uint8_t etherhdr_size = sizeof (*eth);
+ grub_uint16_t vlantag = 0;
+
+
+ /* Check if a vlan-tag is present. If so, the ethernet header is 4 bytes */
+ /* longer than the original one. The vlantag id is extracted and the header */
+ /* is reseted to the original size. */
+ if (grub_get_unaligned16 (nb->data + etherhdr_size - 2) == VLANTAG_IDENTIFIER)
+ {
+ vlantag = grub_get_unaligned16 (nb->data + etherhdr_size);
+ etherhdr_size += 4;
+ /* Move eth type to the original position */
+ grub_memcpy((char *) nb->data + etherhdr_size - 6,
+ (char *) nb->data + etherhdr_size - 2, 2);
+ }
+
+ eth = (struct etherhdr *) nb->data;
+ type = grub_be_to_cpu16 (eth->type);
+ err = grub_netbuff_pull (nb, etherhdr_size);
+ if (err)
+ return err;
+
+ if (type <= 1500)
+ {
+ llch = (struct llchdr *) nb->data;
+ type = llch->dsap & LLCADDRMASK;
+
+ if (llch->dsap == 0xaa && llch->ssap == 0xaa && llch->ctrl == 0x3)
+ {
+ err = grub_netbuff_pull (nb, sizeof (*llch));
+ if (err)
+ return err;
+ snaph = (struct snaphdr *) nb->data;
+ type = snaph->type;
+ }
+ }
+
+ 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));
+
+ switch (type)
+ {
+ /* ARP packet. */
+ case GRUB_NET_ETHERTYPE_ARP:
+ grub_net_arp_receive (nb, card, &vlantag);
+ grub_netbuff_free (nb);
+ 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,
+ &vlantag);
+ }
+ 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 0000000..b616cf4
--- /dev/null
+++ b/grub-core/net/http.c
@@ -0,0 +1,562 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/misc.h>
+#include <grub/net/tcp.h>
+#include <grub/net/ip.h>
+#include <grub/net/ethernet.h>
+#include <grub/net/netbuff.h>
+#include <grub/net.h>
+#include <grub/mm.h>
+#include <grub/dl.h>
+#include <grub/file.h>
+#include <grub/i18n.h>
+
+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;
+ file->device->net->stall = 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)
+ {
+ data->errmsg = grub_strdup (_("unsupported HTTP response"));
+ data->first_line_recv = 1;
+ return GRUB_ERR_NONE;
+ }
+ ptr += sizeof ("HTTP/1.1 ") - 1;
+ code = grub_strtoul (ptr, (const char **)&ptr, 10);
+ if (grub_errno)
+ return grub_errno;
+ switch (code)
+ {
+ case 200:
+ case 206:
+ 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;
+ /* TRANSLATORS: GRUB HTTP code is pretty young. So even perfectly
+ valid answers like 403 will trigger this very generic message. */
+ 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, (const char **)&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);
+ data->sock = 0;
+ if (data->current_line)
+ grub_free (data->current_line);
+ data->current_line = 0;
+ file->device->net->eof = 1;
+ file->device->net->stall = 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;
+
+ if (!data->sock)
+ {
+ grub_netbuff_free (nb);
+ return GRUB_ERR_NONE;
+ }
+
+ 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 (file->device->net->packs.count >= 20)
+ file->device->net->stall = 1;
+
+ if (file->device->net->packs.count >= 100)
+ grub_net_tcp_stall (data->sock);
+
+ 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);
+ if (file->device->net->packs.count >= 20)
+ {
+ file->device->net->stall = 1;
+ grub_net_tcp_stall (data->sock);
+ }
+
+ 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 ("Range: bytes=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 ("Range: bytes=XXXXXXXXXXXXXXXXXXXX-"
+ "\r\n"),
+ "Range: bytes=%" PRIuGRUB_UINT64_T "-\r\n",
+ offset);
+ 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, NULL,
+ 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, &data->headers_recv);
+ }
+
+ 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);
+ data->errmsg = 0;
+ return data->err;
+ }
+ return grub_error (GRUB_ERR_TIMEOUT, N_("time out opening `%s'"), data->filename);
+ }
+ 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? */
+ if (old_data->sock)
+ grub_net_tcp_close (old_data->sock, GRUB_NET_TCP_ABORT);
+ old_data->sock = 0;
+
+ 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->stall = 0;
+ file->device->net->eof = 0;
+ 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);
+ file->data = 0;
+ 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);
+ file->data = 0;
+ 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)
+ return GRUB_ERR_NONE;
+
+ 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->filename);
+ grub_free (data);
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+http_packets_pulled (struct grub_file *file)
+{
+ http_data_t data = file->data;
+
+ if (file->device->net->packs.count >= 20)
+ return 0;
+
+ if (!file->device->net->eof)
+ file->device->net->stall = 0;
+ if (data && data->sock)
+ grub_net_tcp_unstall (data->sock);
+ return 0;
+}
+
+static struct grub_net_app_protocol grub_http_protocol =
+ {
+ .name = "http",
+ .open = http_open,
+ .close = http_close,
+ .seek = http_seek,
+ .packets_pulled = http_packets_pulled
+ };
+
+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 0000000..b1eef11
--- /dev/null
+++ b/grub-core/net/icmp.c
@@ -0,0 +1,112 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net.h>
+#include <grub/net/ip.h>
+#include <grub/net/netbuff.h>
+
+struct icmp_header
+{
+ grub_uint8_t type;
+ grub_uint8_t code;
+ grub_uint16_t checksum;
+} GRUB_PACKED;
+
+struct ping_header
+{
+ grub_uint16_t id;
+ grub_uint16_t seq;
+} GRUB_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_make_pkt (nb->tail - nb->data + sizeof (*icmphr));
+ if (!nb_reply)
+ {
+ grub_netbuff_free (nb);
+ return grub_errno;
+ }
+ grub_memcpy (nb_reply->data + sizeof (*icmphr), nb->data, nb->tail - nb->data);
+ 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);
+
+ 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 0000000..2cbd95d
--- /dev/null
+++ b/grub-core/net/icmp6.c
@@ -0,0 +1,679 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net.h>
+#include <grub/net/ip.h>
+#include <grub/net/netbuff.h>
+
+struct icmp_header
+{
+ grub_uint8_t type;
+ grub_uint8_t code;
+ grub_uint16_t checksum;
+} GRUB_PACKED;
+
+struct ping_header
+{
+ grub_uint16_t id;
+ grub_uint16_t seq;
+} GRUB_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];
+} GRUB_PACKED;
+
+struct option_header
+{
+ grub_uint8_t type;
+ grub_uint8_t len;
+} GRUB_PACKED;
+
+struct prefix_option
+{
+ struct option_header header;
+ grub_uint8_t prefixlen;
+ grub_uint8_t flags;
+ grub_uint32_t valid_lifetime;
+ grub_uint32_t preferred_lifetime;
+ grub_uint32_t reserved;
+ grub_uint64_t prefix[2];
+} GRUB_PACKED;
+
+struct neighbour_solicit
+{
+ grub_uint32_t reserved;
+ grub_uint64_t target[2];
+} GRUB_PACKED;
+
+struct neighbour_advertise
+{
+ grub_uint32_t flags;
+ grub_uint64_t target[2];
+} GRUB_PACKED;
+
+struct router_solicit
+{
+ grub_uint32_t reserved;
+} GRUB_PACKED;
+
+enum
+ {
+ FLAG_SLAAC = 0x40
+ };
+
+enum
+ {
+ ICMP6_ECHO = 128,
+ ICMP6_ECHO_REPLY = 129,
+ ICMP6_ROUTER_SOLICIT = 133,
+ 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;
+ struct grub_net_network_level_interface *orig_inf = inf;
+ 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 (*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_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;
+ struct router_adv *radv;
+ struct grub_net_network_level_interface *route_inf = NULL;
+ int default_route = 0;
+ if (icmph->code)
+ break;
+ radv = (struct router_adv *)nb->data;
+ err = grub_netbuff_pull (nb, sizeof (struct router_adv));
+ if (err)
+ {
+ grub_netbuff_free (nb);
+ return err;
+ }
+ if (grub_be_to_cpu16 (radv->router_lifetime) > 0)
+ {
+ struct grub_net_route *route;
+
+ FOR_NET_ROUTES (route)
+ {
+ if (!grub_memcmp (&route->gw, source, sizeof (route->gw)))
+ break;
+ }
+ if (route == NULL)
+ default_route = 1;
+ }
+
+ 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->preferred_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->preferred_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)
+ {
+ if (!route_inf)
+ route_inf = inf;
+ continue;
+ }
+
+ grub_dprintf ("net", "creating slaac\n");
+
+ {
+ char *name;
+ name = grub_xasprintf ("%s:%d",
+ slaac->name, slaac->slaac_counter++);
+ if (!name)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+ inf = grub_net_add_addr (name,
+ card, &addr,
+ &slaac->address, 0);
+ if (!route_inf)
+ route_inf = inf;
+ grub_net_add_route (name, netaddr, inf);
+ grub_free (name);
+ }
+ }
+ }
+ }
+ if (default_route)
+ {
+ char *name;
+ grub_net_network_level_netaddress_t netaddr;
+ name = grub_xasprintf ("%s:ra:default6", card->name);
+ if (!name)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ goto next;
+ }
+ /* Default routes take alll of the traffic, so make the mask huge */
+ netaddr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6;
+ netaddr.ipv6.masksize = 0;
+ netaddr.ipv6.base[0] = 0;
+ netaddr.ipv6.base[1] = 0;
+
+ /* May not have gotten slaac info, find a global address on this
+ card. */
+ if (route_inf == NULL)
+ {
+ FOR_NET_NETWORK_LEVEL_INTERFACES (inf)
+ {
+ if (inf->card == card && inf != orig_inf
+ && inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6
+ && grub_net_hwaddr_cmp(&inf->hwaddress,
+ &orig_inf->hwaddress) == 0)
+ {
+ route_inf = inf;
+ break;
+ }
+ }
+ }
+ if (route_inf != NULL)
+ grub_net_add_route_gw (name, netaddr, *source, route_inf);
+ grub_free (name);
+ }
+next:
+ 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 + (i * GRUB_NET_INTERVAL_ADDITION),
+ 0);
+ 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;
+}
+
+grub_err_t
+grub_net_icmp6_send_router_solicit (struct grub_net_network_level_interface *inf)
+{
+ struct grub_net_buff *nb;
+ grub_err_t err = GRUB_ERR_NONE;
+ grub_net_network_level_address_t multicast;
+ grub_net_link_level_address_t ll_multicast;
+ struct option_header *ohdr;
+ struct router_solicit *sol;
+ struct icmp_header *icmphr;
+
+ multicast.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6;
+ multicast.ipv6[0] = grub_cpu_to_be64_compile_time (0xff02ULL << 48);
+ multicast.ipv6[1] = grub_cpu_to_be64_compile_time (0x02ULL);
+
+ err = grub_net_link_layer_resolve (inf, &multicast, &ll_multicast);
+ if (err)
+ return err;
+
+ nb = grub_netbuff_alloc (sizeof (struct router_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 router_solicit)
+ + sizeof (struct option_header)
+ + 6
+ + sizeof (struct icmp_header)
+ + GRUB_NET_OUR_IPV6_HEADER_SIZE
+ + GRUB_NET_MAX_LINK_HEADER_SIZE);
+ if (err)
+ goto fail;
+
+ 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 router_solicit *) nb->data;
+ sol->reserved = 0;
+
+ err = grub_netbuff_push (nb, sizeof (*icmphr));
+ if (err)
+ goto fail;
+
+ icmphr = (struct icmp_header *) nb->data;
+ icmphr->type = ICMP6_ROUTER_SOLICIT;
+ icmphr->code = 0;
+ icmphr->checksum = 0;
+ icmphr->checksum = grub_net_ip_transport_checksum (nb,
+ GRUB_NET_IP_ICMPV6,
+ &inf->address,
+ &multicast);
+ err = grub_net_send_ip_packet (inf, &multicast, &ll_multicast, nb,
+ GRUB_NET_IP_ICMPV6);
+ fail:
+ grub_netbuff_free (nb);
+ return err;
+}
diff --git a/grub-core/net/ip.c b/grub-core/net/ip.c
new file mode 100644
index 0000000..ea5edf8
--- /dev/null
+++ b/grub-core/net/ip.c
@@ -0,0 +1,739 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net/ip.h>
+#include <grub/misc.h>
+#include <grub/net/arp.h>
+#include <grub/net/udp.h>
+#include <grub/net/ethernet.h>
+#include <grub/net.h>
+#include <grub/net/netbuff.h>
+#include <grub/mm.h>
+#include <grub/priority_queue.h>
+#include <grub/time.h>
+
+struct iphdr {
+ grub_uint8_t verhdrlen;
+ grub_uint8_t service;
+ grub_uint16_t len;
+ grub_uint16_t ident;
+ grub_uint16_t frags;
+ grub_uint8_t ttl;
+ grub_uint8_t protocol;
+ grub_uint16_t chksum;
+ grub_uint32_t src;
+ grub_uint32_t dest;
+} GRUB_PACKED ;
+
+enum
+{
+ 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;
+} GRUB_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;
+ struct grub_net_buff *asm_netbuff;
+ 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, grub_size_t len)
+{
+ grub_uint16_t *ip = (grub_uint16_t *) ipv;
+ grub_uint32_t sum = 0;
+
+ for (; len >= 2; len -= 2)
+ {
+ 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);
+}
+
+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;
+ 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);
+
+ err = grub_netbuff_push (nb, sizeof (*iph));
+ if (err)
+ return err;
+
+ iph = (struct iphdr *) nb->data;
+ iph->verhdrlen = ((4 << 4) | 5);
+ iph->service = 0;
+ iph->len = grub_cpu_to_be16 (nb->tail - nb->data);
+ iph->ident = grub_cpu_to_be16 (++id);
+ iph->frags = 0;
+ iph->ttl = 0xff;
+ 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));
+
+ return send_ethernet_packet (inf, nb, *ll_target_addr,
+ GRUB_NET_ETHERTYPE_IP);
+}
+
+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_uint16_t *vlantag,
+ grub_uint8_t ttl)
+{
+ struct grub_net_network_level_interface *inf = NULL;
+ 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 (proto == GRUB_NET_IP_UDP && grub_be_to_cpu16 (udph->dst) == 68)
+ {
+ const struct grub_net_bootp_packet *bootp;
+ 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)
+ {
+ grub_netbuff_free (nb);
+ return err;
+ }
+
+ bootp = (const struct grub_net_bootp_packet *) nb->data;
+
+ FOR_NET_NETWORK_LEVEL_INTERFACES (inf)
+ if (inf->card == card
+ && inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV
+ && inf->hwaddress.type == GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET
+ && grub_memcmp (inf->hwaddress.mac, &bootp->mac_addr,
+ sizeof (inf->hwaddress.mac)) == 0)
+ {
+ grub_net_process_dhcp (nb, inf);
+ grub_netbuff_free (nb);
+ return GRUB_ERR_NONE;
+ }
+ grub_netbuff_free (nb);
+ return GRUB_ERR_NONE;
+ }
+ }
+
+ 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;
+
+ /* Verify vlantag id */
+ if (inf->card == card && inf->vlantag != *vlantag)
+ {
+ grub_dprintf ("net", "invalid vlantag! %x != %x\n",
+ inf->vlantag, *vlantag);
+ 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))
+ {
+ multicast = 1;
+ break;
+ }
+ }
+
+ 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)))
+ {
+ 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_netbuff_free (rsm->asm_netbuff);
+ grub_priority_queue_destroy (rsm->pq);
+ grub_free (rsm);
+}
+
+static void
+free_old_fragments (void)
+{
+ struct reassemble *rsm, **prev;
+ grub_uint64_t limit_time = grub_get_time_ms ();
+
+ limit_time = (limit_time > 90000) ? limit_time - 90000 : 0;
+
+ for (prev = &reassembles, rsm = *prev; rsm; rsm = *prev)
+ if (rsm->last_time < limit_time)
+ {
+ *prev = rsm->next;
+ free_rsm (rsm);
+ }
+ else
+ {
+ prev = &rsm->next;
+ }
+}
+
+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,
+ grub_uint16_t *vlantag)
+{
+ 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",
+ (grub_ssize_t) (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, vlantag, 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_netbuff = 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_netbuff = grub_netbuff_alloc (rsm->total_len);
+ if (!rsm->asm_netbuff)
+ {
+ *prev = rsm->next;
+ free_rsm (rsm);
+ return grub_errno;
+ }
+ }
+ if (!rsm->asm_netbuff)
+ return GRUB_ERR_NONE;
+
+ while (1)
+ {
+ struct grub_net_buff **nb_top_p, *nb_top;
+ grub_size_t copy;
+ 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))
+ {
+ grub_netbuff_free (nb_top);
+ 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_netbuff->data[8 * (grub_be_to_cpu16 (iph->frags)
+ & OFFSET_MASK)],
+ nb_top->data, copy);
+
+ if ((grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS))
+ {
+ grub_netbuff_free (nb_top);
+ continue;
+ }
+ grub_netbuff_free (nb_top);
+
+ ret = rsm->asm_netbuff;
+ proto = rsm->proto;
+ src = rsm->source;
+ dst = rsm->dest;
+ ttl = rsm->ttl;
+
+ rsm->asm_netbuff = 0;
+ res_len = rsm->total_len;
+ *prev = rsm->next;
+ free_rsm (rsm);
+
+ if (grub_netbuff_put (ret, res_len))
+ {
+ grub_netbuff_free (ret);
+ return GRUB_ERR_NONE;
+ }
+
+ 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, vlantag,
+ ttl);
+ }
+}
+
+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;
+ grub_err_t err;
+
+ 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");
+
+ err = grub_netbuff_push (nb, sizeof (*iph));
+ if (err)
+ return err;
+
+ iph = (struct ip6hdr *) nb->data;
+ iph->version_class_flow = grub_cpu_to_be32_compile_time ((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_BUG, "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,
+ grub_uint16_t *vlantag)
+{
+ 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",
+ (grub_ssize_t) (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, vlantag, 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,
+ grub_uint16_t *vlantag)
+{
+ struct iphdr *iph = (struct iphdr *) nb->data;
+
+ if ((iph->verhdrlen >> 4) == 4)
+ return grub_net_recv_ip4_packets (nb, card, hwaddress, src_hwaddress,
+ vlantag);
+ if ((iph->verhdrlen >> 4) == 6)
+ return grub_net_recv_ip6_packets (nb, card, hwaddress, src_hwaddress,
+ vlantag);
+ 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
new file mode 100644
index 0000000..4d3eb5c
--- /dev/null
+++ b/grub-core/net/net.c
@@ -0,0 +1,1952 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2010,2011,2012,2013 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net.h>
+#include <grub/net/netbuff.h>
+#include <grub/time.h>
+#include <grub/file.h>
+#include <grub/i18n.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/dl.h>
+#include <grub/command.h>
+#include <grub/env.h>
+#include <grub/net/ethernet.h>
+#include <grub/net/arp.h>
+#include <grub/net/ip.h>
+#include <grub/loader.h>
+#include <grub/bufio.h>
+#include <grub/kernel.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+char *grub_net_default_server;
+
+struct grub_net_route *grub_net_routes = NULL;
+struct grub_net_network_level_interface *grub_net_network_level_interfaces = NULL;
+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]));
+ if (card->link_layer_table == NULL)
+ return;
+ }
+
+ 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_BUG,
+ "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,
+ N_("timeout: could not resolve hardware address"));
+}
+
+void
+grub_net_card_unregister (struct grub_net_card *card)
+{
+ struct grub_net_network_level_interface *inf, *next;
+ FOR_NET_NETWORK_LEVEL_INTERFACES_SAFE(inf, next)
+ if (inf->card == card)
+ grub_net_network_level_interface_unregister (inf);
+ if (card->opened)
+ {
+ if (card->driver->close)
+ card->driver->close (card);
+ card->opened = 0;
+ }
+ grub_list_remove (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;
+}
+
+static void
+grub_net_network_level_interface_register (struct grub_net_network_level_interface *inter);
+
+static struct grub_net_network_level_interface *
+grub_net_add_addr_real (char *name,
+ struct grub_net_card *card,
+ 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;
+
+ inter = grub_zalloc (sizeof (*inter));
+ if (!inter)
+ return NULL;
+
+ inter->name = name;
+ 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;
+ inter->dhcp_acklen = 0;
+
+ grub_net_network_level_interface_register (inter);
+
+ return inter;
+}
+
+struct grub_net_network_level_interface *
+grub_net_add_addr (const char *name,
+ struct grub_net_card *card,
+ const grub_net_network_level_address_t *addr,
+ const grub_net_link_level_address_t *hwaddress,
+ grub_net_interface_flags_t flags)
+{
+ char *name_dup = grub_strdup (name);
+ struct grub_net_network_level_interface *ret;
+
+ if (!name_dup)
+ return NULL;
+ ret = grub_net_add_addr_real (name_dup, card, addr, hwaddress, flags);
+ if (!ret)
+ grub_free (name_dup);
+ return ret;
+}
+
+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;
+ char *ptr;
+ grub_net_network_level_address_t addr;
+
+ addr.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6;
+ addr.ipv6[0] = grub_cpu_to_be64_compile_time (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;
+ }
+
+ name = grub_malloc (grub_strlen (card->name)
+ + GRUB_NET_MAX_STR_HWADDR_LEN
+ + sizeof (":link"));
+ if (!name)
+ return NULL;
+
+ 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_real (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_calloc (ncards, sizeof (ifaces[0]));
+ slaacs = grub_calloc (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)
+ {
+ int done = 1;
+ for (j = 0; j < ncards; j++)
+ {
+ if (slaacs[j]->slaac_counter)
+ continue;
+ err = grub_net_icmp6_send_router_solicit (ifaces[j]);
+ if (err)
+ err = GRUB_ERR_NONE;
+ done = 0;
+ }
+ if (done)
+ break;
+ grub_net_poll_cards (interval, 0);
+ }
+
+ err = GRUB_ERR_NONE;
+ for (j = 0; j < ncards; j++)
+ {
+ if (slaacs[j]->slaac_counter)
+ continue;
+ err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+ N_("couldn't autoconfigure %s"),
+ ifaces[j]->card->name);
+ }
+
+ grub_free (ifaces);
+ grub_free (slaacs);
+ return err;
+}
+
+
+static int
+parse_ip (const char *val, grub_uint32_t *ip, const char **rest)
+{
+ grub_uint32_t newip = 0;
+ int i;
+ const char *ptr = val;
+
+ for (i = 0; i < 4; i++)
+ {
+ unsigned long t;
+ t = grub_strtoul (ptr, &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;
+ newip |= (t << 24);
+ if (i != 3 && *ptr != '.')
+ return 0;
+ ptr++;
+ }
+ *ip = grub_cpu_to_le32 (newip);
+ if (rest)
+ *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, &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;
+}
+
+static int
+match_net (const grub_net_network_level_netaddress_t *net,
+ const grub_net_network_level_address_t *addr)
+{
+ if (net->type != addr->type)
+ return 0;
+ switch (net->type)
+ {
+ case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV:
+ return 0;
+ case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4:
+ {
+ 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 == 0)
+ return 1;
+ 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;
+}
+
+grub_err_t
+grub_net_resolve_address (const char *name,
+ grub_net_network_level_address_t *addr)
+{
+ const char *rest;
+ grub_err_t err;
+ grub_size_t naddresses;
+ struct grub_net_network_level_address *addresses = 0;
+
+ if (parse_ip (name, &addr->ipv4, &rest) && *rest == 0)
+ {
+ addr->type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ return GRUB_ERR_NONE;
+ }
+ 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
+grub_net_resolve_net_address (const char *name,
+ grub_net_network_level_netaddress_t *addr)
+{
+ const char *rest;
+ if (parse_ip (name, &addr->ipv4.base, &rest))
+ {
+ addr->type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+ if (*rest == '/')
+ {
+ addr->ipv4.masksize = grub_strtoul (rest + 1, &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, &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;
+ }
+ }
+ return grub_error (GRUB_ERR_NET_BAD_ADDRESS,
+ N_("unrecognised network 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,
+ struct grub_net_network_level_interface **interf)
+{
+ struct grub_net_route *route;
+ unsigned int depth = 0;
+ unsigned int routecnt = 0;
+ struct grub_net_network_level_protocol *prot = NULL;
+ grub_net_network_level_address_t curtarget = addr;
+
+ *gateway = addr;
+
+ FOR_NET_ROUTES(route)
+ routecnt++;
+
+ for (depth = 0; depth < routecnt + 2 && depth < GRUB_UINT_MAX; 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_cmp (route, bestroute) > 0)
+ bestroute = route;
+ }
+ if (bestroute == NULL)
+ return grub_error (GRUB_ERR_NET_NO_ROUTE,
+ N_("destination unreachable"));
+
+ if (!bestroute->is_gateway)
+ {
+ *interf = bestroute->interface;
+ return GRUB_ERR_NONE;
+ }
+ if (depth == 0)
+ {
+ *gateway = bestroute->gw;
+ if (bestroute->interface != NULL)
+ {
+ *interf = bestroute->interface;
+ return GRUB_ERR_NONE;
+ }
+ }
+ curtarget = bestroute->gw;
+ }
+
+ return grub_error (GRUB_ERR_NET_ROUTE_LOOP,
+ /* TRANSLATORS: route loop is a condition when e.g.
+ to contact server A you need to go through B
+ and to contact B you need to go through A. */
+ N_("route loop detected"));
+}
+
+static grub_err_t
+grub_cmd_deladdr (struct grub_command *cmd __attribute__ ((unused)),
+ int argc, char **args)
+{
+ struct grub_net_network_level_interface *inter;
+
+ if (argc != 1)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
+
+ FOR_NET_NETWORK_LEVEL_INTERFACES (inter)
+ if (grub_strcmp (inter->name, args[0]) == 0)
+ break;
+ if (inter == NULL)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("address not found"));
+
+ if (inter->flags & GRUB_NET_INTERFACE_PERMANENT)
+ return grub_error (GRUB_ERR_IO,
+ N_("you can't delete this address"));
+
+ grub_net_network_level_interface_unregister (inter);
+ grub_free (inter->name);
+ grub_free (inter);
+
+ return GRUB_ERR_NONE;
+}
+
+void
+grub_net_addr_to_str (const grub_net_network_level_address_t *target, char *buf)
+{
+ switch (target->type)
+ {
+ case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV:
+ COMPILE_TIME_ASSERT (sizeof ("temporary") < GRUB_NET_MAX_STR_ADDR_LEN);
+ 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);
+ grub_snprintf (buf, GRUB_NET_MAX_STR_ADDR_LEN, "%d.%d.%d.%d",
+ ((n >> 24) & 0xff), ((n >> 16) & 0xff),
+ ((n >> 8) & 0xff), ((n >> 0) & 0xff));
+ }
+ return;
+ }
+ grub_snprintf (buf, GRUB_NET_MAX_STR_ADDR_LEN,
+ "Unknown address type %d", target->type);
+}
+
+
+void
+grub_net_hwaddr_to_str (const grub_net_link_level_address_t *addr, char *str)
+{
+ str[0] = 0;
+ switch (addr->type)
+ {
+ case GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET:
+ {
+ char *ptr;
+ unsigned i;
+ for (ptr = str, i = 0; i < ARRAY_SIZE (addr->mac); i++)
+ {
+ grub_snprintf (ptr, GRUB_NET_MAX_STR_HWADDR_LEN - (ptr - str),
+ "%02x:", addr->mac[i] & 0xff);
+ ptr += (sizeof ("XX:") - 1);
+ }
+ return;
+ }
+ }
+ grub_printf (_("Unsupported hw address type %d\n"), addr->type);
+}
+
+int
+grub_net_hwaddr_cmp (const grub_net_link_level_address_t *a,
+ const grub_net_link_level_address_t *b)
+{
+ if (a->type < b->type)
+ return -1;
+ if (a->type > b->type)
+ return +1;
+ switch (a->type)
+ {
+ case GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET:
+ return grub_memcmp (a->mac, b->mac, sizeof (a->mac));
+ }
+ grub_printf (_("Unsupported hw address type %d\n"), a->type);
+ 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)),
+ const char *val __attribute__ ((unused)))
+{
+ return NULL;
+}
+
+/* FIXME: implement this. */
+static char *
+addr_set_env (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val __attribute__ ((unused)))
+{
+ return NULL;
+}
+
+static char *
+defserver_set_env (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val)
+{
+ grub_free (grub_net_default_server);
+ grub_net_default_server = grub_strdup (val);
+ return grub_strdup (val);
+}
+
+static const char *
+defserver_get_env (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val __attribute__ ((unused)))
+{
+ return grub_net_default_server ? : "";
+}
+
+static const char *
+defip_get_env (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val __attribute__ ((unused)))
+{
+ const char *intf = grub_env_get ("net_default_interface");
+ const char *ret = NULL;
+ if (intf)
+ {
+ char *buf = grub_xasprintf ("net_%s_ip", intf);
+ if (buf)
+ ret = grub_env_get (buf);
+ grub_free (buf);
+ }
+ return ret;
+}
+
+static char *
+defip_set_env (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val)
+{
+ const char *intf = grub_env_get ("net_default_interface");
+ if (intf)
+ {
+ char *buf = grub_xasprintf ("net_%s_ip", intf);
+ if (buf)
+ grub_env_set (buf, val);
+ grub_free (buf);
+ }
+ return NULL;
+}
+
+
+static const char *
+defmac_get_env (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val __attribute__ ((unused)))
+{
+ const char *intf = grub_env_get ("net_default_interface");
+ const char *ret = NULL;
+ if (intf)
+ {
+ char *buf = grub_xasprintf ("net_%s_mac", intf);
+ if (buf)
+ ret = grub_env_get (buf);
+ grub_free (buf);
+ }
+ return ret;
+}
+
+static char *
+defmac_set_env (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val)
+{
+ const char *intf = grub_env_get ("net_default_interface");
+ if (intf)
+ {
+ char *buf = grub_xasprintf ("net_%s_mac", intf);
+ if (buf)
+ grub_env_set (buf, val);
+ grub_free (buf);
+ }
+ return NULL;
+}
+
+
+static void
+grub_net_network_level_interface_register (struct grub_net_network_level_interface *inter)
+{
+ {
+ char buf[GRUB_NET_MAX_STR_HWADDR_LEN];
+ char *name;
+ char *ptr;
+ grub_net_hwaddr_to_str (&inter->hwaddress, buf);
+ name = grub_xasprintf ("net_%s_mac", inter->name);
+ if (!name)
+ return;
+ for (ptr = name; *ptr; ptr++)
+ if (*ptr == ':')
+ *ptr = '_';
+ grub_env_set (name, buf);
+ grub_register_variable_hook (name, 0, hwaddr_set_env);
+ grub_env_export (name);
+ grub_free (name);
+ }
+
+ {
+ char buf[GRUB_NET_MAX_STR_ADDR_LEN];
+ char *name;
+ char *ptr;
+ grub_net_addr_to_str (&inter->address, buf);
+ name = grub_xasprintf ("net_%s_ip", inter->name);
+ if (!name)
+ return;
+ for (ptr = name; *ptr; ptr++)
+ if (*ptr == ':')
+ *ptr = '_';
+ grub_env_set (name, buf);
+ grub_register_variable_hook (name, 0, addr_set_env);
+ grub_env_export (name);
+ grub_free (name);
+ }
+
+ inter->card->num_ifaces++;
+ inter->prev = &grub_net_network_level_interfaces;
+ inter->next = grub_net_network_level_interfaces;
+ if (inter->next)
+ inter->next->prev = &inter->next;
+ grub_net_network_level_interfaces = inter;
+}
+
+
+grub_err_t
+grub_net_add_ipv4_local (struct grub_net_network_level_interface *inter,
+ int mask)
+{
+ grub_uint32_t ip_cpu;
+ struct grub_net_route *route;
+
+ if (inter->address.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4)
+ return 0;
+
+ ip_cpu = grub_be_to_cpu32 (inter->address.ipv4);
+
+ if (mask == -1)
+ {
+ if (!(ip_cpu & 0x80000000))
+ mask = 8;
+ else if (!(ip_cpu & 0x40000000))
+ mask = 16;
+ else if (!(ip_cpu & 0x20000000))
+ mask = 24;
+ }
+ if (mask == -1)
+ return 0;
+
+ route = grub_zalloc (sizeof (*route));
+ if (!route)
+ return grub_errno;
+
+ route->name = grub_xasprintf ("%s:local", inter->name);
+ if (!route->name)
+ {
+ grub_free (route);
+ return grub_errno;
+ }
+
+ 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 0;
+}
+
+/* FIXME: support MAC specifying. */
+static grub_err_t
+grub_cmd_addaddr (struct grub_command *cmd __attribute__ ((unused)),
+ int argc, char **args)
+{
+ struct grub_net_card *card;
+ grub_net_network_level_address_t addr;
+ grub_err_t err;
+ grub_net_interface_flags_t flags = 0;
+ struct grub_net_network_level_interface *inf;
+
+ if (argc != 3)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("three arguments expected"));
+
+ FOR_NET_CARDS (card)
+ if (grub_strcmp (card->name, args[1]) == 0)
+ break;
+ if (card == NULL)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("card not found"));
+
+ err = grub_net_resolve_address (args[2], &addr);
+ if (err)
+ return err;
+
+ if (card->flags & GRUB_NET_CARD_NO_MANUAL_INTERFACES)
+ return grub_error (GRUB_ERR_IO,
+ "this card doesn't support address addition");
+
+ if (card->flags & GRUB_NET_CARD_HWADDRESS_IMMUTABLE)
+ flags |= GRUB_NET_INTERFACE_HWADDRESS_IMMUTABLE;
+
+ inf = grub_net_add_addr (args[0], card, &addr, &card->default_address,
+ flags);
+ if (inf)
+ grub_net_add_ipv4_local (inf, -1);
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_cmd_delroute (struct grub_command *cmd __attribute__ ((unused)),
+ int argc, char **args)
+{
+ struct grub_net_route *route;
+ struct grub_net_route **prev;
+
+ if (argc != 1)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
+
+ for (prev = &grub_net_routes, route = *prev; route; prev = &((*prev)->next),
+ route = *prev)
+ if (grub_strcmp (route->name, args[0]) == 0)
+ {
+ *prev = route->next;
+ grub_free (route->name);
+ grub_free (route);
+ if (!*prev)
+ break;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_net_add_route (const char *name,
+ grub_net_network_level_netaddress_t target,
+ struct grub_net_network_level_interface *inter)
+{
+ struct grub_net_route *route;
+
+ route = grub_zalloc (sizeof (*route));
+ if (!route)
+ return grub_errno;
+
+ route->name = grub_strdup (name);
+ if (!route->name)
+ {
+ grub_free (route);
+ return grub_errno;
+ }
+
+ route->target = target;
+ route->is_gateway = 0;
+ route->interface = inter;
+
+ grub_net_route_register (route);
+
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_net_add_route_gw (const char *name,
+ grub_net_network_level_netaddress_t target,
+ grub_net_network_level_address_t gw,
+ struct grub_net_network_level_interface *inter)
+{
+ struct grub_net_route *route;
+
+ route = grub_zalloc (sizeof (*route));
+ if (!route)
+ return grub_errno;
+
+ route->name = grub_strdup (name);
+ if (!route->name)
+ {
+ grub_free (route);
+ return grub_errno;
+ }
+
+ route->target = target;
+ route->is_gateway = 1;
+ route->gw = gw;
+ route->interface = inter;
+
+ grub_net_route_register (route);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cmd_addroute (struct grub_command *cmd __attribute__ ((unused)),
+ int argc, char **args)
+{
+ grub_net_network_level_netaddress_t target;
+ if (argc < 3)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("three arguments expected"));
+
+ grub_net_resolve_net_address (args[1], &target);
+
+ if (grub_strcmp (args[2], "gw") == 0 && argc >= 4)
+ {
+ grub_err_t err;
+ grub_net_network_level_address_t gw;
+
+ err = grub_net_resolve_address (args[3], &gw);
+ if (err)
+ return err;
+ return grub_net_add_route_gw (args[0], target, gw, NULL);
+ }
+ else
+ {
+ struct grub_net_network_level_interface *inter;
+
+ FOR_NET_NETWORK_LEVEL_INTERFACES (inter)
+ if (grub_strcmp (inter->name, args[2]) == 0)
+ break;
+
+ if (!inter)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("unrecognised network interface `%s'"), args[2]);
+ return grub_net_add_route (args[0], target, inter);
+ }
+}
+
+static void
+print_net_address (const grub_net_network_level_netaddress_t *target)
+{
+ switch (target->type)
+ {
+ case GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV:
+ /* TRANSLATORS: it refers to the network address. */
+ grub_printf ("%s\n", _("temporary"));
+ return;
+ case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4:
+ {
+ grub_uint32_t n = grub_be_to_cpu32 (target->ipv4.base);
+ grub_printf ("%d.%d.%d.%d/%d ", ((n >> 24) & 0xff),
+ ((n >> 16) & 0xff),
+ ((n >> 8) & 0xff),
+ ((n >> 0) & 0xff),
+ 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);
+}
+
+static void
+print_address (const grub_net_network_level_address_t *target)
+{
+ char buf[GRUB_NET_MAX_STR_ADDR_LEN];
+ grub_net_addr_to_str (target, buf);
+ grub_xputs (buf);
+}
+
+static grub_err_t
+grub_cmd_listroutes (struct grub_command *cmd __attribute__ ((unused)),
+ int argc __attribute__ ((unused)),
+ char **args __attribute__ ((unused)))
+{
+ struct grub_net_route *route;
+ FOR_NET_ROUTES(route)
+ {
+ grub_printf ("%s ", route->name);
+ print_net_address (&route->target);
+ if (route->is_gateway)
+ {
+ grub_printf ("gw ");
+ print_address (&route->gw);
+ }
+ else
+ grub_printf ("%s", route->interface->name);
+ grub_printf ("\n");
+ }
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cmd_listcards (struct grub_command *cmd __attribute__ ((unused)),
+ int argc __attribute__ ((unused)),
+ char **args __attribute__ ((unused)))
+{
+ struct grub_net_card *card;
+ FOR_NET_CARDS(card)
+ {
+ 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;
+}
+
+static grub_err_t
+grub_cmd_listaddrs (struct grub_command *cmd __attribute__ ((unused)),
+ int argc __attribute__ ((unused)),
+ char **args __attribute__ ((unused)))
+{
+ struct grub_net_network_level_interface *inf;
+ FOR_NET_NETWORK_LEVEL_INTERFACES (inf)
+ {
+ char bufh[GRUB_NET_MAX_STR_HWADDR_LEN];
+ char bufn[GRUB_NET_MAX_STR_ADDR_LEN];
+ 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);
+ }
+ return GRUB_ERR_NONE;
+}
+
+grub_net_app_level_t grub_net_app_level_list;
+struct grub_net_socket *grub_net_sockets;
+
+static grub_net_t
+grub_net_open_real (const char *name)
+{
+ grub_net_app_level_t proto;
+ const char *protname, *server;
+ grub_size_t protnamelen;
+ int try;
+
+ if (grub_strncmp (name, "pxe:", sizeof ("pxe:") - 1) == 0)
+ {
+ protname = "tftp";
+ protnamelen = sizeof ("tftp") - 1;
+ server = name + sizeof ("pxe:") - 1;
+ }
+ else if (grub_strcmp (name, "pxe") == 0)
+ {
+ protname = "tftp";
+ protnamelen = sizeof ("tftp") - 1;
+ server = grub_net_default_server;
+ }
+ else
+ {
+ const char *comma;
+ comma = grub_strchr (name, ',');
+ if (comma)
+ {
+ protnamelen = comma - name;
+ server = comma + 1;
+ protname = name;
+ }
+ else
+ {
+ protnamelen = grub_strlen (name);
+ server = grub_net_default_server;
+ protname = name;
+ }
+ }
+ if (!server)
+ {
+ grub_error (GRUB_ERR_NET_BAD_ADDRESS,
+ N_("no server is specified"));
+ return NULL;
+ }
+
+ for (try = 0; try < 2; try++)
+ {
+ FOR_NET_APP_LEVEL (proto)
+ {
+ if (grub_memcmp (proto->name, protname, protnamelen) == 0
+ && proto->name[protnamelen] == 0)
+ {
+ grub_net_t ret = grub_zalloc (sizeof (*ret));
+ if (!ret)
+ return NULL;
+ ret->protocol = proto;
+ ret->server = grub_strdup (server);
+ if (!ret->server)
+ {
+ grub_free (ret);
+ return NULL;
+ }
+ ret->fs = &grub_net_fs;
+ return ret;
+ }
+ }
+ if (try == 0)
+ {
+ const char *prefix, *root;
+ char *prefdev, *comma;
+ int skip = 0;
+ grub_size_t devlen;
+
+ /* Do not attempt to load module if it requires protocol provided
+ by this module - it results in infinite recursion. Just continue,
+ fail and cleanup on next iteration.
+ */
+ prefix = grub_env_get ("prefix");
+ if (!prefix)
+ continue;
+
+ prefdev = grub_file_get_device_name (prefix);
+ if (!prefdev)
+ {
+ root = grub_env_get ("root");
+ if (!root)
+ continue;
+ prefdev = grub_strdup (root);
+ if (!prefdev)
+ continue;
+ }
+
+ if (grub_strncmp (prefdev, "pxe", sizeof ("pxe") - 1) == 0 &&
+ (!prefdev[sizeof ("pxe") - 1] || (prefdev[sizeof("pxe") - 1] == ':')))
+ {
+ grub_free (prefdev);
+ prefdev = grub_strdup ("tftp");
+ if (!prefdev)
+ continue;
+ }
+
+ comma = grub_strchr (prefdev, ',');
+ if (comma)
+ *comma = '\0';
+ devlen = grub_strlen (prefdev);
+
+ if (protnamelen == devlen && grub_memcmp (prefdev, protname, devlen) == 0)
+ skip = 1;
+
+ grub_free (prefdev);
+
+ if (skip)
+ continue;
+
+ if (sizeof ("http") - 1 == protnamelen
+ && grub_memcmp ("http", protname, protnamelen) == 0)
+ {
+ grub_dl_load ("http");
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+ if (sizeof ("tftp") - 1 == protnamelen
+ && grub_memcmp ("tftp", protname, protnamelen) == 0)
+ {
+ grub_dl_load ("tftp");
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+ }
+ break;
+ }
+
+ /* Restore original error. */
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("disk `%s' not found"),
+ name);
+
+ return NULL;
+}
+
+static grub_err_t
+grub_net_fs_dir (grub_device_t device, const char *path __attribute__ ((unused)),
+ grub_fs_dir_hook_t hook __attribute__ ((unused)),
+ void *hook_data __attribute__ ((unused)))
+{
+ if (!device->net)
+ return grub_error (GRUB_ERR_BUG, "invalid net device");
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_net_fs_open (struct grub_file *file_out, const char *name)
+{
+ grub_err_t err;
+ struct grub_file *file, *bufio;
+
+ file = grub_malloc (sizeof (*file));
+ if (!file)
+ return grub_errno;
+
+ grub_memcpy (file, file_out, sizeof (struct grub_file));
+ file->device->net->packs.first = NULL;
+ file->device->net->packs.last = NULL;
+ file->device->net->name = grub_strdup (name);
+ if (!file->device->net->name)
+ {
+ grub_free (file);
+ return grub_errno;
+ }
+
+ err = file->device->net->protocol->open (file, name);
+ if (err)
+ {
+ while (file->device->net->packs.first)
+ {
+ grub_netbuff_free (file->device->net->packs.first->nb);
+ grub_net_remove_packet (file->device->net->packs.first);
+ }
+ grub_free (file->device->net->name);
+ grub_free (file);
+ return err;
+ }
+ bufio = grub_bufio_open (file, 32768);
+ if (! bufio)
+ {
+ 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);
+ grub_free (file->device->net->name);
+ grub_free (file);
+ return grub_errno;
+ }
+
+ grub_memcpy (file_out, bufio, sizeof (struct grub_file));
+ grub_free (bufio);
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_net_fs_close (grub_file_t file)
+{
+ 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);
+ grub_free (file->device->net->name);
+ return GRUB_ERR_NONE;
+}
+
+static void
+receive_packets (struct grub_net_card *card, int *stop_condition)
+{
+ int received = 0;
+ if (card->num_ifaces == 0)
+ return;
+ if (!card->opened)
+ {
+ grub_err_t err = GRUB_ERR_NONE;
+ if (card->driver->open)
+ err = card->driver->open (card);
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return;
+ }
+ card->opened = 1;
+ }
+ while (received < 100)
+ {
+ /* 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;
+
+ if (received > 10 && stop_condition && *stop_condition)
+ break;
+
+ nb = card->driver->recv (card);
+ if (!nb)
+ {
+ card->last_poll = grub_get_time_ms ();
+ break;
+ }
+ received++;
+ 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 ();
+}
+
+static char *
+grub_env_write_readonly (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val __attribute__ ((unused)))
+{
+ return NULL;
+}
+
+grub_err_t
+grub_env_set_net_property (const char *intername, const char *suffix,
+ const char *value, grub_size_t len)
+{
+ char *varname, *varvalue;
+ char *ptr;
+
+ varname = grub_xasprintf ("net_%s_%s", intername, suffix);
+ if (!varname)
+ return grub_errno;
+ for (ptr = varname; *ptr; ptr++)
+ if (*ptr == ':')
+ *ptr = '_';
+ varvalue = grub_malloc (len + 1);
+ if (!varvalue)
+ {
+ grub_free (varname);
+ return grub_errno;
+ }
+
+ grub_memcpy (varvalue, value, len);
+ varvalue[len] = 0;
+ grub_err_t ret = grub_env_set (varname, varvalue);
+ grub_register_variable_hook (varname, 0, grub_env_write_readonly);
+ grub_env_export (varname);
+ grub_free (varname);
+ grub_free (varvalue);
+
+ return ret;
+}
+
+void
+grub_net_poll_cards (unsigned time, int *stop_condition)
+{
+ struct grub_net_card *card;
+ grub_uint64_t start_time;
+ start_time = grub_get_time_ms ();
+ while ((grub_get_time_ms () - start_time) < time
+ && (!stop_condition || !*stop_condition))
+ FOR_NET_CARDS (card)
+ receive_packets (card, stop_condition);
+ grub_net_tcp_retransmit ();
+}
+
+static void
+grub_net_poll_cards_idle_real (void)
+{
+ struct grub_net_card *card;
+ FOR_NET_CARDS (card)
+ {
+ grub_uint64_t ctime = grub_get_time_ms ();
+
+ if (ctime < card->last_poll
+ || ctime >= card->last_poll + card->idle_poll_delay_ms)
+ receive_packets (card, 0);
+ }
+ 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 net = file->device->net;
+ struct grub_net_buff *nb;
+ char *ptr = buf;
+ grub_size_t amount, total = 0;
+ int try = 0;
+
+ while (try <= GRUB_NET_TRIES)
+ {
+ while (net->packs.first)
+ {
+ try = 0;
+ nb = net->packs.first->nb;
+ amount = nb->tail - nb->data;
+ if (amount > len)
+ amount = len;
+ len -= amount;
+ total += amount;
+ file->device->net->offset += amount;
+ if (grub_file_progress_hook)
+ grub_file_progress_hook (0, 0, amount, file);
+ if (buf)
+ {
+ grub_memcpy (ptr, nb->data, amount);
+ ptr += amount;
+ }
+ if (amount == (grub_size_t) (nb->tail - nb->data))
+ {
+ grub_netbuff_free (nb);
+ grub_net_remove_packet (net->packs.first);
+ }
+ else
+ nb->data += amount;
+
+ if (!len)
+ {
+ if (net->protocol->packets_pulled)
+ net->protocol->packets_pulled (file);
+ return total;
+ }
+ }
+ if (net->protocol->packets_pulled)
+ net->protocol->packets_pulled (file);
+
+ if (!net->eof)
+ {
+ try++;
+ grub_net_poll_cards (GRUB_NET_INTERVAL +
+ (try * GRUB_NET_INTERVAL_ADDITION), &net->stall);
+ }
+ else
+ return total;
+ }
+ grub_error (GRUB_ERR_TIMEOUT, N_("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)
+{
+ if (offset == file->device->net->offset)
+ return GRUB_ERR_NONE;
+
+ if (offset > file->device->net->offset)
+ {
+ if (!file->device->net->protocol->seek || have_ahead (file) >= offset)
+ {
+ grub_net_fs_read_real (file, NULL,
+ offset - file->device->net->offset);
+ return grub_errno;
+ }
+ return file->device->net->protocol->seek (file, offset);
+ }
+
+ {
+ 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;
+ file->device->net->stall = 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
+grub_net_fs_read (grub_file_t file, char *buf, grub_size_t len)
+{
+ if (file->offset != file->device->net->offset)
+ {
+ grub_err_t err;
+ err = grub_net_seek_real (file, file->offset);
+ if (err)
+ return err;
+ }
+ return grub_net_fs_read_real (file, buf, len);
+}
+
+static struct grub_fs grub_net_fs =
+ {
+ .name = "netfs",
+ .fs_dir = grub_net_fs_dir,
+ .fs_open = grub_net_fs_open,
+ .fs_read = grub_net_fs_read,
+ .fs_close = grub_net_fs_close,
+ .fs_label = NULL,
+ .fs_uuid = NULL,
+ .fs_mtime = NULL,
+ };
+
+static grub_err_t
+grub_net_fini_hw (int noreturn __attribute__ ((unused)))
+{
+ struct grub_net_card *card;
+ FOR_NET_CARDS (card)
+ if (card->opened)
+ {
+ if (card->driver->close)
+ card->driver->close (card);
+ card->opened = 0;
+ }
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_net_restore_hw (void)
+{
+ return GRUB_ERR_NONE;
+}
+
+static int
+grub_config_search_through (char *config, char *suffix,
+ grub_size_t num_tries, grub_size_t slice_size)
+{
+ while (num_tries-- > 0)
+ {
+ grub_file_t file;
+
+ grub_dprintf ("net", "attempt to fetch config %s\n", config);
+
+ file = grub_file_open (config, GRUB_FILE_TYPE_CONFIG);
+
+ if (file)
+ {
+ grub_file_close (file);
+ return 0;
+ }
+ else
+ {
+ if (grub_errno == GRUB_ERR_IO)
+ grub_errno = GRUB_ERR_NONE;
+ }
+
+ if (grub_strlen (suffix) < slice_size)
+ break;
+
+ config[grub_strlen (config) - slice_size] = '\0';
+ }
+
+ return 1;
+}
+
+grub_err_t
+grub_net_search_config_file (char *config)
+{
+ grub_size_t config_len;
+ char *suffix;
+
+ config_len = grub_strlen (config);
+ config[config_len] = '-';
+ suffix = config + config_len + 1;
+
+ struct grub_net_network_level_interface *inf;
+ FOR_NET_NETWORK_LEVEL_INTERFACES (inf)
+ {
+ /* By the Client UUID. */
+ char *ptr;
+ int client_uuid_len;
+ char *client_uuid_var;
+ const char *client_uuid;
+
+ client_uuid_len = sizeof ("net_") + grub_strlen (inf->name) +
+ sizeof ("_clientuuid") + 1;
+
+ client_uuid_var = grub_zalloc (client_uuid_len);
+ if (!client_uuid_var)
+ return grub_errno;
+
+ grub_snprintf (client_uuid_var, client_uuid_len,
+ "net_%s_clientuuid", inf->name);
+
+ client_uuid = grub_env_get (client_uuid_var);
+ grub_free (client_uuid_var);
+
+ if (client_uuid)
+ {
+ grub_strcpy (suffix, client_uuid);
+ if (grub_config_search_through (config, suffix, 1, 0) == 0)
+ return GRUB_ERR_NONE;
+ }
+
+ /* By the MAC address. */
+
+ /* Add ethernet type */
+ grub_strcpy (suffix, "01-");
+
+ grub_net_hwaddr_to_str (&inf->hwaddress, suffix + 3);
+
+ for (ptr = suffix; *ptr; ptr++)
+ if (*ptr == ':')
+ *ptr = '-';
+
+ if (grub_config_search_through (config, suffix, 1, 0) == 0)
+ return GRUB_ERR_NONE;
+
+ /* By IP address */
+
+ switch ((&inf->address)->type)
+ {
+ case GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4:
+ {
+ grub_uint32_t n = grub_be_to_cpu32 ((&inf->address)->ipv4);
+
+ grub_snprintf (suffix, GRUB_NET_MAX_STR_ADDR_LEN, "%02X%02X%02X%02X", \
+ ((n >> 24) & 0xff), ((n >> 16) & 0xff), \
+ ((n >> 8) & 0xff), ((n >> 0) & 0xff));
+
+ if (grub_config_search_through (config, suffix, 8, 1) == 0)
+ return GRUB_ERR_NONE;
+ break;
+ }
+ 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, ((&inf->address)->ipv6), 16);
+ grub_net_addr_to_str (&base, buf);
+
+ for (ptr = buf; *ptr; ptr++)
+ if (*ptr == ':')
+ *ptr = '-';
+
+ grub_snprintf (suffix, GRUB_NET_MAX_STR_ADDR_LEN, "%s", buf);
+ if (grub_config_search_through (config, suffix, 1, 0) == 0)
+ return GRUB_ERR_NONE;
+ 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_BUG,
+ "unsupported address type %d", (&inf->address)->type);
+ }
+ }
+
+ /* Remove the remaining minus sign at the end. */
+ config[config_len] = '\0';
+
+ return GRUB_ERR_NONE;
+}
+
+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, cmd_slaac;
+
+GRUB_MOD_INIT(net)
+{
+ grub_register_variable_hook ("net_default_server", defserver_get_env,
+ defserver_set_env);
+ grub_env_export ("net_default_server");
+ grub_register_variable_hook ("pxe_default_server", defserver_get_env,
+ defserver_set_env);
+ grub_env_export ("pxe_default_server");
+ grub_register_variable_hook ("net_default_ip", defip_get_env,
+ defip_set_env);
+ grub_env_export ("net_default_ip");
+ grub_register_variable_hook ("net_default_mac", defmac_get_env,
+ defmac_set_env);
+ grub_env_export ("net_default_mac");
+
+ cmd_addaddr = grub_register_command ("net_add_addr", grub_cmd_addaddr,
+ /* TRANSLATORS: HWADDRESS stands for
+ "hardware address". */
+ N_("SHORTNAME CARD ADDRESS [HWADDRESS]"),
+ N_("Add a network address."));
+ cmd_slaac = grub_register_command ("net_ipv6_autoconf",
+ grub_cmd_ipv6_autoconf,
+ N_("[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."));
+ cmd_addroute = grub_register_command ("net_add_route", grub_cmd_addroute,
+ /* TRANSLATORS: "gw" is a keyword. */
+ N_("SHORTNAME NET [INTERFACE| gw GATEWAY]"),
+ N_("Add a network route."));
+ cmd_delroute = grub_register_command ("net_del_route", grub_cmd_delroute,
+ N_("SHORTNAME"),
+ N_("Delete a network route."));
+ cmd_lsroutes = grub_register_command ("net_ls_routes", grub_cmd_listroutes,
+ "", N_("list network routes"));
+ cmd_lscards = grub_register_command ("net_ls_cards", grub_cmd_listcards,
+ "", N_("list network cards"));
+ cmd_lsaddr = grub_register_command ("net_ls_addr", grub_cmd_listaddrs,
+ "", N_("list network addresses"));
+ grub_bootp_init ();
+ grub_dns_init ();
+
+ grub_net_open = grub_net_open_real;
+ fini_hnd = grub_loader_register_preboot_hook (grub_net_fini_hw,
+ grub_net_restore_hw,
+ GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
+ grub_net_poll_cards_idle = grub_net_poll_cards_idle_real;
+}
+
+GRUB_MOD_FINI(net)
+{
+ grub_register_variable_hook ("net_default_server", 0, 0);
+ grub_register_variable_hook ("pxe_default_server", 0, 0);
+
+ grub_bootp_fini ();
+ grub_dns_fini ();
+ grub_unregister_command (cmd_addaddr);
+ grub_unregister_command (cmd_deladdr);
+ grub_unregister_command (cmd_addroute);
+ grub_unregister_command (cmd_delroute);
+ 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);
+ grub_loader_unregister_preboot_hook (fini_hnd);
+ grub_net_poll_cards_idle = grub_net_poll_cards_idle_real;
+}
diff --git a/grub-core/net/netbuff.c b/grub-core/net/netbuff.c
new file mode 100644
index 0000000..dbeeefe
--- /dev/null
+++ b/grub-core/net/netbuff.c
@@ -0,0 +1,133 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/net/netbuff.h>
+
+grub_err_t
+grub_netbuff_put (struct grub_net_buff *nb, grub_size_t len)
+{
+ nb->tail += len;
+ if (nb->tail > nb->end)
+ return grub_error (GRUB_ERR_BUG, "put out of the packet range.");
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_netbuff_unput (struct grub_net_buff *nb, grub_size_t len)
+{
+ nb->tail -= len;
+ if (nb->tail < nb->head)
+ return grub_error (GRUB_ERR_BUG,
+ "unput out of the packet range.");
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_netbuff_push (struct grub_net_buff *nb, grub_size_t len)
+{
+ nb->data -= len;
+ if (nb->data < nb->head)
+ return grub_error (GRUB_ERR_BUG,
+ "push out of the packet range.");
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_netbuff_pull (struct grub_net_buff *nb, grub_size_t len)
+{
+ nb->data += len;
+ if (nb->data > nb->end)
+ return grub_error (GRUB_ERR_BUG,
+ "pull out of the packet range.");
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_netbuff_reserve (struct grub_net_buff *nb, grub_size_t len)
+{
+ nb->data += len;
+ nb->tail += len;
+ if ((nb->tail > nb->end) || (nb->data > nb->end))
+ return grub_error (GRUB_ERR_BUG,
+ "reserve out of the packet range.");
+ return GRUB_ERR_NONE;
+}
+
+struct grub_net_buff *
+grub_netbuff_alloc (grub_size_t len)
+{
+ struct grub_net_buff *nb;
+ void *data;
+
+ COMPILE_TIME_ASSERT (NETBUFF_ALIGN % sizeof (grub_properly_aligned_t) == 0);
+
+ if (len < NETBUFFMINLEN)
+ len = NETBUFFMINLEN;
+
+ len = ALIGN_UP (len, NETBUFF_ALIGN);
+#ifdef GRUB_MACHINE_EMU
+ data = grub_malloc (len + sizeof (*nb));
+#else
+ data = grub_memalign (NETBUFF_ALIGN, len + sizeof (*nb));
+#endif
+ if (!data)
+ return NULL;
+ 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 = (grub_uint8_t *) nb;
+ return nb;
+}
+
+struct grub_net_buff *
+grub_netbuff_make_pkt (grub_size_t len)
+{
+ struct grub_net_buff *nb;
+ grub_err_t err;
+ nb = grub_netbuff_alloc (len + 512);
+ if (!nb)
+ return NULL;
+ err = grub_netbuff_reserve (nb, len + 512);
+ if (err)
+ goto fail;
+ err = grub_netbuff_push (nb, len);
+ if (err)
+ goto fail;
+ return nb;
+ fail:
+ grub_netbuff_free (nb);
+ return NULL;
+}
+
+void
+grub_netbuff_free (struct grub_net_buff *nb)
+{
+ if (!nb)
+ return;
+ grub_free (nb->head);
+}
+
+grub_err_t
+grub_netbuff_clear (struct grub_net_buff *nb)
+{
+ nb->data = nb->tail = nb->head;
+ return GRUB_ERR_NONE;
+}
diff --git a/grub-core/net/tcp.c b/grub-core/net/tcp.c
new file mode 100644
index 0000000..e8ad34b
--- /dev/null
+++ b/grub-core/net/tcp.c
@@ -0,0 +1,1020 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net.h>
+#include <grub/net/ip.h>
+#include <grub/net/tcp.h>
+#include <grub/net/netbuff.h>
+#include <grub/time.h>
+#include <grub/priority_queue.h>
+
+#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 unacked **prev;
+ 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;
+ struct grub_net_tcp_socket **prev;
+
+ int established;
+ int i_closed;
+ int they_closed;
+ int in_port;
+ int out_port;
+ int errors;
+ int they_reseted;
+ int i_reseted;
+ int i_stall;
+ 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;
+ struct grub_net_tcp_listen **prev;
+
+ 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;
+} GRUB_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;
+} GRUB_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;
+} GRUB_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 (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 = !sock->i_stall ? grub_cpu_to_be16 (sock->my_window)
+ : 0;
+ }
+ 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;
+ grub_net_network_level_address_t gateway;
+ struct grub_net_network_level_interface *inf;
+
+ sock->recv_hook = recv_hook;
+ sock->error_hook = error_hook;
+ sock->fin_hook = fin_hook;
+ sock->hook_data = hook_data;
+
+ err = grub_net_route_address (sock->out_nla, &gateway, &inf);
+ if (err)
+ return err;
+
+ err = grub_net_link_layer_resolve (sock->inf, &gateway, &(sock->ll_target_addr));
+ if (err)
+ return err;
+
+ 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_BUG, "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)
+ {
+ grub_free (socket);
+ return NULL;
+ }
+
+ err = grub_netbuff_reserve (nb, 128);
+ if (err)
+ {
+ grub_free (socket);
+ grub_netbuff_free (nb);
+ return NULL;
+ }
+
+ err = grub_netbuff_put (nb, sizeof (*tcph));
+ if (err)
+ {
+ grub_free (socket);
+ grub_netbuff_free (nb);
+ return NULL;
+ }
+ socket->pq = grub_priority_queue_new (sizeof (struct grub_net_buff *), cmp);
+ if (!socket->pq)
+ {
+ grub_free (socket);
+ 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 (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, &socket->established);
+ if (socket->established)
+ break;
+ }
+ if (!socket->established)
+ {
+ grub_list_remove (GRUB_AS_LIST (socket));
+ if (socket->they_reseted)
+ grub_error (GRUB_ERR_NET_PORT_CLOSED,
+ N_("connection refused"));
+ else
+ grub_error (GRUB_ERR_NET_NO_ANSWER,
+ N_("connection timeout"));
+
+ 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 = !socket->i_stall ? grub_cpu_to_be16 (socket->my_window)
+ : 0;
+ 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 = !socket->i_stall ? grub_cpu_to_be16 (socket->my_window) : 0;
+ 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",
+ (grub_size_t) (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)
+ {
+ ack (sock);
+ 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_top);
+ }
+ 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_free (sock);
+ 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;
+}
+
+void
+grub_net_tcp_stall (grub_net_tcp_socket_t sock)
+{
+ if (sock->i_stall)
+ return;
+ sock->i_stall = 1;
+ ack (sock);
+}
+
+void
+grub_net_tcp_unstall (grub_net_tcp_socket_t sock)
+{
+ if (!sock->i_stall)
+ return;
+ sock->i_stall = 0;
+ ack (sock);
+}
diff --git a/grub-core/net/tftp.c b/grub-core/net/tftp.c
new file mode 100644
index 0000000..7f44b30
--- /dev/null
+++ b/grub-core/net/tftp.c
@@ -0,0 +1,479 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/misc.h>
+#include <grub/net/udp.h>
+#include <grub/net/ip.h>
+#include <grub/net/ethernet.h>
+#include <grub/net/netbuff.h>
+#include <grub/net.h>
+#include <grub/mm.h>
+#include <grub/dl.h>
+#include <grub/file.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* IP port for the MTFTP server used for Intel's PXE */
+enum
+ {
+ MTFTP_SERVER_PORT = 75,
+ MTFTP_CLIENT_PORT = 76,
+ /* IP port for the TFTP server */
+ TFTP_SERVER_PORT = 69
+ };
+
+enum
+ {
+ TFTP_DEFAULTSIZE_PACKET = 512,
+ };
+
+enum
+ {
+ TFTP_CODE_EOF = 1,
+ TFTP_CODE_MORE = 2,
+ TFTP_CODE_ERROR = 3,
+ TFTP_CODE_BOOT = 4,
+ TFTP_CODE_CFG = 5
+ };
+
+enum
+ {
+ TFTP_RRQ = 1,
+ TFTP_WRQ = 2,
+ TFTP_DATA = 3,
+ TFTP_ACK = 4,
+ TFTP_ERROR = 5,
+ TFTP_OACK = 6
+ };
+
+enum
+ {
+ TFTP_EUNDEF = 0, /* not defined */
+ TFTP_ENOTFOUND = 1, /* file not found */
+ TFTP_EACCESS = 2, /* access violation */
+ TFTP_ENOSPACE = 3, /* disk full or allocation exceeded */
+ TFTP_EBADOP = 4, /* illegal TFTP operation */
+ TFTP_EBADID = 5, /* unknown transfer ID */
+ TFTP_EEXISTS = 6, /* file already exists */
+ TFTP_ENOUSER = 7 /* no such user */
+ };
+
+struct tftphdr {
+ grub_uint16_t opcode;
+ union {
+ grub_int8_t rrq[TFTP_DEFAULTSIZE_PACKET];
+ struct {
+ grub_uint16_t block;
+ grub_int8_t download[0];
+ } data;
+ struct {
+ grub_uint16_t block;
+ } ack;
+ struct {
+ grub_uint16_t errcode;
+ grub_int8_t errmsg[TFTP_DEFAULTSIZE_PACKET];
+ } err;
+ struct {
+ grub_int8_t data[TFTP_DEFAULTSIZE_PACKET+2];
+ } oack;
+ } u;
+} GRUB_PACKED ;
+
+
+typedef struct tftp_data
+{
+ grub_uint64_t file_size;
+ grub_uint64_t block;
+ grub_uint32_t block_size;
+ grub_uint64_t ack_sent;
+ int have_oack;
+ struct grub_error_saved save_err;
+ grub_net_udp_socket_t sock;
+} *tftp_data_t;
+
+static grub_err_t
+ack (tftp_data_t data, grub_uint64_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_compile_time (TFTP_ACK);
+ tftph_ack->u.ack.block = grub_cpu_to_be16 (block);
+
+ err = grub_net_send_udp_packet (data->sock, &nb_ack);
+ if (err)
+ return err;
+ data->ack_sent = block;
+ return GRUB_ERR_NONE;
+}
+
+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;
+ tftp_data_t data = file->data;
+ grub_err_t err;
+ grub_uint8_t *ptr;
+
+ if (nb->tail - nb->data < (grub_ssize_t) sizeof (tftph->opcode))
+ {
+ grub_dprintf ("tftp", "TFTP packet too small\n");
+ return GRUB_ERR_NONE;
+ }
+
+ tftph = (struct tftphdr *) nb->data;
+ switch (grub_be_to_cpu16 (tftph->opcode))
+ {
+ case TFTP_OACK:
+ data->block_size = TFTP_DEFAULTSIZE_PACKET;
+ data->have_oack = 1;
+ 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 ((char *) ptr + sizeof ("tsize\0")
+ - 1, 0, 0);
+ if (grub_memcmp (ptr, "blksize\0", sizeof ("blksize\0") - 1) == 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);
+ err = ack (data, 0);
+ grub_error_save (&data->save_err);
+ return GRUB_ERR_NONE;
+ case TFTP_DATA:
+ if (nb->tail - nb->data < (grub_ssize_t) (sizeof (tftph->opcode)
+ + sizeof (tftph->u.data.block)))
+ {
+ grub_dprintf ("tftp", "TFTP packet too small\n");
+ return GRUB_ERR_NONE;
+ }
+
+ /*
+ * Ack old/retransmitted block.
+ *
+ * The block number is a 16-bit counter, thus the maximum file size that
+ * could be transfered is 65535 * block size. Most TFTP hosts support to
+ * roll-over the block counter to allow unlimited transfer file size.
+ *
+ * This behavior is not defined in the RFC 1350 [0] but is implemented by
+ * most TFTP clients and hosts.
+ *
+ * [0]: https://tools.ietf.org/html/rfc1350
+ */
+ if (grub_be_to_cpu16 (tftph->u.data.block) < ((grub_uint16_t) (data->block + 1)))
+ ack (data, grub_be_to_cpu16 (tftph->u.data.block));
+ /* Ignore unexpected block. */
+ else if (grub_be_to_cpu16 (tftph->u.data.block) > ((grub_uint16_t) (data->block + 1)))
+ grub_dprintf ("tftp", "TFTP unexpected block # %d\n", tftph->u.data.block);
+ else
+ {
+ unsigned size;
+
+ if (file->device->net->packs.count < 50)
+ {
+ err = ack (data, data->block + 1);
+ if (err)
+ return err;
+ }
+ else
+ file->device->net->stall = 1;
+
+ err = grub_netbuff_pull (nb, sizeof (tftph->opcode) +
+ sizeof (tftph->u.data.block));
+ if (err)
+ return err;
+ size = nb->tail - nb->data;
+
+ data->block++;
+ if (size < data->block_size)
+ {
+ if (data->ack_sent < data->block)
+ ack (data, data->block);
+ file->device->net->eof = 1;
+ file->device->net->stall = 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, 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);
+ /* Do not free nb. */
+ return GRUB_ERR_NONE;
+ }
+ }
+ grub_netbuff_free (nb);
+ return GRUB_ERR_NONE;
+ case TFTP_ERROR:
+ data->have_oack = 1;
+ grub_netbuff_free (nb);
+ grub_error (GRUB_ERR_IO, "%s", tftph->u.err.errmsg);
+ grub_error_save (&data->save_err);
+ return GRUB_ERR_NONE;
+ default:
+ grub_netbuff_free (nb);
+ return GRUB_ERR_NONE;
+ }
+}
+
+/*
+ * Create a normalized copy of the filename. Compress any string of consecutive
+ * forward slashes to a single forward slash.
+ */
+static void
+grub_normalize_filename (char *normalized, const char *filename)
+{
+ char *dest = normalized;
+ const char *src = filename;
+
+ while (*src != '\0')
+ {
+ if (src[0] == '/' && src[1] == '/')
+ src++;
+ else
+ *dest++ = *src++;
+ }
+ *dest = '\0';
+}
+
+static grub_err_t
+tftp_open (struct grub_file *file, const char *filename)
+{
+ struct tftphdr *tftph;
+ char *rrq;
+ int i;
+ int rrqlen;
+ int hdrlen;
+ 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)
+ return grub_errno;
+
+ nb.head = open_data;
+ nb.end = open_data + sizeof (open_data);
+ grub_netbuff_clear (&nb);
+
+ grub_netbuff_reserve (&nb, 1500);
+ err = grub_netbuff_push (&nb, sizeof (*tftph));
+ if (err)
+ {
+ grub_free (data);
+ return err;
+ }
+
+ tftph = (struct tftphdr *) nb.data;
+
+ rrq = (char *) tftph->u.rrq;
+ rrqlen = 0;
+
+ tftph->opcode = grub_cpu_to_be16_compile_time (TFTP_RRQ);
+
+ /*
+ * Copy and normalize the filename to work-around issues on some TFTP
+ * servers when file names are being matched for remapping.
+ */
+ grub_normalize_filename (rrq, filename);
+ rrqlen += grub_strlen (rrq) + 1;
+ rrq += grub_strlen (rrq) + 1;
+
+ grub_strcpy (rrq, "octet");
+ rrqlen += grub_strlen ("octet") + 1;
+ rrq += grub_strlen ("octet") + 1;
+
+ grub_strcpy (rrq, "blksize");
+ rrqlen += grub_strlen ("blksize") + 1;
+ rrq += grub_strlen ("blksize") + 1;
+
+ grub_strcpy (rrq, "1024");
+ rrqlen += grub_strlen ("1024") + 1;
+ rrq += grub_strlen ("1024") + 1;
+
+ grub_strcpy (rrq, "tsize");
+ rrqlen += grub_strlen ("tsize") + 1;
+ rrq += grub_strlen ("tsize") + 1;
+
+ grub_strcpy (rrq, "0");
+ rrqlen += grub_strlen ("0") + 1;
+ rrq += grub_strlen ("0") + 1;
+ hdrlen = sizeof (tftph->opcode) + rrqlen;
+
+ err = grub_netbuff_unput (&nb, nb.tail - (nb.data + hdrlen));
+ if (err)
+ {
+ grub_free (data);
+ return err;
+ }
+
+ file->not_easily_seekable = 1;
+ file->data = data;
+
+ err = grub_net_resolve_address (file->device->net->server, &addr);
+ if (err)
+ {
+ grub_free (data);
+ return err;
+ }
+
+ data->sock = grub_net_udp_open (addr,
+ TFTP_SERVER_PORT, tftp_receive,
+ file);
+ if (!data->sock)
+ {
+ grub_free (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);
+ grub_free (data);
+ return err;
+ }
+ grub_net_poll_cards (GRUB_NET_INTERVAL + (i * GRUB_NET_INTERVAL_ADDITION),
+ &data->have_oack);
+ if (data->have_oack)
+ break;
+ }
+
+ if (!data->have_oack)
+ grub_error (GRUB_ERR_TIMEOUT, N_("time out opening `%s'"), filename);
+ else
+ grub_error_load (&data->save_err);
+ if (grub_errno)
+ {
+ grub_net_udp_close (data->sock);
+ grub_free (data);
+ return grub_errno;
+ }
+
+ file->size = data->file_size;
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+tftp_close (struct grub_file *file)
+{
+ tftp_data_t data = file->data;
+
+ if (data->sock)
+ {
+ grub_uint8_t nbdata[512];
+ grub_err_t err;
+ struct grub_net_buff nb_err;
+ struct tftphdr *tftph;
+
+ nb_err.head = nbdata;
+ nb_err.end = nbdata + sizeof (nbdata);
+
+ grub_netbuff_clear (&nb_err);
+ grub_netbuff_reserve (&nb_err, 512);
+ err = grub_netbuff_push (&nb_err, sizeof (tftph->opcode)
+ + sizeof (tftph->u.err.errcode)
+ + sizeof ("closed"));
+ if (!err)
+ {
+ tftph = (struct tftphdr *) nb_err.data;
+ tftph->opcode = grub_cpu_to_be16_compile_time (TFTP_ERROR);
+ tftph->u.err.errcode = grub_cpu_to_be16_compile_time (TFTP_EUNDEF);
+ grub_memcpy (tftph->u.err.errmsg, "closed", sizeof ("closed"));
+
+ err = grub_net_send_udp_packet (data->sock, &nb_err);
+ }
+ if (err)
+ grub_print_error ();
+ grub_net_udp_close (data->sock);
+ }
+ grub_free (data);
+ file->data = NULL;
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+tftp_packets_pulled (struct grub_file *file)
+{
+ tftp_data_t data = file->data;
+ if (file->device->net->packs.count >= 50)
+ return 0;
+
+ if (!file->device->net->eof)
+ file->device->net->stall = 0;
+ if (data->ack_sent >= data->block)
+ return 0;
+ return ack (data, data->block);
+}
+
+static struct grub_net_app_protocol grub_tftp_protocol =
+ {
+ .name = "tftp",
+ .open = tftp_open,
+ .close = tftp_close,
+ .packets_pulled = tftp_packets_pulled
+ };
+
+GRUB_MOD_INIT (tftp)
+{
+ grub_net_app_level_register (&grub_tftp_protocol);
+}
+
+GRUB_MOD_FINI (tftp)
+{
+ grub_net_app_level_unregister (&grub_tftp_protocol);
+}
diff --git a/grub-core/net/udp.c b/grub-core/net/udp.c
new file mode 100644
index 0000000..df7fb95
--- /dev/null
+++ b/grub-core/net/udp.c
@@ -0,0 +1,211 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/net.h>
+#include <grub/net/udp.h>
+#include <grub/net/ip.h>
+#include <grub/net/netbuff.h>
+#include <grub/time.h>
+
+struct grub_net_udp_socket
+{
+ struct grub_net_udp_socket *next;
+ struct grub_net_udp_socket **prev;
+
+ 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 (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_udp_socket_t sock,
+ struct grub_net_buff *nb,
+ void *data),
+ void *recv_hook_data)
+{
+ grub_err_t err;
+ struct grub_net_network_level_interface *inf;
+ grub_net_network_level_address_t gateway;
+ grub_net_udp_socket_t socket;
+ static int in_port = 25300;
+ grub_net_link_level_address_t ll_target_addr;
+
+ if (addr.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4
+ && addr.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6)
+ {
+ grub_error (GRUB_ERR_BUG, "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->status = GRUB_NET_SOCKET_START;
+ socket->recv_hook = recv_hook;
+ socket->recv_hook_data = recv_hook_data;
+
+ udp_socket_register (socket);
+
+ return socket;
+}
+
+grub_err_t
+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->in_port);
+ udph->dst = grub_cpu_to_be16 (socket->out_port);
+
+ udph->chksum = 0;
+ udph->len = grub_cpu_to_be16 (nb->tail - nb->data);
+
+ 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,
+ const grub_net_network_level_address_t *source)
+{
+ struct udphdr *udph;
+ grub_net_udp_socket_t sock;
+ grub_err_t err;
+
+ /* 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",
+ (grub_size_t) (nb->tail - nb->data));
+ grub_netbuff_free (nb);
+ return GRUB_ERR_NONE;
+ }
+
+ FOR_UDP_SOCKETS (sock)
+ {
+ 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 (udph->chksum)
+ {
+ 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. */
+ if (sock->recv_hook)
+ sock->recv_hook (sock, nb, sock->recv_hook_data);
+ else
+ grub_netbuff_free (nb);
+ return GRUB_ERR_NONE;
+ }
+ }
+ grub_netbuff_free (nb);
+ return GRUB_ERR_NONE;
+}