]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[ipv4] Generalise fragment reassembly mechanism
authorMichael Brown <mcb30@ipxe.org>
Tue, 27 Aug 2013 15:08:15 +0000 (16:08 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 27 Aug 2013 15:39:43 +0000 (16:39 +0100)
Generalise the concept of fragment reassembly to allow for code
sharing between IPv4 and IPv6 protocols.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/fragment.h [new file with mode: 0644]
src/include/ipxe/ip.h
src/net/fragment.c [new file with mode: 0644]
src/net/ipv4.c

diff --git a/src/include/ipxe/fragment.h b/src/include/ipxe/fragment.h
new file mode 100644 (file)
index 0000000..6b47439
--- /dev/null
@@ -0,0 +1,68 @@
+#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 */
index ca508e274b207c46a8f68ddbda3b2695251e00df..3234b7b0ef8af69e6438e5b13ae740cd11c0b10d 100644 (file)
@@ -70,18 +70,6 @@ struct ipv4_miniroute {
        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;
diff --git a/src/net/fragment.c b/src/net/fragment.c
new file mode 100644 (file)
index 0000000..3e1dfdf
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * 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;
+}
index 791d419514440152a6182c4ca9288fa0445580c0..106e8e795e0a2ed109d6b1ae920f2c1fed239cc9 100644 (file)
@@ -14,7 +14,7 @@
 #include <ipxe/tcpip.h>
 #include <ipxe/dhcp.h>
 #include <ipxe/settings.h>
-#include <ipxe/timer.h>
+#include <ipxe/fragment.h>
 
 /** @file
  *
@@ -30,12 +30,6 @@ static uint8_t next_ident_high = 0;
 /** 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
  *
@@ -133,131 +127,59 @@ static struct ipv4_miniroute * ipv4_route ( struct in_addr *dest ) {
 }
 
 /**
- * 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
  *
@@ -526,14 +448,14 @@ static int ipv4_rx ( struct io_buffer *iobuf,
 
        /* 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