]> git.proxmox.com Git - grub2.git/commitdiff
basic tcp implementation
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sun, 10 Jul 2011 06:46:48 +0000 (08:46 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sun, 10 Jul 2011 06:46:48 +0000 (08:46 +0200)
12 files changed:
grub-core/Makefile.core.def
grub-core/net/bootp.c
grub-core/net/ip.c
grub-core/net/net.c
grub-core/net/tcp.c [new file with mode: 0644]
grub-core/net/tftp.c
grub-core/net/udp.c
include/grub/err.h
include/grub/net.h
include/grub/net/ip.h
include/grub/net/tcp.h [new file with mode: 0644]
include/grub/net/udp.h

index 9bc8935958e1241da55af74e1259d6257b4ef421..673092b4fe508b77e534da5a02761a3cafb93d92 100644 (file)
@@ -1597,6 +1597,7 @@ module = {
   common = net/bootp.c;
   common = net/ip.c;
   common = net/udp.c;
+  common = net/tcp.c;
   common = net/icmp.c;
   common = net/ethernet.c;
   common = net/arp.c;
index 1428c2a07fc71be109bc5d4db1898770c515711e..b1db0514776d33f1659fe6cece7331f798426a6b 100644 (file)
@@ -473,7 +473,7 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)),
              t = 0;
            }
          pack->ident = grub_cpu_to_be32 (t);
-         pack->seconds = 0;//grub_cpu_to_be16 (t);
+         pack->seconds = grub_cpu_to_be16 (t);
 
          grub_memcpy (&pack->mac_addr, &ifaces[j].hwaddress.mac, 6); 
 
@@ -484,9 +484,11 @@ grub_cmd_bootp (struct grub_command *cmd __attribute__ ((unused)),
          udph->dst = grub_cpu_to_be16 (67);
          udph->chksum = 0;
          udph->len = grub_cpu_to_be16 (nb->tail - nb->data);
-
          target.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
          target.ipv4 = 0xffffffff;
+         udph->chksum = grub_net_ip_transport_checksum (nb, GRUB_NET_IP_UDP,
+                                                        &ifaces[j].address,
+                                                        &target);
 
          err = grub_net_send_ip_packet (&ifaces[j], &target, nb,
                                         GRUB_NET_IP_UDP);
index 9144eabc62b282b1d6fd9bc8c1a67613bb0c36c7..69579970f658c7473a069508c497b8edf8769f4d 100644 (file)
@@ -100,13 +100,13 @@ grub_net_ip_chksum (void *ipv, grub_size_t len)
   for (; len >= 2; len -= 2)
     {
       sum += grub_be_to_cpu16 (*(ip++));
-      if (sum > 0xFFFF)
+      if (sum >= 0xFFFF)
        sum -= 0xFFFF;
     }
   if (len)
     {
       sum += *((grub_uint8_t *) ip) << 8;
-      if (sum > 0xFFFF)
+      if (sum >= 0xFFFF)
        sum -= 0xFFFF;
     }
 
@@ -229,6 +229,13 @@ handle_dgram (struct grub_net_buff *nb,
   struct grub_net_network_level_interface *inf = NULL;
   grub_err_t err;
   grub_net_network_level_address_t source;
+  grub_net_network_level_address_t dest;
+
+  source.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+  source.ipv4 = src;
+
+  dest.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
+  dest.ipv4 = dst;
 
   /* DHCP needs special treatment since we don't know IP yet.  */
   {
@@ -241,6 +248,27 @@ handle_dgram (struct grub_net_buff *nb,
              && inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV
              && grub_net_hwaddr_cmp (&inf->hwaddress, hwaddress) == 0)
            {
+             if (udph->chksum)
+               {
+                 grub_uint16_t chk, expected;
+                 chk = udph->chksum;
+                 udph->chksum = 0;
+                 expected = grub_net_ip_transport_checksum (nb,
+                                                            GRUB_NET_IP_UDP,
+                                                            &source,
+                                                            &dest);
+                 if (expected != chk)
+                   {
+                     grub_dprintf ("net", "Invalid UDP checksum. "
+                                   "Expected %x, got %x\n", 
+                                   grub_be_to_cpu16 (expected),
+                                   grub_be_to_cpu16 (chk));
+                     grub_netbuff_free (nb);
+                     return GRUB_ERR_NONE;
+                   }
+                 udph->chksum = chk;
+               }
+
              err = grub_netbuff_pull (nb, sizeof (*udph));
              if (err)
                return err;
@@ -268,13 +296,12 @@ handle_dgram (struct grub_net_buff *nb,
       return GRUB_ERR_NONE;
     }
 
-  source.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
-  source.ipv4 = src;
-
   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);
     default:
index ab0fbd3c5ad08e1c7a4f8282892412254caaf550..0ff8d01ff829e2b0708ad99ba46411ef99322b7d 100644 (file)
@@ -793,6 +793,7 @@ grub_net_poll_cards (unsigned time)
       while ((grub_get_time_ms () - start_time) < time)        
        receive_packets (card);
     }
+  grub_net_tcp_retransmit ();
 }
 
 static void
@@ -807,6 +808,7 @@ grub_net_poll_cards_idle_real (void)
        || ctime >= card->last_poll + card->idle_poll_delay_ms)
       receive_packets (card);
   }
