--- /dev/null
+#ifndef _IPXE_FRAGMENT_H
+#define _IPXE_FRAGMENT_H
+
+/** @file
+ *
+ * Fragment reassembly
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <ipxe/list.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/retry.h>
+
+/** Fragment reassembly timeout */
+#define FRAGMENT_TIMEOUT ( TICKS_PER_SEC / 2 )
+
+/** A fragment reassembly buffer */
+struct fragment {
+ /* List of fragment reassembly buffers */
+ struct list_head list;
+ /** Reassembled packet */
+ struct io_buffer *iobuf;
+ /** Length of non-fragmentable portion of reassembled packet */
+ size_t hdrlen;
+ /** Reassembly timer */
+ struct retry_timer timer;
+};
+
+/** A fragment reassembler */
+struct fragment_reassembler {
+ /** List of fragment reassembly buffers */
+ struct list_head list;
+ /**
+ * Check if fragment matches fragment reassembly buffer
+ *
+ * @v fragment Fragment reassembly buffer
+ * @v iobuf I/O buffer
+ * @v hdrlen Length of non-fragmentable potion of I/O buffer
+ * @ret is_fragment Fragment matches this reassembly buffer
+ */
+ int ( * is_fragment ) ( struct fragment *fragment,
+ struct io_buffer *iobuf, size_t hdrlen );
+ /**
+ * Get fragment offset
+ *
+ * @v iobuf I/O buffer
+ * @v hdrlen Length of non-fragmentable potion of I/O buffer
+ * @ret offset Offset
+ */
+ size_t ( * fragment_offset ) ( struct io_buffer *iobuf, size_t hdrlen );
+ /**
+ * Check if more fragments exist
+ *
+ * @v iobuf I/O buffer
+ * @v hdrlen Length of non-fragmentable potion of I/O buffer
+ * @ret more_frags More fragments exist
+ */
+ int ( * more_fragments ) ( struct io_buffer *iobuf, size_t hdrlen );
+};
+
+extern struct io_buffer *
+fragment_reassemble ( struct fragment_reassembler *fragments,
+ struct io_buffer *iobuf, size_t *hdrlen );
+
+#endif /* _IPXE_FRAGMENT_H */
struct in_addr gateway;
};
-/* IPv4 fragment reassembly buffer */
-struct ipv4_fragment {
- /* List of fragment reassembly buffers */
- struct list_head list;
- /** Reassembled packet */
- struct io_buffer *iobuf;
- /** Current offset */
- size_t offset;
- /** Reassembly timer */
- struct retry_timer timer;
-};
-
extern struct list_head ipv4_miniroutes;
extern struct net_protocol ipv4_protocol __net_protocol;
--- /dev/null
+/*
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program 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 2 of the
+ * License, or any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ipxe/retry.h>
+#include <ipxe/timer.h>
+#include <ipxe/fragment.h>
+
+/** @file
+ *
+ * Fragment reassembly
+ *
+ */
+
+/**
+ * Expire fragment reassembly buffer
+ *
+ * @v timer Retry timer
+ * @v fail Failure indicator
+ */
+static void fragment_expired ( struct retry_timer *timer, int fail __unused ) {
+ struct fragment *fragment =
+ container_of ( timer, struct fragment, timer );
+
+ DBGC ( fragment, "FRAG %p expired\n", fragment );
+ free_iob ( fragment->iobuf );
+ list_del ( &fragment->list );
+ free ( fragment );
+}
+
+/**
+ * Find fragment reassembly buffer
+ *
+ * @v fragments Fragment reassembler
+ * @v iobuf I/O buffer
+ * @v hdrlen Length of non-fragmentable potion of I/O buffer
+ * @ret fragment Fragment reassembly buffer, or NULL if not found
+ */
+static struct fragment * fragment_find ( struct fragment_reassembler *fragments,
+ struct io_buffer *iobuf,
+ size_t hdrlen ) {
+ struct fragment *fragment;
+
+ list_for_each_entry ( fragment, &fragments->list, list ) {
+ if ( fragments->is_fragment ( fragment, iobuf, hdrlen ) )
+ return fragment;
+ }
+ return NULL;
+}
+
+/**
+ * Reassemble packet
+ *
+ * @v fragments Fragment reassembler
+ * @v iobuf I/O buffer
+ * @v hdrlen Length of non-fragmentable potion of I/O buffer
+ * @ret iobuf Reassembled packet, or NULL
+ *
+ * This function takes ownership of the I/O buffer. Note that the
+ * length of the non-fragmentable portion may be modified.
+ */
+struct io_buffer * fragment_reassemble ( struct fragment_reassembler *fragments,
+ struct io_buffer *iobuf,
+ size_t *hdrlen ) {
+ struct fragment *fragment;
+ struct io_buffer *new_iobuf;
+ size_t new_len;
+ size_t offset;
+ size_t expected_offset;
+ int more_frags;
+
+ /* Find matching fragment reassembly buffer, if any */
+ fragment = fragment_find ( fragments, iobuf, *hdrlen );
+
+ /* Drop out-of-order fragments */
+ offset = fragments->fragment_offset ( iobuf, *hdrlen );
+ expected_offset = ( fragment ? ( iob_len ( fragment->iobuf ) -
+ fragment->hdrlen ) : 0 );
+ if ( offset != expected_offset ) {
+ DBGC ( fragment, "FRAG %p dropping out-of-sequence fragment "
+ "[%zd,%zd), expected [%zd,...)\n", fragment, offset,
+ ( offset + iob_len ( iobuf ) - *hdrlen ),
+ expected_offset );
+ goto drop;
+ }
+
+ /* Create or extend fragment reassembly buffer as applicable */
+ if ( ! fragment ) {
+
+ /* Create new fragment reassembly buffer */
+ fragment = zalloc ( sizeof ( *fragment ) );
+ if ( ! fragment )
+ goto drop;
+ list_add ( &fragment->list, &fragments->list );
+ fragment->iobuf = iobuf;
+ fragment->hdrlen = *hdrlen;
+ timer_init ( &fragment->timer, fragment_expired, NULL );
+ DBGC ( fragment, "FRAG %p [0,%zd)\n", fragment,
+ ( iob_len ( iobuf ) - *hdrlen ) );
+
+ } else {
+
+ /* Check if this is the final fragment */
+ more_frags = fragments->more_fragments ( iobuf, *hdrlen );
+ DBGC ( fragment, "FRAG %p [%zd,%zd)%s\n", fragment,
+ offset, ( offset + iob_len ( iobuf ) - *hdrlen ),
+ ( more_frags ? "" : " complete" ) );
+
+ /* Extend fragment reassembly buffer. Preserve I/O
+ * buffer headroom to allow for code which modifies
+ * and resends the buffer (e.g. ICMP echo responses).
+ */
+ iob_pull ( iobuf, *hdrlen );
+ new_len = ( iob_headroom ( fragment->iobuf ) +
+ iob_len ( fragment->iobuf ) + iob_len ( iobuf ) );
+ new_iobuf = alloc_iob ( new_len );
+ if ( ! new_iobuf ) {
+ DBGC ( fragment, "FRAG %p could not extend reassembly "
+ "buffer to %zd bytes\n", fragment, new_len );
+ goto drop;
+ }
+ iob_reserve ( new_iobuf, iob_headroom ( fragment->iobuf ) );
+ memcpy ( iob_put ( new_iobuf, iob_len ( fragment->iobuf ) ),
+ fragment->iobuf->data, iob_len ( fragment->iobuf ) );
+ memcpy ( iob_put ( new_iobuf, iob_len ( iobuf ) ),
+ iobuf->data, iob_len ( iobuf ) );
+ free_iob ( fragment->iobuf );
+ fragment->iobuf = new_iobuf;
+ free_iob ( iobuf );
+
+ /* Stop fragment reassembly timer */
+ stop_timer ( &fragment->timer );
+
+ /* If this is the final fragment, return it */
+ if ( ! more_frags ) {
+ iobuf = fragment->iobuf;
+ *hdrlen = fragment->hdrlen;
+ list_del ( &fragment->list );
+ free ( fragment );
+ return iobuf;
+ }
+ }
+
+ /* (Re)start fragment reassembly timer */
+ start_timer_fixed ( &fragment->timer, FRAGMENT_TIMEOUT );
+
+ return NULL;
+
+ drop:
+ free_iob ( iobuf );
+ return NULL;
+}
#include <ipxe/tcpip.h>
#include <ipxe/dhcp.h>
#include <ipxe/settings.h>
-#include <ipxe/timer.h>
+#include <ipxe/fragment.h>
/** @file
*
/** List of IPv4 miniroutes */
struct list_head ipv4_miniroutes = LIST_HEAD_INIT ( ipv4_miniroutes );
-/** List of fragment reassembly buffers */
-static LIST_HEAD ( ipv4_fragments );
-
-/** Fragment reassembly timeout */
-#define IP_FRAG_TIMEOUT ( TICKS_PER_SEC / 2 )
-
/**
* Add IPv4 minirouting table entry
*
}
/**
- * Expire fragment reassembly buffer
+ * Check if IPv4 fragment matches fragment reassembly buffer
*
- * @v timer Retry timer
- * @v fail Failure indicator
+ * @v fragment Fragment reassembly buffer
+ * @v iobuf I/O buffer
+ * @v hdrlen Length of non-fragmentable potion of I/O buffer
+ * @ret is_fragment Fragment matches this reassembly buffer
*/
-static void ipv4_fragment_expired ( struct retry_timer *timer,
- int fail __unused ) {
- struct ipv4_fragment *frag =
- container_of ( timer, struct ipv4_fragment, timer );
- struct iphdr *iphdr = frag->iobuf->data;
-
- DBGC ( iphdr->src, "IPv4 fragment %04x expired\n",
- ntohs ( iphdr->ident ) );
- free_iob ( frag->iobuf );
- list_del ( &frag->list );
- free ( frag );
+static int ipv4_is_fragment ( struct fragment *fragment,
+ struct io_buffer *iobuf,
+ size_t hdrlen __unused ) {
+ struct iphdr *frag_iphdr = fragment->iobuf->data;
+ struct iphdr *iphdr = iobuf->data;
+
+ return ( ( iphdr->src.s_addr == frag_iphdr->src.s_addr ) &&
+ ( iphdr->ident == frag_iphdr->ident ) );
}
/**
- * Find matching fragment reassembly buffer
+ * Get IPv4 fragment offset
*
- * @v iphdr IPv4 header
- * @ret frag Fragment reassembly buffer, or NULL
+ * @v iobuf I/O buffer
+ * @v hdrlen Length of non-fragmentable potion of I/O buffer
+ * @ret offset Offset
*/
-static struct ipv4_fragment * ipv4_fragment ( struct iphdr *iphdr ) {
- struct ipv4_fragment *frag;
- struct iphdr *frag_iphdr;
-
- list_for_each_entry ( frag, &ipv4_fragments, list ) {
- frag_iphdr = frag->iobuf->data;
-
- if ( ( iphdr->src.s_addr == frag_iphdr->src.s_addr ) &&
- ( iphdr->ident == frag_iphdr->ident ) ) {
- return frag;
- }
- }
+static size_t ipv4_fragment_offset ( struct io_buffer *iobuf,
+ size_t hdrlen __unused ) {
+ struct iphdr *iphdr = iobuf->data;
- return NULL;
+ return ( ( ntohs ( iphdr->frags ) & IP_MASK_OFFSET ) << 3 );
}
/**
- * Fragment reassembler
+ * Check if more fragments exist
*
* @v iobuf I/O buffer
- * @ret iobuf Reassembled packet, or NULL
+ * @v hdrlen Length of non-fragmentable potion of I/O buffer
+ * @ret more_frags More fragments exist
*/
-static struct io_buffer * ipv4_reassemble ( struct io_buffer *iobuf ) {
+static int ipv4_more_fragments ( struct io_buffer *iobuf,
+ size_t hdrlen __unused ) {
struct iphdr *iphdr = iobuf->data;
- size_t offset = ( ( ntohs ( iphdr->frags ) & IP_MASK_OFFSET ) << 3 );
- unsigned int more_frags = ( iphdr->frags & htons ( IP_MASK_MOREFRAGS ));
- size_t hdrlen = ( ( iphdr->verhdrlen & IP_MASK_HLEN ) * 4 );
- struct ipv4_fragment *frag;
- size_t expected_offset;
- struct io_buffer *new_iobuf;
-
- /* Find matching fragment reassembly buffer, if any */
- frag = ipv4_fragment ( iphdr );
-
- /* Drop out-of-order fragments */
- expected_offset = ( frag ? frag->offset : 0 );
- if ( offset != expected_offset ) {
- DBGC ( iphdr->src, "IPv4 dropping out-of-sequence fragment "
- "%04x (%zd+%zd, expected %zd)\n",
- ntohs ( iphdr->ident ), offset,
- ( iob_len ( iobuf ) - hdrlen ), expected_offset );
- goto drop;
- }
-
- /* Create or extend fragment reassembly buffer as applicable */
- if ( frag == NULL ) {
- /* Create new fragment reassembly buffer */
- frag = zalloc ( sizeof ( *frag ) );
- if ( ! frag )
- goto drop;
- list_add ( &frag->list, &ipv4_fragments );
- frag->iobuf = iobuf;
- frag->offset = ( iob_len ( iobuf ) - hdrlen );
- timer_init ( &frag->timer, ipv4_fragment_expired, NULL );
-
- } else {
-
- /* Extend reassembly buffer */
- iob_pull ( iobuf, hdrlen );
- new_iobuf = alloc_iob ( iob_len ( frag->iobuf ) +
- iob_len ( iobuf ) );
- if ( ! new_iobuf ) {
- DBGC ( iphdr->src, "IPv4 could not extend reassembly "
- "buffer to %zd bytes\n",
- iob_len ( frag->iobuf ) + iob_len ( iobuf ) );
- goto drop;
- }
- memcpy ( iob_put ( new_iobuf, iob_len ( frag->iobuf ) ),
- frag->iobuf->data, iob_len ( frag->iobuf ) );
- memcpy ( iob_put ( new_iobuf, iob_len ( iobuf ) ),
- iobuf->data, iob_len ( iobuf ) );
- free_iob ( frag->iobuf );
- frag->iobuf = new_iobuf;
- frag->offset += iob_len ( iobuf );
- free_iob ( iobuf );
- iphdr = frag->iobuf->data;
- iphdr->len = ntohs ( iob_len ( frag->iobuf ) );
-
- /* Stop fragment reassembly timer */
- stop_timer ( &frag->timer );
-
- /* If this is the final fragment, return it */
- if ( ! more_frags ) {
- iobuf = frag->iobuf;
- list_del ( &frag->list );
- free ( frag );
- return iobuf;
- }
- }
-
- /* (Re)start fragment reassembly timer */
- start_timer_fixed ( &frag->timer, IP_FRAG_TIMEOUT );
-
- return NULL;
-
- drop:
- free_iob ( iobuf );
- return NULL;
+ return ( iphdr->frags & htons ( IP_MASK_MOREFRAGS ) );
}
+/** IPv4 fragment reassembler */
+static struct fragment_reassembler ipv4_reassembler = {
+ .list = LIST_HEAD_INIT ( ipv4_reassembler.list ),
+ .is_fragment = ipv4_is_fragment,
+ .fragment_offset = ipv4_fragment_offset,
+ .more_fragments = ipv4_more_fragments,
+};
+
/**
* Add IPv4 pseudo-header checksum to existing checksum
*
/* Perform fragment reassembly if applicable */
if ( iphdr->frags & htons ( IP_MASK_OFFSET | IP_MASK_MOREFRAGS ) ) {
- /* Pass the fragment to ipv4_reassemble() which returns
+ /* Pass the fragment to fragment_reassemble() which returns
* either a fully reassembled I/O buffer or NULL.
*/
- iobuf = ipv4_reassemble ( iobuf );
+ iobuf = fragment_reassemble ( &ipv4_reassembler, iobuf,
+ &hdrlen );
if ( ! iobuf )
return 0;
iphdr = iobuf->data;
- hdrlen = ( ( iphdr->verhdrlen & IP_MASK_HLEN ) * 4 );
}
/* Construct socket addresses, calculate pseudo-header