support ip fragmentation

This commit is contained in:
Vladimir 'phcoder' Serbinenko 2011-07-09 00:27:27 +02:00
parent e2955971a3
commit fecdbd6b17
9 changed files with 374 additions and 98 deletions

View file

@ -104,6 +104,13 @@ grub_priority_queue_top (grub_priority_queue_t pq)
return element (pq, 0);
}
void
grub_priority_queue_destroy (grub_priority_queue_t pq)
{
grub_free (pq->els);
grub_free (pq);
}
grub_priority_queue_t
grub_priority_queue_new (grub_size_t elsize,
grub_comparator_t cmp)

View file

@ -38,7 +38,7 @@ grub_net_arp_resolve (struct grub_net_network_level_interface *inf,
struct grub_net_buff nb;
struct arphdr *arp_header;
grub_net_link_level_address_t target_hw_addr;
char *aux, arp_data[128];
grub_uint8_t *aux, arp_data[128];
grub_err_t err;
int i;
@ -74,7 +74,7 @@ grub_net_arp_resolve (struct grub_net_network_level_interface *inf,
arp_header->hln = 6;
arp_header->pln = 4;
arp_header->op = grub_cpu_to_be16 (ARP_REQUEST);
aux = (char *) arp_header + sizeof (*arp_header);
aux = (grub_uint8_t *) arp_header + sizeof (*arp_header);
/* Sender hardware address. */
grub_memcpy (aux, &inf->hwaddress.mac, 6);

View file

@ -57,6 +57,8 @@ send_ethernet_packet (struct grub_net_network_level_interface *inf,
struct etherhdr *eth;
grub_err_t err;
COMPILE_TIME_ASSERT (sizeof (*eth) < GRUB_NET_MAX_LINK_HEADER_SIZE);
err = grub_netbuff_push (nb, sizeof (*eth));
if (err)
return err;

View file

@ -24,6 +24,8 @@
#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;
@ -56,6 +58,39 @@ struct ip6hdr
grub_uint8_t daddr[16];
} __attribute__ ((packed));
static int
cmp (const void *a__, const void *b__)
{
struct grub_net_buff *a_ = *(struct grub_net_buff **) a__;
struct grub_net_buff *b_ = *(struct grub_net_buff **) b__;
struct iphdr *a = (struct iphdr *) a_->data;
struct iphdr *b = (struct iphdr *) b_->data;
/* We want the first elements to be on top. */
if ((grub_be_to_cpu16 (a->frags) & OFFSET_MASK)
< (grub_be_to_cpu16 (b->frags) & OFFSET_MASK))
return +1;
if ((grub_be_to_cpu16 (a->frags) & OFFSET_MASK)
> (grub_be_to_cpu16 (b->frags) & OFFSET_MASK))
return -1;
return 0;
}
struct reassemble
{
struct reassemble *next;
grub_uint32_t source;
grub_uint32_t dest;
grub_uint16_t id;
grub_uint8_t proto;
grub_uint64_t last_time;
grub_priority_queue_t pq;
grub_uint8_t *asm_buffer;
grub_size_t total_len;
grub_size_t cur_ptr;
};
struct reassemble *reassembles;
grub_uint16_t
grub_net_ip_chksum (void *ipv, grub_size_t len)
{
@ -78,6 +113,72 @@ grub_net_ip_chksum (void *ipv, grub_size_t len)
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;
}
grub_err_t
grub_net_send_ip_packet (struct grub_net_network_level_interface * inf,
const grub_net_network_level_address_t * target,
@ -85,10 +186,19 @@ grub_net_send_ip_packet (struct grub_net_network_level_interface * inf,
grub_net_ip_protocol_t proto)
{
struct iphdr *iph;
static int id = 0x2400;
grub_net_link_level_address_t ll_target_addr;
grub_err_t err;
COMPILE_TIME_ASSERT (GRUB_NET_OUR_IPV4_HEADER_SIZE == sizeof (*iph));
/* Determine link layer target address via ARP. */
err = grub_net_arp_resolve (inf, target, &ll_target_addr);
if (err)
return err;
if (nb->tail - nb->data + sizeof (struct iphdr) > inf->card->mtu)
return send_fragmented (inf, target, nb, proto, ll_target_addr);
grub_netbuff_push (nb, sizeof (*iph));
iph = (struct iphdr *) nb->data;
@ -105,88 +215,26 @@ grub_net_send_ip_packet (struct grub_net_network_level_interface * inf,
iph->chksum = 0;
iph->chksum = grub_net_ip_chksum ((void *) nb->data, sizeof (*iph));
/* Determine link layer target address via ARP. */
err = grub_net_arp_resolve (inf, target, &ll_target_addr);
if (err)
return err;
return send_ethernet_packet (inf, nb, ll_target_addr,
GRUB_NET_ETHERTYPE_IP);
}
grub_err_t
grub_net_recv_ip_packets (struct grub_net_buff * nb,
const struct grub_net_card * card,
const grub_net_link_level_address_t * hwaddress)
static grub_err_t
handle_dgram (struct grub_net_buff *nb,
const struct grub_net_card *card,
const grub_net_link_level_address_t *hwaddress,
grub_net_ip_protocol_t proto, grub_uint32_t src,
grub_uint32_t dst)
{
struct iphdr *iph = (struct iphdr *) nb->data;
grub_err_t err;
struct grub_net_network_level_interface *inf = NULL;
grub_err_t err;
grub_net_network_level_address_t source;
if (((grub_addr_t) nb->data) & 3)
grub_fatal ("unaligned %p\n", nb->data);
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;
}
err = grub_netbuff_pull (nb, (iph->verhdrlen & 0xf) * sizeof (grub_uint32_t));
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
+ (iph->verhdrlen & 0xf)
* sizeof (grub_uint32_t));
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;
}
}
/* Fragmented packet. Bad. */
if (((grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS) != 0)
|| (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) != 0)
{
/* FIXME. */
grub_netbuff_free (nb);
return GRUB_ERR_NONE;
}
/* DHCP needs special treatment since we don't know IP yet. */
{
struct udphdr *udph;
udph = (struct udphdr *) nb->data;
if (iph->protocol == GRUB_NET_IP_UDP && grub_be_to_cpu16 (udph->dst) == 68)
if (proto == GRUB_NET_IP_UDP && grub_be_to_cpu16 (udph->dst) == 68)
{
FOR_NET_NETWORK_LEVEL_INTERFACES (inf)
if (inf->card == card
@ -209,7 +257,7 @@ grub_net_recv_ip_packets (struct grub_net_buff * nb,
{
if (inf->card == card
&& inf->address.type == GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4
&& inf->address.ipv4 == iph->dest
&& inf->address.ipv4 == dst
&& grub_net_hwaddr_cmp (&inf->hwaddress, hwaddress) == 0)
break;
}
@ -221,9 +269,9 @@ grub_net_recv_ip_packets (struct grub_net_buff * nb,
}
source.type = GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4;
source.ipv4 = iph->src;
source.ipv4 = src;
switch (iph->protocol)
switch (proto)
{
case GRUB_NET_IP_UDP:
return grub_net_recv_udp_packet (nb, inf, &source);
@ -233,6 +281,224 @@ grub_net_recv_ip_packets (struct grub_net_buff * nb,
grub_netbuff_free (nb);
break;
}
return GRUB_ERR_NONE;
}
static void
free_rsm (struct reassemble *rsm)
{
struct grub_net_buff **nb;
while ((nb = grub_priority_queue_top (rsm->pq)))
{
grub_netbuff_free (*nb);
grub_priority_queue_pop (rsm->pq);
}
grub_free (rsm->asm_buffer);
grub_priority_queue_destroy (rsm->pq);
}
static void
free_old_fragments (void)
{
struct reassemble *rsm, **prev;
grub_uint64_t limit_time = grub_get_time_ms () - 90000;
for (prev = &reassembles, rsm = *prev; rsm; prev = &rsm->next, rsm = *prev)
if (rsm->last_time < limit_time)
{
*prev = rsm->next;
free_rsm (rsm);
}
}
grub_err_t
grub_net_recv_ip_packets (struct grub_net_buff * nb,
const struct grub_net_card * card,
const grub_net_link_level_address_t * hwaddress)
{
struct 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: %d\n",
(iph->verhdrlen & 0xf));
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)
{
err = grub_netbuff_pull (nb, ((iph->verhdrlen & 0xf)
* sizeof (grub_uint32_t)));
if (err)
{
grub_netbuff_free (nb);
return err;
}
return handle_dgram (nb, card, hwaddress, iph->protocol,
iph->src, iph->dest);
}
for (prev = &reassembles, rsm = *prev; rsm; prev = &rsm->next, rsm = *prev)
if (rsm->source == iph->src && rsm->dest == iph->dest
&& rsm->id == iph->ident && rsm->proto == iph->protocol)
break;
if (!rsm)
{
rsm = grub_malloc (sizeof (*rsm));
if (!rsm)
return grub_errno;
rsm->source = iph->src;
rsm->dest = iph->dest;
rsm->id = iph->ident;
rsm->proto = iph->protocol;
rsm->next = reassembles;
reassembles = rsm;
prev = &reassembles;
rsm->pq = grub_priority_queue_new (sizeof (struct grub_net_buff **), cmp);
if (!rsm->pq)
{
grub_free (rsm);
return grub_errno;
}
rsm->asm_buffer = 0;
rsm->total_len = 0;
rsm->cur_ptr = 0;
}
rsm->last_time = grub_get_time_ms ();
free_old_fragments ();
err = grub_priority_queue_push (rsm->pq, &nb);
if (err)
return err;
if (!(grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS))
{
rsm->total_len = (8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK)
+ (nb->tail - nb->data));
rsm->total_len -= ((iph->verhdrlen & 0xf) * sizeof (grub_uint32_t));
rsm->asm_buffer = grub_zalloc (rsm->total_len);
if (!rsm->asm_buffer)
{
*prev = rsm->next;
free_rsm (rsm);
return grub_errno;
}
}
if (!rsm->asm_buffer)
return GRUB_ERR_NONE;
while (1)
{
struct grub_net_buff **nb_top_p, *nb_top;
grub_size_t copy;
grub_uint8_t *res;
grub_size_t res_len;
struct grub_net_buff *ret;
grub_net_ip_protocol_t proto;
grub_uint32_t src;
grub_uint32_t dst;
nb_top_p = grub_priority_queue_top (rsm->pq);
if (!nb_top_p)
return GRUB_ERR_NONE;
nb_top = *nb_top_p;
grub_priority_queue_pop (rsm->pq);
iph = (struct iphdr *) nb_top->data;
err = grub_netbuff_pull (nb_top, ((iph->verhdrlen & 0xf)
* sizeof (grub_uint32_t)));
if (err)
{
grub_netbuff_free (nb_top);
return err;
}
if (rsm->cur_ptr < (grub_size_t) 8 * (grub_be_to_cpu16 (iph->frags)
& OFFSET_MASK))
return GRUB_ERR_NONE;
rsm->cur_ptr = (8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK)
+ (nb_top->tail - nb_top->head));
if ((grub_size_t) 8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK)
>= rsm->total_len)
{
grub_netbuff_free (nb_top);
continue;
}
copy = nb_top->tail - nb_top->data;
if (rsm->total_len - 8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK)
< copy)
copy = rsm->total_len - 8 * (grub_be_to_cpu16 (iph->frags)
& OFFSET_MASK);
grub_memcpy (&rsm->asm_buffer[8 * (grub_be_to_cpu16 (iph->frags)
& OFFSET_MASK)],
nb_top->data, copy);
if ((grub_be_to_cpu16 (iph->frags) & MORE_FRAGMENTS))
continue;
res = rsm->asm_buffer;
proto = rsm->proto;
src = rsm->source;
dst = rsm->dest;
rsm->asm_buffer = 0;
res_len = rsm->total_len;
*prev = rsm->next;
free_rsm (rsm);
ret = grub_malloc (sizeof (*ret));
if (!ret)
{
grub_free (res);
return grub_errno;
}
ret->data = ret->head = res;
ret->tail = ret->end = res + res_len;
return handle_dgram (ret, card, hwaddress, proto, src, dst);
}
return GRUB_ERR_NONE;
}