+  grub_net_tcp_retransmit ();
 }
 
 /*  Read from the packets list*/
diff --git a/grub-core/net/tcp.c b/grub-core/net/tcp.c
new file mode 100644 (file)
index 0000000..3bc3d5c
--- /dev/null
@@ -0,0 +1,637 @@
+/*
+ *  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 1000
+#define TCP_SYN_RETRANSMISSION_COUNT 3
+#define TCP_RETRANSMISSION_TIMEOUT 10000
+#define TCP_RETRANSMISSION_COUNT 5
+
+struct unacked
+{
+  struct unacked *next;
+  struct grub_net_buff *nb;
+  grub_uint64_t last_try;
+  int try_count;
+};
+
+enum
+  {
+    TCP_FIN = 0x1,
+    TCP_SYN = 0x2,
+    TCP_ACK = 0x10
+  };
+
+struct grub_net_tcp_socket
+{
+  struct grub_net_tcp_socket *next;
+
+  int established;
+  int i_closed;
+  int they_closed;
+  int in_port;
+  int out_port;
+  int errors;
+  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 *hook_data;
+  grub_net_network_level_address_t out_nla;
+  struct grub_net_network_level_interface *inf;
+  grub_net_packets_t packs;
+  grub_priority_queue_t pq;
+};
+
+struct tcphdr
+{
+  grub_uint16_t src;
+  grub_uint16_t dst;
+  grub_uint32_t seqnr;
+  grub_uint32_t ack;
+  grub_uint16_t flags;
+  grub_uint16_t window;
+  grub_uint16_t checksum;
+  grub_uint16_t urgent;
+} __attribute__ ((packed));
+
+struct tcp_pseudohdr
+{
+  grub_uint32_t src;
+  grub_uint32_t dst;
+  grub_uint8_t zero;
+  grub_uint8_t proto;
+  grub_uint16_t tcp_length;
+} __attribute__ ((packed));
+
+struct grub_net_tcp_socket *tcp_sockets;
+
+#define FOR_TCP_SOCKETS(var) for (var = tcp_sockets; var; var = var->next)
+
+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 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), nb,
+                                GRUB_NET_IP_TCP);
+  if (err)
+    return err;
+  nb->data = nbd;
+  return GRUB_ERR_NONE;
+}
+
+void
+grub_net_tcp_close (grub_net_tcp_socket_t sock)
+{
+  struct grub_net_buff *nb_fin;
+  struct tcphdr *tcph_fin;
+  grub_err_t err;
+
+  sock->i_closed = 1;
+
+  nb_fin = grub_netbuff_alloc (sizeof (*tcph_fin) + 128);
+  if (!nb_fin)
+    return;
+  err = grub_netbuff_reserve (nb_fin, 128);
+  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 (0);
+  tcph_fin->flags = grub_cpu_to_be16 ((5 << 12) | TCP_FIN);
+  tcph_fin->window = grub_cpu_to_be16 (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 (grub_net_tcp_socket_t sock)
+{
+  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;
+  tcph_ack->ack = grub_cpu_to_be32 (sock->their_cur_seq);
+  tcph_ack->flags = grub_cpu_to_be16 ((5 << 12) | TCP_ACK);
+  tcph_ack->window = grub_cpu_to_be16 (sock->my_window);
+  tcph_ack->urgent = 0;
+  tcph_ack->src = grub_cpu_to_be16 (sock->in_port);
+  tcph_ack->dst = grub_cpu_to_be16 (sock->out_port);
+  err = tcp_send (nb_ack, sock);
+  if (err)
+    {
+      grub_dprintf ("net", "error acking socket\n");
+      grub_errno = GRUB_ERR_NONE;
+    }
+}
+
+static void
+kill_socket (grub_net_tcp_socket_t sock)
+{
+  if (sock->error_hook)
+    sock->error_hook (sock, sock->hook_data);
+  grub_net_tcp_close (sock);
+}
+
+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)
+      {
+       grub_uint8_t *nbd;
+       grub_err_t err;
+
+       if (unack->last_try > limit_time)
+         continue;
+       
+       if (unack->try_count > TCP_RETRANSMISSION_COUNT)
+         {
+           kill_socket (sock);
+           break;
+         }
+       unack->try_count++;
+       unack->last_try = ctime;
+       nbd = unack->nb->data;
+       err = grub_net_send_ip_packet (sock->inf, &(sock->out_nla), 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)
+{
+  struct tcp_pseudohdr ph;
+  grub_uint16_t a, b;
+  grub_uint32_t c;
+
+  a = ~grub_be_to_cpu16 (grub_net_ip_chksum ((void *) nb->data,
+                                            nb->tail - nb->data));
+
+  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)));
+  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_destroy (sock->pq);
+}
+
+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 *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;
+
+  err = grub_net_resolve_address (server, &addr);
+  if (err)
+    return NULL;
+
+  if (addr.type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4)
+    {
+      grub_error (GRUB_ERR_BAD_ARGUMENT, "not a IPv4 address");
+      return NULL;
+    }
+  err = grub_net_route_address (addr, &gateway, &inf);
+  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->in_port = in_port++;
+  socket->recv_hook = recv_hook;
+  socket->error_hook = error_hook;
+  socket->hook_data = hook_data;
+
+  nb = grub_netbuff_alloc (sizeof (*tcph) + 128);
+  if (!nb)
+    return NULL;
+  err = grub_netbuff_reserve (nb, 128);
+  if (err)
+    {
+      grub_netbuff_free (nb);
+      return NULL;
+    }
+
+  err = grub_netbuff_put (nb, sizeof (*tcph));
+  if (err)
+    {
+      grub_netbuff_free (nb);
+      return NULL;
+    }
+  socket->pq = grub_priority_queue_new (sizeof (struct grub_net_buff *), cmp);
+  if (!socket->pq)
+    {
+      grub_netbuff_free (nb);
+      return NULL;
+    }
+
+  tcph = (void *) nb->data;
+  socket->my_start_seq = grub_get_time_ms ();
+  socket->my_cur_seq = socket->my_start_seq + 1;
+  socket->my_window = 8192;
+  tcph->seqnr = grub_cpu_to_be32 (socket->my_start_seq);
+  tcph->ack = grub_cpu_to_be32 (0);
+  tcph->flags = grub_cpu_to_be16 ((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), nb,
+                                    GRUB_NET_IP_TCP);
+      if (err)
+       {
+         grub_list_remove (GRUB_AS_LIST_P (&tcp_sockets),
+                           GRUB_AS_LIST (socket));
+         grub_free (socket);
+         grub_netbuff_free (nb);
+         return NULL;
+       }
+      for (j = 0; (j < TCP_SYN_RETRANSMISSION_TIMEOUT / 10 
+                  && !socket->established); j++)
+       grub_net_poll_cards (10);
+      if (socket->established)
+       break;
+    }
+  if (!socket->established)
+    {
+      grub_list_remove (GRUB_AS_LIST_P (&tcp_sockets),
+                       GRUB_AS_LIST (socket));
+      grub_free (socket);
+      grub_error (GRUB_ERR_NET_NO_ANSWER, "no answer");
+
+      grub_netbuff_free (nb);
+      destroy_pq (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)
+{
+  struct tcphdr *tcph;
+  grub_err_t err;
+
+  err = grub_netbuff_push (nb, sizeof (*tcph));
+  if (err)
+    return err;
+
+  tcph = (struct tcphdr *) nb->data;
+  tcph->ack = grub_cpu_to_be32 (0);
+  tcph->flags = grub_cpu_to_be16 ((5 << 12));
+  tcph->window = grub_cpu_to_be16 (socket->my_window);
+  tcph->urgent = 0;
+  return tcp_send (nb, socket);
+}
+
+grub_err_t
+grub_net_recv_tcp_packet (struct grub_net_buff *nb,
+                         struct grub_net_network_level_interface *inf,
+                         const grub_net_network_level_address_t *source)
+{
+  struct tcphdr *tcph;
+  grub_net_tcp_socket_t sock;
+  grub_err_t err;
+
+  tcph = (struct tcphdr *) nb->data;
+  if ((grub_be_to_cpu16 (tcph->flags) >> 12) < 5)
+    {
+      grub_dprintf ("net", "TCP header too short: %u\n",
+                   grub_be_to_cpu16 (tcph->flags) >> 12);
+      grub_netbuff_free (nb);
+      return GRUB_ERR_NONE;
+    }
+  if (nb->tail - nb->data < (grub_ssize_t) ((grub_be_to_cpu16 (tcph->flags)
+                                            >> 12) * sizeof (grub_uint32_t)))
+    {
+      grub_dprintf ("net", "TCP packet too short: %" PRIuGRUB_SIZE "\n",
+                   nb->tail - nb->data);
+      grub_netbuff_free (nb);
+      return GRUB_ERR_NONE;
+    }
+
+  FOR_TCP_SOCKETS (sock)
+  {
+    if (grub_be_to_cpu16 (tcph->dst) == sock->in_port
+       && grub_be_to_cpu16 (tcph->src) == sock->out_port
+       && inf == sock->inf
+       && source->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4
+       && source->ipv4 == sock->out_nla.ipv4)
+      {
+       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_ACK)
+         {
+           struct unacked *unack, *next;
+           grub_uint32_t acked = grub_be_to_cpu16 (tcph->ack);
+           for (unack = sock->unack_first; unack; unack = next)
+             {
+               grub_uint32_t seqnr;
+               next = unack->next;
+               seqnr = grub_be_to_cpu32 (((struct tcphdr *) unack->nb->data)
+                                         ->seqnr);
+               seqnr += (nb->tail - nb->data
+                         - (grub_be_to_cpu16 (tcph->flags) >> 12) * 4);
+               if (grub_be_to_cpu16 (tcph->flags) & TCP_FIN)
+                 seqnr++;
+
+               if (seqnr > acked)
+                 break;
+               grub_netbuff_free (unack->nb);
+               grub_free (unack);
+             }
+           sock->unack_first = unack;
+           if (!sock->unack_last)
+             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;
+         }
+
+       err = grub_priority_queue_push (sock->pq, &nb);
+       if (err)
+         return err;
+
+       {
+         struct grub_net_buff **nb_top_p, *nb_top;
+         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_priority_queue_pop (sock->pq);
+           }
+         if (grub_be_to_cpu32 (tcph->seqnr) != sock->their_cur_seq)
+           return GRUB_ERR_NONE;
+         while (1)
+           {
+             nb_top_p = grub_priority_queue_top (sock->pq);
+             if (!nb_top_p)
+               break;
+             nb_top = *nb_top_p;
+             tcph = (struct tcphdr *) nb_top->data;
+
+             if (grub_be_to_cpu32 (tcph->seqnr) != sock->their_cur_seq)
+               break;
+             grub_priority_queue_pop (sock->pq);
+
+             err = grub_netbuff_pull (nb, (grub_be_to_cpu16 (tcph->flags)
+                                           >> 12) * sizeof (grub_uint32_t));
+             if (err)
+               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;
+                 sock->their_cur_seq++;
+               }
+             /* 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);
+             else
+               grub_netbuff_free (nb);
+           }
+         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);
+           grub_net_remove_packet (sock->packs.first);
+         }
+       
+       return GRUB_ERR_NONE;
+      }
+  }
+  grub_netbuff_free (nb);
+  return GRUB_ERR_NONE;
+}
index 16616cfeab0f3a671220efc544397811244b0f4b..bc8886442bce963db6e950324e511be9445e5429 100644 (file)
@@ -207,7 +207,6 @@ tftp_receive (grub_net_udp_socket_t sock __attribute__ ((unused)),
              return GRUB_ERR_NONE;
            nb_top = *nb_top_p;
            tftph = (struct tftphdr *) nb_top->data;
-           grub_refresh ();
            if (grub_be_to_cpu16 (tftph->u.data.block) >= data->block + 1)
              break;
            grub_priority_queue_pop (data->pq);
@@ -279,6 +278,7 @@ tftp_open (struct grub_file *file, const char *filename)
   struct grub_net_buff nb;
   tftp_data_t data;
   grub_err_t err;
+  grub_uint8_t *nbd;
 
   data = grub_zalloc (sizeof (*data));
   if (!data)
@@ -344,21 +344,11 @@ tftp_open (struct grub_file *file, const char *filename)
       return grub_errno;
     }
 
-  err = grub_net_send_udp_packet (data->sock, &nb);
-  if (err)
-    {
-      grub_net_udp_close (data->sock);
-      destroy_pq (data);
-      return err;
-    }
-
   /* Receive OACK packet.  */
