Skip to content

Commit

Permalink
[ipv4] Generalise fragment reassembly mechanism
Browse files Browse the repository at this point in the history
Generalise the concept of fragment reassembly to allow for code
sharing between IPv4 and IPv6 protocols.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
  • Loading branch information
mcb30 committed Aug 27, 2013
1 parent ae0124c commit 22a0c44
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 129 deletions.
68 changes: 68 additions & 0 deletions 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 <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 */
12 changes: 0 additions & 12 deletions src/include/ipxe/ip.h
Expand Up @@ -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;
Expand Down
172 changes: 172 additions & 0 deletions src/net/fragment.c
@@ -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;
}

0 comments on commit 22a0c44

Please sign in to comment.