View file

@ -21,7 +21,6 @@
#include <grub/mm.h>
#include <grub/net/netbuff.h>
grub_err_t
grub_netbuff_put (struct grub_net_buff *nb, grub_size_t len)
{
@ -87,7 +86,7 @@ grub_netbuff_alloc (grub_size_t len)
return NULL;
nb = (struct grub_net_buff *) ((grub_uint8_t *) data + len);
nb->head = nb->data = nb->tail = data;
nb->end = (char *) nb;
nb->end = (grub_uint8_t *) nb;
return nb;
}

View file

@ -112,10 +112,10 @@ tftp_receive (grub_net_udp_socket_t sock __attribute__ ((unused)),
{
grub_file_t file = f;
struct tftphdr *tftph = (void *) nb->data;
char nbdata[512];
grub_uint8_t nbdata[512];
tftp_data_t data = file->data;
grub_err_t err;
char *ptr;
grub_uint8_t *ptr;
struct grub_net_buff nb_ack;
nb_ack.head = nbdata;
@ -130,15 +130,11 @@ tftp_receive (grub_net_udp_socket_t sock __attribute__ ((unused)),
for (ptr = nb->data + sizeof (tftph->opcode); ptr < nb->tail;)
{
if (grub_memcmp (ptr, "tsize\0", sizeof ("tsize\0") - 1) == 0)
{
data->file_size = grub_strtoul (ptr + sizeof ("tsize\0") - 1,
0, 0);
}
data->file_size = grub_strtoul ((char *) ptr + sizeof ("tsize\0")
- 1, 0, 0);
if (grub_memcmp (ptr, "blksize\0", sizeof ("blksize\0") - 1) == 0)
{
data->block_size = grub_strtoul (ptr + sizeof ("blksize\0") - 1,
0, 0);
}
data->block_size = grub_strtoul ((char *) ptr + sizeof ("blksize\0")
- 1, 0, 0);
while (ptr < nb->tail && *ptr)
ptr++;
ptr++;
@ -210,7 +206,7 @@ tftp_open (struct grub_file *file, const char *filename)
int i;
int rrqlen;
int hdrlen;
char open_data[1500];
grub_uint8_t open_data[1500];
struct grub_net_buff nb;
tftp_data_t data;
grub_err_t err;
@ -312,7 +308,7 @@ tftp_close (struct grub_file *file)
if (data->sock)
{
char nbdata[512];
grub_uint8_t nbdata[512];
grub_err_t err;
struct grub_net_buff nb_err;
struct tftphdr *tftph;

View file

@ -110,6 +110,8 @@ grub_net_send_udp_packet (const grub_net_udp_socket_t socket,
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;

View file

@ -27,6 +27,10 @@
#include <grub/mm.h>
#include <grub/net/netbuff.h>
#define GRUB_NET_MAX_LINK_HEADER_SIZE 64
#define GRUB_NET_UDP_HEADER_SIZE 8
#define GRUB_NET_OUR_IPV4_HEADER_SIZE 20
typedef enum grub_link_level_protocol_id
{
GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET

View file

@ -8,14 +8,14 @@
struct grub_net_buff
{
/*Pointer to the start of the buffer*/
char *head;
/*Pointer to the data */
char *data;
/*Pointer to the tail */
char *tail;
/*Pointer to the end of the buffer*/
char *end;
/* Pointer to the start of the buffer. */
grub_uint8_t *head;
/* Pointer to the data. */
grub_uint8_t *data;
/* Pointer to the tail. */
grub_uint8_t *tail;
/* Pointer to the end of the buffer. */
grub_uint8_t *end;
};
grub_err_t grub_netbuff_put (struct grub_net_buff *net_buff ,grub_size_t len);