+  nbd = nb.data;
   for (i = 0; i < 3; i++)
     {
-      grub_net_poll_cards (100);
-      if (data->have_oack)
-       break;
-      /* Retry.  */
+      nb.data = nbd;
       err = grub_net_send_udp_packet (data->sock, &nb);
       if (err)
        {
@@ -366,6 +356,9 @@ tftp_open (struct grub_file *file, const char *filename)
          destroy_pq (data);
          return err;
        }
+      grub_net_poll_cards (100);
+      if (data->have_oack)
+       break;
     }
 
   if (!data->have_oack)
index 0a43fe24f7f253f4b7f44d37dca0e3183800f937..e816547ac045775561a702210b201963e9241347 100644 (file)
@@ -120,10 +120,13 @@ grub_net_send_udp_packet (const grub_net_udp_socket_t socket,
   udph->src = grub_cpu_to_be16 (socket->in_port);
   udph->dst = grub_cpu_to_be16 (socket->out_port);
 
-  /* No chechksum. */
   udph->chksum = 0;
   udph->len = grub_cpu_to_be16 (nb->tail - nb->data);
 
+  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), nb,
                                  GRUB_NET_IP_UDP);
 }
@@ -136,26 +139,60 @@ grub_net_recv_udp_packet (struct grub_net_buff *nb,
   struct udphdr *udph;
   grub_net_udp_socket_t sock;
   grub_err_t err;
+
   udph = (struct udphdr *) nb->data;
-  err = grub_netbuff_pull (nb, sizeof (*udph));
-  if (err)
-    return err;
+  if (nb->tail - nb->data < (grub_ssize_t) sizeof (*udph))
+    {
+      grub_dprintf ("net", "UDP packet too short: %" PRIuGRUB_SIZE "\n",
+                   nb->tail - nb->data);
+      grub_netbuff_free (nb);
+      return GRUB_ERR_NONE;
+    }
 
   FOR_UDP_SOCKETS (sock)
   {
     if (grub_be_to_cpu16 (udph->dst) == sock->in_port
-       && inf == sock->inf && sock->recv_hook
+       && inf == sock->inf
        && source->type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4
-       && source->ipv4 == sock->out_nla.ipv4)
+       && source->ipv4 == sock->out_nla.ipv4
+       && (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.  */
-       sock->recv_hook (sock, nb, sock->recv_hook_data);
+       if (sock->recv_hook)
+         sock->recv_hook (sock, nb, sock->recv_hook_data);
+       else
+         grub_netbuff_free (nb);
        return GRUB_ERR_NONE;
       }
   }
index 40f39dd5dd4957f3901ce61872e6c57e957573d7..e03378d709b0b7d6005b8afe30f20548f60d3007 100644 (file)
@@ -59,6 +59,7 @@ typedef enum
     GRUB_ERR_NET_BAD_ADDRESS,
     GRUB_ERR_NET_ROUTE_LOOP,
     GRUB_ERR_NET_NO_ROUTE,
+    GRUB_ERR_NET_NO_ANSWER,
     GRUB_ERR_WAIT,
     GRUB_ERR_BUG
   }
