diff --git a/src/include/ipxe/fragment.h b/src/include/ipxe/fragment.h new file mode 100644 index 00000000..6b47439d --- /dev/null +++ b/src/include/ipxe/fragment.h @@ -0,0 +1,68 @@ +#ifndef _IPXE_FRAGMENT_H +#define _IPXE_FRAGMENT_H + +/** @file + * + * Fragment reassembly + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include + +/** 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 */ diff --git a/src/include/ipxe/ip.h b/src/include/ipxe/ip.h index ca508e27..3234b7b0 100644 --- a/src/include/ipxe/ip.h +++ b/src/include/ipxe/ip.h @@ -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 index 00000000..3e1dfdf7 --- /dev/null +++ b/src/net/fragment.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2013 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include + +/** @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; +} diff --git a/src/net/ipv4.c b/src/net/ipv4.c index 791d4195..106e8e79 100644 --- a/src/net/ipv4.c +++ b/src/net/ipv4.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include /** @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; +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; - DBGC ( iphdr->src, "IPv4 fragment %04x expired\n", - ntohs ( iphdr->ident ) ); - free_iob ( frag->iobuf ); - list_del ( &frag->list ); - free ( frag ); + return ( ( iphdr->src.s_addr == frag_iphdr->src.s_addr ) && + ( iphdr->ident == frag_iphdr->ident ) ); } /** - * Find matching fragment reassembly buffer - * - * @v iphdr IPv4 header - * @ret frag Fragment reassembly buffer, or NULL - */ -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; - } - } - - return NULL; -} - -/** - * Fragment reassembler + * Get IPv4 fragment offset * * @v iobuf I/O buffer - * @ret iobuf Reassembled packet, or NULL + * @v hdrlen Length of non-fragmentable potion of I/O buffer + * @ret offset Offset */ -static struct io_buffer * ipv4_reassemble ( struct io_buffer *iobuf ) { +static size_t ipv4_fragment_offset ( 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 ( ( ntohs ( iphdr->frags ) & IP_MASK_OFFSET ) << 3 ); } +/** + * 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 + */ +static int ipv4_more_fragments ( struct io_buffer *iobuf, + size_t hdrlen __unused ) { + struct iphdr *iphdr = iobuf->data; + + 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