RFC 815 UNKNOWN IP · FRAGMENTATION · ALGORITHMS

IP Reassembly: IP Datagram Reassembly Algorithms

A one-buffer method for rebuilding a fragmented IP datagram, whatever order the pieces arrive in.

On rfc-editor.org ↗
PUBLISHED
July 1982
AUTHORS
D. Clark
ORG
MIT
STREAM
Legacy
STEPS PER FRAGMENT
8
TESTS PER HOLE
4
MINIMUM HOLE SIZE
8 octets
MAXIMUM INTERNET HEADER
64 octets
BUFFER MATCH FIELDS
Foreign and local address, protocol ID, identification field
HOLE MANAGEMENT CODE
About 20 lines
DOI
10.17487/RFC0815

RFC text

Original text · RFC Editor View on rfc-editor.org ↗
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.

ISO Plain Language

On this page
PROBLEMA datagram split into fragments must be rebuilt at the destination, and the fragments may arrive in any order, overlap each other or arrive twice.
MECHANISMKeep one buffer the size of the finished datagram and a list of holes, the byte ranges still missing. Each arriving fragment is compared with every hole in four tests, and holes are trimmed, split or deleted.
WHY IT MATTERSWhen the hole list is empty the datagram is complete. The RFC reports a test implementation in BCPL of under one and one-half pages of listing.

§1Introduction and the Reassembly Problem

One of the mechanisms of IP is fragmentation and reassembly. Under certain circumstances, a datagram originally transmitted as a single unit arrives at its final destination broken into several fragments. The IP layer at the receiving host must accumulate these fragments until enough have arrived to reconstitute the original datagram.

The IP specification gives a complete description of the reassembly mechanism and one possible algorithm for it, based on keeping track of arriving fragments in a vector of bits. RFC 815 describes an alternate approach that reduces the bookkeeping, requires for storage only one buffer equal in size to the final datagram being reassembled, and can handle any number of fragments arriving in any order with any possible pattern of overlap and duplication.

A first look suggests the job is complicated: a new fragment may precisely fill the space between two fragments, may overlap with them, may completely duplicate them, or may sit in the middle of a gap without touching either end of it. The memo argues that the process is in fact extremely simple when it is expressed properly.

When a message is too big for the network, it is split into pieces called fragments, and the receiver has to put them back together. This memo gives a way to do that with one buffer and a short list of the gaps that are still missing.

§2Holes and Hole Descriptors

A partially reassembled datagram consists of certain sequences of octets that have already arrived and certain areas still to come. The memo calls these missing areas holes. Each hole is characterised 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 is the hole descriptor, and all the hole descriptors for one datagram are gathered together in the hole descriptor list.

The general form of the algorithm is simple. When a new fragment arrives it may fill in one or more of the existing holes, so each entry in the hole descriptor list is examined to see whether that hole is eliminated by the incoming fragment, and if it is, the entry is deleted from the list. Eventually a fragment arrives that eliminates every entry, and at that point the datagram has been completely reassembled and can be passed to higher protocol levels for further processing.

A hole is a range of byte positions in the message that has not arrived yet. The receiver keeps a list of holes, and each time a fragment arrives it works out which gaps it fills and updates the list.

§3The Eight-Step Fragment Processing Algorithm

An arriving fragment can fill a hole in several ways: it can completely fill the hole, it can leave space at the beginning or the end of it, or it can lie in the middle and break the hole in two. If properly expressed, the algorithm compares each hole to the arriving fragment in only four tests.

The algorithm starts when the earliest fragment of the datagram arrives. The receiver creates an empty data buffer and puts one entry in the hole descriptor list, the entry that describes the datagram as completely missing: hole.first equals zero and hole.last equals infinity, which is presumably implemented as a very large integer, greater than 576, of the implementor's choice. The arriving fragment is described by fragment.first, its first octet, and fragment.last, its last octet.

The eight steps then repeat for each fragment. Step one selects the next hole descriptor, and there is nothing more to do if there are no entries left. Step two skips the hole when fragment.first is greater than hole.last, and step three skips it when fragment.last is less than hole.first; in both cases the fragment does not overlap the hole in any way. Step four deletes the current descriptor, because the fragment interacts with this hole and the old descriptor is no longer valid. Step five creates a new descriptor when fragment.first is greater than hole.first, covering the part still missing at the front of the hole, from hole.first to fragment.first minus one. Step six creates another new descriptor when fragment.last is less than hole.last and the more fragments flag is true, covering the part still missing at the back, from fragment.last plus one to hole.last. The more fragments flag matters because the descriptor that reaches from the end of the buffer to infinity can only be discarded once the last fragment of the datagram has arrived. Step seven returns to step one, and step eight says that when the list is empty the datagram is complete and is passed to the higher level protocol processor.