index e7631b8842a6ff6c9b6c34c7618ff2272f535d4a..d71edfd1ee1b2f041acde154bbc0c33038e5a10b 100644 (file)
@@ -415,6 +415,9 @@ grub_net_network_level_interface_unregister (struct grub_net_network_level_inter
   inter->prev = 0;
 }
 
+void
+grub_net_tcp_retransmit (void);
+
 extern char *grub_net_default_server;
 
 #endif /* ! GRUB_NET_HEADER */
index a3c64c1cb2a8e08e4d390458f720c9c4e6ee4680..e385e5ac1154306a08b8856f18529cd7cb8f7cb7 100644 (file)
@@ -50,5 +50,15 @@ grub_err_t
 grub_net_recv_udp_packet (struct grub_net_buff *nb,
                          struct grub_net_network_level_interface *inf,
                          const grub_net_network_level_address_t *src);
+grub_err_t
+grub_net_recv_tcp_packet (struct grub_net_buff *nb,
+                         struct grub_net_network_level_interface *inf,
+                         const grub_net_network_level_address_t *source);
+
+grub_uint16_t
+grub_net_ip_transport_checksum (struct grub_net_buff *nb,
+                               grub_uint16_t proto,
+                               const grub_net_network_level_address_t *src,
+                               const grub_net_network_level_address_t *dst);
 
 #endif 
