IP Reassembly: IP Datagram Reassembly Algorithms
A way to rebuild a fragmented IP datagram with one buffer and a list of missing gaps, whatever order the pieces arrive in.
Clark, D.D., "IP datagram reassembly algorithms", RFC 815, DOI 10.17487/RFC0815, July 1982, <https://www.rfc-editor.org/info/rfc815>.
@misc{rfc815,
series = {Request for Comments},
number = 815,
howpublished = {RFC 815},
publisher = {RFC Editor},
doi = {10.17487/RFC0815},
url = {https://www.rfc-editor.org/info/rfc815},
author = {D.D. Clark},
title = {{IP datagram reassembly algorithms}},
pagetotal = 8,
year = 1982,
month = jul,
} RFC text
RFC: 815
IP DATAGRAM REASSEMBLY ALGORITHMS
David D. Clark
MIT Laboratory for Computer Science
Computer Systems and Communications Group
July, 1982
1. Introduction
One of the mechanisms of IP is fragmentation and reassembly. Under
certain circumstances, a datagram originally transmitted as a single
unit will arrive at its final destination broken into several fragments.
The IP layer at the receiving host must accumulate these fragments until
enough have arrived to completely reconstitute the original datagram.
The specification document for IP gives a complete description of the
reassembly mechanism, and contains several examples. It also provides
one possible algorithm for reassembly, based on keeping track of
arriving fragments in a vector of bits. This document describes an
alternate approach which should prove more suitable in some machines.
A superficial examination of the reassembly process may suggest
that it is rather complicated. First, it is necessary to keep track of
all the fragments, which suggests a small bookkeeping job. Second, when
a new fragment arrives, it may combine with the existing fragments in a
number of different ways. It may precisely fill the space between two
fragments, or it may overlap with existing fragments, or completely
2
duplicate existing fragments, or partially fill a space between two
fragments without abutting either of them. Thus, it might seem that the
reassembly process might involve designing a fairly complicated
algorithm that tests for a number of different options.
In fact, the process of reassembly is extremely simple. This
document describes a way of dealing with reassembly which reduces the
bookkeeping problem to a minimum, which requires for storage only one
buffer equal in size to the final datagram being reassembled, which can
reassemble a datagram from any number of fragments arriving in any order
with any possible pattern of overlap and duplication, and which is
appropriate for almost any sort of operating system.
The reader should consult the IP specification document to be sure
that he is completely familiar with the general concept of reassembly,
and the particular header fields and vocabulary used to describe the
process.
2. The Algorithm
In order to define this reassembly algorithm, it is necessary to
define some terms. A partially reassembled datagram consists of certain
sequences of octets that have already arrived, and certain areas still
to come. We will refer to these missing areas as "holes". Each hole
can be characterized by two numbers, hole.first, the number of the first
octet in the hole, and hole.last, the number of the last octet in the
hole. This pair of numbers we will call the "hole descriptor", and we
will assume that all of the hole descriptors for a particular datagram
are gathered together in the "hole descriptor list".
3
The general form of the algorithm is as follows. When a new
fragment of the datagram arrives, it will possibly fill in one or more
of the existing holes. We will examine each of the entries in the hole
descriptor list to see whether the hole in question is eliminated by
this incoming fragment. If so, we will delete that entry from the list.
Eventually, a fragment will arrive which eliminates every entry from the
list. At this point, the datagram has been completely reassembled and
can be passed to higher protocol levels for further processing.
The algorithm will be described in two phases. In the first part,
we will show the sequence of steps which are executed when a new
fragment arrives, in order to determine whether or not any of the
existing holes are filled by the new fragment. In the second part of
this description, we will show a ridiculously simple algorithm for
management of the hole descriptor list.
3. Fragment Processing Algorithm
An arriving fragment can fill any of the existing holes in a number
of ways. Most simply, it can completely fill a hole. Alternatively, it
may leave some remaining space at either the beginning or the end of an
existing hole. Or finally, it can lie in the middle of an existing
hole, breaking the hole in half and leaving a smaller hole at each end.
Because of these possibilities, it might seem that a number of tests
must be made when a new fragment arrives, leading to a rather
complicated algorithm. In fact, if properly expressed, the algorithm
can compare each hole to the arriving fragment in only four tests.
4
We start the algorithm when the earliest fragment of the datagram
arrives. We begin by creating an empty data buffer area and putting one
entry in its hole descriptor list, the entry which describes the
datagram as being completely missing. In this case, hole.first equals
zero, and hole.last equals infinity. (Infinity is presumably implemented
by a very large integer, greater than 576, of the implementor's choice.)
The following eight steps are then used to insert each of the arriving
fragments into the buffer area where the complete datagram is being
built up. The arriving fragment is described by fragment.first, the
first octet of the fragment, and fragment.last, the last octet of the
fragment.
1. Select the next hole descriptor from the hole descriptor
list. If there are no more entries, go to step eight.
2. If fragment.first is greater than hole.last, go to step one.
3. If fragment.last is less than hole.first, go to step one.
- (If either step two or step three is true, then the
newly arrived fragment does not overlap with the hole in
any way, so we need pay no further attention to this
hole. We return to the beginning of the algorithm where
we select the next hole for examination.)
4. Delete the current entry from the hole descriptor list.
- (Since neither step two nor step three was true, the
newly arrived fragment does interact with this hole in
some way. Therefore, the current descriptor will no
longer be valid. We will destroy it, and in the next
two steps we will determine whether or not it is
necessary to create any new hole descriptors.)
5. If fragment.first is greater than hole.first, then create a
new hole descriptor "new_hole" with new_hole.first equal to
hole.first, and new_hole.last equal to fragment.first minus
one.
5
- (If the test in step five is true, then the first part
of the original hole is not filled by this fragment. We
create a new descriptor for this smaller hole.)
6. If fragment.last is less than hole.last and fragment.more
fragments is true, then create a new hole descriptor
"new_hole", with new_hole.first equal to fragment.last plus
one and new_hole.last equal to hole.last.
- (This test is the mirror of step five with one
additional feature. Initially, we did not know how long
the reassembled datagram would be, and therefore we
created a hole reaching from zero to infinity.
Eventually, we will receive the last fragment of the
datagram. At this point, that hole descriptor which
reaches from the last octet of the buffer to infinity
can be discarded. The fragment which contains the last
fragment indicates this fact by a flag in the internet
header called "more fragments". The test of this bit in
this statement prevents us from creating a descriptor
for the unneeded hole which describes the space from the
end of the datagram to infinity.)
7. Go to step one.
8. If the hole descriptor list is now empty, the datagram is now
complete. Pass it on to the higher level protocol processor
for further handling. Otherwise, return.
4. Part Two: Managing the Hole Descriptor List
The main complexity in the eight step algorithm above is not
performing the arithmetical tests, but in adding and deleting entries
from the hole descriptor list. One could imagine an implementation in
which the storage management package was many times more complicated
than the rest of the algorithm, since there is no specified upper limit
on the number of hole descriptors which will exist for a datagram during
reassembly. There is a very simple way to deal with the hole
descriptors, however. Just put each hole descriptor in the first octets
6
of the hole itself. Note that by the definition of the reassembly
algorithm, the minimum size of a hole is eight octets. To store
hole.first and hole.last will presumably require two octets each. An
additional two octets will be required to thread together the entries on
the hole descriptor list. This leaves at least two more octets to deal
with implementation idiosyncrasies.
There is only one obvious pitfall to this storage strategy. One
must execute the eight step algorithm above before copying the data from
the fragment into the reassembly buffer. If one were to copy the data
first, it might smash one or more hole descriptors. Once the algorithm
above has been run, any hole descriptors which are about to be smashed
have already been rendered obsolete.
5. Loose Ends
Scattering the hole descriptors throughout the reassembly buffer
itself requires that they be threaded onto some sort of list so that
they can be found. This in turn implies that there must be a pointer to
the head of the list. In many cases, this pointer can be stored in some
sort of descriptor block which the implementation associates with each
reassembly buffer. If no such storage is available, a dirty but
effective trick is to store the head of the list in a part of the
internet header in the reassembly buffer which is no longer needed. An
obvious location is the checksum field.
When the final fragment of the datagram arrives, the packet length
field in the internet header should be filled in.
7
6. Options
The preceding description made one unacceptable simplification. It
assumed that there were no internet options associated with the datagram
being reassembled. The difficulty with options is that until one
receives the first fragment of the datagram, one cannot tell how big the
internet header will be. This is because, while certain options are
copied identically into every fragment of a datagram, other options,
such as "record route", are put in the first fragment only. (The "first
fragment" is the fragment containing octet zero of the original
datagram.)
Until one knows how big the internet header is, one does not know
where to copy the data from each fragment into the reassembly buffer.
If the earliest fragment to arrive happens to be the first fragment,
then this is no problem. Otherwise, there are two solutions. First,
one can leave space in the reassembly buffer for the maximum possible
internet header. In fact, the maximum size is not very large, 64
octets. Alternatively, one can simply gamble that the first fragment
will contain no options. If, when the first fragment finally arrives,
there are options, one can then shift the data in the buffer a
sufficient distance for allow for them. The only peril in copying the
data is that one will trash the pointers that thread the hole
descriptors together. It is easy to see how to untrash the pointers.
The source and record route options have the interesting feature
that, since different fragments can follow different paths, they may
arrive with different return routes recorded in different fragments.
8
Normally, this is more information than the receiving Internet module
needs. The specified procedure is to take the return route recorded in
the first fragment and ignore the other versions.
7. The Complete Algorithm
In addition to the algorithm described above there are two parts to
the reassembly process. First, when a fragment arrives, it is necessary
to find the reassembly buffer associated with that fragment. This
requires some mechanism for searching all the existing reassembly
buffers. The correct reassembly buffer is identified by an equality of
the following fields: the foreign and local internet address, the
protocol ID, and the identification field.
The final part of the algorithm is some sort of timer based
mechanism which decrements the time to live field of each partially
reassembled datagram, so that incomplete datagrams which have outlived
their usefulness can be detected and deleted. One can either create a
demon which comes alive once a second and decrements the field in each
datagram by one, or one can read the clock when each first fragment
arrives, and queue some sort of timer call, using whatever system
mechanism is appropriate, to reap the datagram when its time has come.
An implementation of the complete algorithm comprising all these
parts was constructed in BCPL as a test. The complete algorithm took
less than one and one-half pages of listing, and generated approximately
400 nova machine instructions. That portion of the algorithm actually
involved with management of hole descriptors is about 20 lines of code.
9
The version of the algorithm described here is actually a
simplification of the author's original version, thanks to an insightful
observation by Elizabeth Martin at MIT.
AI ELI5
On this page
§1Why Reassembly Looks Hard
IP can split a datagram into fragments on its way, so the receiving host must collect fragments until it can rebuild the original. The IP specification already describes reassembly and gives one algorithm, based on a vector of bits that tracks arriving fragments. RFC 815 describes an alternative that should suit some machines better.
A new fragment might fill the space between two fragments exactly, overlap existing ones, duplicate them, or only partly fill a gap. The RFC's point is that the process is, in fact, extremely simple. The method needs only one buffer equal in size to the final datagram, copes with any number of fragments in any order and with any overlap or duplication, and fits almost any operating system.
Putting a torn picture back together sounds messy because the pieces can come in any order or twice. This memo shows a tidy trick that works anyway.
§2Holes and the Hole Descriptor List
A partly reassembled datagram has stretches of octets that have arrived and stretches still to come. The missing stretches are called holes. Each hole is described by two numbers, hole.first (the first octet of the hole) and hole.last (the last octet). That pair is the hole descriptor, and all descriptors for one datagram form the hole descriptor list.
When a fragment arrives it may fill some holes. Each hole it touches is removed from the list. Eventually a fragment arrives that leaves the list empty, and the datagram can be passed up to the higher-level protocol.
A hole is a gap in your strip of pieces. You keep a list of where each gap starts and ends, and cross a gap off when it is filled.
§3The Eight-Step Fragment Algorithm
Reassembly starts when the earliest fragment of the datagram arrives. The host creates an empty buffer and puts one entry in the list: hole.first equal to zero and hole.last equal to infinity, which is a very large integer greater than 576. Each arriving fragment is described by fragment.first and fragment.last, and then eight steps run:
- Take the next hole descriptor; if there are none left, go to step eight.
- If
fragment.firstis greater thanhole.last, go to step one. - If
fragment.lastis less thanhole.first, go to step one. - Delete the current descriptor from the list.
- If
fragment.firstis greater thanhole.first, create a new hole fromhole.firsttofragment.firstminus one. - If
fragment.lastis less thanhole.lastand the fragment's more-fragments flag is true, create a new hole fromfragment.lastplus one tohole.last. - Go to step one.
- If the list is now empty, the datagram is complete and goes to the higher-level protocol; otherwise return.
Steps two and three skip holes the fragment does not touch. The test of the more-fragments flag in step six stops the algorithm creating a hole from the end of the datagram to infinity once the last fragment has been seen.
For each new piece, you look at every gap. If the piece misses the gap, ignore it. If it touches the gap, erase that gap and write down any smaller leftover gaps on either side.
§4Storing the List Inside the Buffer
The hard part is not the arithmetic but adding and deleting list entries, since there is no upper limit on how many holes can exist. The RFC's answer is to put each hole descriptor in the first octets of the hole itself. By the algorithm's definition a hole is at least eight octets long. Storing hole.first and hole.last presumably takes two octets each, two more thread the entries together, and at least two are left over for implementation quirks.
There is one pitfall: run the eight-step algorithm before copying the fragment's data into the buffer. Copying first could smash descriptors, but after the algorithm has run, any descriptors about to be overwritten are already obsolete.
Because descriptors are scattered, a pointer to the head of the list is needed. It can live in a descriptor block kept with each buffer, or, as a "dirty but effective" trick, in an unneeded part of the internet header in the buffer such as the checksum field. When the final fragment arrives, the packet length field in the internet header should be filled in.
Instead of a separate notebook for the gaps, you write each gap's note right inside the empty space it describes. Just make a note before you fill the space.
§5Internet Options and the Header Size
The basic description ignored internet options. Some options are copied into every fragment, but others, such as record route, appear only in the first fragment (the one containing octet zero). Until the first fragment arrives, the size of the internet header is unknown, so the host does not know where in the buffer to copy data.
If the first fragment is the earliest to arrive, there is no problem. Otherwise there are two solutions: leave room in the buffer for the maximum header, which is 64 octets, or gamble that the first fragment has no options and shift the data later if it does. Shifting can trash the pointers that thread the hole descriptors, but the RFC says it is easy to repair them.
Source route and record route options can arrive with different return routes in different fragments. The specified procedure is to use the return route from the first fragment and ignore the others.
Sometimes you do not know how big the label on the first piece is until it shows up. You either leave extra room for it or move things over later.
§6Finding Buffers and Timing Out
Two more parts complete the process. First, an arriving fragment must be matched to its reassembly buffer. The right buffer is the one where the foreign and local internet addresses, the protocol ID and the identification field are all equal.
Second, a timer must decrement the time to live field of each partly reassembled datagram, so incomplete datagrams that have outlived their usefulness are detected and deleted. This can be a demon that wakes once a second and decrements each field by one, or a timer call queued when the first fragment arrives.
The author built the complete algorithm in BCPL as a test. It took less than one and one-half pages of listing and generated approximately 400 nova machine instructions, and the part that manages hole descriptors is about 20 lines of code. The RFC credits Elizabeth Martin at MIT for an observation that simplified the original version.
You match each piece to the right puzzle by its labels, and you throw away puzzles that have waited too long for missing pieces.