Each arriving fragment is compared with every gap in the list. If the two do not overlap, the gap is left alone; if they do, the old gap is removed and any smaller gap left at either end is added back to the list.

THE FOUR TESTS THE ALGORITHM MAKES ON EACH HOLE
  • Fragment starts after the holeTest: fragment.first is greater than hole.last. The fragment does not overlap this hole, so the hole is left alone.
  • Fragment ends before the holeTest: fragment.last is less than hole.first. Again the fragment does not touch this hole.
  • Fragment overlaps the holeNeither test above is true, so the old hole descriptor is deleted and the hole is described again from whatever is still missing.
  • A smaller hole is leftA new descriptor is created for the part of the hole in front of the fragment, and for the part behind it when the more fragments flag is set.
ILLUSTRATIVE: THE EIGHT STEPS CONDENSED FROM THE RFC
Fragment processing, condensed
1. take the next hole; if there are none, go to 8
2. if fragment.first > hole.last, go to 1
3. if fragment.last < hole.first, go to 1
4. delete the hole descriptor
5. if fragment.first > hole.first, add a hole from hole.first to fragment.first - 1
6. if fragment.last < hole.last and more fragments, add a hole from fragment.last + 1 to hole.last
7. go to 1
8. if the hole list is empty, the datagram is complete

§4Storing the Descriptors in the Buffer

The main complexity in the eight-step algorithm 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 is many times more complicated than the rest of the algorithm, since no upper limit is specified on the number of hole descriptors that will exist for a datagram during reassembly. The memo's answer is to put each hole descriptor in the first octets of the hole itself. By the definition of the reassembly algorithm the minimum size of a hole is eight octets. Storing hole.first and hole.last will presumably require two octets each, and an additional two octets will be required to thread the entries together on the list, which leaves at least two more octets to deal with implementation idiosyncrasies.

There is one obvious pitfall in this storage strategy. The eight-step algorithm must be executed before the data from the fragment is copied into the reassembly buffer, because copying first might smash one or more hole descriptors. Once the algorithm has run, any hole descriptors that are about to be smashed have already been rendered obsolete.

Scattering the descriptors through the buffer means they must be threaded onto a list, which implies a pointer to the head of the list. In many cases that pointer can be stored in a descriptor block that the implementation associates with each reassembly buffer. If no such storage is available, the memo offers a dirty but effective trick: store the head of the list in a part of the internet header in the reassembly buffer that is no longer needed, an obvious location being the checksum field. When the final fragment of the datagram arrives, the packet length field in the internet header should be filled in.

The list of gaps is stored inside the gaps themselves, so no separate bookkeeping area is needed. The algorithm has to run before the fragment's bytes are copied into the buffer, or the stored list would be overwritten.

§6Options and the Unknown Header Length

The preceding description made one unacceptable simplification: it assumed that there were no internet options associated with the datagram being reassembled. The difficulty is that until the first fragment of the datagram is received, nobody can tell how big the internet header will be, because certain options are copied identically into every fragment while others, such as record route, are put in the first fragment only. The first fragment is the one containing octet zero of the original datagram.

Until the size of the internet header is known, the receiver 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, there is no problem. Otherwise there are two solutions. The receiver can leave space in the reassembly buffer for the maximum possible internet header, which is not very large at 64 octets, or it can gamble that the first fragment will contain no options and, when the first fragment finally arrives, shift the data in the buffer a sufficient distance to make room. The only peril in copying the data is that the pointers that thread the hole descriptors together will be trashed, and the memo notes that it is easy to see how to untrash them.

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. That is more information than the receiving Internet module needs, and the specified procedure is to take the return route recorded in the first fragment and ignore the other versions.

Some IP headers carry extra options, and their size is only known once the first fragment arrives. The receiver either reserves room for the largest possible header or moves the data along when the real header size is known.

§7The Complete Algorithm

Two further parts complete the reassembly process. First, when a fragment arrives, the receiver has to find the reassembly buffer associated with it, which requires some mechanism for searching all the existing buffers. The correct reassembly buffer is identified by equality of four fields: the foreign and local internet address, the protocol ID, and the identification field.

Second, a timer based mechanism 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 way is to create a demon that comes alive once a second and decrements the field in each datagram by one. The other is to read the clock when each first fragment arrives and queue a 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 of these parts, was constructed in BCPL as a test. It took less than one and one-half pages of listing and generated approximately 400 nova machine instructions. The portion actually involved with the management of hole descriptors is about 20 lines of code. The version described here is a simplification of the author's original version, thanks to an insightful observation by Elizabeth Martin at MIT.

To finish, the receiver matches each fragment to the right buffer using the addresses, the protocol and the identification field, and it discards buffers that have waited too long for the missing pieces.