diff --git a/include/grub/net/tcp.h b/include/grub/net/tcp.h
new file mode 100644 (file)
index 0000000..88cc687
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *  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/>.
+ */
+
+#ifndef GRUB_NET_TCP_HEADER
+#define GRUB_NET_TCP_HEADER    1
+#include <grub/types.h>
+#include <grub/net.h>
+
+struct grub_net_tcp_socket;
+typedef struct grub_net_tcp_socket *grub_net_tcp_socket_t;
+
+grub_net_tcp_socket_t
+grub_net_tcp_open (char *server,
+                  grub_uint16_t out_port,
+                  grub_err_t (*recv_hook) (grub_net_tcp_socket_t sock,
+                                           struct grub_net_buff *nb,
+                                           void *data),
+                  void (*error_hook) (grub_net_tcp_socket_t sock,
+                                      void *data),
+                  void *hook_data);
+
+grub_err_t
+grub_net_send_tcp_packet (const grub_net_tcp_socket_t socket,
+                         struct grub_net_buff *nb);
+
+void
+grub_net_tcp_close (grub_net_tcp_socket_t sock);
+
+#endif
index 8d978569d51d84771e723add49a1615a64fb20c4..fca94ba47786a3cc3df9baf500290600a2c781e3 100644 (file)
@@ -1,3 +1,21 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2010,2011  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
 #ifndef GRUB_NET_UDP_HEADER
 #define GRUB_NET_UDP_HEADER    1
 #include <grub/types.h>